Compare commits

...

304 Commits

Author SHA1 Message Date
Tina
7e1ab5fcd2 feat: Remove local routing setting (#7462) [hotfix for prod] (#7469)
* fix: change default tx deadline to 10m (#7451)

* fix: change deadline to 10m

* test: add unit tests

* fix: improve unit tests

* feat: Remove local routing setting (#7462)

* remove client side router preference

* update e2e test

* fix comment

---------

Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com>
2023-10-13 17:09:14 -04:00
Tina
3f510aabcc fix: change default tx deadline to 10m (#7451) [hotfix for prod] (#7468)
fix: change default tx deadline to 10m (#7451)

* fix: change deadline to 10m

* test: add unit tests

* fix: improve unit tests

Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com>
2023-10-13 15:46:02 -04:00
eddie
b29968d014 fix: hotfix update setting user.router_preference for analytics (#7459)
fix: delay setting user.router_preference until statsig and redux initialize
2023-10-12 14:18:04 -07:00
Thomas Thachil
a07556b87d chore(): update deeplink package names (#7449) 2023-10-11 13:13:37 -04:00
UL Service Account
5b551af25a ci: add global CODEOWNERS 2023-10-06 19:47:30 +00:00
UL Service Account
69f6ca2635 ci(t9n): download translations from crowdin 2023-10-06 19:47:30 +00:00
Jack Short
2c7381ff47 fix: removing scrollbar on swap with banner (#7434) 2023-10-06 15:33:04 -04:00
Jack Short
6e4746a7fe feat: uk disclaimer banner (#7428)
* feat: uk disclaimer banner

* bad merge with sitemap

* button

* cypress test

* intercept ordering

* comments

* sitemap was committed idk why

* font weights

* moving uk disclaimer

* removing trash
2023-10-06 14:00:07 -04:00
Kristie Huang
48379c66ce feat: [info] remove balance summaries from TDP (#7430) 2023-10-06 13:07:29 -04:00
eddie
1b7f0d11fd fix: override user pref in analytics (#7420) 2023-10-06 09:21:18 -07:00
Kristie Huang
db1d264ad3 fix: unhide native gas token from miniportfolio (#7374)
* fix: unhide native gas token from miniportfolio

* wip tests & gql types

* fix tests, default hide small balances

* pr review

* fix e2e hidden count
2023-10-06 12:11:44 -04:00
Connor McEwen
fd24cb890a fix: meta tag injector uses property, not name (#7431) 2023-10-06 11:46:39 -04:00
cartcrom
932c4482d2 feat: updated rate/routing tooltips (#7412)
* feat: updated routing tooltips

* refactor: gas price formatting

* fit: boolean rendering
2023-10-05 17:25:53 -04:00
Connor McEwen
2d8dac5c15 fix: merge issue (#7427)
* fix: merge issue

* update snapshots
2023-10-05 16:30:57 -04:00
Kristie Huang
0e3d188a9a feat: add feature flags settings overrides box (#7406)
* add feature flags settings overrides box

* cat

* useGate hook monstrosity

* pr changes

* exclude devflagsbox from code cov

* pr review

* mobile bottom bar

* nit

* initialize atom

* fix atom initialization

* remove comments

* fix devbox initialization

* nit remove diff
2023-10-05 15:48:02 -04:00
cartcrom
1be62f0bec feat: updated slippage ui (#7409)
* feat: updated slippage ui

* fix: update settings to also have period in max slippage string

* test: update e2e test search string
2023-10-05 15:34:23 -04:00
Connor McEwen
e6519a7dd1 feat: support redirects for a list of header paths (#7411)
* add country code to meta tag

* use blocked paths header

* proper types

* add test

* Update functions/components/metaTagInjector.ts

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

* Update functions/components/metaTagInjector.ts

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

* Update src/pages/App.tsx

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

* pr suggestions

* skip failing e2e

* revert test change

* take file from main

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-10-05 15:25:45 -04:00
eddie
3ced65b8a4 feat: add sitemap for app.uniswap.org (#7408)
* feat: add sitemap for app.uniswap.org

* feat: script to update lastmod

* fix: deps and snapshots

* fix: use xml2js

* fix: improve test and sitemap
2023-10-05 12:19:58 -07:00
eddie
bab8506919 fix: dont crash on invalid tokenId (#7410) 2023-10-05 12:12:34 -07:00
eddie
4a79280edc feat: allow manual test runs (#7415) 2023-10-05 12:12:21 -07:00
eddie
53f0ca9b7e fix: disable UniswapX opt-out in e2e tests (#7423) 2023-10-05 11:54:24 -07:00
eddie
0381200fec fix: ignore large slices in immutable check (#7425) 2023-10-05 11:40:52 -07:00
Matthew Spector
040ebb5475 fix: Remove Minus Sign for FOT Display (#7419) 2023-10-05 11:33:28 -07:00
Charles Bachmeier
0752314d87 fix: click on test row directly (#7424) 2023-10-05 11:08:39 -07:00
Charles Bachmeier
9db5fd104a fix: use in house token for low volume test (#7414)
* fix: use discontinued project for low volume test

* use token I created

* update comment

* no info available

* remove socials

* update comment

* checksummed address
2023-10-04 18:18:57 -07:00
cartcrom
b9db195017 feat: gas costs ui updates (#7405)
* feat: gas costs ui updates

* lint

* test: update snapshots

* test: update other snapshots
2023-10-04 16:08:00 -04:00
eddie
b6bdbcf587 test: mock http requests in jest (#7394)
* test: try no coverage for unit tests

* test: onlychanged

* test: try parallel

* fix: no http in unit tests

* fix: deduplicate

* fix: nock only

* fix: maxworkers 2

* fix: remove nock

* fix: restore nock

* fix: comment

* fix: try again with jest-offline

* fix: remove jest-offline

* test: try 2 again

* fix: 100%
2023-10-03 14:37:27 -07:00
eddie
cc325b2fbe fix: no stale trade when otherCurrency is missing (#7403) 2023-10-03 14:33:48 -07:00
Jack Short
2694379c97 chore: currency percentages (#7358)
* formatPercent

* hook deps

* price chart

* price chart formatting

* bug bash findings

* Update src/utils/formatNumbers.ts

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

* fixing merge errors

* unit tests

* special cases

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-10-03 13:29:36 -07:00
cartcrom
82aaf0784a refactor: swap line items and tooltips decomposition (#7390)
* refactor: swap line items and tooltips decomposition

* test: test line items directly

* refactor: added tooltip prop

* refactor: preview trade logic

* fix: percentage color

* lint

* fix: exchange rate alignment

* fix: initial pr comments

* test: fix snapshots

* refactor: var naming

* fix: uneeded dep array var

* refactor: small nit
2023-10-03 15:22:07 -04:00
Jordan Frankfurt
55a509cad8 chore: update @web3-react/* (#7398)
* chore: update @web3-react/walletconnect-v2

* update remaining packages and their patches
2023-10-03 13:46:02 -05:00
Tina
463dd6fdfb feat: Move UniswapX signature expiry back to deadline (#7402)
startTime -> deadline
2023-10-03 14:29:03 -04:00
Kristie Huang
3ad4fb6846 feat: change address screening service to Uniswap /screen API (#7339)
* change fetch url to uniswap api

* clean code + write tests

* nit updates

* nit-rename test

---------

Co-authored-by: Kristie Huang <kristie.huang@uniswap.org>
2023-10-02 13:04:30 -04:00
gnewfield
1c76277c46 feat: polish blocked, nonexistent NFT collection page (#7373)
* add pages for nonexistent and blocked NFT collections

* add padding

* fix themed text import

* update design and revise based on pr comments
2023-10-02 13:02:28 -04:00
Charles Bachmeier
f90f81b3d9 feat: add error message to wallet connect fail event (#7387) 2023-10-02 09:56:35 -07:00
Charles Bachmeier
81accd1864 feat: [info] Add Liquidity and Swap buttons on PDP (#7382)
* feat: setup initial pool details page and route

* add pool data query and call on enw page

* make query dynamic to url chainId

* Get and display Header info

* add token symbols

* split header into its own file

* add helper function to not default to eth chain

* add helper function tests

* add header component tests

* add mocked test for PDP

* use valid values

* allow unsupported BE chains supported by thegraph

* typecheck

* remove useless row

* no longer needed child

* use first and last child

* move mock consts to their own file

* skele linear task

* return null

* descriptiive pool not found bool

* modify correct logo container

* update snapshots

* instantiate all chain apollo clients

* added snapshot test

* merge main and update snapshots

* Update src/pages/PoolDetails/PoolDetailsHeader.tsx

Co-authored-by: Nate Wienert <natewienert@gmail.com>

* type feeTier

* setup init stats component

* correctly query pool data for t24, t48, and tWeek timestamps

* add comments

* sanitize pool data and update tests

* correct test data

* add todo

* lint

* show correct data

* remove logs

* use formatter

* showing colored bars

* styled graph

* get muted color

* refactor: move getColor to src

* refactor useColor to use getColor function

* remove consts

* refactor files

* 1st class var support courtesy of carter

* remove logging and adds comments

* mobile styling

* move Stats to its own file

* add test cases

* add test file

* update padding

* remove old test file

* respond to feedback

* right column wrapper

* add non-functional pdp buttons

* update tests

* add button functionality

* working tokenId for position

* split buttons in their own file

* add tests

* reduce screenshots

---------

Co-authored-by: Nate Wienert <natewienert@gmail.com>
2023-10-02 09:56:17 -07:00
eddie
524ce49fcb feat: dynamic defaultCurrencyCode for moonpay (#7383) 2023-10-02 09:48:55 -07:00
Kristie Huang
cbec108172 feat: add chains dynamicconfig for feature flags (#7389)
* feat: add feature flags dynamic config for chains

* fix

* add better chainid error checking

* quiet linter

* refactor & should be quick_route_chains only
2023-09-29 14:53:01 -04:00
eddie
3a4dc91e49 fix: wrap/unwrap activity parsing (#7384)
* fix: add unit test

* fix: re-use swap parse logic
2023-09-29 10:26:09 -07:00
Jack Short
af80079957 feat: remove buy button and landing terminology for uk (#7386)
* feat: remove buy button and landing terminology for uk

* removing tarballs

* mocked setup

* setting compliance to gb

* turning back on defaults

* cache for user

* moving to hook and grid sizing

* fixing tests

* comments

* landinage page cards

* cypress test

* removing extra store
2023-09-29 12:32:05 -04:00
Tina
c7a8e9e5a7 feat: Quick routes (#7348)
* wip, added PreviewTrade and now amending request arg type

* updates

* update logic to progress to swap review screen

* add token tax info to preview trades

* add loading component

* add feature flag and fix analytics and perf stuff

* update debounce amount

* add latencyMs measure

* change types

* add inline comments

* actually pass in feature flags

* dep array

* fix snapshot and unit tests

* fix unit tests

* update font color for loading text

* remove all chains feature flag

* remove from feature flag modal

* dont flicker review modal when allowance is loading

* remove comment

* add snapshot tests

* triple equals

* add comment

* change cast
2023-09-29 12:20:10 -04:00
eddie
e6362212c6 feat: uniswapx deadline (#7376)
* feat: uniswapX time-to-sign

* fix: animation timing

* fix: bug

* fix: improve props and remove memo
2023-09-28 10:52:35 -07:00
Charles Bachmeier
d63bdf1887 feat: [info] Add Stats Section to PDP (#7353)
* feat: setup initial pool details page and route

* add pool data query and call on enw page

* make query dynamic to url chainId

* Get and display Header info

* add token symbols

* split header into its own file

* add helper function to not default to eth chain

* add helper function tests

* add header component tests

* add mocked test for PDP

* use valid values

* allow unsupported BE chains supported by thegraph

* typecheck

* remove useless row

* no longer needed child

* use first and last child

* move mock consts to their own file

* skele linear task

* return null

* descriptiive pool not found bool

* modify correct logo container

* update snapshots

* instantiate all chain apollo clients

* added snapshot test

* merge main and update snapshots

* Update src/pages/PoolDetails/PoolDetailsHeader.tsx

Co-authored-by: Nate Wienert <natewienert@gmail.com>

* type feeTier

* setup init stats component

* correctly query pool data for t24, t48, and tWeek timestamps

* add comments

* sanitize pool data and update tests

* correct test data

* add todo

* lint

* show correct data

* remove logs

* use formatter

* showing colored bars

* styled graph

* get muted color

* refactor: move getColor to src

* refactor useColor to use getColor function

* remove consts

* refactor files

* 1st class var support courtesy of carter

* remove logging and adds comments

* mobile styling

* move Stats to its own file

* add test cases

* add test file

* update padding

* remove old test file

* respond to feedback

* right column wrapper

* update tests

---------

Co-authored-by: Nate Wienert <natewienert@gmail.com>
2023-09-28 09:27:29 -07:00
eddie
3bb55c6b5d fix: hide price when no amount input (#7379)
* fix: hide price when no amount input

* fix: snapshots
2023-09-27 10:47:16 -07:00
Kristie Huang
71212f7e32 fix: use sentence case for text (#7375) 2023-09-27 12:46:09 -04:00
eddie
731ff4a485 feat: user property git commit hash (#7378) 2023-09-26 13:37:08 -07:00
eddie
519ba8963a test: unit tests for parseRemote (#7359) 2023-09-26 13:36:56 -07:00
Jack Short
ec784ccb36 chore: updating input currency panel to handle comma separator (#7336) 2023-09-26 12:33:15 -05:00
Brendan Wong
20d8404717 fix: update polygon branding (#7190)
* update polygon branding

* Update matic-token-icon.svg
2023-09-25 12:11:12 -04:00
Jordan Frankfurt
809841df0a feat: new app provider w/ fallback behavior (#7205)
* feat: new app provider w/ fallback behavior

* progress

* update useContractX signer params

* Revert "update useContractX signer params"

This reverts commit 386d1580df.

* extend jsonrpcprovider

* add mainnet quicknode example, use old staticJsonRpc extension

* add tests

* unit testing

* fixes to tests/tsc

* Update src/state/routing/gas.ts

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

* pr review

* e2e tests should only talk to the chain via connected wallet

* Revert "e2e tests should only talk to the chain via connected wallet"

This reverts commit 0ce76eb7e4.

* add charlie's null nit

* fix e2e

* add feature flag

* Update cypress/support/setupTests.ts

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

* pr review

* pr feedback

* fix tests

* add generic send test

* fix merge error

* add a failure rate calculation and inline comments on scoring algo w/ an example

* fix sort test

* cleaner provider creation

* simplify sort

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-09-22 14:05:27 -05:00
Tina
2dc5a6efb4 fix: Remove e2e test (#7369)
* remove now untrue test

* add test about missing stats
2023-09-22 11:35:54 -07:00
eddie
7cd72a706d feat: show usd price with no input amount (#7328)
* feat: show usd price with no input amount

* fix: snapshots

* fix: make tokens undefined only - no null

* fix: readability

* fix: syntax improvements
2023-09-22 10:31:23 -07:00
Kristie Huang
4f8956f79a fix: should show slippage/deadline on LP flow settings (#7367)
* fix: should show slippage/deadline on LP flow settings

* write unit tests & update

---------

Co-authored-by: Kristie Huang <kristie.huang@uniswap.org>
2023-09-22 13:12:32 -04:00
Charles Bachmeier
beef7f2d86 feat: Condense color extraction logic and improve fallback (#7347)
* refactor: move getColor to src

* refactor useColor to use getColor function

* remove consts

* refactor files

* clean up color convert fn

* move getColor test and import test images

* hardcode array buffers for images

* add invalid png
2023-09-22 10:09:05 -07:00
Zach Pomerantz
b667662b49 fix: lazy load nft profile (#7361) 2023-09-22 10:08:22 -07:00
Zach Pomerantz
ed87df6269 feat: account suspense (#7337)
* feat: eagerly connect outside of react lifecycle

* test: reflect selected wallet in localStorage

* test: spy only on portfolio balances

* feat: connectionReady

* feat: connecting state

* feat: leave space for address

* fix tests

* better meta

* fix

* fix wallet change

* add interactivity earlier

* add validation

* update localstorage key in cypress setup

* even less thrash

* load per account

* simplify, hopefully

* explanatory

* inf render

* whoopsie

* ordering
2023-09-22 09:57:35 -07:00
Charles Bachmeier
622c72d4a8 feat: [info] Migrate Historical Pool Data Queries (#7310)
* correctly query pool data for t24, t48, and tWeek timestamps

* add comments

* sanitize pool data and update tests

* correct test data

* add todo

* lint

* remove logs

* 1st class var support courtesy of carter

* remove logging and adds comments
2023-09-22 09:47:25 -07:00
eddie
df6c44d2c4 fix: use Date as a performance tracker fallback (#7349) 2023-09-22 09:40:25 -07:00
eddie
59e7a2867a fix: ui fixes on the add liquidity flow (#7352) 2023-09-22 09:40:10 -07:00
eddie
0a31428d7a feat: uniswapX opt-out update (#7368) 2023-09-22 09:38:47 -07:00
Zach Pomerantz
fbc7e64032 chore: prohibit barrels (#7362)
* chore: prohibit barrels

* lint

* lint

* more lint

* add motivation to eslint message
2023-09-21 19:38:17 -07:00
eddie
4a8fb760d2 fix: show common bases on mobile (#7365)
* fix: show common bases on mobile

* test: add unit tests
2023-09-21 13:26:59 -07:00
eddie
15c510b742 fix: reset LDO approvals (#7345)
* fix: reset LDO approvals

* fix: constant file, better name

* Update src/components/swap/constants.ts

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

---------

Co-authored-by: Charles Bachmeier <charles@bachmeier.io>
2023-09-21 13:03:25 -07:00
eddie
e81b0a4d1f test: unit tests for activity tab createGroups, and update cloud test snapshots (#7364)
* test: unit tests for activity tab createGroups

* fix: update cloud test snapshots
2023-09-21 12:03:10 -07:00
Zach Pomerantz
b9fc65ec9a feat: lazy-load safe only if iframed (#7360) 2023-09-21 11:31:38 -07:00
Zach Pomerantz
d73c368ee4 fix: defer popper style recalc until use (#7330) 2023-09-21 11:29:43 -07:00
Zach Pomerantz
5e1c430657 build: improve tree-shaking (#7325)
* build: improve tree-shaking

* dedup terser
2023-09-21 11:29:22 -07:00
eddie
d4f19e42f8 feat: log chain changed (#7350) 2023-09-20 14:58:28 -07:00
cartcrom
60593df077 refactor: price chart organization (#7304)
* refactor: implement chart model type

* refactor: move PriceChart component into charts folder

* refactor: relocate pricechart test file

* lint

* fix: pr comments

* fix: use formatter hook in price chart file
2023-09-20 15:58:41 -04:00
Nate Wienert
aeef2c2356 fix: Fix visual issues in Spore (#7240)
* feat: make meta theme-color adapt to new spore background colors

* fix: make glow behind swap modal use a blur strategy rather than box shadow for a more squared glow

* test: grainy bg

* fix: make pool liquidity add input focus border same as swap

* remove svg grain
2023-09-20 09:44:25 -10:00
Zach Pomerantz
54880d201a feat: eagerly connect outside of react lifecycle (#7334)
* feat: eagerly connect outside of react lifecycle

* test: reflect selected wallet in localStorage

* test: spy only on portfolio balances
2023-09-20 12:31:54 -07:00
Zach Pomerantz
0f6581bf47 fix: preconnect (#7340) 2023-09-20 12:30:20 -07:00
Zach Pomerantz
37c3330897 fix: wait to fetch lists until rehydration (#7332) 2023-09-20 09:40:57 -07:00
Zach Pomerantz
7a981923f6 fix: do not re-init active locale (#7329) 2023-09-20 09:32:50 -07:00
Brendan Wong
9672c2db9a fix: remove settings button from lp page (#7105)
remove settings button from lp page
2023-09-19 14:16:46 -07:00
Charles Bachmeier
13f57d8d73 feat: block dynamic link previews for blocked collections (#7344)
* feat: block link previews for blocked collections

* update collection test

* single invalid

* move blocklist to its own const file

* rename file to blocklist
2023-09-19 11:07:07 -07:00
Charles Bachmeier
43f4d0f1b0 chore: block requested collection (#7342) 2023-09-19 08:06:58 -07:00
Zach Pomerantz
19c83c92ab test: spy only on portfolio balances (#7335) 2023-09-19 07:59:21 -07:00
Jack Short
91c2013522 chore: only exposing useFormatter (#7308) 2023-09-18 20:21:21 -04:00
eddie
cf09e80934 fix: fetch balances when opening token selector in LP page (#7321)
* fix: fetch balances when opening token selector in LP page

* fix: move PrefetchBalancesWrapper
2023-09-18 14:49:39 -07:00
Zach Pomerantz
ad9879b4f9 fix: avoid scrollTo on pageload (#7323) 2023-09-18 13:09:01 -07:00
Zach Pomerantz
c528c6169e fix: block number memoization (#7331) 2023-09-18 13:08:51 -07:00
Zach Pomerantz
33c93b5ded fix: spurious Swap re-renders (#7333) 2023-09-18 13:08:37 -07:00
Charles Bachmeier
5ba046f111 feat: remove overlay cutoff (#7322) 2023-09-18 11:59:01 -07:00
Callil Capuozzo
5414a7c7ef fix: [Spore] polish search (#7297)
* Polish search bar styles

* fix hover state

* Change background to surface1

* update tests

* Update styles
2023-09-16 10:22:38 -04:00
Zach Pomerantz
22fd0cc7bb build: fix sourcemap warnings (#7318) 2023-09-15 19:41:38 -07:00
Kristie Huang
784fbfe7b1 test: add remove-liquidity interface tests (#7309)
* fix: duplicate or single-token remove-liquidity routes should show error page

* use maxUint256 for nonexistent pool

* move tests back to rem-liq

* rename pooled token id

* nit: use uni address from sdk core

* nit: use maxuint256 from sdk core

* nit: use liqudityValue for doublecurrencylogo

---------

Co-authored-by: Kristie Huang <kristie.huang@uniswap.org>
2023-09-15 16:05:56 -04:00
Zach Pomerantz
f47d00be37 feat: lazy-load popups/modals/chrome (#7313)
* fix: synchronously load first page

* feat: lazy-load popups/modals/chrome
2023-09-15 12:40:19 -07:00
Tina
2eb145ab80 feat: Add intent to swap quote request (#7245)
add intent to swap quote
2023-09-15 15:30:22 -04:00
Zach Pomerantz
adaa7f1a0d feat: lazy-load smart-order-router (#7317) 2023-09-15 12:24:00 -07:00
Zach Pomerantz
2506c95816 feat: lazy-load non-swap routes (#7315)
* feat: lazy-load non-swap routes

* retain swap nav location
2023-09-15 12:10:01 -07:00
Jack Short
6303293037 chore: updating all numbers in swap flow to be converted properly (#7301)
* format price impact

* format price

* adding confirm swap modal

* removing export

* adding export back

* correct active currency

* activeLocalCurrencyIsUSD

* fallback to usd if no conversion rate for previous
2023-09-15 14:42:30 -04:00
Matthew Spector
185cb52501 chore: Remove Hardcoded Uni Wallet Supported Chains List (#7320) 2023-09-15 11:04:32 -07:00
Zach Pomerantz
92cf5b42c0 fix: lazy-load web3-react libs (#7319) 2023-09-15 10:28:06 -07:00
Nicolas Brugneaux
ea45289460 feat: replace cMC02 by WBTC in the celo tokens (#7244) 2023-09-15 11:38:53 -05:00
Zach Pomerantz
7f81073037 fix: synchronously load first page (#7312) 2023-09-14 17:50:48 -07:00
eddie
ac9a6f0398 fix: v2 pool ui (#7316)
* fix: v2 pool ui

* fix: use one wrapper
2023-09-14 14:51:39 -07:00
eddie
172160deb5 feat: improved logging 9 12 (#7306)
* feat: add chain id and update name

* log metamask version

* fix: tests
2023-09-14 12:03:14 -07:00
Nate Wienert
7fc005024d fix: ci added CODEOWNERS github format (#7285)
* fix: ci added CODEOWNERS github format

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

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

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-09-14 07:59:28 -10:00
Nate Wienert
05ba0e4a66 fix: make popups appear above drawer and near top conditionally only … (#7201)
* fix: make popups appear above drawer and near top conditionally only when drawer is open

* remove logic around top bar
2023-09-14 07:49:59 -10:00
Jordan Frankfurt
5c53d8237d test: update cloud test snapshots (#7311)
fix: -u on cloud tests
2023-09-14 12:27:58 -05:00
cartcrom
6aaf0db78d refactor: price chart utility functions (#7266)
* refactor: price chart utility functions

* fix: snapshot

* fix: combine useMemos

* fix: pr nits
2023-09-14 13:13:17 -04:00
eddie
1c3ce8fdb7 fix: remove GA code (#7303)
* feat: move ga events to amplitude

* fix: remove GA code
2023-09-12 13:23:49 -07:00
eddie
3e05db4b87 feat: check chain ID before all transactions (#7177)
* feat: check chain ID before all transactions

* fix: use WrongChainError

* fix: remove asyncs
2023-09-12 11:20:06 -07:00
eddie
a9b2179eab feat: move ga events to amplitude (#7300)
* feat: move ga events to amplitude

* fix: add deets to add events
2023-09-12 10:28:12 -07:00
Callil Capuozzo
0fffff66dc fix: [Spore] general polish items (#7302)
* Add polish notes

* Polish balances

* update gap in mini portfolio names
2023-09-11 19:37:31 -04:00
eddie
1316a45c7a fix: zustand, migrate to createWithEqualityFn (#7220) 2023-09-11 16:06:36 -07:00
Charles Bachmeier
dcf7d29357 feat: [info] Initial Pool Details Page (#7250)
* feat: setup initial pool details page and route

* add pool data query and call on enw page

* make query dynamic to url chainId

* Get and display Header info

* add token symbols

* split header into its own file

* add helper function to not default to eth chain

* add helper function tests

* add header component tests

* add mocked test for PDP

* use valid values

* allow unsupported BE chains supported by thegraph

* typecheck

* remove useless row

* no longer needed child

* use first and last child

* move mock consts to their own file

* skele linear task

* return null

* descriptiive pool not found bool

* modify correct logo container

* update snapshots

* instantiate all chain apollo clients

* added snapshot test

* merge main and update snapshots

* Update src/pages/PoolDetails/PoolDetailsHeader.tsx

Co-authored-by: Nate Wienert <natewienert@gmail.com>

* type feeTier

---------

Co-authored-by: Nate Wienert <natewienert@gmail.com>
2023-09-11 14:48:16 -07:00
Jack Short
184d515e16 chore: formatter to use local currency price (#7273)
* chore: updating formatCurrencyAmount to handle multiple langs and currencies

* missed advanced swap details

* missed confirmed swap modal

* removing updating visual effects

* removing it from parseLocale

* chore: displaying local currency and language formatting

* making effects visible

* moving to hook

* useFormatCurrencyAmount

* moving it to useformatter hook

* exporting formatting locales

* missed one parsed remote

* initial passover of using conversion rate in formatting function

* moving hook to bottom of file

* moving to bottom

* moving to bottom

* fallback to usd

* refactors

* better fallback protection

* moving to function

* fixing typecheck
2023-09-11 14:42:48 -04:00
Jack Short
86e4dd5153 chore: updating formatCurrencyAmount to handle multiple langs and cur… (#7263)
* chore: updating formatCurrencyAmount to handle multiple langs and currencies

* missed advanced swap details

* missed confirmed swap modal

* removing updating visual effects

* removing it from parseLocale

* chore: displaying local currency and language formatting

* making effects visible

* moving to hook

* useFormatCurrencyAmount

* moving it to useformatter hook

* exporting formatting locales

* missed one parsed remote

* moving hook to bottom of file

* moving to bottom
2023-09-11 13:51:11 -04:00
cartcrom
e4d103b015 refactor: price chart timestamps (#7265)
* refactor: price chart timestamps

* fix: remove unnused file

* refactor: util file name

* fix: use correct var for axis

* refactor: use backup var instead of throwing error for timeMinute interval
2023-09-11 13:16:30 -04:00
eddie
f71c781530 fix: add tax amounts to swap quote received (#7299) 2023-09-11 10:15:02 -07:00
Jack Short
736e395cd7 chore: displaying local currency and language formatting (#7267)
* chore: displaying local currency and language formatting

* moving to hook

* moving it to useformatter hook

* moving hook to bottom of file
2023-09-11 13:13:49 -04:00
Jack Short
8021135879 chore: remove extra blocknumber from dep array (#7295) 2023-09-11 12:29:39 -04:00
Tina
acdeb402ec feat: Log auto slippage setting for gas estimate failed event (#7231)
* log auto slippage setting for gas estimate failed

* add to dep array
2023-09-11 08:41:31 -07:00
eddie
84f5d8f94d fix: only log swap success for swap txs (#7284) 2023-09-08 12:03:59 -07:00
cartcrom
bb28235bee refactor: consolidate price delta components (#7268)
* refactor: move delta component out of price chart file

* refactor: remove PortfolioArrow

* refactor: search row arrow cell

* fix: svg prop + snapshots failing unit tests

* refactor: css nit
2023-09-08 14:58:39 -04:00
cartcrom
f69226c1d1 fix: use updated feature flag (#7278) 2023-09-08 14:42:03 -04:00
cartcrom
35ca1b974e fix: remove sentry warnings for failed tax fetches (#7281) 2023-09-08 14:41:47 -04:00
Nate Wienert
fdb9d8a8c9 feat: redirect /address links to the wallet download landing page or app store (#7210) 2023-09-08 08:33:21 -10:00
Zach Pomerantz
16cefb9cdb test: fix cloud tests (#7280) 2023-09-08 10:53:22 -07:00
Zach Pomerantz
63bf1c0ac8 feat: path-based routing (#7275) 2023-09-08 10:43:59 -07:00
Nate Wienert
44e3b87ae1 fix: CODEOWNERS to use GitHub syntax currently uses google (#7200)
* improve cypress test

* fix: CODEOWNERS uses Github syntax
2023-09-08 07:22:52 -10:00
eddie
a3fbab9163 feat: uniswapx start time buffer (#7279)
* wip

* feat: use startTimeBufferSecs from API response
2023-09-08 09:30:57 -07:00
cartcrom
1ac1002c37 fix: fot ui bug in swap details dropdown (#7277)
* init

* fix: completely hide tax rows while syncing

* test: use syncing = false for fot tests
2023-09-08 10:27:18 -04:00
eddie
08e0fb7f4b fix: replace base rpc urls with public rpcs (#7259)
* fix: replace base rpc urls with public rpcs

* fix: designation of safe vs fallback

* fix: remove duplicate
2023-09-07 13:56:30 -07:00
eddie
24d62d9594 feat: uniswapX default feature flag (#7246)
* feat: uniswapX default feature flag

* fix: bad merge

* fix: toggle behavior and style issues

* fix: toggle behavior
2023-09-07 12:52:24 -07:00
cartcrom
28ea390863 fix: gate FOT UI (#7272)
* fix: gate FOT UI in swap component

* feat: help article
2023-09-07 14:18:04 -04:00
eddie
4ba0d8ffc0 test: unit tests for swapFlowLoggers (#7257) 2023-09-07 10:07:56 -07:00
Jordan Frankfurt
7306423192 fix: eliminate flake from account connect race condition (#7212)
* fix: reduce flake from race condition

* use interface instead of type for props

* closeMenu instead of closeModal with type

* simplify useCloseModal

* fix bug

* pr feedback

* remove unnecessary code

* fix button wrapper style

* remove ApplicationModal.WALLET

* update color for spore
2023-09-06 17:52:47 -05:00
Zach Pomerantz
147a9bcbb2 fix: move off deprecated gql fields (#7269) 2023-09-06 13:48:02 -07:00
Charles Bachmeier
652a8305c8 feat: add BNB support to Token Search and Explore (#7223) 2023-09-05 10:57:21 -07:00
Jack Short
80a77dd567 fix: typecheck failing on main (#7264) 2023-09-05 13:54:19 -04:00
Jack Short
5951d0c40c feat: adding currency conversion hook to useUSDPrice (#7251)
* feat: adding currency conversion hook

* 5m cache policy

* conversion rate hook

* usd check in outer hook

* converting usd price to localcurrencyprice

* checking usd manually
2023-09-05 13:38:33 -04:00
Jack Short
60acc689ee chore: updating numberFormat for other currencies and languages (#7239)
* adding currency settings option

* moving menu item to shared component

* adding supported currencies

* currency menu items

* currency url params

* currency selector e2e tests

* fixing tests

* currency icons

* removing eslint

* removing another eslint disable

* renaming to local currency

* more name changes

* design updates

* adding currencies

* changing formatter rules to formatter options

* renaming file

* fixing lint

* custom currency symbol in number formatting

* refactoring input to number formatter

* locale selection

* updating all format numbers with currency and locale

* updating portfolio

* better formatting symbols

* removing adding locale and currency

* upgrading constants to be translatable

* refactoring

* adding tests

* updating comment

* removing 0 from hardcoded input and comment
2023-09-05 12:59:43 -04:00
Charles Bachmeier
cd520a9e2c refactor: add source to subgraph queries (#7253)
* feat: add source to subgraph queries

* update test
2023-09-05 08:44:57 -07:00
cartcrom
b6e388c68c feat: dynamic FOT tax fetching (#7252)
* feat: disable exact_output FOT swaps

* fix: pr comments

* test: update snapshots

* feat: dynamically fetch tax rates

* remove tax constants file

* test: update useRoutingAPITrade expected args

* fix: useSwapTaxes nits

* lint: useSwapTaxes dependency array
2023-09-01 14:56:26 -04:00
cartcrom
45a138dec1 fix: detect refunds on exact_out native swaps (#7227)
* fix: detect refunds on exact_out native swaps

* fix: correct comment
2023-09-01 14:55:42 -04:00
cartcrom
26d2ab53e8 feat: disable exact_output FOT swaps (#7237)
* feat: disable exact_output FOT swaps

* fix: pr comments

* test: update snapshots
2023-09-01 14:23:21 -04:00
gnewfield
e50ecc8309 fix: un-nest translation on AddLiquidityV2 page (#7258)
fix nested translation components causing translation error
2023-09-01 13:05:49 -04:00
eddie
406d7fe964 feat: remove dead statsig flag (#7229)
* feat: remove dead statsig flag

* fix: e2e test
2023-08-30 13:08:18 -07:00
eddie
366f4d98ef fix: remove unused feature flags (#7234)
* fix: remove unused feature flags

* fix: remove flag from tests

* fix: tests

* fix: remove useUniswapX param
2023-08-30 12:37:50 -07:00
Jack Short
4eda18a4d5 feat: currency selector (#7196)
* adding currency settings option

* moving menu item to shared component

* adding supported currencies

* currency menu items

* currency url params

* currency selector e2e tests

* fixing tests

* currency icons

* removing eslint

* removing another eslint disable

* renaming to local currency

* more name changes

* design updates

* renaming file

* fixing lint

* Update src/components/AccountDrawer/SettingsMenu.tsx

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

* alphabetical ordering currencies

* column padding

* padding only for mobile

* memoizing into switch

---------

Co-authored-by: Charles Bachmeier <charles@bachmeier.io>
2023-08-30 14:48:53 -04:00
eddie
ea66b8b959 fix: null check in lists code (#7248) 2023-08-30 11:41:40 -07:00
Charles Bachmeier
1eab4291f6 refactor: log NO_ROUTE found instead of paging (#7242)
* refactor: log no route found instead of paging

* typo

* log router preference

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-08-29 13:59:51 -07:00
eddie
bed144b994 fix: remove base goerli from chain selector (#7238)
* fix: remove base goerli from chain selector

* fix: remove all base goerli references

* fix: typecheck
2023-08-29 13:25:10 -07:00
Charles Bachmeier
dac334f975 feat: add info site migration feature flags (#7228)
* feat: add info site migration feature flags

* feat: feature flag modal improvements

* scrollbar padding

* button padding
2023-08-28 13:13:31 -07:00
Charles Bachmeier
5478cb0c7b feat: useUSDPrice for nft eth usd price (#7232)
* feat: useStableCointValue for nft eth usd price

* add new file

* useUSDPrice

* rename
2023-08-28 09:09:30 -07:00
Nate Wienert
b47cebd40b fix: Spore color fixes (#7233)
* fix: bridge chain popup line break

* fix: background initial gradient to match the default ETH bg for light and dark mode
2023-08-25 11:43:26 -10:00
Tina
bb51be545b feat: Support UniswapX exact out trades (#7225)
* add feature flag for uniswapx exact out trades

* dont route PRICE lookups through uniswapx

* add e2e test
2023-08-25 15:00:22 -04:00
Charles Bachmeier
ce8105bf08 feat: track connected wallet provider calls to be replaced with BE/infura (#7197)
* feat: track connected wallet provider calls to be replaced with be/infura

* update analytics events

* wrap analytics events in useEffects

* Update src/hooks/useContract.ts

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

---------

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-08-25 09:51:56 -07:00
Nate Wienert
59db4c5b02 feat: Spore colors refresh (#7118)
* Add colors and type and fix everywhere

* fix text.tsx

* Color and text adjustments

* Further tweaks

* Removed promotional gradient

Changed to pink

* Add new icons and tweak broken colors

* Kill shadows

Removes shadows from NFT cards, Pools and Tokens tables

* Update icons

Update filled and info icons to filled versions

* Update bag icon

Changed to fill style

* Change share icon

Changed to the new filled arrow

* Fix merge errors

* update tests

* Complete find and replace old colors

* Fix colors on pool pages

* Update index.test.tsx.snap

* fix header hover states

* update test

* Update connect button hover state

* Update styles design bash

* Update tests

* Update fonts

* fix buy button font weight

* update tests

* fix jumping input boxes

* lint

* lints

* update tests

* redo auth header

* fix issues

* fix snapshots

* use individual weights instead of variable for nicer $ signn

* update tests

* make dark mode glow distinct

* remove commented out code

* icons in react

* update textSecondary

* fix feedback

* port over token test fix

* lint

* fix: make popups appear above drawer and near top conditionally only when drawer is open

* Revert "fix: make popups appear above drawer and near top conditionally only when drawer is open"

This reverts commit 9946971443.

---------

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
Co-authored-by: pp-hh-ii-ll <111304124+pp-hh-ii-ll@users.noreply.github.com>
Co-authored-by: Callil Capuozzo <callil@uniswap.org>
2023-08-24 17:14:24 -10:00
Jack Short
1f6f1f1dbd fix: smart contract wallets unsupported (#7226)
* fix: smart contract wallets unsupported

* logging to amp

* response was deleted
2023-08-24 16:56:07 -04:00
cartcrom
1c142bb71f feat: workaround for FOT swaps (#7218)
* fix: workaround to include tax in slippage tolerance

* refactor: simplify tax line item components

* docs: update FOT comments

* fix: simplify tax calculation logic

* docs: update comment and add TODO

* fix: use postTaxAmountOut for price change calculation

* fix: use preTaxStablecoinPriceImpact for swab button warnings

* test: unit test for swap details/modal

* feat: add feature flag to gate FOT changes

* docs: add comment to postTaxOutputAmount getter

* fix: reword fee tooltip

* refactor: warning theme color variable usage

* fix: update snapshots

* fix: use preTaxStablecoinPriceImpact for price impact prompt logic

* lint: dependency array
2023-08-24 10:56:48 -04:00
eddie
3e67982bb4 fix: reset USDT logic (#7219) 2023-08-23 16:09:07 -07:00
Charles Bachmeier
bb79c73416 feat: add sentry monitoring for NO_ROUTE error (#7204)
feat: add sentry monitoring for no_route error
2023-08-23 14:59:26 -07:00
Jordan Frankfurt
b1da7d87f9 fix: proposal creation layout on safari (#7203)
* fix: proposal creation layout on safari

* add important to header margin
2023-08-23 16:06:24 -05:00
Jordan Frankfurt
a79324f23c fix: repair broken link (#7198) 2023-08-23 16:06:09 -05:00
Jordan Frankfurt
9008db5166 fix: add v2 to lp importer link (#7199) 2023-08-23 15:58:09 -05:00
eddie
bcc57e4612 fix: null check on elapsedTime for some browsers (#7213) 2023-08-23 10:53:51 -07:00
eddie
8c7315760a fix: make WrongChainError more specific (#7175)
* fix: setTraceError in swap callback

* fix: move error class to util file
2023-08-23 09:30:51 -07:00
Jordan Frankfurt
290083b414 fix: isolate assertions into single tests (#7209) 2023-08-22 16:34:29 -05:00
cartcrom
4bc778ff3e feat: add client and router blocknumbers to analytics (#7170) 2023-08-22 16:03:23 -04:00
Jordan Frankfurt
49ae6ef6bd fix: ensure lists are present before running update (#7206)
* fix: ensure lists are present before running update

* pr input from zzmp
2023-08-22 14:52:59 -05:00
Nate Wienert
3dab1da5ea fix: cypress flaky token details test (#7193)
improve cypress test
2023-08-21 08:46:08 -10: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
eddie
fa4e75b777 feat: base network (#6997)
* feat: initial support for base goerli

* wip: update dependencies

* chore: yarn deduplicate

* feat: mainnet wip support

* feat: mainnet wip support

* fix: radial gradient

* fix: logo update

* fix: ur address

* fix: add weth to common bases

* fix: updates

* fix: yarn dedup

* fix: correct rpc url

* fix: correct explorer url

* feat: add USDbC to common bases

* fix lint warnings

* bump SOR version

* fix: fallback URLs

* feat: statsig flag for base support (#7079)

* feat: put base support behind statsig flag

* fix: null checks

* fix: hide pool page

* fix: baseEnabledChains

* feat: update sor

---------

Co-authored-by: Jordan Frankfurt <jordan@uniswap.org>
2023-08-04 11:43:11 -07:00
Brendan Wong
f845695f6e feat: caches GraphQL queries for Cloudflare workers (#6929)
* 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

* 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

* 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

* final feedback

* Update yarn.lock

* final final feedback

* final final final feedback!

* final final final final feedback?
2023-08-04 14:12:20 -04:00
eddie
dbb2f7f6a2 fix: re-enable UniswapX after disabling (#7080)
fix: allow gouda after user disables
2023-08-03 16:45:04 -07:00
eddie
264f145708 fix: Router label copy (#7060) 2023-08-03 15:08:59 -07:00
eddie
bb2677ab1b fix: match PendingModalContent number formatting w/ SwapModalHeader (#7053)
* fix: match PendingModalContent number formatting w/ SwapModalHeader

* fix: typo

* fix: remove argument
2023-08-03 12:50:46 -07:00
eddie
29e46455c1 fix: use submitted icon on mainnet (#7055)
* fix: use submitted icon on mainnet

* fix: e2e test

* fix: some cleanup
2023-08-03 12:15:25 -07:00
Jack Short
cfc9748036 fix: adjusting slippage in a different language (#7073)
* fix: adjusting slippage in a different language

* making overflow hidden on close

* passing width instead of visibility

* min width
2023-08-03 15:05:47 -04:00
eddie
715555f340 feat: time-to-swap metric (#7051)
* test(cypress): disable infura from browser

* build: typecheck cypress

* build: es5

* build: rm cypress videos

* fix failing tests

* skip nft failure and rm infra-175

* feat: implement tts metric

* wip e2e test

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

* test: adjust test options

* fix: move to helper method

* fix: merge and make code style change

* fix: use local variable to track first event

* fix: amplitude cypress command

* fix: use file-level var

* fix: clear input in test

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-08-03 11:33:16 -07:00
cartcrom
6a1f17ab5a feat: update cancelled local tx's (#7008)
* feat: update cancelled local tx's

* fix: use updated queries

* test: add cancel reducer and hook tests

* fix: spelling in test descriptor

* feat: improved activity descriptors

* fix: check activity groups instead of activity

* fix: pending hooks cleanup

* fix: destruct object from pending hook

* refactor: update usage of pending util

* fix: removed unused util
2023-08-03 12:35:51 -04:00
Brendan Wong
cd43e0beaa fix: failing token cloud function tests (#7074)
* fix: failing token cloud function tests

* utilize enum from chains
2023-08-03 12:30:50 -04:00
gnewfield
24d00f7c39 fix: init pool behavior (#7052)
* Disable increment/decrement  when range prices undefined

* Enable full-range price selection for new pools

* Add cypress tests

* Fix lint error
2023-08-03 10:52:18 -04:00
Brendan Wong
b8573930b9 fix: update v2 pool information link (#7058)
update link
2023-08-02 19:07:08 -04:00
Brendan Wong
2c1e608f84 fix: closes settings state when MP is closed (#7065)
* fix: close settings menu on drawer close

* migrate change to default menu

* remove indent

* add cleanup
2023-08-02 19:06:57 -04:00
Brendan Wong
f90066263e fix: bring back safeNamehash (#7050)
* fix: bring back safeNamehash

* Update safeNamehash.test.ts
2023-08-02 15:15:10 -07:00
Nate Wienert
1daf15df9f chore: align @uniswap/analytics dep versions with wallet (#7037)
* chore: align @uniswap/analytics dep versions with wallet

* 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: 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: Zach Pomerantz <zzmp@uniswap.org>
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: Jack Short <john.short.tj@gmail.com>
2023-08-02 15:00:59 -07:00
Brendan Wong
708a813f2a feat: MP closes on scroll down (#6682)
* feat: MP closes on scroll down

* fix: make dismissal smoother

* fix: implement react gestures and fix scroll

* fix: fix drag when not starting from top

* fix: double scroll on mobile

* fix: comments for clarity

* fix: mobile scrolling?

* remove console logs

* potential fix?

* remove change again

* maybe fix?

* cope

* even more cope

* even more more cope

* copiest

* make less buggy

* nope

* maybe?

* HOLD

* maybe

* try 2

* maybe

* hopefully

* test

* another try

* cope

* redo

* attempt 2

* hmm

* maybe

* I THINK
2023-08-02 17:15:23 -04:00
gnewfield
18b50283d3 fix: clip long token names (#7066)
* Clip token name with ellipsis style

* Use built-in ellipsis style
2023-08-02 17:02:40 -04:00
Brendan Wong
d9ff7b15a1 fix: swap flashing the wrong value after the input is cleared. (#6719)
* fix: set input panel to 0 when recalculating

* fix: only reset number when input cleared

* fix: remove useEffect and more graciously handle trade mutation
2023-08-02 16:04:50 -04:00
Ankit Boghra
cc5309b18f fix: broken link for polygon mumbai bridge (#6986)
fix: broken link for polygon bridge

Co-authored-by: Charlie B <charles@bachmeier.io>
2023-08-02 12:20:21 -07:00
eddie
a060bf1079 fix: remove unused variable (#7061) 2023-08-02 12:06:04 -07:00
Jordan Frankfurt
1ffb9421b2 fix: style and copy updates to the swap box (#7006)
* add you pay/you receive label to swap inputs

* update styles

* update snapshots
2023-08-02 13:39:05 -05:00
Zach Pomerantz
7978ed97a9 test(cypress): clean up types/assumptions/infura (#7046) 2023-08-01 22:13:19 -07:00
Thomas Thachil
b4b6c347e3 feat: add mobile deeplinks json to public folder (#7059) 2023-08-01 14:43:48 -04:00
Jack Short
83cb750b2f fix: failing nft buy test (#7049) 2023-07-31 14:06:45 -04:00
Jordan Frankfurt
fc0bf229a7 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>
2023-07-31 10:04:22 -05:00
Zach Pomerantz
b55680dbce test: deflake and clean universal search (#7034) 2023-07-28 16:36:13 -07:00
Brendan Wong
136e68201f 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>
2023-07-28 17:50:02 -04:00
Brendan Wong
b4bb3d72d5 fix: text wrap and spacing on mobile TDP (#6909)
fix text wrap and spacing on mobile TDP
2023-07-28 17:24:35 -04:00
Brendan Wong
3f812f4aee fix: clearing input on connect wallet (#6928)
* fix: clearing input on connect wallet

* update e2e tests
2023-07-28 16:45:58 -04:00
eddie
02883aca13 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
2023-07-28 12:48:12 -07:00
Brendan Wong
ace81ecc84 fix: remove flakey token test (#7029) 2023-07-28 13:31:19 -04:00
gnewfield
0aafcdf885 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
2023-07-28 13:22:48 -04:00
cartcrom
f55062f9a9 test: mock quotes to avoid errors logged (#7031) 2023-07-28 11:39:49 -04:00
cartcrom
1fde2504b4 fix: use deeplink instead of universal link (#7017)
* fix: use deeplink instead of universal link

* docs: added comment
2023-07-28 10:58:01 -04:00
Zach Pomerantz
6a3abbfb56 fix: update mainnet block (#7042)
* fix: update mainnet block

* fix: typecheck
2023-07-27 15:40:10 -07:00
gnewfield
9ced714718 fix: improve currency select styling (#7009)
* fix: remove currency select drop shadows and make deposit label non-interactive

* rename labelOnly prop to pointerEvents
2023-07-27 14:27:59 -04:00
Brendan Wong
8ed6481f16 fix: display ens name/avatar on other chains (#6981)
* maintain ens info on other chains

* update ens hook

* feedback!

* initial test

* test progress

* e2e test?

* final feedback

* fix(lint): rm unused test var

* fix final bugs

* last lint issue

* fix: update catches

* fix: define catch handlers

* fix: use error in catch

* empty commit

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-07-27 13:34:26 -04:00
cartcrom
d160d1a929 feat: don't fetch UniswapX quotes if user has disabled UniswapX (#7028) 2023-07-27 13:33:20 -04:00
Brendan Wong
1092bc2c58 fix: collision of network banner and swap settings (#6910)
* change z-index of network banner

* change to use pointer-events rather than deprecated value
2023-07-27 13:22:03 -04:00
Zach Pomerantz
a2e56aaabd revert: "fix: update token tests for cloudflare functions" (#7027)
Revert "fix: update token tests for cloudflare functions (#7026)"

This reverts commit b58d4b72ab.
2023-07-27 09:49:18 -07:00
Tina
7ec6a965d3 feat: bump uniswapx-sdk (#7023)
* bump uniswapx-sdk

* add feature flag

* use undefined if feature flag is off

* move to top level config

* remove unused variable
2023-07-27 12:16:17 -04:00
Brendan Wong
b58d4b72ab fix: update token tests for cloudflare functions (#7026)
fix: update tests
2023-07-26 17:58:56 -04:00
Zach Pomerantz
a2d98607ea fix: use redirect for landing (#6993)
* fix: use redirect for landing

* chore: rm console.log
2023-07-26 13:24:33 -07:00
eddie
27d9152949 fix: add amplitude event for uniswapX orders failing to post (#6977) 2023-07-26 12:10:22 -07:00
Zach Pomerantz
3cb069283c build: fix promotion sha (#7022) 2023-07-26 11:51:49 -07:00
Brendan Wong
4d084d0055 fix: mock graphql pool queries in e2e tests (#7018)
mock graphql query
2023-07-26 13:34:17 -04:00
Jordan Frankfurt
4b39dfbd69 fix: update safety check cache time (#7005) 2023-07-26 10:14:54 -05:00
eddie
6c5c1c0032 fix: remove duplicate hook for permit2 approvals (#6999)
* fix: remove duplicate hook for permit2 approvals

* fix: address comments
2023-07-25 14:08:25 -07:00
Brendan Wong
22112c763c feat: automated testing for cloud functions (#6931)
* 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

* cleanup return values

* Create README.md

* Revert "Create README.md"

This reverts commit 7a91c98d38.

* First round of feedback

* comments

* 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

* review changes
2023-07-25 15:12:13 -04:00
Brendan Wong
b230cb62f4 feat: readme for cloud functions (#6914)
* 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

* cleanup return values

* Create README.md

* Revert "Create README.md"

This reverts commit 7a91c98d38.

* Revert "Revert "Create README.md""

This reverts commit d0a4f5b951.

* add docs for html rewriter

* Update README.md

* remove previously removed files
2023-07-25 15:06:28 -04:00
Nate Wienert
9f71e384b2 fix: use deferred value to avoid suspense issues with inputting text (#6996)
fix: use deferred value to avoid suspense issues with inputting text during supsended render causing errors
2023-07-25 06:57:32 -10:00
Nate Wienert
8592a4a54d fix: flaky test covering searchbar (#7002)
fix flaky test covering searchbar
2023-07-24 15:06:10 -10:00
Charles Bachmeier
ccc94fdfc2 feat: update infura fork block height for cypress tests to be dynamic (#6998)
* import creation block from ur sdk

* update ur sdk
2023-07-24 18:00:29 -07:00
Zach Pomerantz
4537a4dc94 fix: display already-loaded imgs with no transition (#6990) 2023-07-24 16:21:05 -07:00
Jack Short
db0cb41b61 fix: fixing price range filter passing as number (#6994) 2023-07-24 17:51:04 -04:00
eddie
4ced70f737 fix: use 100vh to position bottom sheet (#6989) 2023-07-24 10:02:48 -07:00
930 changed files with 168384 additions and 26820 deletions

1
.env
View File

@@ -5,6 +5,7 @@ REACT_APP_AWS_API_REGION="us-east-2"
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf"
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://magical-alien-tab.quiknode.pro/669e87e569a8277d3fbd9e202f9df93189f19f4c"
REACT_APP_MOONPAY_API="https://api.moonpay.com"
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=staging"
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"

View File

@@ -12,4 +12,5 @@ REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
REACT_APP_SENTRY_ENABLED=true
REACT_APP_SENTRY_TRACES_SAMPLE_RATE=0.00003
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://ultra-blue-flower.quiknode.pro/770b22d5f362c537bc8fe19b034c45b22958f880"
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3?source=uniswap"

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,20 @@ module.exports = {
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'@typescript-eslint/no-restricted-imports': [
'error',
{
...restrictedImports,
paths: [
...restrictedImports.paths,
{
name: '@uniswap/smart-order-router',
message: 'Only import types, unless you are in the client-side SOR, to preserve lazy-loading.',
allowTypeImports: true,
},
],
},
],
'import/no-restricted-paths': [
'error',
{
@@ -55,6 +70,13 @@ module.exports = {
],
},
],
'no-restricted-syntax': [
'error',
{
selector: ':matches(ExportAllDeclaration)',
message: 'Barrel exports bloat the bundle size by preventing tree-shaking.',
},
],
},
},
{

2
.github/CODEOWNERS vendored
View File

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

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

@@ -64,7 +64,7 @@ jobs:
- name: Add CODEOWNERS
run: |
echo '@uniswap/web-admins' > CODEOWNERS
echo '* @uniswap/web-admins' > CODEOWNERS
git add CODEOWNERS
git commit -m 'ci: add global CODEOWNERS'

View File

@@ -21,7 +21,7 @@ jobs:
const statuses = await github.rest.repos.listCommitStatusesForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.head_commit.sha
ref: context.sha
})
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
core.info('Status: ' + status)

View File

@@ -11,6 +11,7 @@ on:
- main
- releases/staging
pull_request:
workflow_dispatch:
jobs:
lint:
@@ -18,7 +19,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 +36,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 +65,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,25 +82,40 @@ 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
cypress-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cypress-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cypress-tsc-
- run: yarn typecheck:cypress
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# Allows for parallel re-runs of cypress tests without re-building.
cypress-rerun:
runs-on: ubuntu-latest
@@ -107,7 +123,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
@@ -116,7 +132,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') }}
@@ -126,10 +142,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 }}
@@ -158,17 +174,66 @@ 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: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-tsc-
- run: yarn typecheck:cloud
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cloud typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
cloud-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-jest-
# Ignore start:cloud output so it doesn't flood the test output.
# Only use 1 worker for testing, as the other is used to run start:cloud (the proxy server under test).
- run: yarn start-server-and-test 'yarn start:cloud >/dev/null' 3000 'yarn test:cloud --coverage --maxWorkers=1'
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: cloud-tests
pre:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
runs-on: ubuntu-latest
@@ -188,7 +253,7 @@ jobs:
post:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
needs: [pre, lint, typecheck, deps-tests, unit-tests, cypress-test-matrix]
needs: [pre, lint, typecheck, deps-tests, unit-tests, cypress-test-matrix, cloud-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6.4.1
@@ -209,4 +274,5 @@ jobs:
${{ needs.typecheck.result == 'success' }} &&
${{ needs.deps-tests.result == 'success' }} &&
${{ needs.unit-tests.result == 'success' }} &&
${{ needs.cypress-test-matrix.result == 'success' }}
${{ needs.cypress-test-matrix.result == 'success' }} &&
${{ needs.cloud-tests.result == 'success' }}

4
.gitignore vendored
View File

@@ -19,6 +19,8 @@ schema.graphql
# testing
/coverage
/cache
/functions/coverage
/.swc
# builds
/build
@@ -51,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

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

View File

@@ -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,13 @@ ignore:
- "**/instrumented/**/*"
- "**/styles/**/*"
- "styles/**/*"
- "**/styled.tsx"
- "**/constants/**/*"
- "constants/**/*"
- "src/dev/*"
coverage:
status:
# Omit merging unit/e2e reports into the defaults, as it is nonsensical.
project: off
patch: off
@@ -25,20 +26,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

@@ -1,5 +1,5 @@
overrideExisting: true
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3?source=uniswap'
generates:
./src/graphql/thegraph/schema/schema.graphql:
plugins:

View File

@@ -5,12 +5,14 @@ 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 { 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 +23,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 +45,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 +68,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 +86,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) {
@@ -161,19 +130,32 @@ module.exports = {
},
})
// Retain source maps for node_modules packages:
webpackConfig.module.rules[0] = {
...webpackConfig.module.rules[0],
exclude: /node_modules/,
}
// 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
})
// Run terser compression on node_modules before tree-shaking, so that tree-shaking is more effective.
// This works by eliminating dead code, so that webpack can identify unused imports and tree-shake them;
// it is only necessary for node_modules - it is done through linting for our own source code -
// see https://medium.com/engineering-housing/dead-code-elimination-and-tree-shaking-at-housing-part-1-307a94b30f23#7e03:
webpackConfig.module.rules.push({
enforce: 'post',
test: /node_modules.*\.(js)$/,
loader: path.join(__dirname, 'scripts/terser-loader.js'),
options: { compress: true, mangle: false },
})
// Configure webpack optimization:
webpackConfig.optimization = Object.assign(
webpackConfig.optimization,
@@ -192,16 +174,8 @@ module.exports = {
: {}
)
// Configure webpack caching:
webpackConfig.cache = Object.assign(webpackConfig.cache, {
cacheDirectory: getCacheDirectory('webpack'),
})
// Ignore failed source mappings to avoid spamming the console.
// Source mappings for a package will fail if the package does not provide them, but the build will still succeed,
// so it is unnecessary (and bothersome) to log it. This should be turned off when debugging missing sourcemaps.
// See https://webpack.js.org/loaders/source-map-loader#ignoring-warnings.
webpackConfig.ignoreWarnings = [/Failed to parse source map/]
// Configure webpack resolution. webpackConfig.cache is unused with swc-loader, but the resolver can still cache:
webpackConfig.resolve = Object.assign(webpackConfig.resolve, { unsafeCache: true })
return webpackConfig
},

View File

@@ -1,30 +1,16 @@
import codeCoverageTask from '@cypress/code-coverage/task'
import { defineConfig } from 'cypress'
import { setupHardhatEvents } from 'cypress-hardhat'
import { unlinkSync } from 'fs'
export default defineConfig({
projectId: 'yp82ef',
defaultCommandTimeout: 24000, // 2x average block time
chromeWebSecurity: false,
experimentalMemoryManagement: true, // better memory management, see https://github.com/cypress-io/cypress/pull/25462
retries: { runMode: 2 },
videoCompression: false,
retries: { runMode: process.env.CYPRESS_RETRIES ? +process.env.CYPRESS_RETRIES : 2 },
video: false, // GH provides 2 CPUs, and cypress video eats one up, see https://github.com/cypress-io/cypress/issues/20468#issuecomment-1307608025
e2e: {
async setupNodeEvents(on, config) {
await setupHardhatEvents(on, config)
codeCoverageTask(on, config)
// Delete recorded videos for specs that passed without flakes.
on('after:spec', async (spec, results) => {
if (results && results.video) {
// If there were no failures (including flakes), delete the recorded video.
if (!results.tests?.some((test) => test.attempts.some((attempt) => attempt?.state === 'failed'))) {
unlinkSync(results.video)
}
}
})
return config
},
baseUrl: 'http://localhost:3000',

View File

@@ -52,7 +52,7 @@ This becomes more relevant as you work with data on the blockchain, as you'll ne
```
cy.hardhat().then(async (hardhat) => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
@@ -68,7 +68,7 @@ cy.hardhat().then(async (hardhat) => {
```
```
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
@@ -87,7 +87,6 @@ cy.hardhat().then(async (hardhat) => {
### Working with the blockchain (ie hardhat)
Our tests use a local hardhat node to simulate blockchain transactions. This can be accessed with `cy.hardhat().then((hardhat) => ...)`.
Currently, tests using hardhat must opt-in in when they load the page: `cy.visit('/swap', { ethereum: 'hardhat' })`. This will not be necessary once we've totally migrated to hardhat.
By default, automining is turned on, so that any transaction that you send to the blockchain is mined immediately. If you want to assert on intermediate states (between sending a transaction and mining it), you can turn off automining: `cy.hardhat({ automine: false })`.

View File

@@ -4,61 +4,103 @@ import { aliasQuery, hasQuery } from '../utils/graphql-test-utils'
describe('Add Liquidity', () => {
beforeEach(() => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req) => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3?source=uniswap', (req) => {
aliasQuery(req, 'feeTierDistribution')
})
})
it('loads the two correct tokens', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/500')
it('loads the token pair', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH/500')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
cy.contains('0.05% fee tier')
})
it('does not crash if ETH is duplicated', () => {
cy.visit('/add/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'ETH')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
it('does not crash if token is duplicated', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'UNI')
})
it.skip('token not in storage is loaded', () => {
cy.visit('/add/0x07865c6e87b9f70255377e024ace6630c1eaa37f/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'USDC')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'UNI')
})
it.skip('single token can be selected', () => {
cy.visit('/add/0x07865c6e87b9f70255377e024ace6630c1eaa37f')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'USDC')
it('single token can be selected', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
})
it.skip('loads fee tier distribution', () => {
it('loads fee tier distribution', () => {
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
if (hasQuery(req, 'FeeTierDistributionQuery')) {
req.alias = 'FeeTierDistributionQuery'
cy.intercept(
'POST',
'/subgraphs/name/uniswap/uniswap-v3?source=uniswap',
(req: CyHttpMessages.IncomingHttpRequest) => {
if (hasQuery(req, 'FeeTierDistribution')) {
req.alias = 'FeeTierDistribution'
req.reply({
body: {
data: {
...feeTierDistribution,
req.reply({
body: {
data: {
...feeTierDistribution,
},
},
},
headers: {
'access-control-allow-origin': '*',
},
})
headers: {
'access-control-allow-origin': '*',
},
})
}
}
})
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6')
cy.wait('@FeeTierDistributionQuery')
)
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH')
cy.wait('@FeeTierDistribution')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40%')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40% select')
})
})
it('disables increment and decrement until initial prices are inputted', () => {
// ETH / BITCOIN pool (0.05% tier not created)
cy.visit('/add/ETH/0x72e4f9F808C49A2a61dE9C5896298920Dc4EEEa9/500')
// Set starting price in order to enable price range step counters
cy.get('.start-price-input').type('1000')
// Min Price increment / decrement buttons should be disabled
cy.get('[data-testid="increment-price-range"]').eq(0).should('be.disabled')
cy.get('[data-testid="decrement-price-range"]').eq(0).should('be.disabled')
// Enter min price, which should enable the buttons
cy.get('.rate-input-0').eq(0).type('900').blur()
cy.get('[data-testid="increment-price-range"]').eq(0).should('not.be.disabled')
cy.get('[data-testid="decrement-price-range"]').eq(0).should('not.be.disabled')
// Repeat for Max Price step counter
cy.get('[data-testid="increment-price-range"]').eq(1).should('be.disabled')
cy.get('[data-testid="decrement-price-range"]').eq(1).should('be.disabled')
// Enter max price, which should enable the buttons
cy.get('.rate-input-0').eq(1).type('1100').blur()
cy.get('[data-testid="increment-price-range"]').eq(1).should('not.be.disabled')
cy.get('[data-testid="decrement-price-range"]').eq(1).should('not.be.disabled')
})
it('allows full range selection on new pool creation', () => {
// ETH / BITCOIN pool (0.05% tier not created)
cy.visit('/add/ETH/0x72e4f9F808C49A2a61dE9C5896298920Dc4EEEa9/500')
// Set starting price in order to enable price range step counters
cy.get('.start-price-input').type('1000')
cy.get('[data-testid="set-full-range"]').click()
// Check that the min price is 0 and the max price is infinity
cy.get('.rate-input-0').eq(0).should('have.value', '0')
cy.get('.rate-input-0').eq(1).should('have.value', '∞')
// Increment and decrement buttons are disabled when full range is selected
cy.get('[data-testid="increment-price-range"]').eq(0).should('be.disabled')
cy.get('[data-testid="decrement-price-range"]').eq(0).should('be.disabled')
cy.get('[data-testid="increment-price-range"]').eq(1).should('be.disabled')
cy.get('[data-testid="decrement-price-range"]').eq(1).should('be.disabled')
// Check that url params were added
cy.url().then((url) => {
const params = new URLSearchParams(url)
const minPrice = params.get('minPrice')
const maxPrice = params.get('maxPrice')
// Note: although 0 and ∞ displayed, actual values in query are ticks at limit
return minPrice && maxPrice && parseFloat(minPrice) < parseFloat(maxPrice)
})
})
})

View File

@@ -1,9 +1,8 @@
import { FeatureFlag } from '../../src/featureFlags'
import { getTestSelector } from '../utils'
describe('Buy Crypto Modal', () => {
it('should open and close', () => {
cy.visit('/', { featureFlags: [FeatureFlag.fiatOnRampButtonOnSwap] })
cy.visit('/')
// Open the fiat onramp modal
cy.get(getTestSelector('buy-fiat-button')).click()
@@ -16,7 +15,7 @@ describe('Buy Crypto Modal', () => {
it('should open and close, mobile viewport', () => {
cy.viewport('iphone-6')
cy.visit('/', { featureFlags: [FeatureFlag.fiatOnRampButtonOnSwap] })
cy.visit('/')
// Open the fiat onramp modal
cy.get(getTestSelector('buy-fiat-button')).click()

View File

@@ -39,4 +39,30 @@ describe('Landing Page', () => {
cy.get(getTestSelector('pool-nav-link')).last().click()
cy.url().should('include', '/pools')
})
it('does not render uk compliance banner in US', () => {
cy.visit('/swap')
cy.contains('UK disclaimer').should('not.exist')
})
it('renders uk compliance banner in uk', () => {
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
const requestBody = JSON.stringify(req.body)
const byteSize = new Blob([requestBody]).size
req.alias = 'amplitude'
req.reply(
JSON.stringify({
code: 200,
server_upload_time: Date.now(),
payload_size_bytes: byteSize,
events_ingested: req.body.events.length,
}),
{
'origin-country': 'GB',
}
)
})
cy.visit('/swap')
cy.contains('UK disclaimer')
})
})

View File

@@ -2,30 +2,48 @@ import { getTestSelector } from '../../utils'
describe('Mini Portfolio account drawer', () => {
beforeEach(() => {
cy.intercept(/api.uniswap.org\/v1\/graphql/, cy.spy().as('gqlSpy'))
cy.visit('/swap', { ethereum: 'hardhat' })
const portfolioSpy = cy.spy().as('portfolioSpy')
cy.intercept(/api.uniswap.org\/v1\/graphql/, (req) => {
if (req.body.operationName === 'PortfolioBalances') {
portfolioSpy(req)
}
})
cy.visit('/swap')
})
it('fetches balances when account button is first hovered', () => {
// The balances should not be fetched before the account button is hovered
cy.get('@gqlSpy').should('not.have.been.called')
cy.get('@portfolioSpy').should('not.have.been.called')
// Balances should have been fetched once after hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@gqlSpy').should('have.been.calledOnce')
cy.get('@portfolioSpy').should('have.been.calledOnce')
})
it('should not re-fetch balances on second hover', () => {
// The balances should not be fetched before the account button is hovered
cy.get('@portfolioSpy').should('not.have.been.called')
// Balances should have been fetched once after hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@portfolioSpy').should('have.been.calledOnce')
// Balances should not be refetched upon second hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@gqlSpy').should('have.been.calledOnce')
cy.get('@portfolioSpy').should('have.been.calledOnce')
})
it('should not re-fetch balances when the account drawer is opened', () => {
// The balances should not be fetched before the account button is hovered
cy.get('@portfolioSpy').should('not.have.been.called')
// Balances should have been fetched once after hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@portfolioSpy').should('have.been.calledOnce')
// Balances should not be refetched upon opening drawer
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('@gqlSpy').should('have.been.calledOnce')
// Balances should not be refetched upon closing & reopening drawer
cy.get(getTestSelector('close-account-drawer')).click()
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('@gqlSpy').should('have.been.calledOnce')
cy.get('@portfolioSpy').should('have.been.calledOnce')
})
it('fetches account information', () => {
@@ -35,16 +53,17 @@ describe('Mini Portfolio account drawer', () => {
// Verify that wallet state loads correctly
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Tokens')
cy.get(getTestSelector('mini-portfolio-page')).contains('Hidden (201)')
cy.get(getTestSelector('mini-portfolio-page')).contains('Hidden (197)')
cy.intercept(/graphql/, { fixture: 'mini-portfolio/nfts.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('I Got Plenty')
cy.intercept(/graphql/, { fixture: 'mini-portfolio/pools.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Pools').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('No pools yet')
cy.intercept(/graphql/, { fixture: 'mini-portfolio/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')
})
@@ -76,4 +95,36 @@ describe('Mini Portfolio account drawer', () => {
})
})
})
it('fetches ENS name', () => {
cy.hardhat().then(() => {
const haydenAccount = '0x50EC05ADe8280758E2077fcBC08D878D4aef79C3'
const haydenENS = 'hayden.eth'
// Opens the account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// Simulate wallet changing to Hayden's account
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
// Hayden's ENS name should be shown
cy.contains(haydenENS).should('exist')
// Close account drawer
cy.get(getTestSelector('close-account-drawer')).click()
// Switch chain to Polygon
cy.get(getTestSelector('chain-selector')).eq(1).click()
cy.contains('Polygon').click()
//Reopen account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// Simulate wallet changing to Hayden's account
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
// Hayden's ENS name should be shown
cy.contains(haydenENS).should('exist')
})
})
})

View File

@@ -94,9 +94,7 @@ describe('mini-portfolio activity history', () => {
})
it('should deduplicate activity history by nonce', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' }).hardhat({
automine: false,
})
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`).hardhat({ automine: false })
// Input swap info.
cy.get('#swap-currency-input .token-amount-input').clear().type('1').should('have.value', '1')

View File

@@ -1,7 +1,6 @@
import { getTestSelector } from '../utils'
const PUDGY_COLLECTION_ADDRESS = '0xbd3531da5cf5857e7cfaa92426877b022e612cf8'
const BONSAI_COLLECTION_ADDRESS = '0xec9c519d49856fd2f8133a0741b4dbe002ce211b'
describe('Testing nfts', () => {
it('should load nft leaderboard', () => {
@@ -13,7 +12,7 @@ describe('Testing nfts', () => {
})
it('should load pudgy penguin collection page', () => {
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-collection-asset')).should('exist')
cy.get(getTestSelector('nft-collection-filter-buy-now')).should('not.exist')
cy.get(getTestSelector('nft-filter')).first().click()
@@ -21,13 +20,13 @@ describe('Testing nfts', () => {
})
it('should be able to navigate to activity', () => {
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-activity')).first().click()
cy.get(getTestSelector('nft-activity-row')).should('exist')
})
it('should go to the details page', () => {
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-filter')).first().click()
cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
cy.get(getTestSelector('nft-collection-asset')).first().click()
@@ -38,7 +37,10 @@ describe('Testing nfts', () => {
})
it('should toggle buy now on details page', () => {
cy.visit(`#/nfts/asset/${BONSAI_COLLECTION_ADDRESS}/7580`)
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-filter')).first().click()
cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
cy.get(getTestSelector('nft-collection-asset')).first().click()
cy.get(getTestSelector('nft-details-description-text')).should('exist')
cy.get(getTestSelector('nft-details-description')).click()
cy.get(getTestSelector('nft-details-description-text')).should('not.exist')
@@ -50,7 +52,7 @@ describe('Testing nfts', () => {
cy.visit('/')
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
cy.get(getTestSelector('mini-portfolio-nft')).click()
cy.get(getTestSelector('mini-portfolio-nft')).first().click()
cy.get(getTestSelector('mini-portfolio-navbar')).should('not.be.visible')
})
})

View File

@@ -17,9 +17,7 @@ function initiateSwap() {
describe('Permit2', () => {
function setupInputs(inputToken: Token, outputToken: Token) {
// Sets up a swap between inputToken and outputToken.
cy.visit(`/swap/?inputCurrency=${inputToken.address}&outputCurrency=${outputToken.address}`, {
ethereum: 'hardhat',
})
cy.visit(`/swap/?inputCurrency=${inputToken.address}&outputCurrency=${outputToken.address}`)
cy.get('#swap-currency-input .token-amount-input').type('0.01')
}
@@ -29,7 +27,7 @@ describe('Permit2', () => {
cy.hardhat()
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: inputToken }))
.then((allowance) => {
Cypress.log({ name: `Token allowace: ${allowance.toString()}` })
Cypress.log({ name: `Token allowance: ${allowance.toString()}` })
cy.wrap(allowance).should('deep.equal', MaxUint256)
})
}
@@ -39,7 +37,7 @@ describe('Permit2', () => {
cy.hardhat()
.then(({ approval, wallet }) => approval.getPermit2Allowance({ owner: wallet, token: inputToken }))
.then((allowance) => {
Cypress.log({ name: `Permit2 allowace: ${allowance.amount.toString()}` })
Cypress.log({ name: `Permit2 allowance: ${allowance.amount.toString()}` })
cy.wrap(allowance.amount).should('deep.equal', MaxUint160)
// Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds.
const THIRTY_DAYS_SECONDS = 2_592_000
@@ -77,8 +75,9 @@ describe('Permit2', () => {
cy.contains('Allow DAI to be used for swapping')
cy.wait('@eth_signTypedData_v4')
cy.wait('@eth_sendRawTransaction')
cy.contains('Swap submitted')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
@@ -101,7 +100,7 @@ describe('Permit2', () => {
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
})
@@ -144,7 +143,27 @@ describe('Permit2', () => {
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
})
it('swaps USDT with existing permit, and existing and sufficient token approval', () => {
cy.hardhat().then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDT, 2e6))
await hardhat.mine()
await hardhat.approval.setTokenAllowanceForPermit2({ owner: hardhat.wallet, token: USDT }, 1e6)
await hardhat.mine()
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: USDT })
await hardhat.mine()
})
setupInputs(USDT, USDC_MAINNET)
cy.get('#swap-currency-input .token-amount-input').clear().type('1')
initiateSwap()
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
})
})
@@ -160,7 +179,7 @@ describe('Permit2', () => {
initiateSwap()
// Verify transaction
cy.contains('Success')
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
})
@@ -199,7 +218,7 @@ describe('Permit2', () => {
cy.contains('Confirm swap').click()
// Verify permit2 approval
cy.contains('Success')
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
@@ -233,7 +252,7 @@ describe('Permit2', () => {
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
cy.contains('Success')
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
@@ -251,7 +270,7 @@ describe('Permit2', () => {
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
cy.contains('Success')
cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})

View File

@@ -1,25 +1,33 @@
import { ChainId, MaxUint256, UNI_ADDRESSES } from '@uniswap/sdk-core'
const UNI_MAINNET = UNI_ADDRESSES[ChainId.MAINNET]
describe('Remove Liquidity', () => {
it('eth remove', () => {
cy.visit('/remove/v2/ETH/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
it('loads the token pair in v2', () => {
cy.visit(`/remove/v2/ETH/${UNI_MAINNET}`)
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'UNI')
})
it('eth remove swap order', () => {
cy.visit('/remove/v2/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'UNI')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
it('loads the token pair in v3', () => {
cy.visit(`/remove/1`)
cy.get('#remove-liquidity-tokens').should('contain.text', 'UNI/ETH')
cy.get('#remove-pooled-tokena-symbol').should('contain.text', 'Pooled UNI')
cy.get('#remove-pooled-tokenb-symbol').should('contain.text', 'Pooled ETH')
})
it('loads the two correct tokens', () => {
cy.visit('/remove/v2/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'UNI')
})
it('should redirect to error pages if pool does not exist', () => {
// Duplicate-token v2 pools redirect to position unavailable
cy.visit(`/remove/v2/ETH/ETH`)
cy.contains('Position unavailable')
it('does not crash if ETH is duplicated', () => {
cy.visit('/remove/v2/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
// Single-token pools don't exist
cy.visit('/remove/v2/ETH')
cy.url().should('match', /\/not-found/)
// Nonexistent v3 pool
cy.visit(`/remove/${MaxUint256}`)
cy.contains('Position unavailable')
})
})

View File

@@ -1,15 +1,13 @@
import { BigNumber } from '@ethersproject/bignumber'
import { ChainId } from '@uniswap/sdk-core'
import { CurrencyAmount } from '@uniswap/sdk-core'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
import { DAI, USDC_MAINNET } from '../../../src/constants/tokens'
import { getBalance, getTestSelector } from '../../utils'
const UNI_MAINNET = UNI[ChainId.MAINNET]
describe('Swap errors', () => {
it('wallet rejection', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.hardhat().then((hardhat) => {
// Stub the wallet to reject any transaction.
cy.stub(hardhat.wallet, 'sendTransaction').log(false).rejects(new Error('user cancelled'))
@@ -30,7 +28,7 @@ describe('Swap errors', () => {
})
it('transaction past deadline', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {
// Enter amount to swap
@@ -64,10 +62,19 @@ describe('Swap errors', () => {
})
})
it.skip('slippage failure', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${UNI_MAINNET.address}`, { ethereum: 'hardhat' })
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {
it('slippage failure', () => {
cy.visit(`/swap?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
cy.hardhat({ automine: false }).then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 500e6))
await hardhat.mine()
await Promise.all([
hardhat.approval.setTokenAllowanceForPermit2({ owner: hardhat.wallet, token: USDC_MAINNET }),
hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: USDC_MAINNET }),
])
await hardhat.mine()
})
getBalance(DAI).then((initialBalance) => {
// Gas estimation fails for this transaction (that would normally fail), so we stub it.
cy.hardhat().then((hardhat) => {
const send = cy.stub(hardhat.provider, 'send').log(false)
@@ -90,7 +97,9 @@ describe('Swap errors', () => {
cy.contains('Confirm swap').click()
cy.wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
cy.contains('Swap submitted')
cy.get(getTestSelector('confirmation-close-icon')).click()
if (i === 0) {
cy.get(getTestSelector('confirmation-close-icon')).click()
}
}
cy.get(getTestSelector('web3-status-connected')).should('contain', '2 Pending')
@@ -98,10 +107,13 @@ describe('Swap errors', () => {
cy.hardhat().then((hardhat) => hardhat.mine())
cy.wait('@eth_getTransactionReceipt')
// Verify transaction did not occur
cy.contains('Swap failed')
// Verify only 1 transaction occurred
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Swapped')
cy.get(getTestSelector('popups')).contains('Swap failed')
getBalance(UNI_MAINNET).should('eq', initialBalance)
getBalance(DAI).should('be.closeTo', initialBalance + 200, 1)
})
})
})

View File

@@ -1,16 +1,34 @@
import { FeatureFlag } from '../../../src/featureFlags'
import { getTestSelector } from '../../utils'
describe('Swap settings', () => {
it('Opens and closes the settings menu', () => {
cy.visit('/swap', { featureFlags: [FeatureFlag.uniswapXEnabled], ethereum: 'hardhat' })
cy.visit('/swap')
cy.contains('Settings').should('not.exist')
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Max slippage').should('exist')
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')
cy.contains('Local routing').should('exist')
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Settings').should('not.exist')
})
it('should open the mobile settings menu', () => {
// Set viewport to iPhone 6
cy.viewport('iphone-6')
cy.visit('/swap')
// Click the button to open the settings dialog
cy.get(getTestSelector('open-settings-dialog-button')).click({ waitForAnimations: true })
// Verify the mobile settings menu and its contents
cy.get(getTestSelector('mobile-settings-menu'))
.should('exist')
.within(() => {
cy.contains('Max. slippage').should('exist')
cy.contains('UniswapX').should('exist')
cy.contains('Transaction deadline').should('exist')
cy.get(getTestSelector('mobile-settings-close')).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'
@@ -52,7 +53,7 @@ describe('Swap', () => {
})
it('swaps ETH for USDC', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
cy.visit('/swap')
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {
// Select USDC
@@ -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

@@ -0,0 +1,383 @@
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
import { FeatureFlag } from 'featureFlags'
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}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
})
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}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
})
it('can swap exact-in trades using uniswapX', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// 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('can swap exact-out trades using uniswapX', () => {
// Setup a swap
cy.get('#swap-currency-output .token-amount-input').type('300')
cy.contains('Try it now').click()
// 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}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
})
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}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
})
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

@@ -6,9 +6,7 @@ const WETH = WETH9[ChainId.MAINNET]
describe('Swap wrap', () => {
beforeEach(() => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${WETH.address}`, { ethereum: 'hardhat' }).hardhat({
automine: false,
})
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${WETH.address}`).hardhat({ automine: false })
})
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {

View File

@@ -48,40 +48,33 @@ describe('Token details', () => {
})
it('token with warning and low trading volume should have all information populated', () => {
// Shiba predator token, low trading volume and also has warning modal
cy.visit('/tokens/ethereum/0xa71d0588EAf47f12B13cF8eC750430d21DF04974')
// Null token created for this test, 0 trading volume and has warning modal
cy.visit('/tokens/ethereum/0x1eFBB78C8b917f67986BcE54cE575069c0143681')
// Should have missing price chart when price unavailable (expected for this token)
if (cy.get('[data-cy="chart-header"]').contains('Price Unavailable')) {
if (cy.get('[data-cy="chart-header"]').contains('Price unavailable')) {
cy.get('[data-cy="missing-chart"]').should('exist')
}
// Stats should have: TVL, 24H Volume, 52W low, 52W high
cy.get(getTestSelector('token-details-stats')).should('exist')
cy.get(getTestSelector('token-details-stats')).within(() => {
cy.get('[data-cy="tvl"]').should('exist')
cy.get('[data-cy="volume-24h"]').should('exist')
cy.get('[data-cy="52w-low"]').should('exist')
cy.get('[data-cy="52w-high"]').should('exist')
})
// Stats should not exist
cy.get(getTestSelector('token-details-stats')).should('not.exist')
// About section should have description of token
cy.get(getTestSelector('token-details-about-section')).should('exist')
cy.contains('QOM is the Shiba Predator').should('exist')
cy.contains('No token information available').should('exist')
// Links section should link out to Etherscan, More analytics, Website, Twitter
// Links section should link out to Etherscan, More analytics
cy.get('[data-cy="resources-container"]').within(() => {
cy.contains('Etherscan')
.should('have.attr', 'href')
.and('include', 'etherscan.io/address/0xa71d0588EAf47f12B13cF8eC750430d21DF04974')
.and('include', 'etherscan.io/address/0x1eFBB78C8b917f67986BcE54cE575069c0143681')
cy.contains('More analytics')
.should('have.attr', 'href')
.and('include', 'info.uniswap.org/#/tokens/0xa71d0588EAf47f12B13cF8eC750430d21DF04974')
cy.contains('Website').should('have.attr', 'href').and('include', 'qom')
cy.contains('Twitter').should('have.attr', 'href').and('include', 'twitter.com/ShibaPredator1')
.and('include', 'info.uniswap.org/#/tokens/0x1eFBB78C8b917f67986BcE54cE575069c0143681')
})
// Contract address should be displayed
cy.contains('0xa71d0588EAf47f12B13cF8eC750430d21DF04974').should('exist')
cy.contains('0x1eFBB78C8b917f67986BcE54cE575069c0143681').should('exist')
// Warning label should show if relevant ([spec](https://www.notion.so/3f7fce6f93694be08a94a6984d50298e))
cy.get('[data-cy="token-safety-message"]')
@@ -93,9 +86,7 @@ describe('Token details', () => {
beforeEach(() => {
// On mobile widths, we just link back to /swap instead of rendering the swap component.
cy.viewport(1200, 800)
cy.visit(`/tokens/ethereum/${UNI_MAINNET.address}`, {
ethereum: 'hardhat',
}).then(() => {
cy.visit(`/tokens/ethereum/${UNI_MAINNET.address}`).then(() => {
cy.wait('@eth_blockNumber')
cy.scrollTo('top')
})
@@ -110,7 +101,7 @@ describe('Token details', () => {
it('should automatically navigate to the new TDP', () => {
cy.get(`#swap-currency-output .open-currency-select-button`).click()
cy.contains('WETH').click()
cy.get('[data-reach-dialog-content]').contains('WETH').click()
cy.url().should('include', `${WETH9[1].address}`)
cy.url().should('not.include', `${UNI_MAINNET.address}`)
})
@@ -145,7 +136,7 @@ describe('Token details', () => {
})
it('should show a L2 token even if the user is connected to a different network', () => {
cy.visit('/tokens', { ethereum: 'hardhat' })
cy.visit('/tokens')
cy.get(getTestSelector('tokens-network-filter-selected')).click()
cy.get(getTestSelector('tokens-network-filter-option-arbitrum')).click()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Arbitrum')

View File

@@ -11,6 +11,10 @@ describe('Token explore filter', () => {
function searchFor(filter: string) {
cy.get('[data-cy="explore-tokens-search-input"]').clear().type(filter).type('{enter}')
// wait for it to finish the filtered render
cy.get('[data-cy="token-name"]').first().contains(filter, {
matchCase: false,
})
}
it('should filter correctly by dao search term', () => {
@@ -18,7 +22,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

@@ -36,7 +36,7 @@ describe('Token explore', () => {
.then(function ($elem) {
cy.wrap($elem.text()).as('yearlyEthVol')
})
expect(cy.get('@dailyEthVol')).to.not.equal(cy.get('@yearlyEthVol'))
cy.get('@dailyEthVol').should('not.equal', cy.get('@yearlyEthVol'))
})
it('should navigate to token detail page when row clicked', () => {
@@ -69,8 +69,6 @@ describe('Token explore', () => {
cy.get(getTestSelector('tokens-network-filter-selected')).click()
cy.get(getTestSelector('tokens-network-filter-option-optimism')).click()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism')
cy.reload()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism')
cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', 'Ethereum')
})
})

View File

@@ -1,3 +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
@@ -6,75 +13,60 @@ describe('Universal search bar', () => {
beforeEach(() => {
cy.visit('/')
openSearch()
})
function getSearchBar() {
return cy.get('[data-cy="search-bar-input"]').last()
}
it('should yield clickable result for regular token or nft collection search term', () => {
// Search for uni token by name.
it('should yield clickable result that is then added to recent searches', () => {
// Search for UNI token by name.
openSearch()
getSearchBar().clear().type('uni')
cy.get('[data-cy="searchbar-token-row-UNI"]')
cy.get(getTestSelector(`searchbar-token-row-ETHEREUM-${UNI_ADDRESS}`))
.should('contain.text', 'Uniswap')
.and('contain.text', 'UNI')
.and('contain.text', '$')
.and('contain.text', '%')
cy.get('[data-cy="searchbar-token-row-UNI"]').first().click()
cy.location('hash').should('equal', '#/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
})
it('should go to the selected result when recent results are shown', () => {
// Search for uni token by name.
getSearchBar().type('uni')
cy.get('[data-cy="searchbar-token-row-UNI"]')
// Clear search
getSearchBar().clear()
// Close search
getSearchBar().type('{esc}')
.click()
cy.location('pathname').should('equal', '/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
openSearch()
// Search a different token by name.
getSearchBar().type('eth')
// Validate ETH result now exists.
cy.get('[data-cy="searchbar-token-row-ETH"]')
// Hit enter
getSearchBar().type('{enter}')
// Validate we went to ethereum address
cy.url().should('contain', 'tokens/ethereum/NATIVE')
})
it.skip('should show recent tokens and popular tokens with empty search term', () => {
cy.get('[data-cy="magnifying-icon"]')
.parent()
.then(($navIcon) => {
$navIcon.click()
})
// Recently searched UNI token should exist.
getSearchBar().clear()
cy.get('[data-cy="searchbar-dropdown"]')
.contains('[data-cy="searchbar-dropdown"]', 'Recent searches')
.find('[data-cy="searchbar-token-row-UNI"]')
cy.get(getTestSelector('searchbar-dropdown'))
.contains(getTestSelector('searchbar-dropdown'), 'Recent searches')
.find(getTestSelector(`searchbar-token-row-ETHEREUM-${UNI_ADDRESS}`))
.should('exist')
// Most popular 3 tokens should be shown.
cy.get('[data-cy="searchbar-dropdown"]')
.contains('[data-cy="searchbar-dropdown"]', 'Popular tokens')
.find('[data-cy^="searchbar-token-row"]')
.its('length')
.should('be.eq', 3)
})
it.skip('should show blocked badge when blocked token is searched for', () => {
// Search for mTSLA, which is a blocked token.
getSearchBar().clear().type('mtsla')
cy.get('[data-cy="searchbar-token-row-mTSLA"]').find('[data-cy="blocked-icon"]').should('exist')
})
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-ETHEREUM-NATIVE'))
// Validate that we go to the searched/selected result.
cy.get(getTestSelector('searchbar-token-row-ETHEREUM-NATIVE')).click()
cy.url().should('contain', 'tokens/ethereum/NATIVE')
}
)
})

View File

@@ -3,7 +3,7 @@ import { DISCONNECTED_WALLET_USER_STATE } from '../../utils/user-state'
describe('disconnect wallet', () => {
it('should clear state', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
cy.visit('/swap')
cy.get('#swap-currency-input .token-amount-input').clear().type('1')
// Verify wallet is connected
@@ -19,16 +19,16 @@ describe('disconnect wallet', () => {
// Verify wallet has disconnected
cy.contains('Connect a wallet').should('exist')
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect')
cy.contains('Connect Wallet')
cy.contains('Connect wallet')
// Verify swap input is cleared
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get('#swap-currency-input .token-amount-input').should('have.value', '1')
})
})
describe('connect wallet', () => {
it('should load state', () => {
cy.visit('/swap', { ethereum: 'hardhat', userState: DISCONNECTED_WALLET_USER_STATE })
cy.visit('/swap', { userState: DISCONNECTED_WALLET_USER_STATE })
// Connect the wallet
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect').click()

View File

@@ -12,7 +12,7 @@ function switchChain(chain: string) {
describe('network switching', () => {
beforeEach(() => {
cy.visit('/swap', { ethereum: 'hardhat' })
cy.visit('/swap')
cy.get(getTestSelector('web3-status-connected'))
})
@@ -123,14 +123,14 @@ describe('network switching', () => {
describe('network switching from URL param', () => {
it('should switch network from URL param', () => {
cy.visit('/swap?chain=polygon', { ethereum: 'hardhat' })
cy.visit('/swap?chain=polygon')
cy.get(getTestSelector('web3-status-connected'))
cy.wait('@wallet_switchEthereumChain')
waitsForActiveChain('Polygon')
})
it('should be able to switch network after loading from URL param', () => {
cy.visit('/swap?chain=polygon', { ethereum: 'hardhat' })
cy.visit('/swap?chain=polygon')
cy.get(getTestSelector('web3-status-connected'))
cy.wait('@wallet_switchEthereumChain')
waitsForActiveChain('Polygon')

View File

@@ -1,3 +1,5 @@
import { FeatureFlag } from 'featureFlags'
import { getTestSelector } from '../utils'
describe('Wallet Dropdown', () => {
@@ -21,16 +23,20 @@ describe('Wallet Dropdown', () => {
})
}
function itChangesLocale() {
function itChangesLocale({ featureFlag = false }: { featureFlag?: boolean } = {}) {
it('should change locale', () => {
cy.contains('Uniswap available in: English').should('not.exist')
if (featureFlag) {
cy.get(getTestSelector('language-settings-button')).click()
}
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
cy.location('hash').should('match', /\?lng=af-ZA$/)
cy.location('search').should('match', /\?lng=af-ZA$/)
cy.contains('Uniswap available in: English')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.location('hash').should('match', /\?lng=en-US$/)
cy.location('search').should('match', /\?lng=en-US$/)
cy.contains('Uniswap available in: English').should('not.exist')
})
}
@@ -45,6 +51,15 @@ describe('Wallet Dropdown', () => {
itChangesLocale()
})
describe('should change locale with feature flag', () => {
beforeEach(() => {
cy.visit('/', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
itChangesLocale({ featureFlag: true })
})
describe('testnet toggle', () => {
beforeEach(() => {
cy.visit('/swap')
@@ -100,7 +115,7 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(152, 161, 192)')
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(155, 155, 155)')
})
it('should properly use light system theme when auto theme setting is selected', () => {
@@ -108,7 +123,7 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(119, 128, 160)')
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(125, 125, 125)')
})
})
@@ -129,4 +144,34 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('wallet-settings')).should('not.be.visible')
})
})
describe('local currency', () => {
it('loads local currency from the query param', () => {
cy.visit('/', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.contains('USD')
cy.visit('/?cur=AUD', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.contains('AUD')
})
it('loads local currency from menu', () => {
cy.visit('/', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.contains('USD')
cy.get(getTestSelector('local-currency-settings-button')).click()
cy.get(getTestSelector('wallet-local-currency-item')).contains('AUD').click({ force: true })
cy.location('search').should('match', /\?cur=AUD$/)
cy.contains('AUD')
cy.get(getTestSelector('wallet-local-currency-item')).contains('USD').click({ force: true })
cy.location('search').should('match', /\?cur=USD$/)
cy.contains('USD')
})
})
})

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,580 @@
{
"data": {
"portfolios": [
{
"id": "UG9ydGZvbGlvOjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2Ng==",
"assetActivities": [
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnM09EQm1NamcwTURSak1qRXpPRGd6TVRVM00yRXdOakJtTVRaaE1UQTNaV0ZtTW1Jd01qazFZbUZqTmpjNU5tUm1ZamN5TW1WbVl6VmpPVE5tTmpRM1h6QjRaak01Wm1RMlpUVXhZV0ZrT0RobU5tWTBZMlUyWVdJNE9ESTNNamM1WTJabVptSTVNakkyTmw4d2VEQXdNREF3TURBek1HWTBPV0ptTW1Vd01ESmxOakJqTm1Wa01UWTJNV1ppTWpNME5tUTRPREk9",
"timestamp": 1684364195,
"chain": "ETHEREUM",
"details": {
"__typename": "TransactionDetails",
"id": "VHJhbnNhY3Rpb246MHg3ODBmMjg0MDRjMjEzODgzMTU3M2EwNjBmMTZhMTA3ZWFmMmIwMjk1YmFjNjc5NmRmYjcyMmVmYzVjOTNmNjQ3XzB4ZjM5ZmQ2ZTUxYWFkODhmNmY0Y2U2YWI4ODI3Mjc5Y2ZmZmI5MjI2Nl8weDAwMDAwMDAzMGY0OWJmMmUwMDJlNjBjNmVkMTY2MWZiMjM0NmQ4ODI=",
"type": "UNKNOWN",
"blockNumber": 17282434,
"hash": "0x780f28404c2138831573a060f16a107eaf2b0295bac6796dfb722efc5c93f647",
"status": "CONFIRMED",
"to": "0x000000030f49bf2e002e60c6ed1661fb2346d882",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"nonce": 465,
"assetChanges": []
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhobFl6QTROMkpoTjJJMk4yUTFPVEEwTVdNMU5XTXdObU16TkdNNVlXVmhPVEUxTkRreVpUYzRNRFl4WldRd016TTBNMlprWmprMU1qa3dPR1U0WTJSa1h6QjRaR0psWmpNM05HWmtaamhrTnpNMVpUYzFPRGxoT1dFNVpUSmpOV0V3T1RGbFlqSmtZbVUxTjE4d2VHWXpPV1prTm1VMU1XRmhaRGc0WmpabU5HTmxObUZpT0RneU56STNPV05tWm1aaU9USXlOalk9",
"timestamp": 1684364135,
"chain": "ETHEREUM",
"details": {
"__typename": "TransactionDetails",
"id": "VHJhbnNhY3Rpb246MHhlYzA4N2JhN2I2N2Q1OTA0MWM1NWMwNmMzNGM5YWVhOTE1NDkyZTc4MDYxZWQwMzM0M2ZkZjk1MjkwOGU4Y2RkXzB4ZGJlZjM3NGZkZjhkNzM1ZTc1ODlhOWE5ZTJjNWEwOTFlYjJkYmU1N18weGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjY=",
"type": "RECEIVE",
"blockNumber": 17282429,
"hash": "0xec087ba7b67d59041c55c06c34c9aea915492e78061ed03343fdf952908e8cdd",
"status": "CONFIRMED",
"to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"from": "0xdbef374fdf8d735e7589a9a9e2c5a091eb2dbe57",
"nonce": 66,
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGRiZWYzNzRmZGY4ZDczNWU3NTg5YTlhOWUyYzVhMDkxZWIyZGJlNTdfMHhmMzlmZDZlNTFhYWQ4OGY2ZjRjZTZhYjg4MjcyNzljZmZmYjkyMjY2XzB4ZWMwODdiYTdiNjdkNTkwNDFjNTVjMDZjMzRjOWFlYTkxNTQ5MmU3ODA2MWVkMDMzNDNmZGY5NTI5MDhlOGNkZA==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.001",
"sender": "0xdbef374fdf8d735e7589a9a9e2c5a091eb2dbe57",
"recipient": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"direction": "IN",
"transactedValue": {
"id": "QW1vdW50OjEuODI5NjcwMDAwMDAwMDAwMV9VU0Q=",
"currency": "USD",
"value": 1.8296700000000001,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhoaE9URXdPVFEwT1Rka01UVmpNelpsWWprd1pXUXpZVEkwWW1Wa09ESTBOalpqWmpKaU9URXpNV1l4WkRVMk1EUmlNelppWW1aallqRTBOMkUzTURnNFh6QjRaV1JoTldVeE9ERXhORFppTVdZNVlUZG1OREJtT0RWak1HUmhNek0wT1RNNE5ESXdaRFV4TkY4d2VHWXpPV1prTm1VMU1XRmhaRGc0WmpabU5HTmxObUZpT0RneU56STNPV05tWm1aaU9USXlOalk9",
"timestamp": 1684319903,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHhhOTEwOTQ0OTdkMTVjMzZlYjkwZWQzYTI0YmVkODI0NjZjZjJiOTEzMWYxZDU2MDRiMzZiYmZjYjE0N2E3MDg4XzB4ZWRhNWUxODExNDZiMWY5YTdmNDBmODVjMGRhMzM0OTM4NDIwZDUxNF8weGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjY=",
"type": "RECEIVE",
"blockNumber": 17278819,
"hash": "0xa91094497d15c36eb90ed3a24bed82466cf2b9131f1d5604b36bbfcb147a7088",
"status": "CONFIRMED",
"to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"from": "0xeda5e181146b1f9a7f40f85c0da334938420d514",
"nonce": 5,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGVkYTVlMTgxMTQ2YjFmOWE3ZjQwZjg1YzBkYTMzNDkzODQyMGQ1MTRfMHhmMzlmZDZlNTFhYWQ4OGY2ZjRjZTZhYjg4MjcyNzljZmZmYjkyMjY2XzB4YTkxMDk0NDk3ZDE1YzM2ZWI5MGVkM2EyNGJlZDgyNDY2Y2YyYjkxMzFmMWQ1NjA0YjM2YmJmY2IxNDdhNzA4OA==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.15",
"sender": "0xeda5e181146b1f9a7f40f85c0da334938420d514",
"recipient": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"direction": "IN",
"transactedValue": {
"id": "QW1vdW50OjI3NC40NTA1X1VTRA==",
"currency": "USD",
"value": 274.4505,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnMFkyUm1Nell6T0dRME1ERXdOV1U1WkRZMVlUZGxObUV6WVdFMlpHTXpNREZpWVRNNVpHTXlNV1ppT0dGaE5USTBNVFppT1ROaE5tWXhOVEUwTWpReVh6QjRaak01Wm1RMlpUVXhZV0ZrT0RobU5tWTBZMlUyWVdJNE9ESTNNamM1WTJabVptSTVNakkyTmw4d2VHUmxOR1F6WVRJME5XUXlZall4WW1WaE1tTmlaREl4TmpVNE1XVXlaR1ZrTmpWbFl6azFNRFE9",
"timestamp": 1684319903,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHg0Y2RmMzYzOGQ0MDEwNWU5ZDY1YTdlNmEzYWE2ZGMzMDFiYTM5ZGMyMWZiOGFhNTI0MTZiOTNhNmYxNTE0MjQyXzB4ZjM5ZmQ2ZTUxYWFkODhmNmY0Y2U2YWI4ODI3Mjc5Y2ZmZmI5MjI2Nl8weGRlNGQzYTI0NWQyYjYxYmVhMmNiZDIxNjU4MWUyZGVkNjVlYzk1MDQ=",
"type": "SEND",
"blockNumber": 17278819,
"hash": "0x4cdf3638d40105e9d65a7e6a3aa6dc301ba39dc21fb8aa52416b93a6f1514242",
"status": "CONFIRMED",
"to": "0xde4d3a245d2b61bea2cbd216581e2ded65ec9504",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"nonce": 464,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjZfMHhkZTRkM2EyNDVkMmI2MWJlYTJjYmQyMTY1ODFlMmRlZDY1ZWM5NTA0XzB4NGNkZjM2MzhkNDAxMDVlOWQ2NWE3ZTZhM2FhNmRjMzAxYmEzOWRjMjFmYjhhYTUyNDE2YjkzYTZmMTUxNDI0Mg==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.00134999999999999",
"sender": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"recipient": "0xde4d3a245d2b61bea2cbd216581e2ded65ec9504",
"direction": "OUT",
"transactedValue": {
"id": "QW1vdW50OjIuNDcwMDU0NDk5OTk5OTgyX1VTRA==",
"currency": "USD",
"value": 2.470054499999982,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnM04yRXhPVGRoWmpjek9EUXpNRFk0WVRCaVlqUmlaV1V6WWpabFptWmxaakpsTkdZMFptTXlNR1UxWVRGbVltSTBOak14WXpoak1UQTROMk15WWpjM1h6QjRaak01Wm1RMlpUVXhZV0ZrT0RobU5tWTBZMlUyWVdJNE9ESTNNamM1WTJabVptSTVNakkyTmw4d2VHWTFZekZoTnpCbU5qY3pPV0k1TW1ZNU4yTmtOVE5qTXpFMk1ETTJNbU14TXpBMVpUa3hZVGc9",
"timestamp": 1684202579,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHg3N2ExOTdhZjczODQzMDY4YTBiYjRiZWUzYjZlZmZlZjJlNGY0ZmMyMGU1YTFmYmI0NjMxYzhjMTA4N2MyYjc3XzB4ZjM5ZmQ2ZTUxYWFkODhmNmY0Y2U2YWI4ODI3Mjc5Y2ZmZmI5MjI2Nl8weGY1YzFhNzBmNjczOWI5MmY5N2NkNTNjMzE2MDM2MmMxMzA1ZTkxYTg=",
"type": "SEND",
"blockNumber": 17269191,
"hash": "0x77a197af73843068a0bb4bee3b6effef2e4f4fc20e5a1fbb4631c8c1087c2b77",
"status": "CONFIRMED",
"to": "0xf5c1a70f6739b92f97cd53c3160362c1305e91a8",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"nonce": 463,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjZfMHhmNWMxYTcwZjY3MzliOTJmOTdjZDUzYzMxNjAzNjJjMTMwNWU5MWE4XzB4NzdhMTk3YWY3Mzg0MzA2OGEwYmI0YmVlM2I2ZWZmZWYyZTRmNGZjMjBlNWExZmJiNDYzMWM4YzEwODdjMmI3Nw==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.001216034894406018",
"sender": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"recipient": "0xf5c1a70f6739b92f97cd53c3160362c1305e91a8",
"direction": "OUT",
"transactedValue": {
"id": "QW1vdW50OjIuMjI0OTQyNTY1MjQ3ODU5X1VTRA==",
"currency": "USD",
"value": 2.224942565247859,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnMlpXSmtZbVJrTVRZMk0yVmxNV1ZrT0RVeE16TXlZelUyWmpkall6YzJaV1ZqTVROaE5qTm1PVEkxTldOa1ltWXlZVEUxWWpReFl6azBPVGhrWW1Wa1h6QjROREZpTXpBNU1qTTJZemczWWpGaVl6Wm1ZVGhsWWpnMk5UZ3pNMlUwTkRFMU9HWmhPVGt4WVY4d2VHWXpPV1prTm1VMU1XRmhaRGc0WmpabU5HTmxObUZpT0RneU56STNPV05tWm1aaU9USXlOalk9",
"timestamp": 1684202579,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHg2ZWJkYmRkMTY2M2VlMWVkODUxMzMyYzU2ZjdjYzc2ZWVjMTNhNjNmOTI1NWNkYmYyYTE1YjQxYzk0OThkYmVkXzB4NDFiMzA5MjM2Yzg3YjFiYzZmYThlYjg2NTgzM2U0NDE1OGZhOTkxYV8weGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjY=",
"type": "RECEIVE",
"blockNumber": 17269191,
"hash": "0x6ebdbdd1663ee1ed851332c56f7cc76eec13a63f9255cdbf2a15b41c9498dbed",
"status": "CONFIRMED",
"to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"from": "0x41b309236c87b1bc6fa8eb865833e44158fa991a",
"nonce": 111266,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweDQxYjMwOTIzNmM4N2IxYmM2ZmE4ZWI4NjU4MzNlNDQxNThmYTk5MWFfMHhmMzlmZDZlNTFhYWQ4OGY2ZjRjZTZhYjg4MjcyNzljZmZmYjkyMjY2XzB4NmViZGJkZDE2NjNlZTFlZDg1MTMzMmM1NmY3Y2M3NmVlYzEzYTYzZjkyNTVjZGJmMmExNWI0MWM5NDk4ZGJlZA==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.00275365",
"sender": "0x41b309236c87b1bc6fa8eb865833e44158fa991a",
"recipient": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"direction": "IN",
"transactedValue": {
"id": "QW1vdW50OjUuMDM4MjcwNzk1NV9VU0Q=",
"currency": "USD",
"value": 5.0382707955,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnNU5EUmlNR00wTVROa1l6QmpNekU0TUdFelkyTTNZakUyT1RCbVlqZzBNRFExWm1FME9UTXpObUV5WmprNE16VmpORFpqTURsak1UY3lObUUzTm1aalh6QjRaak01Wm1RMlpUVXhZV0ZrT0RobU5tWTBZMlUyWVdJNE9ESTNNamM1WTJabVptSTVNakkyTmw4d2VEWXlNakJsTURoak9XUTJNMkZpTjJKaE1tVTFOalk0TXpsbU5ESTVaV1ZsWm1VeE9UbGlOMlU9",
"timestamp": 1684171943,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHg5NDRiMGM0MTNkYzBjMzE4MGEzY2M3YjE2OTBmYjg0MDQ1ZmE0OTMzNmEyZjk4MzVjNDZjMDljMTcyNmE3NmZjXzB4ZjM5ZmQ2ZTUxYWFkODhmNmY0Y2U2YWI4ODI3Mjc5Y2ZmZmI5MjI2Nl8weDYyMjBlMDhjOWQ2M2FiN2JhMmU1NjY4MzlmNDI5ZWVlZmUxOTliN2U=",
"type": "SEND",
"blockNumber": 17266680,
"hash": "0x944b0c413dc0c3180a3cc7b1690fb84045fa49336a2f9835c46c09c1726a76fc",
"status": "CONFIRMED",
"to": "0x6220e08c9d63ab7ba2e566839f429eeefe199b7e",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"nonce": 462,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjZfMHg2MjIwZTA4YzlkNjNhYjdiYTJlNTY2ODM5ZjQyOWVlZWZlMTk5YjdlXzB4OTQ0YjBjNDEzZGMwYzMxODBhM2NjN2IxNjkwZmI4NDA0NWZhNDkzMzZhMmY5ODM1YzQ2YzA5YzE3MjZhNzZmYw==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.003476850926189204",
"sender": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"recipient": "0x6220e08c9d63ab7ba2e566839f429eeefe199b7e",
"direction": "OUT",
"transactedValue": {
"id": "QW1vdW50OjYuMzYxNDg5ODM0MTIwNjAxX1VTRA==",
"currency": "USD",
"value": 6.361489834120601,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhneE0yRTRNRGxsT1RZd05USmhOVGxrWlRjNU56WXhObVZrTlRjME1qTTVNakV3WkRJMVpUY3hNRGhqTkRjek9EbG1NbVJoTnpjeU5qTXhZbVZpTUdZMlh6QjRaak01Wm1RMlpUVXhZV0ZrT0RobU5tWTBZMlUyWVdJNE9ESTNNamM1WTJabVptSTVNakkyTmw4d2VEWXlNakJsTURoak9XUTJNMkZpTjJKaE1tVTFOalk0TXpsbU5ESTVaV1ZsWm1VeE9UbGlOMlU9",
"timestamp": 1684171943,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHgxM2E4MDllOTYwNTJhNTlkZTc5NzYxNmVkNTc0MjM5MjEwZDI1ZTcxMDhjNDczODlmMmRhNzcyNjMxYmViMGY2XzB4ZjM5ZmQ2ZTUxYWFkODhmNmY0Y2U2YWI4ODI3Mjc5Y2ZmZmI5MjI2Nl8weDYyMjBlMDhjOWQ2M2FiN2JhMmU1NjY4MzlmNDI5ZWVlZmUxOTliN2U=",
"type": "SEND",
"blockNumber": 17266680,
"hash": "0x13a809e96052a59de797616ed574239210d25e7108c47389f2da772631beb0f6",
"status": "CONFIRMED",
"to": "0x6220e08c9d63ab7ba2e566839f429eeefe199b7e",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"nonce": 461,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjZfMHg2MjIwZTA4YzlkNjNhYjdiYTJlNTY2ODM5ZjQyOWVlZWZlMTk5YjdlXzB4MTNhODA5ZTk2MDUyYTU5ZGU3OTc2MTZlZDU3NDIzOTIxMGQyNWU3MTA4YzQ3Mzg5ZjJkYTc3MjYzMWJlYjBmNg==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.000900000000000318",
"sender": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"recipient": "0x6220e08c9d63ab7ba2e566839f429eeefe199b7e",
"direction": "OUT",
"transactedValue": {
"id": "QW1vdW50OjEuNjQ2NzAzMDAwMDAwNTgxOF9VU0Q=",
"currency": "USD",
"value": 1.6467030000005818,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhobFkyRTJNVEZrTlRVME1EZGxPVGt6WlRFM1lqWmtaVGhpWVRJMFlqWXlOREpqWVRSbFlXWTBORGN3TkRKbFpHRmtNRFE0TTJNNFptSTJabUU0WkRJNVh6QjROekU0WVRVeE5ESXhNR0kwTnpWaU9USXhOVGd6WldGaU5ERXlaV0ptTUdaaVlXUm1NMkl6T1Y4d2VHWXpPV1prTm1VMU1XRmhaRGc0WmpabU5HTmxObUZpT0RneU56STNPV05tWm1aaU9USXlOalk9",
"timestamp": 1684171931,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHhlY2E2MTFkNTU0MDdlOTkzZTE3YjZkZThiYTI0YjYyNDJjYTRlYWY0NDcwNDJlZGFkMDQ4M2M4ZmI2ZmE4ZDI5XzB4NzE4YTUxNDIxMGI0NzViOTIxNTgzZWFiNDEyZWJmMGZiYWRmM2IzOV8weGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjY=",
"type": "RECEIVE",
"blockNumber": 17266679,
"hash": "0xeca611d55407e993e17b6de8ba24b6242ca4eaf447042edad0483c8fb6fa8d29",
"status": "CONFIRMED",
"to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"from": "0x718a514210b475b921583eab412ebf0fbadf3b39",
"nonce": 92,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweDcxOGE1MTQyMTBiNDc1YjkyMTU4M2VhYjQxMmViZjBmYmFkZjNiMzlfMHhmMzlmZDZlNTFhYWQ4OGY2ZjRjZTZhYjg4MjcyNzljZmZmYjkyMjY2XzB4ZWNhNjExZDU1NDA3ZTk5M2UxN2I2ZGU4YmEyNGI2MjQyY2E0ZWFmNDQ3MDQyZWRhZDA0ODNjOGZiNmZhOGQyOQ==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.01",
"sender": "0x718a514210b475b921583eab412ebf0fbadf3b39",
"recipient": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"direction": "IN",
"transactedValue": {
"id": "QW1vdW50OjE4LjI5NjdfVVNE",
"currency": "USD",
"value": 18.2967,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnMllqTTJNelEwT1daaU1HWTROems0TkRnM1pqWmlOREkwTkRjMFkySXdNbVF5WlRVNE1EZ3dPVEpoWVRneE1EVm1ObUU0T1dOalpHTTBORGRsTURSa1h6QjRaak01Wm1RMlpUVXhZV0ZrT0RobU5tWTBZMlUyWVdJNE9ESTNNamM1WTJabVptSTVNakkyTmw4d2VEQXdNREF3TURBek1HWTBPV0ptTW1Vd01ESmxOakJqTm1Wa01UWTJNV1ppTWpNME5tUTRPREk9",
"timestamp": 1684085063,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHg2YjM2MzQ0OWZiMGY4Nzk4NDg3ZjZiNDI0NDc0Y2IwMmQyZTU4MDgwOTJhYTgxMDVmNmE4OWNjZGM0NDdlMDRkXzB4ZjM5ZmQ2ZTUxYWFkODhmNmY0Y2U2YWI4ODI3Mjc5Y2ZmZmI5MjI2Nl8weDAwMDAwMDAzMGY0OWJmMmUwMDJlNjBjNmVkMTY2MWZiMjM0NmQ4ODI=",
"type": "UNKNOWN",
"blockNumber": 17259555,
"hash": "0x6b363449fb0f8798487f6b424474cb02d2e5808092aa8105f6a89ccdc447e04d",
"status": "CONFIRMED",
"to": "0x000000030f49bf2e002e60c6ed1661fb2346d882",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"nonce": 460,
"__typename": "TransactionDetails",
"assetChanges": []
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnNFlXRTVNVFJqTkRjeU5qWTNNVGxqWkRFeE1EYzNOMkprTnpZek0yVTFOV1kyWkdWbVpXRmpPVEV4TlRjd09EZzNZVEEyWXpNNE5UTmxaV0kyTldZeVh6QjRaR0V4TTJRMk5HVmpPVFZqWkRZM056VXlPVEZpTVdNek1qRXdNamN4TWpGaVpUSXdPV1JtTUY4d2VHWXpPV1prTm1VMU1XRmhaRGc0WmpabU5HTmxObUZpT0RneU56STNPV05tWm1aaU9USXlOalk9",
"timestamp": 1684085051,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHg4YWE5MTRjNDcyNjY3MTljZDExMDc3N2JkNzYzM2U1NWY2ZGVmZWFjOTExNTcwODg3YTA2YzM4NTNlZWI2NWYyXzB4ZGExM2Q2NGVjOTVjZDY3NzUyOTFiMWMzMjEwMjcxMjFiZTIwOWRmMF8weGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjY=",
"type": "RECEIVE",
"blockNumber": 17259554,
"hash": "0x8aa914c47266719cd110777bd7633e55f6defeac911570887a06c3853eeb65f2",
"status": "CONFIRMED",
"to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"from": "0xda13d64ec95cd6775291b1c321027121be209df0",
"nonce": 832,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGRhMTNkNjRlYzk1Y2Q2Nzc1MjkxYjFjMzIxMDI3MTIxYmUyMDlkZjBfMHhmMzlmZDZlNTFhYWQ4OGY2ZjRjZTZhYjg4MjcyNzljZmZmYjkyMjY2XzB4OGFhOTE0YzQ3MjY2NzE5Y2QxMTA3NzdiZDc2MzNlNTVmNmRlZmVhYzkxMTU3MDg4N2EwNmMzODUzZWViNjVmMg==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fbnVsbA==",
"name": "Ether",
"symbol": "ETH",
"address": null,
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNX251bGw=",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly90b2tlbi1pY29ucy5zMy5hbWF6b25hd3MuY29tL2V0aC5wbmc=",
"url": "https://token-icons.s3.amazonaws.com/eth.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "NATIVE",
"quantity": "0.00129866",
"sender": "0xda13d64ec95cd6775291b1c321027121be209df0",
"recipient": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"direction": "IN",
"transactedValue": {
"id": "QW1vdW50OjIuMzc2MTE5MjQyMl9VU0Q=",
"currency": "USD",
"value": 2.3761192422,
"__typename": "Amount"
}
}
]
},
"__typename": "AssetActivity"
},
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnM00yTXdZMlJpTnpReU9UVTJZVFUxWXpZd016YzBOemd6TkRRNVpUSmpNbVZtTURnM1lqUTVPRFl4TVdGak5EZ3dZalJrTVRFMU1UbGhZemRpTXpZNVh6QjRaak01Wm1RMlpUVXhZV0ZrT0RobU5tWTBZMlUyWVdJNE9ESTNNamM1WTJabVptSTVNakkyTmw4d2VHUXpaR1UwTkRneE5qTXlNakl5TURVME9UazJZVE0yTlRsaE5UTXlNR0k1TWpWbU5qUXhNR1k9",
"timestamp": 1684006019,
"chain": "ETHEREUM",
"details": {
"id": "VHJhbnNhY3Rpb246MHg3M2MwY2RiNzQyOTU2YTU1YzYwMzc0NzgzNDQ5ZTJjMmVmMDg3YjQ5ODYxMWFjNDgwYjRkMTE1MTlhYzdiMzY5XzB4ZjM5ZmQ2ZTUxYWFkODhmNmY0Y2U2YWI4ODI3Mjc5Y2ZmZmI5MjI2Nl8weGQzZGU0NDgxNjMyMjIyMDU0OTk2YTM2NTlhNTMyMGI5MjVmNjQxMGY=",
"type": "SEND",
"blockNumber": 17253116,
"hash": "0x73c0cdb742956a55c60374783449e2c2ef087b498611ac480b4d11519ac7b369",
"status": "CONFIRMED",
"to": "0xd3de4481632222054996a3659a5320b925f6410f",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"nonce": 459,
"__typename": "TransactionDetails",
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweGYzOWZkNmU1MWFhZDg4ZjZmNGNlNmFiODgyNzI3OWNmZmZiOTIyNjZfMHhiZTgyODI1NjRlYzJiNzAwMDlmMmQ2ODk1NDAxMmViMDlmNDhiYzhkXzB4NzNjMGNkYjc0Mjk1NmE1NWM2MDM3NDc4MzQ0OWUyYzJlZjA4N2I0OTg2MTFhYzQ4MGI0ZDExNTE5YWM3YjM2OQ==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fMHhkM2RlNDQ4MTYzMjIyMjA1NDk5NmEzNjU5YTUzMjBiOTI1ZjY0MTBm",
"name": "EL CHAPO",
"symbol": "CHAPO",
"address": "0xd3de4481632222054996a3659a5320b925f6410f",
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNXzB4ZDNkZTQ0ODE2MzIyMjIwNTQ5OTZhMzY1OWE1MzIwYjkyNWY2NDEwZg==",
"isSpam": true,
"logo": null,
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "ERC20",
"quantity": "50000000000000.002683081102196736",
"sender": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"recipient": "0xbe8282564ec2b70009f2d68954012eb09f48bc8d",
"direction": "OUT",
"transactedValue": null
}
]
},
"__typename": "AssetActivity"
}
],
"__typename": "Portfolio"
}
]
},
"errors": []
}

View File

@@ -0,0 +1 @@
{}

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

View File

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

View File

@@ -3,9 +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 { injected } from './ethereum'
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
@@ -13,15 +12,19 @@ declare global {
interface ApplicationWindow {
ethereum: Eip1193Bridge
}
interface Chainable<Subject> {
/**
* Wait for a specific event to be sent to amplitude. If the event is found, the subject will be the event.
*
* @param {string} eventName - The type of the event to search for e.g. SwapEventName.SWAP_TRANSACTION_COMPLETED
* @param {number} timeout - The maximum amount of time (in ms) to wait for the event.
* @returns {Chainable<Subject>}
*/
waitForAmplitudeEvent(eventName: string, timeout?: number): Chainable<Subject>
}
interface VisitOptions {
serviceWorker?: true
featureFlags?: Array<FeatureFlag>
/**
* The mock ethereum provider to inject into the page.
* @default 'goerli'
*/
// TODO(INFRA-175): Migrate all usage of 'goerli' to 'hardhat'.
ethereum?: 'goerli' | 'hardhat'
featureFlags?: Array<{ name: FeatureFlag; value: boolean }>
/**
* Initial user state.
* @default {@type import('../utils/user-state').CONNECTED_WALLET_USER_STATE}
@@ -38,43 +41,55 @@ Cypress.Commands.overwrite(
(original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => {
if (typeof url !== 'string') throw new Error('Invalid arguments. The first argument to cy.visit must be the path.')
// Add a hash in the URL if it is not present (to use hash-based routing correctly with queryParams).
let hashUrl = url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url
if (options?.ethereum === 'goerli') hashUrl += `${url.includes('?') ? '&' : '?'}chain=goerli`
return cy
.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 })
.provider()
.then((provider) =>
original({
...options,
url: hashUrl,
url,
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,
...CONNECTED_WALLET_USER_STATE,
...(options?.userState ?? {}),
})
// Set feature flags, if configured.
if (options?.featureFlags) {
const featureFlags = options.featureFlags.reduce((flags, flag) => ({ ...flags, [flag]: 'enabled' }), {})
const featureFlags = options.featureFlags.reduce(
(flags, flag) => ({ ...flags, [flag.name]: flag.value ? 'enabled' : 'control' }),
{}
)
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
}
// Inject the mock ethereum provider.
if (options?.ethereum === 'hardhat') {
win.ethereum = provider
} else {
win.ethereum = injected
}
win.ethereum = provider
},
})
)
}
)
Cypress.Commands.add('waitForAmplitudeEvent', (eventName, timeout = 5000 /* 5s */) => {
const startTime = new Date().getTime()
function checkRequest() {
return cy.wait('@amplitude', { timeout }).then((interception) => {
const events = interception.request.body.events
const event = events.find((event: any) => event.event_type === eventName)
if (event) {
return cy.wrap(event)
} else if (new Date().getTime() - startTime > timeout) {
throw new Error(`Event ${eventName} not found within the specified timeout`)
} else {
return checkRequest()
}
})
}
return checkRequest()
})

View File

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

View File

@@ -1,72 +0,0 @@
/**
* Updates cy.visit() to include an injected window.ethereum provider.
*/
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
import { JsonRpcProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet'
import { ChainId } from '@uniswap/sdk-core'
// todo: figure out how env vars actually work in CI
// const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e770523b4666d19'
// address of the above key
const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address
const CHAIN_ID = ChainId.GOERLI
const HEXLIFIED_CHAIN_ID = `0x${CHAIN_ID.toString(16)}`
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 5)
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
export const injected = new (class extends Eip1193Bridge {
chainId = CHAIN_ID
async sendAsync(...args: any[]) {
console.debug('sendAsync called', ...args)
return this.send(...args)
}
async send(...args: any[]) {
console.debug('send called', ...args)
const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function'
let callback
let method
let params
if (isCallbackForm) {
callback = args[1]
method = args[0].method
params = args[0].params
} else {
method = args[0]
params = args[1]
}
if (method === 'eth_requestAccounts' || method === 'eth_accounts') {
if (isCallbackForm) {
callback({ result: [TEST_ADDRESS_NEVER_USE] })
} else {
return Promise.resolve([TEST_ADDRESS_NEVER_USE])
}
}
if (method === 'eth_chainId') {
if (isCallbackForm) {
callback(null, { result: HEXLIFIED_CHAIN_ID })
} else {
return Promise.resolve(HEXLIFIED_CHAIN_ID)
}
}
try {
const result = await super.send(method, params)
console.debug('result received', method, params, result)
if (isCallbackForm) {
callback(null, { result })
} else {
return result
}
} catch (error) {
if (isCallbackForm) {
callback(error, null)
} else {
throw error
}
}
}
})(signer, provider)

View File

@@ -9,13 +9,9 @@ beforeEach(() => {
req.headers['origin'] = 'https://app.uniswap.org'
})
// Infura uses a test endpoint, which allow-lists http://localhost:3000 instead.
cy.intercept(/infura.io/, (req) => {
req.headers['referer'] = 'http://localhost:3000'
req.headers['origin'] = 'http://localhost:3000'
req.alias = req.body.method
req.continue()
})
// Network RPCs are disabled for cypress tests - calls should be routed through the connected wallet instead.
cy.intercept(/infura.io/, { statusCode: 404 })
cy.intercept(/quiknode.pro/, { statusCode: 404 })
// Log requests to hardhat.
cy.intercept(/:8545/, logJsonRpc)
@@ -24,13 +20,17 @@ beforeEach(() => {
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
const requestBody = JSON.stringify(req.body)
const byteSize = new Blob([requestBody]).size
req.alias = 'amplitude'
req.reply(
JSON.stringify({
code: 200,
server_upload_time: Date.now(),
payload_size_bytes: byteSize,
events_ingested: req.body.events.length,
})
}),
{
'origin-country': 'US',
}
)
}).intercept('https://*.sentry.io', { statusCode: 200 })
@@ -49,7 +49,7 @@ function logJsonRpc(req: CyHttpMessages.IncomingHttpRequest) {
const log = Cypress.log({
autoEnd: false,
name: req.body.method,
message: req.body.params?.map((param: unknown) =>
message: req.body.params?.map((param: any) =>
typeof param === 'object' ? '{...}' : param?.toString().substring(0, 10)
),
})

View File

@@ -1,18 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"composite": false,
"incremental": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"noEmit": true,
"strict": true,
"isolatedModules": false,
"noImplicitAny": false,
"target": "ES5",
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo
"types": ["cypress", "node"],
"jsx": "react"
},
"exclude": ["node_modules"],
"include": ["**/*.ts"],
"watchOptions": {
"excludeDirectories": ["node_modules"]
}
}

View File

@@ -1,5 +1,42 @@
import { connectionMetaKey } from '../../src/connection/meta'
import { ConnectionType } from '../../src/connection/types'
import { UserState } from '../../src/state/user/reducer'
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: 'INJECTED' }
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, state: UserState) {
// Selected wallet should also be reflected in localStorage, so that eager connections work.
if (state.selectedWallet) {
win.localStorage.setItem(
connectionMetaKey,
JSON.stringify({
type: state.selectedWallet,
})
)
}
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: state,
},
'persist:interface'
)
}
dbRequest.onupgradeneeded = function () {
const db = dbRequest.result
db.createObjectStore('keyvaluepairs')
}
}

50
functions/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Cloudflare Cloud Functions
## Purpose
These functions utilize Cloudflare Functions to dynamically inject meta tags server side for richer link sharing capabilities.
## Functions
Currently, there are 2 types of cloudflare functions developed
- Meta Data Injectors - Workers that inject [Open Graph](https://ogp.me/) standardized meta tags into the `header` of specific webpages.
- Currently we support this functionaltiy for three separate webpages: NFT Assets, NFT Collections, and Token Detail Pages
- These functions query data from GraphQL and then formats them into HTML `meta` tags to be injected
- Dynamically Generated Images - Utilizes Vercel's [Open Graph Image Generation Library](https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation) to create custom thumbnails for specific webpages
- Currently supports NFT Assets, NFT Collections, and Token Detail Pages
- These functions query data from GraphQL, and utilize `Satori` to convert HTML into a png image response which is then returned when the api is called.
- Can be found in the `api/image` folder.
## Testing
Testing is done utilizing a custom jest environment as well as Cloudflare's local tester: `wrangler`. Wrangler enables testing locally by running a proxy to wrap `localhost`. Tests run against a proxy server, so you'll need to start it before running tests:
- Manually run `yarn start:cloud` to setup wrangler on `localhost:3000`
- Run unit tests with `yarn test:cloud`
## Deployment
Functions will be deployed to Cloudlfare where they will be ran automatically when the appropriate route is hit.
## Miscellaneous
- Caching: In order to speed up webpage requests, repeated GraphQL queries will be saved and pulled using Cloudflare's Cache API.
## Scripts
- `yarn start:cloud` (NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --node-compat --proxy=3001 --port=3000 -- yarn start), script to start local wrangler environment
- `npx wrangler pages dev`: this basis of this command which starts a local instance of wrangler to test cloud functions
- `--node-compat`: wrangler option that enables compatibility with Node.js modules
- `--proxy:3001`: telling the proxy to listen on port 3001
- `--port=3000`: telling wrangler to run our proxy on port 3000
- `NODE_OPTIONS=--dns-result-order=ipv4first`: wrangler still serves to IPv4 which isn't compatible with Node 18 which default resolves to IPv6 so we need to specify to serve to IPv4
- `PORT-3001 --yarn start`: runs default yarn start on port 3001
- `yarn test:cloud` (NODE_OPTIONS=--experimental-vm-modules yarn jest functions --watch --config=functions/jest.config.json), script to test cloud functions with jest
- `NODE_OPTIONS=--experimental-vm-modules`: support for ES Modules and Web Assembly
- `--config=functions/jest.config.json`: specifying which config file to use
## Additional Documents
- [Open Graph Protocol](https://ogp.me/)
- [Open Graph Image Generation](https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation)
- [Cloudflare Workers](https://developers.cloudflare.com/workers/)
- [HTML Rewriter](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/)
- [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/)

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, request)).transform(await res)
} catch (e) {
return res
}
}

View File

@@ -1,403 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should inject metadata for valid collections 1`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid collections 2`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid collections 3`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;

View File

@@ -1,403 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should inject metadata for valid assets 1`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid assets 2`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid assets 3`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;

View File

@@ -1,671 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should inject metadata for valid tokens 1`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid tokens 2`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid tokens 3`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid tokens 4`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get FUC on Uniswap"/><meta property="og:image" content="https://assets.coingecko.com/coins/images/30081/large/fuc.png?1683016112"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get FUC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/arbitrum/0x1f52145666c862ed3e2f1da213d479e61b2892af"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get FUC on Uniswap"/><meta property="twitter:image" content="https://assets.coingecko.com/coins/images/30081/large/fuc.png?1683016112"/><meta property="twitter:image:alt" content="Get FUC on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid tokens 5`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<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.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
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');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;

View File

@@ -0,0 +1,76 @@
/* eslint-disable import/no-unused-modules */
import { ImageResponse } from '@vercel/og'
import React from 'react'
import { blocklistedCollections } from '../../../../../src/nft/utils/blocklist'
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
if (blocklistedCollections.includes(collectionAddress)) {
return new Response('Collection unsupported.', { status: 404 })
}
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,29 @@
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)
})
const blockedAssetImageUrl = [
'http://127.0.0.1:3000/api/image/nfts/asset/0xd4d871419714b778ebec2e22c7c53572b573706e/276',
]
test.each(blockedAssetImageUrl)('blockedAssetImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(404)
})

View File

@@ -0,0 +1,122 @@
/* eslint-disable import/no-unused-modules */
import { ImageResponse } from '@vercel/og'
import React from 'react'
import { blocklistedCollections } from '../../../../../src/nft/utils/blocklist'
import { getColor } from '../../../../../src/utils/getColor'
import { CHECK_URL, WATERMARK_URL } from '../../../../constants'
import getCollection from '../../../../utils/getCollection'
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
if (blocklistedCollections.includes(collectionAddress)) {
return new Response('Collection unsupported.', { status: 404 })
}
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,34 @@
import * as matchers from 'jest-extended'
expect.extend(matchers)
const collectionImageUrls = [
'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',
]
const nonexistentImageUrls = [
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
]
test.each([...collectionImageUrls, ...nonexistentImageUrls])('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 invalidCollectionImageUrls = ['http://127.0.0.1:3000/api/image/nfts/collection/0xd3adb33f']
test.each(invalidCollectionImageUrls)('invalidAssetImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBeOneOf([404, 500])
})
const blockedCollectionImageUrls = [
'http://127.0.0.1:3000/api/image/nfts/collection/0xd4d871419714b778ebec2e22c7c53572b573706e',
]
test.each(blockedCollectionImageUrls)('blockedCollectionImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBeOneOf([404, 500])
})

View File

@@ -0,0 +1,177 @@
/* eslint-disable import/no-unused-modules */
import { ImageResponse } from '@vercel/og'
import React from 'react'
import { getColor } from '../../../../src/utils/getColor'
import { WATERMARK_URL } from '../../../constants'
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,60 @@
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',
},
new Request('http://localhost')
)
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)
})
test('should pass through header blocked paths', () => {
const element = {
append: jest.fn(),
} as unknown as Element
const request = new Request('http://localhost')
request.headers.set('x-blocked-paths', '/')
const injector = new MetaTagInjector(
{
title: 'test',
url: 'testUrl',
image: 'testImage',
description: 'testDescription',
},
request
)
injector.element(element)
expect(element.append).toHaveBeenCalledWith(`<meta property="x:blocked-paths" content="/"/>`, { html: true })
})

View File

@@ -2,25 +2,26 @@ type MetaTagInjectorInput = {
title: string
image?: string
url: string
description?: string
}
/**
* Listener class for Cloudflare's HTMLRewriter {@link https://developers.cloudflare.com/workers/runtime-apis/html-rewriter}
* to inject meta tags into the <head> of an HTML document.
*/
export class MetaTagInjector {
constructor(private input: MetaTagInjectorInput) {}
export class MetaTagInjector implements HTMLRewriterElementContentHandlers {
constructor(private input: MetaTagInjectorInput, private request: Request) {}
append(element, property: string, content: string) {
append(element: Element, property: string, content: string) {
element.append(`<meta property="${property}" content="${content}"/>`, { html: true })
}
/**
* Event handler for ElementHandler {@link https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#element-handlers}
*/
element(element) {
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')
@@ -37,5 +38,10 @@ export class MetaTagInjector {
this.append(element, 'twitter:image', this.input.image)
this.append(element, 'twitter:image:alt', this.input.title)
}
const blockedPaths = this.request.headers.get('x-blocked-paths')
if (blockedPaths) {
this.append(element, 'x:blocked-paths', blockedPaths)
}
}
}

2
functions/constants.ts Normal file
View File

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

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

@@ -1,9 +0,0 @@
import { setup } from 'jest-dev-server'
module.exports = async function globalSetup() {
globalThis.servers = await setup({
command: `yarn start:cloud`,
port: 3000,
launchTimeout: 50000,
})
}

View File

@@ -1,5 +0,0 @@
import { teardown } from 'jest-dev-server'
module.exports = async function globalTeardown() {
await teardown(globalThis.servers)
}

View File

@@ -1,6 +0,0 @@
import { setup } from 'jest-dev-server'
declare global {
// eslint-disable-next-line no-var
var servers: Awaited<ReturnType<typeof setup>>
}

View File

@@ -1,10 +1,10 @@
{
"globalSetup": "<rootDir>/global-setup.ts",
"globalTeardown": "<rootDir>/global-teardown.ts",
"setupFilesAfterEnv": ["<rootDir>/setupAfterEnv.ts"],
"preset": "ts-jest",
"transform": {
"'^.+\\.(ts|tsx)?$'": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest"
},
"testTimeout": 50000
"testTimeout": 360000,
"cacheDirectory": "../node_modules/.cache/cloud-jest"
}

View File

@@ -1,20 +1,15 @@
/* eslint-disable import/no-unused-modules */
import { MetaTagInjector } from '../../components/metaTagInjector'
import getAsset from '../../utils/getAsset'
import { getMetadataRequest } from '../../utils/getRequest'
export const onRequest: PagesFunction = async ({ params, request, next }) => {
const { index } = params
const collectionAddress = index[0]?.toString()
const tokenId = index[1]?.toString()
const assetPromise = getAsset(collectionAddress, tokenId, request.url)
const resPromise = next()
const res = next()
try {
const [data, res] = await Promise.all([assetPromise, resPromise])
if (!data) {
return resPromise
}
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(res)
const { index } = params
const collectionAddress = index[0]?.toString()
const tokenId = index[1]?.toString()
return getMetadataRequest(res, request, () => getAsset(collectionAddress, tokenId, request.url))
} catch (e) {
return resPromise
return res
}
}

View File

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

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"/>`)
@@ -48,7 +46,7 @@ const invalidAssets = [
'http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544//2550',
]
test.each(invalidAssets)('should not inject metadata for invalid calls', async (url) => {
test.each(invalidAssets)('should not inject metadata for invalid asset calls', async (url) => {
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).not.toContain('og:title')
expect(body).not.toContain('og:image')

View File

@@ -1,19 +1,14 @@
/* eslint-disable import/no-unused-modules */
import { MetaTagInjector } from '../../components/metaTagInjector'
import getCollection from '../../utils/getCollection'
import { getMetadataRequest } from '../../utils/getRequest'
export const onRequest: PagesFunction = async ({ params, request, next }) => {
const { index } = params
const collectionAddress = index?.toString()
const collectionPromise = getCollection(collectionAddress, request.url)
const resPromise = next()
const res = next()
try {
const [data, res] = await Promise.all([collectionPromise, resPromise])
if (!data) {
return resPromise
}
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(res)
const { index } = params
const collectionAddress = index?.toString()
return getMetadataRequest(res, request, () => getCollection(collectionAddress, request.url))
} catch (e) {
return resPromise
return res
}
}

View File

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

View File

@@ -2,28 +2,34 @@ 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',
},
]
test.each(collections)('should inject metadata for valid collections', async (collection) => {
const nonexistentCollections = [
{
address: '0xed5af388653567af2f388e6224dc7c4b3241c545',
collectionName: '0xed5af388653567af2f388e6224dc7c4b3241c545',
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
},
]
test.each([...collections, ...nonexistentCollections])('should inject metadata for collections', async (collection) => {
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).toMatchSnapshot()
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"/>`)
@@ -37,27 +43,23 @@ 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',
{
address: '0xd3adb33f',
},
]
test.each(invalidCollections)(
'should not inject metadata for invalid urls',
async (url) => {
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).not.toContain('og:title')
expect(body).not.toContain('og:image')
expect(body).not.toContain('og:image:width')
expect(body).not.toContain('og:image:height')
expect(body).not.toContain('og:type')
expect(body).not.toContain('og:url')
expect(body).not.toContain('og:image:alt')
expect(body).not.toContain('twitter:card')
expect(body).not.toContain('twitter:title')
expect(body).not.toContain('twitter:image')
expect(body).not.toContain('twitter:image:alt')
},
50000
)
test.each(invalidCollections)('should not inject metadata for nonexistent collections', async (collection) => {
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).not.toContain('og:title')
expect(body).not.toContain('og:image')
expect(body).not.toContain('og:image:width')
expect(body).not.toContain('og:image:height')
expect(body).not.toContain('og:type')
expect(body).not.toContain('og:url')
expect(body).not.toContain('og:image:alt')
expect(body).not.toContain('twitter:card')
expect(body).not.toContain('twitter:title')
expect(body).not.toContain('twitter:image')
expect(body).not.toContain('twitter:image:alt')
})

View File

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

View File

@@ -1,27 +1,18 @@
/* eslint-disable import/no-unused-modules */
import { MetaTagInjector } from '../components/metaTagInjector'
import { getMetadataRequest } from '../utils/getRequest'
import getToken from '../utils/getToken'
const convertTokenAddress = (tokenAddress: string) => {
return tokenAddress && tokenAddress === 'NATIVE' ? '0x0000000000000000000000000000000000000000' : tokenAddress
}
export const onRequest: PagesFunction = async ({ params, request, next }) => {
const { index } = params
const networkName = index[0]?.toString().toUpperCase()
const tokenAddress = convertTokenAddress(index[1]?.toString())
if (!tokenAddress) {
return next()
}
const tokenPromise = getToken(networkName, tokenAddress, request.url)
const resPromise = next()
const res = next()
try {
const [data, res] = await Promise.all([tokenPromise, resPromise])
if (!data) {
return resPromise
const { index } = params
const networkName = index[0]?.toString()
const tokenAddress = index[1]?.toString()
if (!tokenAddress) {
return res
}
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(res)
return getMetadataRequest(res, request, () => getToken(networkName, tokenAddress, request.url))
} catch (e) {
return resPromise
return res
}
}

View File

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

View File

@@ -3,34 +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',
},
{
address: '0x1f52145666c862ed3e2f1da213d479e61b2892af',
network: 'arbitrum',
symbol: 'FUC',
image: 'https://assets.coingecko.com/coins/images/30081/large/fuc.png?1683016112',
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',
},
]
@@ -39,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"],
"noEmit": true,
"strict": true,
"target": "ES6",
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/functions", // avoid clobbering the build tsbuildinfo
"types": ["jest", "node"],
"isolatedModules": false,
"jsx": "react",
"moduleResolution": "NodeNext",
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/functions", // avoid clobbering the build tsbuildinfo
"types": ["jest", "node", "@cloudflare/workers-types"],
},
"exclude": ["node_modules"],
"include": ["**/*.ts"],
"watchOptions": {
"excludeDirectories": ["node_modules"]
}
"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

@@ -0,0 +1,43 @@
import CacheMock from 'browser-cache-mock'
import { mocked } from '../../src/test-utils/mocked'
import Cache from './cache'
const cacheMock = new CacheMock()
const data = {
title: 'test',
image: 'testImage',
url: 'testUrl',
}
beforeAll(() => {
const globalAny: any = global
globalAny.caches = {
open: async () => cacheMock,
...cacheMock,
}
})
test('Should put cache properly', async () => {
jest.spyOn(cacheMock, 'put')
await Cache.put(data, 'https://example.com')
expect(cacheMock.put).toHaveBeenCalledWith('https://example.com', expect.anything())
const call = mocked(cacheMock.put).mock.calls[0]
const response = JSON.parse(await (call[1] as Response).clone().text())
expect(response).toStrictEqual(data)
await expect(Cache.match('https://example.com')).resolves.toStrictEqual(data)
})
test('Should match cache properly', async () => {
jest.spyOn(cacheMock, 'match').mockResolvedValueOnce(new Response(JSON.stringify(data)))
const response = await Cache.match('https://example.com')
expect(response).toStrictEqual(data)
})
test('Should return undefined if not all data is present', async () => {
jest.spyOn(cacheMock, 'match').mockResolvedValueOnce(new Response(JSON.stringify({ ...data, title: undefined })))
const response = await Cache.match('https://example.com')
expect(response).toBeUndefined()
})

32
functions/utils/cache.ts Normal file
View File

@@ -0,0 +1,32 @@
export interface Data {
title: string
image: string
url: string
name?: string
ogImage?: string
isVerified?: boolean
symbol?: string
}
const CACHE_NAME = 'functions-cache' as const
class Cache {
async match(request: string): Promise<Data | undefined> {
const cache = await caches.open(CACHE_NAME)
const response = await cache.match(request)
if (!response) return undefined
const data: Data = JSON.parse(await response.text())
if (!data.title || !data.image || !data.url) return undefined
return data
}
async put(data: Data, request: string) {
// Set max age to 1 week
const response = new Response(JSON.stringify(data))
response.headers.set('Cache-Control', 'max-age=604800')
const cache = await caches.open(CACHE_NAME)
await cache.put(request, response)
}
}
export default new Cache()

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

@@ -0,0 +1,38 @@
import * as matchers from 'jest-extended'
expect.extend(matchers)
import { mocked } from '../../src/test-utils/mocked'
import Cache, { Data } from './cache'
import { getRequest } from './getRequest'
jest.mock('./cache', () => ({
match: jest.fn(),
put: jest.fn(),
}))
test('should call Cache.match before calling getData when request is not cached', async () => {
const url = 'https://example.com'
const getData = jest.fn().mockResolvedValueOnce({
title: 'test',
image: 'testImage',
url: 'testUrl',
})
await getRequest(url, getData, (data): data is Data => true)
expect(Cache.match).toHaveBeenCalledWith(url)
expect(getData).toHaveBeenCalled()
expect(Cache.match).toHaveBeenCalledBefore(getData)
expect(Cache.put).toHaveBeenCalledAfter(getData)
})
test('getData should not be called when request is cached', async () => {
const url = 'https://example.com'
mocked(Cache.match).mockResolvedValueOnce({
title: 'test',
image: 'testImage',
url: 'testUrl',
})
const getData = jest.fn()
await getRequest(url, getData, (data): data is Data => true)
expect(Cache.match).toHaveBeenCalledWith(url)
expect(getData).not.toHaveBeenCalled()
})

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