Compare commits
93 Commits
Author | SHA1 | Date | |
---|---|---|---|
e2d5f85ce0 | |||
9ee5aa173f | |||
f99f20fe18 | |||
fb0108196c | |||
986cf07391 | |||
48eb3f0005 | |||
a95c8fab5a | |||
1c131ee496 | |||
911af900ed | |||
|
fc7ecc7e3b | ||
|
4a5a41c59e | ||
|
4bec816e6c | ||
|
1d1b15f4ac | ||
|
1ffaf723de | ||
|
dd4b2dc764 | ||
|
5ded55e061 | ||
|
0f4ca592f2 | ||
|
90497dc08a | ||
|
2e618fb2aa | ||
|
0aa5727cdd | ||
|
79e74e1d13 | ||
|
52dc441e31 | ||
|
ff6d1cc510 | ||
|
76157c057e | ||
|
a1bd6f5eb4 | ||
|
f903eedc15 | ||
|
1feeaea181 | ||
|
7b10c94e4d | ||
|
f2f59d52cb | ||
|
a5034cb1c0 | ||
|
2227a38276 | ||
|
9f06747958 | ||
|
c6b44bb5c9 | ||
|
1d64d24d31 | ||
|
d8e43f0834 | ||
|
82f27186cf | ||
|
876d3a1cc3 | ||
|
712f82cb1a | ||
|
682215a574 | ||
|
395b390df6 | ||
|
cee3390b71 | ||
|
418ee08b00 | ||
|
ebfcd8fbbe | ||
|
c27e70b87c | ||
|
b4f3555600 | ||
|
0e87c38548 | ||
|
245d0eed06 | ||
|
46c8caa09c | ||
|
aa056adaf9 | ||
|
ce4df4f79e | ||
|
769a7ab9b5 | ||
|
5cbc56cf65 | ||
|
098c7b9cbe | ||
|
a0d880cf81 | ||
|
443a00a777 | ||
|
9eaa22f644 | ||
|
1c92482855 | ||
|
274d79dfde | ||
|
c2ca9ab93e | ||
|
0fbc826581 | ||
|
e9f784b2bc | ||
|
f9a9469523 | ||
|
b995f4d671 | ||
|
3f62bcf2f0 | ||
|
bd30721989 | ||
|
802d56231a | ||
|
9536df2ff1 | ||
|
ff3ed31dd7 | ||
|
719f82c7c4 | ||
|
0937e35095 | ||
|
3f9b436c86 | ||
|
40afc7388a | ||
|
eff6484a10 | ||
|
635875345e | ||
|
b670affd4c | ||
|
6798bf3cf1 | ||
|
8734ee5986 | ||
|
226fc441a7 | ||
|
36242d14b0 | ||
|
b02352e8bf | ||
|
819e2f5712 | ||
|
5357c58ac9 | ||
|
aada666c1a | ||
|
86fc15907a | ||
|
c5f2df4bc0 | ||
|
71d3661b22 | ||
|
740db0fe16 | ||
|
ed6afb50de | ||
|
9d439e7f62 | ||
|
6b60855362 | ||
|
53b53c2207 | ||
|
2818167131 | ||
|
c2440d1080 |
.env.env.productionhardhat.config.jslingui.config.tspackage.json
.github
.snykcypress.config.tscypress
e2e
add-liquidity.test.tslanding.test.ts
mini-portfolio
swap
token-details.test.tstoken-explore.test.tswallet-dropdown.test.tsfixtures
support
tsconfig.jsonfunctions
api/image/nfts/collection
nfts
tokens/__snapshots__
public
scripts
src
assets
images
svg
components
AccountDrawer
AuthenticatedHeader.tsxDownloadButton.tsx
MiniPortfolio
UniwalletModal.tsxBanner
Charts/PriceChart
CurrencyInputPanel
FeatureFlagModal
FeeSelector
FiatOnrampModal
Icons
LiquidityChartRangeInput
Logo
NavBar
Pools/PoolDetails
10
.env
10
.env
@ -1,8 +1,8 @@
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
ESLINT_NO_DEV_ERRORS=true
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://null.null"
|
||||
REACT_APP_AWS_API_REGION="us-east-2"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://null.null"
|
||||
REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf"
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://magical-alien-tab.quiknode.pro/669e87e569a8277d3fbd9e202f9df93189f19f4c"
|
||||
@ -10,7 +10,7 @@ REACT_APP_MOONPAY_API="https://api.moonpay.com"
|
||||
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=staging"
|
||||
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
|
||||
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
|
||||
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
REACT_APP_UNISWAP_API_URL="https://api.uniswap.org/v2"
|
||||
REACT_APP_STATSIG_PROXY_URL="https://null.null"
|
||||
REACT_APP_TEMP_API_URL="https://null.null"
|
||||
REACT_APP_UNISWAP_API_URL="https://null.null"
|
||||
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"
|
||||
|
@ -1,16 +1,16 @@
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://null.null"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://null.null"
|
||||
REACT_APP_BNB_RPC_URL="https://old-wispy-arrow.bsc.quiknode.pro/f5c060177236065c1058531a0615ab4f7a34a2fd"
|
||||
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_MOONPAY_API="https://api.moonpay.com"
|
||||
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=production"
|
||||
REACT_APP_MOONPAY_LINK="https://null.null"
|
||||
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
|
||||
REACT_APP_SENTRY_ENABLED=true
|
||||
REACT_APP_SENTRY_TRACES_SAMPLE_RATE=0.00003
|
||||
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||
REACT_APP_STATSIG_PROXY_URL="https://null.null"
|
||||
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://ultra-blue-flower.quiknode.pro/770b22d5f362c537bc8fe19b034c45b22958f880"
|
||||
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3?source=uniswap"
|
||||
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
||||
* @uniswap/web-reviewers
|
22
.github/ISSUE_TEMPLATE/bug-report.md
vendored
22
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Describe an issue in the Uniswap Interface
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Bug Description**
|
||||
A clear and concise description of the bug.
|
||||
|
||||
**Steps to Reproduce**
|
||||
|
||||
1. Go to ...
|
||||
2. Click on ...
|
||||
...
|
||||
|
||||
**Expected Behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Additional Context**
|
||||
Add any other context about the problem here (screenshots, whether the bug only occurs only in certain mobile/desktop/browser environments, etc.)
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Support
|
||||
url: https://discord.gg/FCfyBSbCU5
|
||||
about: Please ask and answer questions here
|
||||
- name: List a token
|
||||
url: https://github.com/Uniswap/default-token-list#adding-a-token
|
||||
about: Any requests to add a token to Uniswap should go here
|
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -1,19 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for improving the UX of the Uniswap Interface
|
||||
title: ''
|
||||
labels: 'improvement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
48
.github/actions/report/action.yml
vendored
48
.github/actions/report/action.yml
vendored
@ -1,48 +0,0 @@
|
||||
name: Report
|
||||
description: Report test failures via Slack
|
||||
inputs:
|
||||
name:
|
||||
description: The name of the failing test
|
||||
required: true
|
||||
SLACK_WEBHOOK_URL:
|
||||
description: The webhook URL to send the report to
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "${{ inputs.name }} failing on `${{ github.ref_name }}`",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*${{ inputs.name }} failing on `${{ github.ref_name }}`:* <https://github.com/${{ github.repository}}/actions/runs/${{ github.run_id }}|view failing action>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "_This is blocking pull requests and branch promotions._\n_Please prioritize fixing the build._"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
# The !oncall bot requires its own message:
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "!oncall web"
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
49
.github/actions/setup/action.yml
vendored
49
.github/actions/setup/action.yml
vendored
@ -1,49 +0,0 @@
|
||||
name: Setup
|
||||
description: checkout repo, setup node, and install node_modules
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org
|
||||
# cache is intentionally omitted, as it is faster with yarn v1 to cache node_modules.
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
# node_modules/.cache is intentionally omitted, as this is used for build tool caches.
|
||||
path: |
|
||||
node_modules
|
||||
!node_modules/.cache
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('yarn.lock') }}
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
shell: bash
|
||||
|
||||
# Run patch-package to apply patches to dependencies.
|
||||
- run: yarn patch-package
|
||||
shell: bash
|
||||
|
||||
# Contracts are compiled from source. If source hasn't changed, the contracts do not need to be re-compiled.
|
||||
- uses: actions/cache@v3
|
||||
id: contracts-cache
|
||||
with:
|
||||
path: |
|
||||
src/abis/types
|
||||
src/types/v3
|
||||
key: ${{ runner.os }}-contracts-${{ hashFiles('src/abis/**/*.json', 'node_modules/@uniswap/**/artifacts/contracts/**/*.json') }}
|
||||
- if: steps.contracts-cache.outputs.cache-hit != 'true'
|
||||
run: yarn contracts
|
||||
shell: bash
|
||||
|
||||
# These operations cannot be cached, so they are run concurrently
|
||||
# - ajv: Validators compile quickly, so caching can be omitted.
|
||||
# - graphql: GraphQL is generated from schema and client-side graphql queries. The schema is always fetched and
|
||||
# changes to client-side queries are hard to detect, so it is always re-generated.
|
||||
# - i18n: Messages are extracted from source and compiled. No caching extractor is available (out-of-the-box).
|
||||
- run: yarn concurrently --max-processes=100% npm:ajv npm:graphql npm:i18n
|
||||
shell: bash
|
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@ -1,12 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
# Files stored in repository root
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
allow:
|
||||
- dependency-name: '@uniswap/default-token-list'
|
||||
- dependency-name: '@uniswap/token-lists'
|
||||
reviewers:
|
||||
- 'Uniswap/dependabot-reviewers'
|
52
.github/pull_request_template.md
vendored
52
.github/pull_request_template.md
vendored
@ -1,52 +0,0 @@
|
||||
<!-- Your PR title must follow conventional commits: https://github.com/Uniswap/interface#pr-title -->
|
||||
|
||||
## Description
|
||||
<!-- Summary of change, including motivation and context. -->
|
||||
<!-- Use verb-driven language: "Fixes XYZ" instead of "This change fixes XYZ" -->
|
||||
|
||||
|
||||
<!-- Delete inapplicable lines: -->
|
||||
_Linear ticket:_
|
||||
_Slack thread:_
|
||||
_Relevant docs:_
|
||||
|
||||
|
||||
<!-- Delete this section if your change does not affect UI. -->
|
||||
## Screen capture
|
||||
|
||||
### Before
|
||||
| Mobile | Desktop |
|
||||
| ------------ | ------------ |
|
||||
| paste_before | paste_before |
|
||||
|
||||
|
||||
### After
|
||||
| Mobile | Desktop |
|
||||
| ------------ | ----------- |
|
||||
| paste_after | paste_after |
|
||||
|
||||
|
||||
## Test plan
|
||||
|
||||
<!-- Delete this section if your change is not a bug fix. -->
|
||||
### Reproducing the error
|
||||
|
||||
<!-- Include steps to reproduce the bug. -->
|
||||
1.
|
||||
|
||||
### QA (ie manual testing)
|
||||
|
||||
<!-- Include steps to test the change, ensuring no regression. -->
|
||||
- [ ] N/A
|
||||
|
||||
|
||||
#### Devices
|
||||
<!-- If applicable, include different devices and screen sizes that may be affected, and how you've tested them. -->
|
||||
|
||||
|
||||
### Automated testing
|
||||
|
||||
<!-- If N/A, check and note so it is obvious to your reviewers and does not show up as an incomplete task. -->
|
||||
<!-- eg - [x] Unit test N/A -->
|
||||
- [ ] Unit test
|
||||
- [ ] Integration/E2E test
|
73
.github/workflows/1-main-to-staging.yml
vendored
73
.github/workflows/1-main-to-staging.yml
vendored
@ -1,73 +0,0 @@
|
||||
name: 1 | Push main -> staging
|
||||
|
||||
# This CI job is responsible for pushing the current contents of the `main` branch to the
|
||||
# `releases/staging` branch, which will in turn kick off a deploy to the staging environment.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
# https://stackoverflow.com/questions/57921401/push-to-origin-from-github-action
|
||||
jobs:
|
||||
push-staging:
|
||||
name: 'Push to staging branch'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: push/staging
|
||||
steps:
|
||||
- name: Check test status
|
||||
uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
const statuses = await github.rest.repos.listCommitStatusesForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha
|
||||
})
|
||||
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
|
||||
core.info('Status: ' + status)
|
||||
if (status !== 'success') {
|
||||
core.setFailed('"Test / promotion" must be successful before pushing')
|
||||
}
|
||||
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
|
||||
ref: main
|
||||
|
||||
# The source file must exist for the corresponding translation messages to be downloaded.
|
||||
- run: touch src/locales/en-US.po
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
project_id: 458284
|
||||
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
|
||||
source: 'src/locales/en-US.po'
|
||||
translation: 'src/locales/%locale%.po'
|
||||
localization_branch_name: main
|
||||
create_pull_request: false
|
||||
push_translations: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Git config
|
||||
run: |
|
||||
git config user.name 'UL Service Account'
|
||||
git config user.email 'hello-happy-puppy@users.noreply.github.com'
|
||||
|
||||
- name: Add translations
|
||||
run: |
|
||||
rm src/locales/en-US.po
|
||||
git add -f src/locales/*.po
|
||||
git commit -m 'ci(t9n): download translations from crowdin'
|
||||
|
||||
- name: Add CODEOWNERS
|
||||
run: |
|
||||
echo '* @uniswap/web-admins' > CODEOWNERS
|
||||
git add CODEOWNERS
|
||||
git commit -m 'ci: add global CODEOWNERS'
|
||||
|
||||
- name: Git push
|
||||
run: |
|
||||
git push origin main:releases/staging --force
|
64
.github/workflows/2-deploy-to-staging.yml
vendored
64
.github/workflows/2-deploy-to-staging.yml
vendored
@ -1,64 +0,0 @@
|
||||
name: 2 | Deploy staging
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'releases/staging'
|
||||
|
||||
jobs:
|
||||
deploy-to-staging:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: deploy/staging
|
||||
steps:
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Deploy _started_ for ${{ github.ref_name }}"
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn build
|
||||
env:
|
||||
REACT_APP_STAGING: 1
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
id: pages-deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: interface-staging
|
||||
directory: build
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
|
||||
branch: main
|
||||
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
if: always()
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
- name: Upload source maps to Sentry
|
||||
uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd
|
||||
continue-on-error: true
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
with:
|
||||
environment: staging
|
||||
sourcemaps: './build/static/js'
|
||||
url_prefix: '~/static/js'
|
42
.github/workflows/3-staging-to-prod.yml
vendored
42
.github/workflows/3-staging-to-prod.yml
vendored
@ -1,42 +0,0 @@
|
||||
name: 3 | Push staging -> prod
|
||||
|
||||
# This CI job is responsible for force pushing the content of releases/staging to releases/prod. It
|
||||
# is restricted to web-reviewers through virtue of the GitHub environment protection rules for the
|
||||
# prod environment.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
push-prod:
|
||||
name: 'Push to prod branch'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: push/prod
|
||||
steps:
|
||||
- name: Check test status
|
||||
uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
const statuses = await github.rest.repos.listCommitStatusesForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha
|
||||
})
|
||||
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
|
||||
core.info('Status: ' + status)
|
||||
if (status !== 'success') {
|
||||
core.setFailed('"Test / promotion" must be successful before pushing')
|
||||
}
|
||||
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
|
||||
ref: releases/staging
|
||||
- name: Git config
|
||||
run: |
|
||||
git config user.name "UL Service Account"
|
||||
git config user.email "hello-happy-puppy@users.noreply.github.com"
|
||||
- name: Git push
|
||||
run: |
|
||||
git push origin releases/staging:releases/prod --force
|
111
.github/workflows/4-deploy-to-prod.yml
vendored
111
.github/workflows/4-deploy-to-prod.yml
vendored
@ -1,111 +0,0 @@
|
||||
name: 4 | Deploy prod
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'releases/prod'
|
||||
|
||||
jobs:
|
||||
deploy-to-prod:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: deploy/prod
|
||||
steps:
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Deploy _started_ for ${{ github.ref_name }}"
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn build
|
||||
|
||||
- name: Bump and tag
|
||||
id: github-tag-action
|
||||
uses: mathieudutour/github-tag-action@d745f2e74aaf1ee82e747b181f7a0967978abee0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_branches: releases/prod
|
||||
default_bump: patch
|
||||
|
||||
- name: Pin to IPFS
|
||||
id: pinata
|
||||
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
|
||||
with:
|
||||
pin-name: Uniswap ${{ steps.github-tag-action.outputs.new_tag }}
|
||||
path: './build'
|
||||
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
|
||||
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
|
||||
|
||||
- name: Convert CIDv0 to CIDv1
|
||||
id: convert-cidv0
|
||||
uses: uniswap/convert-cidv0-cidv1@v1.0.0
|
||||
with:
|
||||
cidv0: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- name: Publish release
|
||||
uses: actions/create-release@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.github-tag-action.outputs.new_tag }}
|
||||
release_name: Release ${{ steps.github-tag-action.outputs.new_tag }}
|
||||
body: |
|
||||
IPFS hash of the deployment:
|
||||
- CIDv0: `${{ steps.pinata.outputs.hash }}`
|
||||
- CIDv1: `${{ steps.convert-cidv0.outputs.cidv1 }}`
|
||||
|
||||
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
You can also access the Uniswap Interface from an IPFS gateway.
|
||||
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
|
||||
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
|
||||
Your Uniswap settings are never remembered across different URLs.
|
||||
|
||||
IPFS gateways:
|
||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
||||
- [ipfs://${{ steps.pinata.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
|
||||
|
||||
${{ steps.github-tag-action.outputs.changelog }}
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
id: pages-deployment
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
directory: build
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
|
||||
branch: main
|
||||
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
if: always()
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
- name: Upload source maps to Sentry
|
||||
uses: getsentry/action-release@4744f6a65149f441c5f396d5b0877307c0db52c7
|
||||
continue-on-error: true
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
with:
|
||||
environment: production
|
||||
sourcemaps: './build/static/js'
|
||||
url_prefix: '~/static/js'
|
17
.github/workflows/check-pr-title.yaml
vendored
17
.github/workflows/check-pr-title.yaml
vendored
@ -1,17 +0,0 @@
|
||||
name: Check PR Title
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
# Ensures that the PR title adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||
conventional-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v3.4.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
26
.github/workflows/crowdin.yaml
vendored
26
.github/workflows/crowdin.yaml
vendored
@ -1,26 +0,0 @@
|
||||
name: Crowdin Upload
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
upload-sources:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn i18n:extract
|
||||
|
||||
- name: Upload Crowdin sources
|
||||
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
|
||||
with:
|
||||
upload_sources: true
|
||||
download_translations: false
|
||||
project_id: 458284
|
||||
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
|
||||
source: 'src/locales/en-US.po'
|
||||
translation: 'src/locales/%locale%.po'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@ -1,91 +0,0 @@
|
||||
name: Slack notification on pushes to releases/*
|
||||
|
||||
# This CI job will push notifications to Slack whenever code is merged into any releases/* branch
|
||||
#
|
||||
# The steps of the command line kung-fu shown below are as follows:
|
||||
# First we take the JSON-formatted Github context
|
||||
# echo $GITHUB_CONTEXT \
|
||||
# Then we parse out the specific fields we want for our messages using jq and format it into tab-separated values
|
||||
# | jq '.event.commits[] | [.url, .id[0:7], .author.username, .timestamp, .message] | @tsv' \
|
||||
# We need to do some cleaning on this output - specifically removing quotes and replacing newlines with something easier to split
|
||||
# | sed 's/"//g' | sed 's/\\t/;/g' | sed 's/\\n/;/g' | sed 's/\\//g' \
|
||||
# We then use awk to format the TSV into a Slack message
|
||||
# | awk -F';' '{print "• <"$1"|"$2"> (<https://github.com/"$3"|"$3">, "$4") - "$5}' \
|
||||
# We need to deal with some escaping issues with newlines so that we don't break the Slack message format
|
||||
# | sed 's/$/\\n/g' | tr -d '\n' \
|
||||
# Finally we have to truncate the message to 3,000 characters max, otherwise Slack will reject it
|
||||
# | awk '{print substr($0,0,3000);}' \
|
||||
# Then shove the bytes into a file to store them in their exact format
|
||||
# > /tmp/parsed_github_context
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'releases/*'
|
||||
|
||||
jobs:
|
||||
notify-slack:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: notify/releases
|
||||
steps:
|
||||
- name: Parse event to slug
|
||||
id: parse-slug
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
# Formats the contents of the GitHub event into slugs: one line per commit, formatted for Slack.
|
||||
# Explanation for each line is in the comments above.
|
||||
run: |
|
||||
echo $GITHUB_CONTEXT \
|
||||
| jq '.event.commits[] | [.url, .id[0:7], .author.username, .timestamp, .message] | @tsv' \
|
||||
| sed 's/"//g' | sed 's/\\t/;/g' | sed 's/\\n/;/g' | sed 's/\\//g' \
|
||||
| awk -F';' '{print "• <"$1"|"$2"> (<https://github.com/"$3"|"$3">, "$4") - "$5}' \
|
||||
| sed 's/$/\\n/g' | tr -d '\n' \
|
||||
| awk '{print substr($0,0,3000);}' \
|
||||
> /tmp/parsed_github_context
|
||||
echo "SLACK_COMMITS=$(cat /tmp/parsed_github_context)" >> "$GITHUB_OUTPUT"
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Code merged to <https://github.com/Uniswap/interface/tree/${{ github.ref }}|${{ github.ref_name }}> branch:*\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Actor*: <https://github.com/${{ github.triggering_actor }}/|${{ github.triggering_actor }}>\n*Force pushed*: ${{ github.event.forced || false }}\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "${{ steps.parse-slug.outputs.SLACK_COMMITS || 'New branch created' }}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "<${{ github.event.compare}}|View Diff>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
278
.github/workflows/test.yml
vendored
278
.github/workflows/test.yml
vendored
@ -1,278 +0,0 @@
|
||||
name: Test
|
||||
|
||||
# Many build steps have their own caches, so each job has its own cache to improve subsequent build times.
|
||||
# Build tools are configured to cache to node_modules/.cache, so they are cached independently of node_modules.
|
||||
# Caches are saved every run (by keying on github.run_id), and the most recent available cache is loaded.
|
||||
# See https://jongleberry.medium.com/speed-up-your-ci-and-dx-with-node-modules-cache-ac8df82b7bb0.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- releases/staging
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-eslint-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-eslint-
|
||||
- run: yarn lint
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Lint
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-tsc-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-tsc-
|
||||
- run: yarn typecheck
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Typecheck
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
deps-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn yarn-deduplicate --strategy=highest --list --fail
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Dependency checks
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-jest-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-jest-
|
||||
- run: yarn test --coverage --maxWorkers=100%
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
flags: unit-tests
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Unit tests
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.swc
|
||||
key: ${{ runner.os }}-swc-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-swc-
|
||||
- run: yarn build
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
if-no-files-found: error
|
||||
|
||||
cypress-typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-cypress-tsc-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-cypress-tsc-
|
||||
- run: yarn typecheck:cypress
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Cypress typecheck
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
# Allows for parallel re-runs of cypress tests without re-building.
|
||||
cypress-rerun:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
cypress-test-matrix:
|
||||
needs: [build, cypress-rerun]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
containers: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: /root/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
|
||||
- run: |
|
||||
yarn cypress install
|
||||
yarn cypress info
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
|
||||
- uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: cache
|
||||
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-
|
||||
|
||||
- uses: cypress-io/github-action@v4
|
||||
with:
|
||||
install: false
|
||||
record: true
|
||||
parallel: true
|
||||
start: yarn serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
browser: electron
|
||||
group: e2e
|
||||
spec: ${{ github.ref_name == 'releases/staging' && 'cypress/{e2e,staging}/**/*.test.ts' || 'cypress/e2e/**/*.test.ts' }}
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT_INFO_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message }}
|
||||
COMMIT_INFO_AUTHOR: ${{ github.event.sender.login || github.event.head_commit.author.login }}
|
||||
# Cypress requires an email for filtering by author, but GitHub does not expose one.
|
||||
# GitHub's public profile email can be deterministically produced from user id/login.
|
||||
COMMIT_INFO_EMAIL: ${{ github.event.sender.id || github.event.head_commit.author.id }}+${{ github.event.sender.login || github.event.head_commit.author.login }}@users.noreply.github.com
|
||||
COMMIT_INFO_SHA: ${{ github.event.pull_request.head.sha || github.event.head_commit.sha }}
|
||||
COMMIT_INFO_TIMESTAMP: ${{ github.event.pull_request.updated_at || github.event.head_commit.timestamp }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number }}
|
||||
CYPRESS_PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }}
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Cypress tests
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: hardhat-cache
|
||||
path: cache
|
||||
|
||||
hardhat-cache:
|
||||
needs: [cypress-test-matrix]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: hardhat-cache
|
||||
path: cache
|
||||
- uses: actions/cache/save@v3
|
||||
with:
|
||||
path: cache
|
||||
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
|
||||
|
||||
cloud-typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-cloud-tsc-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-cloud-tsc-
|
||||
- run: yarn typecheck:cloud
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Cloud typecheck
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
cloud-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-cloud-jest-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-cloud-jest-
|
||||
# Ignore start:cloud output so it doesn't flood the test output.
|
||||
# Only use 1 worker for testing, as the other is used to run start:cloud (the proxy server under test).
|
||||
- run: yarn start-server-and-test 'yarn start:cloud >/dev/null' 3000 'yarn test:cloud --coverage --maxWorkers=1'
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
flags: cloud-tests
|
||||
|
||||
pre:
|
||||
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.sha,
|
||||
state: 'pending',
|
||||
context: 'Test / promotion',
|
||||
description: 'Running tests...',
|
||||
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
|
||||
})
|
||||
|
||||
post:
|
||||
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
|
||||
needs: [pre, lint, typecheck, deps-tests, unit-tests, cypress-test-matrix, cloud-tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.sha,
|
||||
state: ${{ env.STATUS }} ? 'success' : 'failure',
|
||||
context: 'Test / promotion',
|
||||
description: ${{ env.STATUS }} ? 'All tests passed' : 'One or more tests failed and are blocking promotion',
|
||||
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
|
||||
})
|
||||
env:
|
||||
STATUS: |
|
||||
${{ needs.lint.result == 'success' }} &&
|
||||
${{ needs.typecheck.result == 'success' }} &&
|
||||
${{ needs.deps-tests.result == 'success' }} &&
|
||||
${{ needs.unit-tests.result == 'success' }} &&
|
||||
${{ needs.cypress-test-matrix.result == 'success' }} &&
|
||||
${{ needs.cloud-tests.result == 'success' }}
|
25
.snyk
25
.snyk
@ -1,25 +0,0 @@
|
||||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.25.0
|
||||
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
|
||||
ignore:
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2964946:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:25:57.347Z
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2958047:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:26:09.720Z
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2958050:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:26:17.702Z
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2965580:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:26:34.283Z
|
||||
patch: {}
|
@ -6,7 +6,7 @@ export default defineConfig({
|
||||
defaultCommandTimeout: 24000, // 2x average block time
|
||||
chromeWebSecurity: false,
|
||||
experimentalMemoryManagement: true, // better memory management, see https://github.com/cypress-io/cypress/pull/25462
|
||||
retries: { runMode: process.env.CYPRESS_RETRIES ? +process.env.CYPRESS_RETRIES : 2 },
|
||||
retries: { runMode: process.env.CYPRESS_RETRIES ? +process.env.CYPRESS_RETRIES : 1 },
|
||||
video: false, // GH provides 2 CPUs, and cypress video eats one up, see https://github.com/cypress-io/cypress/issues/20468#issuecomment-1307608025
|
||||
e2e: {
|
||||
async setupNodeEvents(on, config) {
|
||||
|
@ -16,6 +16,16 @@ describe('Add Liquidity', () => {
|
||||
cy.contains('0.05% fee tier')
|
||||
})
|
||||
|
||||
it('clears the token selection when chain changes', () => {
|
||||
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH/500')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
|
||||
cy.get('[data-testid="chain-selector"]').last().click()
|
||||
cy.contains('Polygon').click()
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('not.contain.text', 'UNI')
|
||||
})
|
||||
|
||||
it('does not crash if token is duplicated', () => {
|
||||
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
|
||||
@ -52,7 +62,7 @@ describe('Add Liquidity', () => {
|
||||
|
||||
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH')
|
||||
cy.wait('@FeeTierDistribution')
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.30% fee tier')
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40% select')
|
||||
})
|
||||
})
|
||||
|
@ -40,6 +40,26 @@ describe('Landing Page', () => {
|
||||
cy.url().should('include', '/pools')
|
||||
})
|
||||
|
||||
it('does not render landing page when / path is blocked', () => {
|
||||
cy.intercept('/', (req) => {
|
||||
req.reply((res) => {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(res.body, 'text/html')
|
||||
const meta = document.createElement('meta')
|
||||
meta.setAttribute('property', 'x:blocked-paths')
|
||||
meta.setAttribute('content', '/,/buy')
|
||||
doc.head.appendChild(meta)
|
||||
|
||||
res.body = doc.documentElement.outerHTML
|
||||
})
|
||||
})
|
||||
cy.visit('/', { userState: DISCONNECTED_WALLET_USER_STATE })
|
||||
|
||||
cy.get(getTestSelector('landing-page')).should('not.exist')
|
||||
cy.get(getTestSelector('buy-fiat-button')).should('not.exist')
|
||||
cy.url().should('include', '/swap')
|
||||
})
|
||||
|
||||
it('does not render uk compliance banner in US', () => {
|
||||
cy.visit('/swap')
|
||||
cy.contains('UK disclaimer').should('not.exist')
|
||||
|
@ -59,9 +59,10 @@ describe('Mini Portfolio account drawer', () => {
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
|
||||
cy.get(getTestSelector('mini-portfolio-page')).contains('I Got Plenty')
|
||||
|
||||
cy.intercept(/graphql/, { fixture: 'mini-portfolio/pools.json' })
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Pools').click()
|
||||
cy.get(getTestSelector('mini-portfolio-page')).contains('No pools yet')
|
||||
// Skip this for now, someone sent test account an NFT on block 17445713 that causes this test to fail
|
||||
// cy.intercept(/graphql/, { fixture: 'mini-portfolio/pools.json' })
|
||||
// cy.get(getTestSelector('mini-portfolio-navbar')).contains('Pools').click()
|
||||
// cy.get(getTestSelector('mini-portfolio-page')).contains('No pools yet')
|
||||
|
||||
cy.intercept(/graphql/, { fixture: 'mini-portfolio/full_activity.json' })
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { InterfaceSectionName } from '@uniswap/analytics-events'
|
||||
import { CurrencyAmount } from '@uniswap/sdk-core'
|
||||
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
|
||||
@ -86,6 +87,7 @@ describe('Swap errors', () => {
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.get(getTestSelector('max-slippage-settings')).click()
|
||||
cy.get(getTestSelector('slippage-input')).clear().type('0.01')
|
||||
cy.get(getTestSelector('toggle-uniswap-x-button')).click() // turn off uniswapx
|
||||
cy.get('body').click('topRight') // close modal
|
||||
cy.get(getTestSelector('slippage-input')).should('not.exist')
|
||||
|
||||
@ -116,4 +118,18 @@ describe('Swap errors', () => {
|
||||
getBalance(DAI).should('be.closeTo', initialBalance + 200, 1)
|
||||
})
|
||||
})
|
||||
|
||||
it('insufficient liquidity', () => {
|
||||
// The API response is too variable so stubbing a 404.
|
||||
cy.intercept('POST', 'https://api.uniswap.org/v2/quote', {
|
||||
statusCode: 404,
|
||||
fixture: 'insufficientLiquidity.json',
|
||||
})
|
||||
|
||||
cy.visit(`/swap?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
||||
cy.get('#swap-currency-output .token-amount-input').type('100000000000000').should('have.value', '100000000000000') // 100 trillion
|
||||
cy.contains('Insufficient liquidity for this trade.')
|
||||
cy.get('#swap-button').should('not.exist')
|
||||
cy.get(getTestSelector(`fiat-value-${InterfaceSectionName.CURRENCY_OUTPUT_PANEL}`)).contains('-')
|
||||
})
|
||||
})
|
||||
|
146
cypress/e2e/swap/fees.test.ts
Normal file
146
cypress/e2e/swap/fees.test.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import { CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { FeatureFlag } from 'featureFlags'
|
||||
|
||||
import { USDC_MAINNET } from '../../../src/constants/tokens'
|
||||
import { getBalance, getTestSelector } from '../../utils'
|
||||
|
||||
describe.skip('Swap with fees', () => {
|
||||
describe('Classic swaps', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/swap', { featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }] })
|
||||
|
||||
// Store trade quote into alias
|
||||
cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => {
|
||||
// Avoid tracking stablecoin pricing fetches
|
||||
if (JSON.parse(req.body).intent !== 'pricing') req.alias = 'quoteFetch'
|
||||
})
|
||||
})
|
||||
|
||||
it('displays $0 fee on swaps without fees', () => {
|
||||
// Set up a stablecoin <> stablecoin swap (no fees)
|
||||
cy.get('#swap-currency-input .open-currency-select-button').click()
|
||||
cy.contains('DAI').click()
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1')
|
||||
|
||||
// Verify 0 fee UI is displayed
|
||||
cy.get(getTestSelector('swap-details-header-row')).click()
|
||||
cy.contains('Fee')
|
||||
cy.contains('$0')
|
||||
})
|
||||
|
||||
it('swaps ETH for USDC exact-out with swap fee', () => {
|
||||
cy.hardhat().then((hardhat) => {
|
||||
getBalance(USDC_MAINNET).then((initialBalance) => {
|
||||
// Set up swap
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1')
|
||||
|
||||
cy.wait('@quoteFetch')
|
||||
.its('response.body')
|
||||
.then(({ quote: { portionBips, portionRecipient, portionAmount } }) => {
|
||||
// Fees are generally expected to always be enabled for ETH -> USDC swaps
|
||||
// If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars
|
||||
if (portionRecipient) return
|
||||
|
||||
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => {
|
||||
const feeCurrencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, portionAmount)
|
||||
|
||||
// Initiate transaction
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Review swap')
|
||||
|
||||
// Verify fee percentage and amount is displayed
|
||||
cy.contains(`Fee (${portionBips / 100}%)`)
|
||||
|
||||
// Confirm transaction
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
// Verify transaction
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
|
||||
// Verify the post-fee output is the expected exact-out amount
|
||||
const finalBalance = initialBalance + 1
|
||||
cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`)
|
||||
getBalance(USDC_MAINNET).should('eq', finalBalance)
|
||||
|
||||
// Verify fee recipient received fee
|
||||
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => {
|
||||
const expectedFinalRecipientBalance = initialRecipientBalance.add(feeCurrencyAmount)
|
||||
cy.then(() => finalRecipientBalance.equalTo(expectedFinalRecipientBalance)).should('be.true')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('swaps ETH for USDC exact-in with swap fee', () => {
|
||||
cy.hardhat().then((hardhat) => {
|
||||
// Set up swap
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-input .token-amount-input').type('.01')
|
||||
|
||||
cy.wait('@quoteFetch')
|
||||
.its('response.body')
|
||||
.then(({ quote: { portionBips, portionRecipient } }) => {
|
||||
// Fees are generally expected to always be enabled for ETH -> USDC swaps
|
||||
// If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars
|
||||
if (portionRecipient) return
|
||||
|
||||
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => {
|
||||
// Initiate transaction
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Review swap')
|
||||
|
||||
// Verify fee percentage and amount is displayed
|
||||
cy.contains(`Fee (${portionBips / 100}%)`)
|
||||
|
||||
// Confirm transaction
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
// Verify transaction
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
|
||||
// Verify fee recipient received fee
|
||||
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => {
|
||||
cy.then(() => finalRecipientBalance.greaterThan(initialRecipientBalance)).should('be.true')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UniswapX swaps', () => {
|
||||
it('displays UniswapX fee in UI', () => {
|
||||
cy.visit('/swap', {
|
||||
featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }],
|
||||
})
|
||||
|
||||
// Intercept the trade quote
|
||||
cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => {
|
||||
// Avoid intercepting stablecoin pricing fetches
|
||||
if (JSON.parse(req.body).intent !== 'pricing') {
|
||||
req.reply({ fixture: 'uniswapx/feeQuote.json' })
|
||||
}
|
||||
})
|
||||
|
||||
// Setup swap
|
||||
cy.get('#swap-currency-input .open-currency-select-button').click()
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.contains('ETH').click()
|
||||
cy.get('#swap-currency-input .token-amount-input').type('200')
|
||||
|
||||
// Verify fee UI is displayed
|
||||
cy.get(getTestSelector('swap-details-header-row')).click()
|
||||
cy.contains('Fee (0.15%)')
|
||||
})
|
||||
})
|
||||
})
|
@ -1,18 +1,35 @@
|
||||
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { FeatureFlag } from 'featureFlags'
|
||||
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
||||
|
||||
import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||
import { getTestSelector } from '../../utils'
|
||||
|
||||
const QuoteEndpoint = 'https://api.uniswap.org/v2/quote'
|
||||
const QuoteWhereUniswapXIsBetter = 'uniswapx/quote1.json'
|
||||
const QuoteWithEthInput = 'uniswapx/quote2.json'
|
||||
|
||||
const QuoteEndpoint = 'https://api.uniswap.org/v2/quote'
|
||||
const OrderSubmissionEndpoint = 'https://api.uniswap.org/v2/order'
|
||||
|
||||
const OrderStatusEndpoint =
|
||||
'https://api.uniswap.org/v2/orders?swapper=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266&orderHashes=0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19'
|
||||
|
||||
/**
|
||||
* Stubs quote to return a quote for non-price requests
|
||||
* Price quotes are blocked with 409, as the backend would not accept them regardless
|
||||
*/
|
||||
function stubNonPriceQuoteWith(fixture: string) {
|
||||
cy.intercept(QuoteEndpoint, (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
let body = req.body
|
||||
if (typeof body === 'string') {
|
||||
body = JSON.parse(body)
|
||||
}
|
||||
if (body.intent === 'pricing') {
|
||||
req.reply({ statusCode: 409 })
|
||||
} else {
|
||||
req.reply({ fixture })
|
||||
}
|
||||
}).as('quote')
|
||||
}
|
||||
|
||||
/** Stubs the provider to return a tx receipt corresponding to the mock filled uniswapx order's txHash */
|
||||
function stubSwapTxReceipt() {
|
||||
cy.hardhat().then((hardhat) => {
|
||||
@ -24,70 +41,39 @@ function stubSwapTxReceipt() {
|
||||
})
|
||||
}
|
||||
|
||||
describe('UniswapX Toggle', () => {
|
||||
// TODO: FIX THESE TESTS where we should NOT stub for pricing requests
|
||||
describe.skip('UniswapX Toggle', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
|
||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||
})
|
||||
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
|
||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
||||
})
|
||||
|
||||
it('only displays uniswapx ui when setting is on', () => {
|
||||
it('displays uniswapx ui when setting is on', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
|
||||
// UniswapX UI should not be visible
|
||||
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist')
|
||||
|
||||
// Opt-in to UniswapX
|
||||
cy.contains('Try it now').click()
|
||||
cy.wait('@quote')
|
||||
|
||||
// UniswapX UI should be visible
|
||||
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('exist')
|
||||
})
|
||||
|
||||
it('prompts opt-in if UniswapX is better', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
|
||||
// UniswapX should not display in gas estimate row before opt-in
|
||||
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist')
|
||||
|
||||
// UniswapX mustache should be visible
|
||||
cy.contains('Try it now').click()
|
||||
|
||||
// Opt-in dialog should now be hidden
|
||||
cy.contains('Try it now').should('not.be.visible')
|
||||
|
||||
// UniswapX should display in gas estimate row
|
||||
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('exist')
|
||||
|
||||
// Opt-in dialog should not reappear if user manually toggles UniswapX off
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.get(getTestSelector('toggle-uniswap-x-button')).click()
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.contains('Try it now').should('not.be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
describe('UniswapX Orders', () => {
|
||||
describe.skip('UniswapX Orders', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
|
||||
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
|
||||
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
|
||||
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' })
|
||||
|
||||
stubSwapTxReceipt()
|
||||
|
||||
cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)))
|
||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||
})
|
||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
||||
})
|
||||
|
||||
it('can swap exact-in trades using uniswapX', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
cy.wait('@quote')
|
||||
|
||||
// Submit uniswapx order signature
|
||||
cy.get('#swap-button').click()
|
||||
@ -106,7 +92,7 @@ describe('UniswapX Orders', () => {
|
||||
it('can swap exact-out trades using uniswapX', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-output .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
cy.wait('@quote')
|
||||
|
||||
// Submit uniswapx order signature
|
||||
cy.get('#swap-button').click()
|
||||
@ -125,7 +111,7 @@ describe('UniswapX Orders', () => {
|
||||
it('renders proper view if uniswapx order expires', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
cy.wait('@quote')
|
||||
|
||||
// Submit uniswapx order signature
|
||||
cy.get('#swap-button').click()
|
||||
@ -141,7 +127,7 @@ describe('UniswapX Orders', () => {
|
||||
it('renders proper view if uniswapx order has insufficient funds', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
cy.wait('@quote')
|
||||
|
||||
// Submit uniswapx order signature
|
||||
cy.get('#swap-button').click()
|
||||
@ -155,9 +141,9 @@ describe('UniswapX Orders', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('UniswapX Eth Input', () => {
|
||||
describe.skip('UniswapX Eth Input', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept(QuoteEndpoint, { fixture: QuoteWithEthInput })
|
||||
stubNonPriceQuoteWith(QuoteWithEthInput)
|
||||
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
|
||||
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' })
|
||||
|
||||
@ -169,15 +155,14 @@ describe('UniswapX Eth Input', () => {
|
||||
|
||||
stubSwapTxReceipt()
|
||||
|
||||
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`, {
|
||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||
})
|
||||
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`)
|
||||
})
|
||||
|
||||
it('can swap using uniswapX with ETH as input', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('1')
|
||||
cy.contains('Try it now').click()
|
||||
|
||||
cy.wait('@quote')
|
||||
|
||||
// Prompt ETH wrap to use for order
|
||||
cy.get('#swap-button').click()
|
||||
@ -206,10 +191,10 @@ describe('UniswapX Eth Input', () => {
|
||||
cy.contains('Swapped')
|
||||
})
|
||||
|
||||
it('switches swap input to WETH after wrap', () => {
|
||||
it('keeps ETH as the input currency before wrap completes', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('1')
|
||||
cy.contains('Try it now').click()
|
||||
cy.wait('@quote')
|
||||
|
||||
// Prompt ETH wrap and confirm
|
||||
cy.get('#swap-button').click()
|
||||
@ -218,16 +203,25 @@ describe('UniswapX Eth Input', () => {
|
||||
|
||||
// Close review modal before wrap is confirmed on chain
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
// Confirm ETH is still the input token before wrap succeeds
|
||||
cy.contains('ETH')
|
||||
})
|
||||
|
||||
it('switches swap input to WETH after wrap', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('1')
|
||||
cy.wait('@quote')
|
||||
|
||||
// Prompt ETH wrap and confirm
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
|
||||
// Confirm wrap is successful and WETH is now input token
|
||||
cy.contains('Wrapped')
|
||||
cy.contains('WETH')
|
||||
|
||||
// Reopen review modal and continue swap
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
// Approve WETH spend
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
@ -242,10 +236,15 @@ describe('UniswapX Eth Input', () => {
|
||||
|
||||
// Verify swap success
|
||||
cy.contains('Swapped')
|
||||
|
||||
// Close modal
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
// The input currency should now be WETH
|
||||
cy.contains('WETH')
|
||||
})
|
||||
})
|
||||
|
||||
describe('UniswapX activity history', () => {
|
||||
describe.skip('UniswapX activity history', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
|
||||
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
|
||||
@ -256,15 +255,12 @@ describe('UniswapX activity history', () => {
|
||||
cy.hardhat().then(async (hardhat) => {
|
||||
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))
|
||||
})
|
||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||
})
|
||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
||||
})
|
||||
|
||||
it('can view UniswapX order status progress in activity', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
|
||||
// Submit uniswapx order signature
|
||||
cy.get('#swap-button').click()
|
||||
@ -292,7 +288,6 @@ describe('UniswapX activity history', () => {
|
||||
it('can view UniswapX order status progress in activity upon expiry', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
|
||||
// Submit uniswapx order signature
|
||||
cy.get('#swap-button').click()
|
||||
@ -319,7 +314,6 @@ describe('UniswapX activity history', () => {
|
||||
it('deduplicates remote vs local uniswapx orders', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
|
||||
// Submit uniswapx order signature
|
||||
cy.get('#swap-button').click()
|
||||
@ -344,14 +338,13 @@ describe('UniswapX activity history', () => {
|
||||
// Open activity history
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
|
||||
|
||||
// Ensure gql and local order have been deduped, such that there is only one swap activity listed
|
||||
// Ensure gql and local order have been deduped, such that there is one swap activity listed
|
||||
cy.get(getTestSelector('activity-content')).contains('Swapped').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('balances should refetch after uniswapx swap', () => {
|
||||
// Setup a swap
|
||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||
cy.contains('Try it now').click()
|
||||
|
||||
const gqlSpy = cy.spy().as('gqlSpy')
|
||||
cy.intercept(/graphql/, (req) => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { FeatureFlag } from 'featureFlags'
|
||||
|
||||
import { ARB, UNI } from '../../src/constants/tokens'
|
||||
import { getTestSelector } from '../utils'
|
||||
@ -14,8 +15,9 @@ describe('Token details', () => {
|
||||
|
||||
it('Uniswap token should have all information populated', () => {
|
||||
// Uniswap token
|
||||
cy.visit(`/tokens/ethereum/${UNI_ADDRESS}`)
|
||||
|
||||
cy.visit(`/tokens/ethereum/${UNI_ADDRESS}`, {
|
||||
featureFlags: [{ name: FeatureFlag.infoTDP, value: false }],
|
||||
})
|
||||
// Price chart should be filled in
|
||||
cy.get('[data-cy="chart-header"]').should('include.text', '$')
|
||||
cy.get('[data-cy="price-chart"]').should('exist')
|
||||
@ -47,6 +49,22 @@ describe('Token details', () => {
|
||||
cy.contains(UNI_ADDRESS).should('exist')
|
||||
})
|
||||
|
||||
it('Uniswap token should have correct stats boxes if infoTDP flag on', () => {
|
||||
// Uniswap token
|
||||
cy.visit(`/tokens/ethereum/${UNI_ADDRESS}`, {
|
||||
featureFlags: [{ name: FeatureFlag.infoTDP, value: true }],
|
||||
})
|
||||
|
||||
// Stats should have: TVL, FDV, market cap, 24H volume
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).within(() => {
|
||||
cy.get('[data-cy="tvl"]').should('include.text', '$')
|
||||
cy.get('[data-cy="fdv"]').should('include.text', '$')
|
||||
cy.get('[data-cy="market-cap"]').should('include.text', '$')
|
||||
cy.get('[data-cy="volume-24h"]').should('include.text', '$')
|
||||
})
|
||||
})
|
||||
|
||||
it('token with warning and low trading volume should have all information populated', () => {
|
||||
// Null token created for this test, 0 trading volume and has warning modal
|
||||
cy.visit('/tokens/ethereum/0x1eFBB78C8b917f67986BcE54cE575069c0143681')
|
||||
|
@ -59,9 +59,7 @@ describe('Token explore', () => {
|
||||
// in metamask modal using plain cypress. this is a workaround.
|
||||
cy.visit('/tokens/polygon')
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Polygon')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('name-cell'))
|
||||
.should('include.text', 'Polygon Matic')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('name-cell')).should('include.text', 'Matic')
|
||||
})
|
||||
|
||||
it('should update when token explore table network changed', () => {
|
||||
|
@ -49,6 +49,34 @@ describe('Wallet Dropdown', () => {
|
||||
})
|
||||
itChangesTheme()
|
||||
itChangesLocale()
|
||||
|
||||
it('should not show buy crypto button in uk', () => {
|
||||
cy.document().then((doc) => {
|
||||
const meta = document.createElement('meta')
|
||||
meta.setAttribute('property', 'x:blocked-paths')
|
||||
meta.setAttribute('content', '/,/nfts,/buy')
|
||||
doc.head.appendChild(meta)
|
||||
})
|
||||
cy.get(getTestSelector('wallet-buy-crypto')).should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('do not render buy button when /buy is blocked', () => {
|
||||
beforeEach(() => {
|
||||
cy.document().then((doc) => {
|
||||
const meta = document.createElement('meta')
|
||||
meta.setAttribute('property', 'x:blocked-paths')
|
||||
meta.setAttribute('content', '/buy')
|
||||
doc.head.appendChild(meta)
|
||||
})
|
||||
cy.visit('/')
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-settings')).click()
|
||||
})
|
||||
|
||||
it('should not render buy button', () => {
|
||||
cy.get(getTestSelector('wallet-buy-crypto')).should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('should change locale with feature flag', () => {
|
||||
|
5
cypress/fixtures/insufficientLiquidity.json
Normal file
5
cypress/fixtures/insufficientLiquidity.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"errorCode": "QUOTE_ERROR",
|
||||
"detail": "No quotes available",
|
||||
"id": "63363cc1-d474-4584-b386-7c356814b79f"
|
||||
}
|
562
cypress/fixtures/uniswapx/feeQuote.json
Normal file
562
cypress/fixtures/uniswapx/feeQuote.json
Normal file
@ -0,0 +1,562 @@
|
||||
{
|
||||
"routing": "DUTCH_LIMIT",
|
||||
"quote": {
|
||||
"orderInfo": {
|
||||
"chainId": 1,
|
||||
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
|
||||
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
|
||||
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
|
||||
"nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633",
|
||||
"deadline": 1697481666,
|
||||
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
|
||||
"additionalValidationData": "0x",
|
||||
"decayStartTime": 1697481594,
|
||||
"decayEndTime": 1697481654,
|
||||
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
|
||||
"exclusivityOverrideBps": "100",
|
||||
"input": {
|
||||
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"startAmount": "200000000",
|
||||
"endAmount": "200000000"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": "123803169993201727",
|
||||
"endAmount": "117908377342236273",
|
||||
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
|
||||
},
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": "185983730585681",
|
||||
"endAmount": "177128258400955",
|
||||
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
|
||||
}
|
||||
]
|
||||
},
|
||||
"encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327",
|
||||
"quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890",
|
||||
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
|
||||
"orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9",
|
||||
"startTimeBufferSecs": 45,
|
||||
"auctionPeriodSecs": 60,
|
||||
"deadlineBufferSecs": 12,
|
||||
"slippageTolerance": "0.5",
|
||||
"permitData": {
|
||||
"domain": {
|
||||
"name": "Permit2",
|
||||
"chainId": 1,
|
||||
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
|
||||
},
|
||||
"types": {
|
||||
"PermitWitnessTransferFrom": [
|
||||
{
|
||||
"name": "permitted",
|
||||
"type": "TokenPermissions"
|
||||
},
|
||||
{
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "witness",
|
||||
"type": "ExclusiveDutchOrder"
|
||||
}
|
||||
],
|
||||
"TokenPermissions": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"ExclusiveDutchOrder": [
|
||||
{
|
||||
"name": "info",
|
||||
"type": "OrderInfo"
|
||||
},
|
||||
{
|
||||
"name": "decayStartTime",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "decayEndTime",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "exclusiveFiller",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "exclusivityOverrideBps",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "inputStartAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputEndAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outputs",
|
||||
"type": "DutchOutput[]"
|
||||
}
|
||||
],
|
||||
"OrderInfo": [
|
||||
{
|
||||
"name": "reactor",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "swapper",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "additionalValidationContract",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "additionalValidationData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"DutchOutput": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "startAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "endAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
}
|
||||
]
|
||||
},
|
||||
"values": {
|
||||
"permitted": {
|
||||
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"amount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x0bebc200"
|
||||
}
|
||||
},
|
||||
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
|
||||
"nonce": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
|
||||
},
|
||||
"deadline": 1697481666,
|
||||
"witness": {
|
||||
"info": {
|
||||
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
|
||||
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
|
||||
"nonce": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
|
||||
},
|
||||
"deadline": 1697481666,
|
||||
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
|
||||
"additionalValidationData": "0x"
|
||||
},
|
||||
"decayStartTime": 1697481594,
|
||||
"decayEndTime": 1697481654,
|
||||
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
|
||||
"exclusivityOverrideBps": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x64"
|
||||
},
|
||||
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"inputStartAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x0bebc200"
|
||||
},
|
||||
"inputEndAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x0bebc200"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x01b7d653c183183f"
|
||||
},
|
||||
"endAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x01a2e50b6386d671"
|
||||
},
|
||||
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
|
||||
},
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0xa926b6321051"
|
||||
},
|
||||
"endAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0xa118e2ebf2bb"
|
||||
},
|
||||
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"portionBips": 15,
|
||||
"portionAmount": "185983730585681",
|
||||
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
|
||||
},
|
||||
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
|
||||
"allQuotes": [
|
||||
{
|
||||
"routing": "DUTCH_LIMIT",
|
||||
"quote": {
|
||||
"orderInfo": {
|
||||
"chainId": 1,
|
||||
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
|
||||
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
|
||||
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
|
||||
"nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633",
|
||||
"deadline": 1697481666,
|
||||
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
|
||||
"additionalValidationData": "0x",
|
||||
"decayStartTime": 1697481594,
|
||||
"decayEndTime": 1697481654,
|
||||
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
|
||||
"exclusivityOverrideBps": "100",
|
||||
"input": {
|
||||
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"startAmount": "200000000",
|
||||
"endAmount": "200000000"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": "123803169993201727",
|
||||
"endAmount": "117908377342236273",
|
||||
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
|
||||
},
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": "185983730585681",
|
||||
"endAmount": "177128258400955",
|
||||
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
|
||||
}
|
||||
]
|
||||
},
|
||||
"encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327",
|
||||
"quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890",
|
||||
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
|
||||
"orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9",
|
||||
"startTimeBufferSecs": 45,
|
||||
"auctionPeriodSecs": 60,
|
||||
"deadlineBufferSecs": 12,
|
||||
"slippageTolerance": "0.5",
|
||||
"permitData": {
|
||||
"domain": {
|
||||
"name": "Permit2",
|
||||
"chainId": 1,
|
||||
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
|
||||
},
|
||||
"types": {
|
||||
"PermitWitnessTransferFrom": [
|
||||
{
|
||||
"name": "permitted",
|
||||
"type": "TokenPermissions"
|
||||
},
|
||||
{
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "witness",
|
||||
"type": "ExclusiveDutchOrder"
|
||||
}
|
||||
],
|
||||
"TokenPermissions": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"ExclusiveDutchOrder": [
|
||||
{
|
||||
"name": "info",
|
||||
"type": "OrderInfo"
|
||||
},
|
||||
{
|
||||
"name": "decayStartTime",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "decayEndTime",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "exclusiveFiller",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "exclusivityOverrideBps",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "inputStartAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputEndAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outputs",
|
||||
"type": "DutchOutput[]"
|
||||
}
|
||||
],
|
||||
"OrderInfo": [
|
||||
{
|
||||
"name": "reactor",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "swapper",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "additionalValidationContract",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "additionalValidationData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"DutchOutput": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "startAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "endAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
}
|
||||
]
|
||||
},
|
||||
"values": {
|
||||
"permitted": {
|
||||
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"amount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x0bebc200"
|
||||
}
|
||||
},
|
||||
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
|
||||
"nonce": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
|
||||
},
|
||||
"deadline": 1697481666,
|
||||
"witness": {
|
||||
"info": {
|
||||
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
|
||||
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
|
||||
"nonce": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
|
||||
},
|
||||
"deadline": 1697481666,
|
||||
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
|
||||
"additionalValidationData": "0x"
|
||||
},
|
||||
"decayStartTime": 1697481594,
|
||||
"decayEndTime": 1697481654,
|
||||
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
|
||||
"exclusivityOverrideBps": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x64"
|
||||
},
|
||||
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"inputStartAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x0bebc200"
|
||||
},
|
||||
"inputEndAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x0bebc200"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x01b7d653c183183f"
|
||||
},
|
||||
"endAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0x01a2e50b6386d671"
|
||||
},
|
||||
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
|
||||
},
|
||||
{
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"startAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0xa926b6321051"
|
||||
},
|
||||
"endAmount": {
|
||||
"type": "BigNumber",
|
||||
"hex": "0xa118e2ebf2bb"
|
||||
},
|
||||
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"portionBips": 15,
|
||||
"portionAmount": "185983730585681",
|
||||
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
|
||||
}
|
||||
},
|
||||
{
|
||||
"routing": "CLASSIC",
|
||||
"quote": {
|
||||
"methodParameters": {
|
||||
"calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000652d85d0000000000000000000000000000000000000000000000000000000000000000308060c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000001bdf1285753b47400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000037a8f295612602f2774d331e562be9e61b83a327000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000001bd45ea74e458eb",
|
||||
"value": "0x00",
|
||||
"to": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD"
|
||||
},
|
||||
"blockNumber": "18364784",
|
||||
"amount": "200000000",
|
||||
"amountDecimals": "200",
|
||||
"quote": "126149127803342909",
|
||||
"quoteDecimals": "0.126149127803342909",
|
||||
"quoteGasAdjusted": "122888348391508943",
|
||||
"quoteGasAdjustedDecimals": "0.122888348391508943",
|
||||
"quoteGasAndPortionAdjusted": "122699124699803928",
|
||||
"quoteGasAndPortionAdjustedDecimals": "0.122699124699803928",
|
||||
"gasUseEstimateQuote": "3260779411833966",
|
||||
"gasUseEstimateQuoteDecimals": "0.003260779411833966",
|
||||
"gasUseEstimate": "240911",
|
||||
"gasUseEstimateUSD": "5.153332510477604328",
|
||||
"simulationStatus": "SUCCESS",
|
||||
"simulationError": false,
|
||||
"gasPriceWei": "13535203506",
|
||||
"route": [
|
||||
[
|
||||
{
|
||||
"type": "v2-pool",
|
||||
"address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
|
||||
"tokenIn": {
|
||||
"chainId": 1,
|
||||
"decimals": "6",
|
||||
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"symbol": "USDC"
|
||||
},
|
||||
"tokenOut": {
|
||||
"chainId": 1,
|
||||
"decimals": "18",
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"symbol": "WETH"
|
||||
},
|
||||
"reserve0": {
|
||||
"token": {
|
||||
"chainId": 1,
|
||||
"decimals": "6",
|
||||
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"symbol": "USDC"
|
||||
},
|
||||
"quotient": "27487668611269"
|
||||
},
|
||||
"reserve1": {
|
||||
"token": {
|
||||
"chainId": 1,
|
||||
"decimals": "18",
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"symbol": "WETH"
|
||||
},
|
||||
"quotient": "17390022942803382004255"
|
||||
},
|
||||
"amountIn": "200000000",
|
||||
"amountOut": "125959904111637894"
|
||||
}
|
||||
]
|
||||
],
|
||||
"routeString": "[V2] 100.00% = USDC -- [0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc] --> WETH",
|
||||
"quoteId": "f46cf31c-251e-470c-bd57-13209015694e",
|
||||
"portionBips": 15,
|
||||
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327",
|
||||
"portionAmount": "189223691705014",
|
||||
"portionAmountDecimals": "0.000189223691705014",
|
||||
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
|
||||
"tradeType": "EXACT_INPUT",
|
||||
"slippage": 0.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -74,18 +74,14 @@ Cypress.Commands.overwrite(
|
||||
}
|
||||
)
|
||||
|
||||
Cypress.Commands.add('waitForAmplitudeEvent', (eventName, timeout = 5000 /* 5s */) => {
|
||||
const startTime = new Date().getTime()
|
||||
|
||||
Cypress.Commands.add('waitForAmplitudeEvent', (eventName) => {
|
||||
function checkRequest() {
|
||||
return cy.wait('@amplitude', { timeout }).then((interception) => {
|
||||
return cy.wait('@amplitude').then((interception) => {
|
||||
const events = interception.request.body.events
|
||||
const event = events.find((event: any) => event.event_type === eventName)
|
||||
|
||||
if (event) {
|
||||
return cy.wrap(event)
|
||||
} else if (new Date().getTime() - startTime > timeout) {
|
||||
throw new Error(`Event ${eventName} not found within the specified timeout`)
|
||||
} else {
|
||||
return checkRequest()
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ beforeEach(() => {
|
||||
)
|
||||
}).intercept('https://*.sentry.io', { statusCode: 200 })
|
||||
|
||||
// Mock statsig to allow us to mock flags.
|
||||
cy.intercept(/statsig/, { statusCode: 409 })
|
||||
|
||||
// Mock our own token list responses to avoid the latency of IPFS.
|
||||
cy.intercept('https://gateway.ipfs.io/ipns/tokens.uniswap.org', TokenListJSON)
|
||||
.intercept('https://gateway.ipfs.io/ipns/extendedtokens.uniswap.org', { statusCode: 404 })
|
||||
|
@ -5,7 +5,7 @@
|
||||
"incremental": true,
|
||||
"isolatedModules": false,
|
||||
"noImplicitAny": false,
|
||||
"target": "ES5",
|
||||
"target": "ES6",
|
||||
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo
|
||||
"types": ["cypress", "node"],
|
||||
},
|
||||
|
@ -7,19 +7,19 @@ const collectionImageUrls = [
|
||||
'http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
|
||||
]
|
||||
|
||||
const nonexistentImageUrls = [
|
||||
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
]
|
||||
|
||||
test.each([...collectionImageUrls, ...nonexistentImageUrls])('collectionImageUrl', async (url) => {
|
||||
test.each([...collectionImageUrls])('collectionImageUrl', async (url) => {
|
||||
const response = await fetch(new Request(url))
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.headers.get('content-type')).toBe('image/png')
|
||||
})
|
||||
|
||||
const nonexistentImageUrls = [
|
||||
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
]
|
||||
|
||||
const invalidCollectionImageUrls = ['http://127.0.0.1:3000/api/image/nfts/collection/0xd3adb33f']
|
||||
|
||||
test.each(invalidCollectionImageUrls)('invalidAssetImageUrl', async (url) => {
|
||||
test.each([...invalidCollectionImageUrls, ...nonexistentImageUrls])('invalidAssetImageUrl', async (url) => {
|
||||
const response = await fetch(new Request(url))
|
||||
expect(response.status).toBeOneOf([404, 500])
|
||||
})
|
||||
|
@ -41,8 +41,9 @@ exports[`should inject metadata for valid assets 1`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -58,9 +59,9 @@ exports[`should inject metadata for valid assets 1`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -68,15 +69,14 @@ exports[`should inject metadata for valid assets 1`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,8 +189,9 @@ exports[`should inject metadata for valid assets 2`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -206,9 +207,9 @@ exports[`should inject metadata for valid assets 2`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -216,15 +217,14 @@ exports[`should inject metadata for valid assets 2`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,8 +337,9 @@ exports[`should inject metadata for valid assets 3`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -354,9 +355,9 @@ exports[`should inject metadata for valid assets 3`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -364,15 +365,14 @@ exports[`should inject metadata for valid assets 3`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,9 @@ exports[`should inject metadata for collections 1`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -58,9 +59,9 @@ exports[`should inject metadata for collections 1`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -68,15 +69,14 @@ exports[`should inject metadata for collections 1`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,8 +189,9 @@ exports[`should inject metadata for collections 2`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -206,9 +207,9 @@ exports[`should inject metadata for collections 2`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -216,15 +217,14 @@ exports[`should inject metadata for collections 2`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,8 +337,9 @@ exports[`should inject metadata for collections 3`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -354,9 +355,9 @@ exports[`should inject metadata for collections 3`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -364,15 +365,14 @@ exports[`should inject metadata for collections 3`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,151 +443,3 @@ exports[`should inject metadata for collections 3`] = `
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for collections 4`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
|
||||
<!--
|
||||
will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#fff" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://api.uniswap.org/" crossorigin/>
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Basel var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Basel';
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Basel';
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-weight: 485;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
text-rendering: optimizeLegibility !important;
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(rgb(19, 19, 19) 0%, rgb(19, 19, 19) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0) 0%, rgba(255, 255, 255, 0) 100%), rgb(255, 255, 255);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545"/><meta property="twitter:image:alt" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
@ -16,15 +16,7 @@ const collections = [
|
||||
},
|
||||
]
|
||||
|
||||
const nonexistentCollections = [
|
||||
{
|
||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
collectionName: '0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
},
|
||||
]
|
||||
|
||||
test.each([...collections, ...nonexistentCollections])('should inject metadata for collections', async (collection) => {
|
||||
test.each([...collections])('should inject metadata for collections', async (collection) => {
|
||||
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).toMatchSnapshot()
|
||||
@ -42,24 +34,33 @@ test.each([...collections, ...nonexistentCollections])('should inject metadata f
|
||||
expect(body).toContain(`<meta property="twitter:image:alt" content="${collection.collectionName} on Uniswap"/>`)
|
||||
})
|
||||
|
||||
const nonexistentCollections = [
|
||||
{
|
||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
},
|
||||
]
|
||||
|
||||
const invalidCollections = [
|
||||
{
|
||||
address: '0xd3adb33f',
|
||||
},
|
||||
]
|
||||
|
||||
test.each(invalidCollections)('should not inject metadata for nonexistent collections', async (collection) => {
|
||||
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).not.toContain('og:title')
|
||||
expect(body).not.toContain('og:image')
|
||||
expect(body).not.toContain('og:image:width')
|
||||
expect(body).not.toContain('og:image:height')
|
||||
expect(body).not.toContain('og:type')
|
||||
expect(body).not.toContain('og:url')
|
||||
expect(body).not.toContain('og:image:alt')
|
||||
expect(body).not.toContain('twitter:card')
|
||||
expect(body).not.toContain('twitter:title')
|
||||
expect(body).not.toContain('twitter:image')
|
||||
expect(body).not.toContain('twitter:image:alt')
|
||||
})
|
||||
test.each([...invalidCollections, ...nonexistentCollections])(
|
||||
'should not inject metadata for nonexistent collections',
|
||||
async (collection) => {
|
||||
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).not.toContain('og:title')
|
||||
expect(body).not.toContain('og:image')
|
||||
expect(body).not.toContain('og:image:width')
|
||||
expect(body).not.toContain('og:image:height')
|
||||
expect(body).not.toContain('og:type')
|
||||
expect(body).not.toContain('og:url')
|
||||
expect(body).not.toContain('og:image:alt')
|
||||
expect(body).not.toContain('twitter:card')
|
||||
expect(body).not.toContain('twitter:title')
|
||||
expect(body).not.toContain('twitter:image')
|
||||
expect(body).not.toContain('twitter:image:alt')
|
||||
}
|
||||
)
|
||||
|
@ -41,8 +41,9 @@ exports[`should inject metadata for valid tokens 1`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -58,9 +59,9 @@ exports[`should inject metadata for valid tokens 1`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -68,15 +69,14 @@ exports[`should inject metadata for valid tokens 1`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,8 +189,9 @@ exports[`should inject metadata for valid tokens 2`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -206,9 +207,9 @@ exports[`should inject metadata for valid tokens 2`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -216,15 +217,14 @@ exports[`should inject metadata for valid tokens 2`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,8 +337,9 @@ exports[`should inject metadata for valid tokens 3`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -354,9 +355,9 @@ exports[`should inject metadata for valid tokens 3`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -364,15 +365,14 @@ exports[`should inject metadata for valid tokens 3`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,8 +485,9 @@ exports[`should inject metadata for valid tokens 4`] = `
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -502,9 +503,9 @@ exports[`should inject metadata for valid tokens 4`] = `
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Medium.woff) format('woff');
|
||||
url('/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -512,15 +513,14 @@ exports[`should inject metadata for valid tokens 4`] = `
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(/fonts/Basel-Book.woff) format('woff');
|
||||
url('/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,8 @@ const forkingConfig = {
|
||||
const forks = {
|
||||
[ChainId.MAINNET]: {
|
||||
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
blockNumber: UNIVERSAL_ROUTER_CREATION_BLOCK(ChainId.MAINNET),
|
||||
// Temporarily hardcoding this to fix e2e tests as we investigate source of swap tests failing on older blocknumbers
|
||||
blockNumber: 18537387,
|
||||
...forkingConfig,
|
||||
},
|
||||
[ChainId.POLYGON]: {
|
||||
|
@ -28,7 +28,6 @@ const linguiConfig = {
|
||||
'ca-ES',
|
||||
'cs-CZ',
|
||||
'da-DK',
|
||||
'de-DE',
|
||||
'el-GR',
|
||||
'en-US',
|
||||
'es-ES',
|
||||
|
18
package.json
18
package.json
@ -116,7 +116,7 @@
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@types/xml2js": "^0.4.12",
|
||||
"@uniswap/default-token-list": "^11.2.0",
|
||||
"@uniswap/default-token-list": "^11.8.0",
|
||||
"@uniswap/eslint-config": "^1.2.0",
|
||||
"@vanilla-extract/jest-transform": "^1.1.1",
|
||||
"@vanilla-extract/webpack-plugin": "^2.2.0",
|
||||
@ -129,7 +129,7 @@
|
||||
"cypress": "12.12.0",
|
||||
"cypress-hardhat": "^2.5.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint": "8.44.0",
|
||||
"eslint-plugin-import": "^2.27",
|
||||
"eslint-plugin-rulesdir": "^0.2.2",
|
||||
"hardhat": "^2.14.0",
|
||||
@ -157,7 +157,6 @@
|
||||
"terser": "^5.19.4",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-transform-graphql-tag": "^0.2.1",
|
||||
"tsafe": "^1.6.4",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.9.4",
|
||||
@ -166,6 +165,9 @@
|
||||
"wrangler": "^3.7.0",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@uniswap/sdk-core": "4.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.2",
|
||||
"@coinbase/wallet-sdk": "^3.6.4",
|
||||
@ -194,18 +196,18 @@
|
||||
"@types/react-helmet": "^6.1.7",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "1.5.0",
|
||||
"@uniswap/analytics-events": "^2.24.0",
|
||||
"@uniswap/analytics-events": "^2.28.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "^1.0.1",
|
||||
"@uniswap/permit2-sdk": "^1.2.0",
|
||||
"@uniswap/redux-multicall": "^1.1.8",
|
||||
"@uniswap/router-sdk": "^1.6.0",
|
||||
"@uniswap/sdk-core": "^4.0.3",
|
||||
"@uniswap/router-sdk": "^1.7.1",
|
||||
"@uniswap/sdk-core": "4.0.7",
|
||||
"@uniswap/smart-order-router": "^3.15.0",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.33",
|
||||
"@uniswap/uniswapx-sdk": "^1.3.0",
|
||||
"@uniswap/universal-router-sdk": "^1.5.6",
|
||||
"@uniswap/uniswapx-sdk": "^1.4.1",
|
||||
"@uniswap/universal-router-sdk": "^1.5.8",
|
||||
"@uniswap/v2-core": "^1.0.1",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.2.0",
|
||||
|
99
public/app-sitemap.xml
Normal file
99
public/app-sitemap.xml
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/tokens</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/send</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/swap</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/pool/v2/find</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/pool/v2</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/pool</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/pools/v2/find</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/pools/v2</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/pools</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/add/v2</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/add</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/migrate/v2</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/profile</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/create-proposal</loc>
|
||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>
|
BIN
public/fonts/Basel-Book.woff2
Normal file
BIN
public/fonts/Basel-Book.woff2
Normal file
Binary file not shown.
BIN
public/fonts/Basel-Medium.woff2
Normal file
BIN
public/fonts/Basel-Medium.woff2
Normal file
Binary file not shown.
@ -40,8 +40,9 @@
|
||||
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
|
||||
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
@ -57,9 +58,9 @@
|
||||
font-weight: 535;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(%PUBLIC_URL%/fonts/Basel-Medium.woff) format('woff');
|
||||
url('%PUBLIC_URL%/fonts/Basel-Medium.woff2') format('woff2'),
|
||||
url('%PUBLIC_URL%/fonts/Basel-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -67,15 +68,14 @@
|
||||
font-weight: 485;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Book';
|
||||
src:
|
||||
url(%PUBLIC_URL%/fonts/Basel-Book.woff) format('woff');
|
||||
url('%PUBLIC_URL%/fonts/Basel-Book.woff') format('woff2'),
|
||||
url('%PUBLIC_URL%/fonts/Basel-Book.woff') format('woff');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Basel', sans-serif;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
518
public/nfts-sitemap.xml
Normal file
518
public/nfts-sitemap.xml
Normal file
@ -0,0 +1,518 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x60e4d786628fea6478f785a6d7e704777c86a7c6</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x99a9b7c1116f9ceeb1652de04d5969cce509b069</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xb7f7f6c52f2e2fdb1963eab30438024864c313f6</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x23581767a106ae21c074b2276d25e5c3e136a68b</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xba30e5f9bb24caa003e9f2f0497ad287fdf95623</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xbd3531da5cf5857e7cfaa92426877b022e612cf8</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x306b1ea3ecdf94ab739f1910bbda052ed4a9f949</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x1a92f7381b9f03921564a437210bb9396471050c</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x5cc5b05a8a13e3fbdb0bb9fccd98d38e50f90c38</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x5af0d9827e0c53e4799bb226655a1de152a425a5</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x3bf2922f4520a8ba0c2efc3d2a1539678dad5e9d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xe785e82358879f061bc3dcac6f0444462d4b5330</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x76be3b62873462d2142405439777e971754e8e77</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xfd43af6d3fe1b916c026f6ac35b3ede068d1ca01</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x1cb1a5e65610aeff2551a50f76a87a7d3fb649c6</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x6339e5e072086621540d0362c4e3cea0d643e114</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xb932a70a57673d89f4acffbe830e8ed7f75fb9e0</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x79fcdef22feed20eddacbb2587640e45491b757f</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xa3aee8bce55beea1951ef834b99f3ac60d1abeeb</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x769272677fab02575e84945f03eca517acc544cc</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x4db1f25d3d98600140dfc18deb7515be5bd293af</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x34eebee6942d8def3c125458d1a86e0a897fd6f9</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x59468516a8259058bad1ca5f8f4bff190d30e066</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x394e3d3044fc89fcdd966d3cb35ac0b32b0cda91</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x60bb1e2aa1c9acafb4d34f71585d7e959f387769</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x28472a58a490c5e09a238847f66a68a47cc76f0f</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x341a1c534248966c4b6afad165b98daed4b964ef</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xccc441ac31f02cd96c153db6fd5fe0a2f4e6a68d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x764aeebcf425d56800ef2c84f2578689415a2daa</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x160c404b2b49cbc3240055ceaee026df1e8497a0</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xd2f668a8461d6761115daf8aeb3cdf5f40c532c6</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x39ee2c7b3cb80254225884ca001f57118c8f21b6</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xd774557b647330c91bf44cfeab205095f7e6c367</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x1792a96e5668ad7c167ab804a100ce42395ce54d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x04afa589e2b933f9463c5639f412b183ec062505</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xe75512aa3bec8f00434bbd6ad8b0a3fbff100ad6</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x348fc118bcc65a92dc033a951af153d14d945312</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x892848074ddea461a15f337250da3ce55580ca85</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x5946aeaab44e65eb370ffaa6a7ef2218cff9b47d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x4b15a9c28034dc83db40cd810001427d3bd7163d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x7ea3cca10668b8346aec0bf1844a49e995527c8b</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xb852c6b5892256c264cc2c888ea462189154d8d7</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x9378368ba6b85c1fba5b131b530f5f5bedf21a18</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x2acab3dea77832c09420663b0e1cb386031ba17b</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x0c2e57efddba8c768147d1fdf9176a0a6ebd5d83</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x08d7c0242953446436f34b4c78fe9da38c73668d</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x8943c7bac1914c9a7aba750bf2b6b09fd21037e0</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x364c828ee171616a39897688a831c2499ad972ec</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x7f36182dee28c45de6072a34d29855bae76dbe2f</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xf61f24c2d93bf2de187546b14425bf631f28d6dc</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x797a48c46be32aafcedcfd3d8992493d8a1f256b</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x123b30e25973fecd8354dd5f41cc45a3065ef88c</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x6632a9d63e142f17a668064d41a21193b49b41a0</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xf4ee95274741437636e748ddac70818b4ed7d043</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x57a204aa1042f6e66dd7730813f4024114d74f37</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xd1258db6ac08eb0e625b75b371c023da478e94a9</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x75e95ba5997eb235f40ecf8347cdb11f18ff640b</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xd532b88607b1877fe20c181cba2550e3bbd6b31c</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xa1d4657e0e6507d5a94d06da93e94dc7c8c44b51</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xedb61f74b0d09b2558f1eeb79b247c1f363ae452</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x7d8820fa92eb1584636f4f5b8515b5476b75171a</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x231d3559aa848bf10366fb9868590f01d34bf240</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xad9fd7cb4fc7a0fbce08d64068f60cbde22ed34c</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x0e9d6552b85be180d941f1ca73ae3e318d2d4f1f</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xb716600ed99b4710152582a124c697a7fe78adbf</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xaadc2d4261199ce24a4b0a57370c4fcf43bb60aa</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x4e1f41613c9084fdb9e34e11fae9412427480e56</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x79986af15539de2db9a5086382daeda917a9cf0c</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xc99c679c50033bbc5321eb88752e89a93e9e83c5</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xc36cf0cfcb5d905b8b513860db0cfe63f6cf9f5c</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x3110ef5f612208724ca51f5761a69081809f03b7</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x036721e5a769cc48b3189efbb9cce4471e8a48b1</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x524cab2ec69124574082676e6f654a18df49a048</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x7ab2352b1d2e185560494d5e577f9d3c238b78c5</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x32973908faee0bf825a343000fe412ebe56f802a</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x7daec605e9e2a1717326eedfd660601e2753a057</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xc1caf0c19a8ac28c41fe59ba6c754e4b9bd54de9</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x33fd426905f149f8376e227d0c9d3340aad17af1</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x466cfcd0525189b573e794f554b8a751279213ac</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x6be69b2a9b153737887cfcdca7781ed1511c7e36</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x80336ad7a747236ef41f47ed2c7641828a480baa</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x9401518f4ebba857baa879d9f76e1cc8b31ed197</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x4b61413d4392c806e6d0ff5ee91e6073c21d6430</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xc3f733ca98e0dad0386979eb96fb1722a1a05e69</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x09233d553058c2f42ba751c87816a8e9fae7ef10</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x960b7a6bcd451c9968473f7bbfd9be826efd549a</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x36d30b3b85255473d27dd0f7fd8f35e36a9d6f06</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x698fbaaca64944376e2cdc4cad86eaa91362cf54</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x497a9a79e82e6fc0ff10a16f6f75e6fcd5ae65a8</loc>
|
||||
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x41a322b28d0ff354040e2cbc676f0320d8c8850d</loc>
|
||||
<lastmod>2023-10-16T18:42:53.632Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0xa9c0a07a7cb84ad1f2ffab06de3e55aab7d523e8</loc>
|
||||
<lastmod>2023-10-16T18:42:53.632Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://app.uniswap.org/nfts/collection/0x942bc2d3e7a589fe5bd4a5c6ef9727dfd82f5c8a</loc>
|
||||
<lastmod>2023-10-16T18:42:53.632Z</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
</urlset>
|
@ -1,10 +1,6 @@
|
||||
# *
|
||||
User-agent: *
|
||||
Disallow: /static/js/
|
||||
Allow: /
|
||||
|
||||
# Host
|
||||
Host: https://app.uniswap.org
|
||||
Disallow:
|
||||
|
||||
# Sitemaps
|
||||
Sitemap: https://app.uniswap.org/sitemap.xml
|
||||
|
1987
public/sitemap.xml
1987
public/sitemap.xml
File diff suppressed because it is too large
Load Diff
1663
public/tokens-sitemap.xml
Normal file
1663
public/tokens-sitemap.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,19 +9,13 @@ const thegraphConfig = require('../graphql.thegraph.config')
|
||||
|
||||
const exec = promisify(child_process.exec)
|
||||
|
||||
function fetchSchema(url, outputFile) {
|
||||
exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`)
|
||||
.then(({ stderr, stdout }) => {
|
||||
if (stderr) {
|
||||
throw new Error(stderr)
|
||||
} else {
|
||||
fs.writeFile(outputFile, stdout)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
console.error(`Failed to fetch schema from ${url}`)
|
||||
})
|
||||
async function fetchSchema(url, outputFile) {
|
||||
try {
|
||||
const { stdout } = await exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`);
|
||||
await fs.writeFile(outputFile, stdout);
|
||||
} catch(err){
|
||||
console.error(`Failed to fetch schema from ${url}`)
|
||||
}
|
||||
}
|
||||
|
||||
fetchSchema(process.env.THE_GRAPH_SCHEMA_ENDPOINT, thegraphConfig.schema)
|
||||
|
@ -29,8 +29,8 @@ const nftTopCollectionsQuery = `
|
||||
}
|
||||
`
|
||||
|
||||
fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||
const sitemapURLs = {}
|
||||
fs.readFile('./public/tokens-sitemap.xml', 'utf8', async (err, data) => {
|
||||
const tokenURLs = {}
|
||||
try {
|
||||
const sitemap = await parseStringPromise(data)
|
||||
if (sitemap.urlset.url) {
|
||||
@ -39,7 +39,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||
if (lastMod < Date.now() - weekMs) {
|
||||
url.lastmod = nowISO
|
||||
}
|
||||
sitemapURLs[url.loc] = true
|
||||
tokenURLs[url.loc] = true
|
||||
})
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||
|
||||
tokenAddresses.forEach((address) => {
|
||||
const tokenURL = `https://app.uniswap.org/tokens/${chainName.toLowerCase()}/${address}`
|
||||
if (!(tokenURL in sitemapURLs)) {
|
||||
if (!(tokenURL in tokenURLs)) {
|
||||
sitemap.urlset.url.push({
|
||||
loc: [tokenURL],
|
||||
lastmod: [nowISO],
|
||||
@ -67,6 +67,39 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||
})
|
||||
}
|
||||
|
||||
const builder = new Builder()
|
||||
const xml = builder.buildObject(sitemap)
|
||||
const path = './public/tokens-sitemap.xml'
|
||||
fs.writeFile(path, xml, (error) => {
|
||||
if (error) throw error
|
||||
const stats = fs.statSync(path)
|
||||
const fileSizeBytes = stats.size
|
||||
const fileSizeMegabytes = fileSizeBytes / (1024 * 1024)
|
||||
|
||||
if (fileSizeMegabytes > 50) {
|
||||
throw new Error('Generated tokens-sitemap.xml file size exceeds 50MB')
|
||||
}
|
||||
console.log('Tokens sitemap updated')
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
fs.readFile('./public/nfts-sitemap.xml', 'utf8', async (err, data) => {
|
||||
const collectionURLs = {}
|
||||
try {
|
||||
const sitemap = await parseStringPromise(data)
|
||||
if (sitemap.urlset.url) {
|
||||
sitemap.urlset.url.forEach((url) => {
|
||||
const lastMod = new Date(url.lastmod).getTime()
|
||||
if (lastMod < Date.now() - weekMs) {
|
||||
url.lastmod = nowISO
|
||||
}
|
||||
collectionURLs[url.loc] = true
|
||||
})
|
||||
}
|
||||
|
||||
const nftResponse = await fetch('https://api.uniswap.org/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -79,7 +112,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||
const collectionAddresses = nftJSON.data.topCollections.edges.map((edge) => edge.node.nftContracts[0].address)
|
||||
collectionAddresses.forEach((address) => {
|
||||
const collectionURL = `https://app.uniswap.org/nfts/collection/${address}`
|
||||
if (!(collectionURL in sitemapURLs)) {
|
||||
if (!(collectionURL in collectionURLs)) {
|
||||
sitemap.urlset.url.push({
|
||||
loc: [collectionURL],
|
||||
lastmod: [nowISO],
|
||||
@ -90,7 +123,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||
|
||||
const builder = new Builder()
|
||||
const xml = builder.buildObject(sitemap)
|
||||
const path = './public/sitemap.xml'
|
||||
const path = './public/nfts-sitemap.xml'
|
||||
fs.writeFile(path, xml, (error) => {
|
||||
if (error) throw error
|
||||
const stats = fs.statSync(path)
|
||||
@ -98,9 +131,9 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||
const fileSizeMegabytes = fileSizeBytes / (1024 * 1024)
|
||||
|
||||
if (fileSizeMegabytes > 50) {
|
||||
throw new Error('Generated sitemap file size exceeds 50MB')
|
||||
throw new Error('Generated nfts-sitemap.xml file size exceeds 50MB')
|
||||
}
|
||||
console.log('Sitemap updated')
|
||||
console.log('NFT collections sitemap updated')
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
BIN
src/assets/images/AndroidWallet-Thumbnail-Dark.png
Normal file
BIN
src/assets/images/AndroidWallet-Thumbnail-Dark.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 11 KiB |
BIN
src/assets/images/AndroidWallet-Thumbnail-Light.png
Normal file
BIN
src/assets/images/AndroidWallet-Thumbnail-Light.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 9.8 KiB |
BIN
src/assets/images/androidAnnouncementBannerQR.png
Normal file
BIN
src/assets/images/androidAnnouncementBannerQR.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.2 KiB |
@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 22.773 22.773" style="enable-background:new 0 0 22.773 22.773;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M15.769,0c0.053,0,0.106,0,0.162,0c0.13,1.606-0.483,2.806-1.228,3.675c-0.731,0.863-1.732,1.7-3.351,1.573
|
||||
c-0.108-1.583,0.506-2.694,1.25-3.561C13.292,0.879,14.557,0.16,15.769,0z"/>
|
||||
<path d="M20.67,16.716c0,0.016,0,0.03,0,0.045c-0.455,1.378-1.104,2.559-1.896,3.655c-0.723,0.995-1.609,2.334-3.191,2.334
|
||||
c-1.367,0-2.275-0.879-3.676-0.903c-1.482-0.024-2.297,0.735-3.652,0.926c-0.155,0-0.31,0-0.462,0
|
||||
c-0.995-0.144-1.798-0.932-2.383-1.642c-1.725-2.098-3.058-4.808-3.306-8.276c0-0.34,0-0.679,0-1.019
|
||||
c0.105-2.482,1.311-4.5,2.914-5.478c0.846-0.52,2.009-0.963,3.304-0.765c0.555,0.086,1.122,0.276,1.619,0.464
|
||||
c0.471,0.181,1.06,0.502,1.618,0.485c0.378-0.011,0.754-0.208,1.135-0.347c1.116-0.403,2.21-0.865,3.652-0.648
|
||||
c1.733,0.262,2.963,1.032,3.723,2.22c-1.466,0.933-2.625,2.339-2.427,4.74C17.818,14.688,19.086,15.964,20.67,16.716z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
Before (image error) Size: 1.6 KiB |
85
src/assets/svg/uniswap_app_logo.svg
Normal file
85
src/assets/svg/uniswap_app_logo.svg
Normal file
@ -0,0 +1,85 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2450_35919)">
|
||||
<rect width="20" height="20" rx="4.60595" fill="#FFFBFF"/>
|
||||
<rect width="20" height="20" rx="4.60595" fill="url(#paint0_linear_2450_35919)"/>
|
||||
<g filter="url(#filter0_f_2450_35919)">
|
||||
<ellipse cx="9.99967" cy="12.0001" rx="9.66667" ry="7.66667" fill="url(#paint1_radial_2450_35919)" fill-opacity="0.8"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_i_2450_35919)">
|
||||
<path d="M14.4974 4.34199C14.5183 3.97475 14.5683 3.73252 14.6689 3.51131C14.7087 3.42374 14.746 3.35208 14.7518 3.35208C14.7575 3.35208 14.7402 3.41671 14.7133 3.49569C14.6402 3.71038 14.6282 4.00402 14.6786 4.34566C14.7425 4.77915 14.7788 4.84169 15.239 5.30995C15.4549 5.52958 15.706 5.80659 15.797 5.92551L15.9625 6.14176L15.797 5.98722C15.5946 5.79823 15.1292 5.42966 15.0263 5.37698C14.9574 5.34164 14.9472 5.34225 14.9046 5.38439C14.8655 5.42322 14.8572 5.48156 14.8518 5.75737C14.8433 6.18723 14.7844 6.46314 14.6424 6.73903C14.5656 6.88824 14.5535 6.8564 14.623 6.68797C14.6749 6.56222 14.6802 6.50694 14.6798 6.0908C14.679 5.25469 14.5793 5.05368 13.9946 4.70934C13.8465 4.62211 13.6024 4.4963 13.4522 4.42976C13.3021 4.36322 13.1828 4.30526 13.1871 4.30093C13.2037 4.28452 13.7739 4.45022 14.0034 4.53811C14.3448 4.66886 14.4012 4.6858 14.4426 4.67003C14.4704 4.65946 14.4839 4.57887 14.4974 4.34199Z" fill="url(#paint2_linear_2450_35919)"/>
|
||||
<path d="M7.28422 3.12561C7.0817 3.09439 7.07316 3.09073 7.16846 3.07618C7.3511 3.04826 7.78237 3.0863 8.07956 3.15653C8.77336 3.32041 9.40468 3.74022 10.0786 4.48582L10.2576 4.68389L10.5137 4.64298C11.5927 4.47068 12.6903 4.60761 13.6084 5.02906C13.8609 5.14501 14.2592 5.3758 14.3089 5.43511C14.3248 5.45402 14.3539 5.57569 14.3736 5.70552C14.4418 6.1547 14.4076 6.499 14.2694 6.75616C14.1941 6.8961 14.19 6.94045 14.2405 7.06021C14.2809 7.15577 14.3935 7.22651 14.5049 7.22636C14.7331 7.22605 14.9787 6.85956 15.0924 6.34963L15.1376 6.14707L15.2272 6.24785C15.7183 6.80076 16.104 7.5548 16.1703 8.09153L16.1875 8.23147L16.105 8.10426C15.9629 7.88534 15.8202 7.73632 15.6374 7.61612C15.3079 7.39947 14.9595 7.32574 14.0368 7.27742C13.2034 7.23379 12.7318 7.16305 12.2641 7.01151C11.4684 6.75372 11.0673 6.41038 10.1221 5.17811C9.70233 4.63077 9.44285 4.32794 9.18474 4.08407C8.59823 3.52993 8.02193 3.23932 7.28422 3.12561Z" fill="url(#paint3_linear_2450_35919)"/>
|
||||
<path d="M7.67762 5.77618C7.26647 5.2137 7.01209 4.3513 7.06716 3.70661L7.08419 3.50711L7.17778 3.52408C7.35352 3.55594 7.65655 3.66801 7.79844 3.75366C8.18783 3.98865 8.3564 4.29805 8.5279 5.09252C8.57814 5.32522 8.64404 5.58856 8.67437 5.67772C8.7232 5.82122 8.90769 6.15641 9.05769 6.3741C9.16573 6.53088 9.09396 6.60518 8.85514 6.58376C8.49039 6.55104 7.99634 6.21221 7.67762 5.77618Z" fill="url(#paint4_linear_2450_35919)"/>
|
||||
<path d="M13.9982 9.96242C12.0768 9.19367 11.4001 8.52639 11.4001 7.40049C11.4001 7.2348 11.4058 7.09924 11.4128 7.09924C11.4198 7.09924 11.4941 7.15392 11.578 7.22076C11.9676 7.5313 12.4039 7.66393 13.6118 7.83903C14.3225 7.94208 14.7225 8.0253 15.0915 8.1469C16.2642 8.53338 16.9898 9.3177 17.1628 10.386C17.2131 10.6964 17.1836 11.2785 17.1021 11.5853C17.0377 11.8276 16.8413 12.2644 16.7892 12.2812C16.7748 12.2858 16.7606 12.2308 16.7569 12.156C16.7372 11.7549 16.5332 11.3643 16.1906 11.0718C15.8011 10.7392 15.2777 10.4744 13.9982 9.96242Z" fill="url(#paint5_linear_2450_35919)"/>
|
||||
<path d="M12.6493 10.2818C12.6252 10.1394 12.5835 9.95763 12.5565 9.87779L12.5075 9.73264L12.5985 9.83421C12.7246 9.97475 12.8241 10.1546 12.9085 10.3942C12.973 10.577 12.9802 10.6314 12.9797 10.9285C12.9792 11.2202 12.9712 11.2813 12.9117 11.4459C12.8179 11.7054 12.7015 11.8894 12.5062 12.0869C12.1552 12.4419 11.704 12.6384 11.0528 12.7199C10.9396 12.734 10.6097 12.7579 10.3197 12.7729C9.58873 12.8107 9.10767 12.8887 8.6754 13.0395C8.61325 13.0612 8.55776 13.0744 8.55213 13.0688C8.53464 13.0515 8.82895 12.8772 9.07204 12.7608C9.41481 12.5968 9.75602 12.5072 10.5205 12.3807C10.8982 12.3182 11.2882 12.2424 11.3872 12.2122C12.3224 11.9273 12.8031 11.1919 12.6493 10.2818Z" fill="url(#paint6_linear_2450_35919)"/>
|
||||
<path d="M13.5301 11.8362C13.2748 11.2909 13.2162 10.7643 13.356 10.2733C13.371 10.2208 13.3951 10.1779 13.4095 10.1779C13.424 10.1779 13.4842 10.2103 13.5434 10.2498C13.661 10.3285 13.897 10.461 14.5255 10.8016C15.3099 11.2265 15.7571 11.5556 16.0612 11.9316C16.3276 12.2608 16.4924 12.6358 16.5717 13.0931C16.6166 13.3521 16.5903 13.9753 16.5234 14.2361C16.3125 15.0585 15.8224 15.7045 15.1233 16.0814C15.0209 16.1366 14.9289 16.182 14.919 16.1822C14.909 16.1824 14.9464 16.0881 15.0019 15.9726C15.2371 15.4841 15.2639 15.0089 15.0861 14.48C14.9772 14.1562 14.7552 13.761 14.3071 13.0932C13.786 12.3167 13.6583 12.11 13.5301 11.8362Z" fill="url(#paint7_linear_2450_35919)"/>
|
||||
<path d="M6.31312 14.7786C7.02613 14.1803 7.9133 13.7553 8.72142 13.6248C9.0697 13.5686 9.64988 13.5909 9.97238 13.673C10.4893 13.8044 10.9518 14.0989 11.1923 14.4497C11.4273 14.7926 11.5281 15.0914 11.6331 15.7561C11.6745 16.0184 11.7196 16.2817 11.7332 16.3413C11.8121 16.6859 11.9655 16.9613 12.1556 17.0996C12.4576 17.3192 12.9776 17.3329 13.4891 17.1346C13.5759 17.1009 13.6513 17.0777 13.6566 17.0829C13.6751 17.1012 13.4175 17.2726 13.2358 17.3628C12.9913 17.4841 12.7969 17.531 12.5386 17.531C12.0701 17.531 11.6811 17.2942 11.3566 16.8113C11.2927 16.7163 11.1491 16.4316 11.0376 16.1788C10.6949 15.4021 10.5257 15.1655 10.1278 14.9066C9.78156 14.6813 9.33501 14.6409 8.99908 14.8046C8.55782 15.0196 8.4347 15.58 8.75075 15.9351C8.87636 16.0762 9.1106 16.198 9.30214 16.2216C9.66047 16.2659 9.96842 15.995 9.96842 15.6355C9.96842 15.4021 9.87813 15.2689 9.65083 15.1669C9.34039 15.0277 9.0067 15.1905 9.0083 15.4803C9.00899 15.6038 9.06314 15.6814 9.1878 15.7375C9.26777 15.7734 9.26963 15.7763 9.20442 15.7628C8.91957 15.7041 8.85285 15.3629 9.08187 15.1364C9.35683 14.8644 9.92541 14.9844 10.1207 15.3556C10.2027 15.5116 10.2122 15.8221 10.1407 16.0096C9.98064 16.4293 9.51395 16.65 9.04053 16.5299C8.71821 16.4481 8.58696 16.3596 8.19835 15.9618C7.52307 15.2706 7.26092 15.1366 6.28739 14.9856L6.10084 14.9567L6.31312 14.7786Z" fill="url(#paint8_linear_2450_35919)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14462 2.40696C5.39974 5.12681 6.95295 6.24897 7.12556 6.48605C7.26807 6.68181 7.21443 6.85781 6.97029 6.99574C6.83452 7.07242 6.55538 7.15013 6.41563 7.15013C6.25755 7.15013 6.20328 7.08953 6.20328 7.08953C6.11163 7.00315 6.06001 7.01826 5.58934 6.18804C4.9359 5.18048 4.38905 4.34467 4.37413 4.33067C4.33964 4.29831 4.34023 4.2994 5.52271 6.40187C5.71377 6.84008 5.56071 7.00093 5.56071 7.06334C5.56071 7.1903 5.52586 7.25704 5.36825 7.43173C5.1055 7.72301 4.98805 8.05029 4.90326 8.72762C4.80821 9.4869 4.54094 10.0233 3.80024 10.9412C3.36666 11.4785 3.29572 11.577 3.18631 11.7936C3.04851 12.0663 3.01062 12.2191 2.99526 12.5635C2.97903 12.9276 3.01064 13.1628 3.12259 13.511C3.2206 13.8158 3.32291 14.017 3.58444 14.4196C3.81014 14.767 3.9401 15.0251 3.9401 15.1261C3.9401 15.2064 3.95554 15.2065 4.30528 15.1281C5.14226 14.9402 5.82189 14.6099 6.20412 14.2051C6.44068 13.9545 6.49622 13.8162 6.49802 13.4728C6.4992 13.2482 6.49125 13.2012 6.43016 13.072C6.33071 12.8617 6.14967 12.6869 5.75064 12.4158C5.2278 12.0606 5.00448 11.7747 4.9428 11.3815C4.8922 11.0588 4.9509 10.8312 5.24014 10.2288C5.53952 9.60524 5.61371 9.33953 5.66389 8.71103C5.69631 8.30498 5.74119 8.14484 5.8586 8.0163C5.98104 7.88226 6.09127 7.83687 6.3943 7.79573C6.88833 7.72866 7.20291 7.60165 7.46149 7.36487C7.6858 7.15946 7.77966 6.96154 7.79407 6.6636L7.805 6.43776L7.67965 6.29253C7.2257 5.76656 2.84061 1.9935 2.81267 1.9935C2.8067 1.9935 2.95609 2.17957 3.14462 2.40696ZM4.19493 12.9755C4.29756 12.7949 4.24303 12.5627 4.07135 12.4492C3.90913 12.342 3.65714 12.3925 3.65714 12.5322C3.65714 12.5748 3.68086 12.6058 3.73431 12.6332C3.82432 12.6792 3.83085 12.731 3.76003 12.8367C3.68832 12.9439 3.6941 13.038 3.77637 13.102C3.90895 13.2052 4.09663 13.1485 4.19493 12.9755Z" fill="url(#paint9_linear_2450_35919)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.11678 7.91419C7.88485 7.98498 7.6594 8.22923 7.58962 8.48535C7.54704 8.6416 7.5712 8.91567 7.63497 9.00032C7.73799 9.13703 7.83762 9.17306 8.10739 9.17117C8.63556 9.16751 9.0947 8.94241 9.14808 8.66101C9.19184 8.43034 8.99019 8.11067 8.71242 7.97032C8.56908 7.89793 8.26425 7.86921 8.11678 7.91419ZM8.7342 8.39395C8.81565 8.27895 8.78002 8.15466 8.64149 8.07059C8.3777 7.91053 7.97877 8.04299 7.97877 8.29063C7.97877 8.4139 8.1868 8.5484 8.37749 8.5484C8.50441 8.5484 8.6781 8.47319 8.7342 8.39395Z" fill="url(#paint10_linear_2450_35919)"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="0.0205078" y="0.0205078" width="19.959" height="19.959" rx="4.58544" stroke="black" stroke-opacity="0.1" stroke-width="0.0410156"/>
|
||||
<defs>
|
||||
<filter id="filter0_f_2450_35919" x="-3.16699" y="0.833389" width="26.333" height="22.3333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1.75" result="effect1_foregroundBlur_2450_35919"/>
|
||||
</filter>
|
||||
<filter id="filter1_i_2450_35919" x="2.8125" y="1.9935" width="14.375" height="15.5376" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="0.47168"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.864916 0 0 0 0 0.401042 0 0 0 0 0.875 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_2450_35919"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_2450_35919" x1="7.66667" y1="20" x2="7.66667" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFF1FF"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_2450_35919" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.0123 11.9901) rotate(35.9783) scale(9.71138 9.26994)">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#FFE3FF"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint2_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FC72FF"/>
|
||||
<stop offset="1" stop-color="#FF41F4"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2450_35919">
|
||||
<rect width="20" height="20" rx="4.60595" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After (image error) Size: 12 KiB |
@ -14,7 +14,6 @@ import Tooltip from 'components/Tooltip'
|
||||
import { getConnection } from 'connection'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { useIsNotOriginCountry } from 'hooks/useIsNotOriginCountry'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
@ -26,6 +25,7 @@ import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled from 'styled-components'
|
||||
import { CopyHelper, ExternalLink, ThemedText } from 'theme/components'
|
||||
import { shortenAddress } from 'utils'
|
||||
import { isPathBlocked } from 'utils/blockedPaths'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from '../../state/application/hooks'
|
||||
@ -160,8 +160,8 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
const resetSellAssets = useSellAsset((state) => state.reset)
|
||||
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
const shouldShowBuyFiatButton = useIsNotOriginCountry('GB')
|
||||
const { formatNumber, formatPercent } = useFormatter()
|
||||
const shouldShowBuyFiatButton = !isPathBlocked('/buy')
|
||||
const { formatNumber, formatDelta } = useFormatter()
|
||||
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
|
||||
@ -284,7 +284,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
{`${formatNumber({
|
||||
input: Math.abs(absoluteChange as number),
|
||||
type: NumberType.PortfolioBalance,
|
||||
})} (${formatPercent(percentChange)})`}
|
||||
})} (${formatDelta(percentChange)})`}
|
||||
</ThemedText.BodySecondary>
|
||||
</>
|
||||
)}
|
||||
|
@ -31,7 +31,7 @@ function BaseButton({ onClick, branded, children }: PropsWithChildren<{ onClick?
|
||||
)
|
||||
}
|
||||
|
||||
// Launches App Store if on an iOS device, else navigates to Uniswap Wallet microsite
|
||||
// Launches App/Play Store if on an iOS/Android device, else navigates to Uniswap Wallet microsite
|
||||
export function DownloadButton({
|
||||
onClick,
|
||||
text = 'Download',
|
||||
|
@ -90,7 +90,7 @@ const DescriptionText = styled(ThemedText.LabelMicro)`
|
||||
|
||||
function useOrderAmounts(
|
||||
orderDetails?: UniswapXOrderDetails
|
||||
): Pick<InterfaceTrade, 'inputAmount' | 'postTaxOutputAmount'> | undefined {
|
||||
): Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> | undefined {
|
||||
const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId)
|
||||
const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId)
|
||||
|
||||
@ -106,7 +106,7 @@ function useOrderAmounts(
|
||||
if (swapInfo.tradeType === TradeType.EXACT_INPUT) {
|
||||
return {
|
||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw),
|
||||
postTaxOutputAmount: CurrencyAmount.fromRawAmount(
|
||||
outputAmount: CurrencyAmount.fromRawAmount(
|
||||
outputCurrency,
|
||||
swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw
|
||||
),
|
||||
@ -114,7 +114,7 @@ function useOrderAmounts(
|
||||
} else {
|
||||
return {
|
||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw),
|
||||
postTaxOutputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
|
||||
outputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,20 +68,24 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "DAI",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "DAI",
|
||||
},
|
||||
Token {
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "Wrapped Ether",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "WETH",
|
||||
},
|
||||
],
|
||||
@ -115,11 +119,13 @@ Object {
|
||||
},
|
||||
Token {
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "Wrapped Ether",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "WETH",
|
||||
},
|
||||
],
|
||||
@ -143,11 +149,13 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "Wrapped Ether",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "WETH",
|
||||
},
|
||||
],
|
||||
@ -186,11 +194,13 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "Wrapped Ether",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "WETH",
|
||||
},
|
||||
],
|
||||
@ -214,20 +224,24 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "Wrapped Ether",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "WETH",
|
||||
},
|
||||
Token {
|
||||
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "DAI",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "DAI",
|
||||
},
|
||||
],
|
||||
@ -251,11 +265,13 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "DAI",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "DAI",
|
||||
},
|
||||
],
|
||||
@ -279,20 +295,24 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "DAI",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "DAI",
|
||||
},
|
||||
Token {
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "Wrapped Ether",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "WETH",
|
||||
},
|
||||
],
|
||||
@ -315,20 +335,24 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "DAI",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "DAI",
|
||||
},
|
||||
Token {
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "Wrapped Ether",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "WETH",
|
||||
},
|
||||
],
|
||||
@ -352,11 +376,13 @@ Object {
|
||||
"currencies": Array [
|
||||
Token {
|
||||
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"buyFeeBps": undefined,
|
||||
"chainId": 1,
|
||||
"decimals": 18,
|
||||
"isNative": false,
|
||||
"isToken": true,
|
||||
"name": "DAI",
|
||||
"sellFeeBps": undefined,
|
||||
"symbol": "DAI",
|
||||
},
|
||||
],
|
||||
|
@ -8,10 +8,10 @@ import { NftCard } from 'nft/components/card'
|
||||
import { detailsHref } from 'nft/components/card/utils'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
import { WalletAsset } from 'nft/types'
|
||||
import { floorFormatter } from 'nft/utils'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
const FloorPrice = styled(Row)`
|
||||
opacity: 0;
|
||||
@ -83,6 +83,8 @@ export function NFT({
|
||||
}
|
||||
|
||||
function NFTDetails({ asset }: { asset: WalletAsset }) {
|
||||
const { formatNumberOrString } = useFormatter()
|
||||
|
||||
return (
|
||||
<Box overflow="hidden" width="full" flexWrap="nowrap">
|
||||
<Row gap="4px">
|
||||
@ -91,7 +93,9 @@ function NFTDetails({ asset }: { asset: WalletAsset }) {
|
||||
</Row>
|
||||
<FloorPrice>
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
{asset.floorPrice ? `${floorFormatter(asset.floorPrice)} ETH` : ' '}
|
||||
{asset.floorPrice
|
||||
? `${formatNumberOrString({ input: asset.floorPrice, type: NumberType.NFTTokenFloorPrice })} ETH`
|
||||
: ' '}
|
||||
</ThemedText.BodySmall>
|
||||
</FloorPrice>
|
||||
</Box>
|
||||
|
@ -110,7 +110,7 @@ const ActiveDot = styled.span<{ closed: boolean; outOfRange: boolean }>`
|
||||
margin-top: 1px;
|
||||
`
|
||||
|
||||
function calculcateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) {
|
||||
function calculateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) {
|
||||
if (!price0 || !price1) return undefined
|
||||
|
||||
const value0 = parseFloat(position.amount0.toExact()) * price0
|
||||
@ -124,7 +124,7 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
|
||||
const { chainId, position, pool, details, inRange, closed } = positionInfo
|
||||
|
||||
const { priceA, priceB, fees: feeValue } = useFeeValues(positionInfo)
|
||||
const liquidityValue = calculcateLiquidityValue(priceA, priceB, position)
|
||||
const liquidityValue = calculateLiquidityValue(priceA, priceB, position)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
|
@ -36,7 +36,9 @@ const DoubleLogoContainer = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const StyledLogoParentContainer = styled.div`
|
||||
const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -57,9 +59,9 @@ const CircleLogoImage = styled.img<{ size: string }>`
|
||||
const L2LogoContainer = styled.div`
|
||||
border-radius: ${getDefaultBorderRadius(16)}px;
|
||||
height: 16px;
|
||||
left: 60%;
|
||||
left: 70%;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
top: 70%;
|
||||
outline: 2px solid ${({ theme }) => theme.surface1};
|
||||
width: 16px;
|
||||
display: flex;
|
||||
@ -155,10 +157,10 @@ function SquareL2Logo({ chainId }: { chainId: ChainId }) {
|
||||
*/
|
||||
export function PortfolioLogo(props: PortfolioLogoProps) {
|
||||
return (
|
||||
<StyledLogoParentContainer style={props.style}>
|
||||
<LogoContainer style={props.style}>
|
||||
{getLogo(props)}
|
||||
<SquareL2Logo chainId={props.chainId} />
|
||||
</StyledLogoParentContainer>
|
||||
</LogoContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,9 @@ import { TraceEvent } from 'analytics'
|
||||
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
|
||||
import Row from 'components/Row'
|
||||
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
|
||||
import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
||||
import { PortfolioTokenBalancePartsFragment } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { PortfolioToken } from 'graphql/data/portfolios'
|
||||
import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
||||
@ -27,7 +29,7 @@ export default function Tokens({ account }: { account: string }) {
|
||||
|
||||
const { data } = useCachedPortfolioBalancesQuery({ account })
|
||||
|
||||
const tokenBalances = data?.portfolios?.[0].tokenBalances as TokenBalance[] | undefined
|
||||
const tokenBalances = data?.portfolios?.[0].tokenBalances
|
||||
|
||||
const { visibleTokens, hiddenTokens } = useMemo(
|
||||
() => splitHiddenTokens(tokenBalances ?? [], { hideSmallBalances }),
|
||||
@ -68,18 +70,23 @@ const TokenNameText = styled(ThemedText.SubHeader)`
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
type PortfolioToken = NonNullable<TokenBalance['token']>
|
||||
|
||||
function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) {
|
||||
const { formatPercent } = useFormatter()
|
||||
function TokenRow({
|
||||
token,
|
||||
quantity,
|
||||
denominatedValue,
|
||||
tokenProjectMarket,
|
||||
}: PortfolioTokenBalancePartsFragment & { token: PortfolioToken }) {
|
||||
const { formatDelta } = useFormatter()
|
||||
const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
|
||||
|
||||
const navigate = useNavigate()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
const isInfoExplorePageEnabled = useInfoExplorePageEnabled()
|
||||
|
||||
const navigateToTokenDetails = useCallback(async () => {
|
||||
navigate(getTokenDetailsURL(token))
|
||||
navigate(getTokenDetailsURL({ ...token, isInfoExplorePageEnabled }))
|
||||
toggleWalletDrawer()
|
||||
}, [navigate, token, toggleWalletDrawer])
|
||||
}, [navigate, token, isInfoExplorePageEnabled, toggleWalletDrawer])
|
||||
const { formatNumber } = useFormatter()
|
||||
|
||||
const currency = gqlToCurrency(token)
|
||||
@ -121,7 +128,7 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
|
||||
</ThemedText.SubHeader>
|
||||
<Row justify="flex-end">
|
||||
<DeltaArrow delta={percentChange} />
|
||||
<ThemedText.BodySecondary>{formatPercent(percentChange)}</ThemedText.BodySecondary>
|
||||
<ThemedText.BodySecondary>{formatDelta(percentChange)}</ThemedText.BodySecondary>
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
|
@ -32,6 +32,14 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -46,9 +54,9 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
|
||||
.c3 {
|
||||
border-radius: 4px;
|
||||
height: 16px;
|
||||
left: 60%;
|
||||
left: 70%;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
top: 70%;
|
||||
outline: 2px solid #FFFFFF;
|
||||
width: 16px;
|
||||
display: -webkit-box;
|
||||
@ -150,6 +158,14 @@ exports[`PortfolioLogo renders without L2 icon 1`] = `
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { WalletConnect as WalletConnectv2 } from '@web3-react/walletconnect-v2'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
@ -13,7 +13,7 @@ import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { CloseIcon, ThemedText } from 'theme/components'
|
||||
import { isIOS } from 'utils/userAgent'
|
||||
import { isAndroid, isIOS } from 'utils/userAgent'
|
||||
|
||||
import uniPng from '../../assets/images/uniwallet_modal_icon.png'
|
||||
import { DownloadButton } from './DownloadButton'
|
||||
@ -42,9 +42,10 @@ export default function UniwalletModal() {
|
||||
const { activationState, cancelActivation } = useActivationState()
|
||||
const [uri, setUri] = useState<string>()
|
||||
|
||||
// Displays the modal if not on iOS, a Uniswap Wallet Connection is pending, & qrcode URI is available
|
||||
// Displays the modal if not on iOS/Android, a Uniswap Wallet Connection is pending, & qrcode URI is available
|
||||
const onLaunchedMobilePlatform = isIOS || isAndroid
|
||||
const open =
|
||||
!isIOS &&
|
||||
!onLaunchedMobilePlatform &&
|
||||
activationState.status === ActivationStatus.PENDING &&
|
||||
activationState.connection.type === ConnectionType.UNISWAP_WALLET_V2 &&
|
||||
!!uri
|
||||
@ -57,7 +58,7 @@ export default function UniwalletModal() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) sendAnalyticsEvent('Uniswap wallet modal opened')
|
||||
if (open) sendAnalyticsEvent(InterfaceEventName.UNIWALLET_CONNECT_MODAL_OPENED)
|
||||
}, [open])
|
||||
|
||||
const theme = useTheme()
|
||||
@ -106,12 +107,10 @@ function InfoSection() {
|
||||
<InfoSectionWrapper>
|
||||
<AutoColumn gap="4px">
|
||||
<ThemedText.SubHeaderSmall color="neutral1">
|
||||
<Trans>Don't have Uniswap Wallet?</Trans>
|
||||
<Trans>Don't have a Uniswap wallet?</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<Trans>
|
||||
Download in the App Store to safely store your tokens and NFTs, swap tokens, and connect to crypto apps.
|
||||
</Trans>
|
||||
<Trans>Safely store and swap tokens with the Uniswap app. Available on iOS and Android.</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</AutoColumn>
|
||||
<Column>
|
||||
|
74
src/components/Banner/AndroidAnnouncementBanner/index.tsx
Normal file
74
src/components/Banner/AndroidAnnouncementBanner/index.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { useScreenSize } from 'hooks/useScreenSize'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useHideAndroidAnnouncementBanner } from 'state/user/hooks'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
import { openDownloadApp } from 'utils/openDownloadApp'
|
||||
import { isMobileSafari } from 'utils/userAgent'
|
||||
|
||||
import androidAnnouncementBannerQR from '../../../assets/images/androidAnnouncementBannerQR.png'
|
||||
import darkAndroidThumbnail from '../../../assets/images/AndroidWallet-Thumbnail-Dark.png'
|
||||
import lightAndroidThumbnail from '../../../assets/images/AndroidWallet-Thumbnail-Light.png'
|
||||
import {
|
||||
Container,
|
||||
DownloadButton,
|
||||
PopupContainer,
|
||||
StyledQrCode,
|
||||
StyledXButton,
|
||||
TextContainer,
|
||||
Thumbnail,
|
||||
} from './styled'
|
||||
|
||||
export default function AndroidAnnouncementBanner() {
|
||||
const [hideAndroidAnnouncementBanner, toggleHideAndroidAnnouncementBanner] = useHideAndroidAnnouncementBanner()
|
||||
const location = useLocation()
|
||||
const isLandingScreen = location.search === '?intro=true' || location.pathname === '/'
|
||||
const screenSize = useScreenSize()
|
||||
|
||||
const shouldDisplay = Boolean(!hideAndroidAnnouncementBanner && !isLandingScreen)
|
||||
const isDarkMode = useIsDarkMode()
|
||||
|
||||
const onClick = () =>
|
||||
openDownloadApp({
|
||||
element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON,
|
||||
})
|
||||
|
||||
if (isMobileSafari) return null
|
||||
|
||||
return (
|
||||
<PopupContainer show={shouldDisplay}>
|
||||
<Container>
|
||||
<Thumbnail src={isDarkMode ? darkAndroidThumbnail : lightAndroidThumbnail} alt="Android app thumbnail" />
|
||||
<TextContainer onClick={!screenSize['xs'] ? onClick : undefined}>
|
||||
<ThemedText.BodySmall lineHeight="20px">
|
||||
<Trans>Uniswap on Android</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
<ThemedText.LabelMicro>
|
||||
<Trans>Available now - download from the Google Play Store today</Trans>
|
||||
</ThemedText.LabelMicro>
|
||||
<DownloadButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onClick()
|
||||
}}
|
||||
>
|
||||
<Trans>Download now</Trans>
|
||||
</DownloadButton>
|
||||
</TextContainer>
|
||||
<StyledQrCode src={androidAnnouncementBannerQR} alt="App OneLink QR code" />
|
||||
<StyledXButton
|
||||
data-testid="uniswap-wallet-banner"
|
||||
size={24}
|
||||
onClick={(e) => {
|
||||
// prevent click from bubbling to UI on the page underneath, i.e. clicking a token row
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
toggleHideAndroidAnnouncementBanner()
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
92
src/components/Banner/AndroidAnnouncementBanner/styled.tsx
Normal file
92
src/components/Banner/AndroidAnnouncementBanner/styled.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import { ButtonText } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import { X } from 'react-feather'
|
||||
import styled from 'styled-components'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
export const PopupContainer = styled.div<{ show: boolean }>`
|
||||
${({ show }) => !show && 'display: none'};
|
||||
background-color: ${({ theme }) => theme.surface2};
|
||||
color: ${({ theme }) => theme.neutral1};
|
||||
position: fixed;
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
user-select: none;
|
||||
border-radius: 20px;
|
||||
bottom: 40px;
|
||||
right: 20px;
|
||||
width: 360px;
|
||||
height: 92px;
|
||||
border: 1.3px solid ${({ theme }) => theme.surface3};
|
||||
|
||||
@media only screen and (max-width: ${BREAKPOINTS.md}px) {
|
||||
bottom: 62px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
|
||||
width: unset;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
`
|
||||
export const StyledXButton = styled(X)`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
right: 0px;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
|
||||
background-color: ${({ theme }) => theme.surface5};
|
||||
color: ${({ theme }) => theme.neutral2};
|
||||
${OpacityHoverState};
|
||||
|
||||
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
gap: 16px;
|
||||
`
|
||||
export const Thumbnail = styled.img`
|
||||
width: 82px;
|
||||
`
|
||||
export const TextContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
color: ${({ theme }) => theme.neutral2};
|
||||
padding: 10px 0px 10px;
|
||||
line-height: 16px;
|
||||
|
||||
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
|
||||
width: 220px;
|
||||
}
|
||||
`
|
||||
export const StyledQrCode = styled.img`
|
||||
padding: 2px;
|
||||
border-radius: 8px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
margin-right: 16px;
|
||||
|
||||
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
export const DownloadButton = styled(ButtonText)`
|
||||
line-height: 16px;
|
||||
font-size: 14px;
|
||||
color: ${({ theme }) => theme.accent1};
|
||||
`
|
@ -1,92 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactComponent as AppleLogo } from 'assets/svg/apple_logo.svg'
|
||||
import baseLogoUrl from 'assets/svg/base_background_icon.svg'
|
||||
import { useScreenSize } from 'hooks/useScreenSize'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useHideBaseWalletBanner } from 'state/user/hooks'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { openDownloadApp, openWalletMicrosite } from 'utils/openDownloadApp'
|
||||
import { isIOS, isMobileSafari } from 'utils/userAgent'
|
||||
|
||||
import { BannerButton, BaseBackgroundImage, ButtonRow, PopupContainer, StyledXButton } from './styled'
|
||||
|
||||
export default function BaseWalletBanner() {
|
||||
const { chainId } = useWeb3React()
|
||||
const [hideBaseWalletBanner, toggleHideBaseWalletBanner] = useHideBaseWalletBanner()
|
||||
const location = useLocation()
|
||||
const isLandingScreen = location.search === '?intro=true' || location.pathname === '/'
|
||||
|
||||
const shouldDisplay = Boolean(!hideBaseWalletBanner && !isLandingScreen && chainId === ChainId.BASE)
|
||||
|
||||
const screenSize = useScreenSize()
|
||||
|
||||
if (isMobileSafari) return null
|
||||
|
||||
return (
|
||||
<PopupContainer show={shouldDisplay}>
|
||||
<StyledXButton
|
||||
data-testid="uniswap-wallet-banner"
|
||||
size={20}
|
||||
onClick={(e) => {
|
||||
// prevent click from bubbling to UI on the page underneath, i.e. clicking a token row
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
toggleHideBaseWalletBanner()
|
||||
}}
|
||||
/>
|
||||
|
||||
<BaseBackgroundImage src={baseLogoUrl} alt="transparent base background logo" />
|
||||
|
||||
<ThemedText.HeadlineMedium fontSize="24px" lineHeight="28px" color="white" maxWidth="224px">
|
||||
<Trans>
|
||||
Swap on{' '}
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M19.5689 10C19.5689 15.4038 15.1806 19.7845 9.76737 19.7845C4.63163 19.7845 0.418433 15.8414 0 10.8225H12.9554V9.17755H0C0.418433 4.15863 4.63163 0.215576 9.76737 0.215576C15.1806 0.215576 19.5689 4.59621 19.5689 10Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>{' '}
|
||||
BASE in the Uniswap wallet
|
||||
</Trans>
|
||||
</ThemedText.HeadlineMedium>
|
||||
|
||||
<ButtonRow>
|
||||
{isIOS ? (
|
||||
<>
|
||||
<BannerButton
|
||||
backgroundColor="white"
|
||||
onClick={() =>
|
||||
openDownloadApp({
|
||||
element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON,
|
||||
appStoreParams: 'pt=123625782&ct=base-app-banner&mt=8',
|
||||
})
|
||||
}
|
||||
>
|
||||
<AppleLogo width={14} height={14} />
|
||||
<ThemedText.LabelSmall color="black" marginLeft="5px">
|
||||
{!screenSize['xs'] ? <Trans>Download</Trans> : <Trans>Download app</Trans>}
|
||||
</ThemedText.LabelSmall>
|
||||
</BannerButton>
|
||||
|
||||
<BannerButton backgroundColor="black" onClick={() => openWalletMicrosite()}>
|
||||
<ThemedText.LabelSmall color="white">
|
||||
<Trans>Learn more</Trans>
|
||||
</ThemedText.LabelSmall>
|
||||
</BannerButton>
|
||||
</>
|
||||
) : (
|
||||
<BannerButton backgroundColor="white" width="125px" onClick={() => openWalletMicrosite()}>
|
||||
<ThemedText.LabelSmall color="black">
|
||||
<Trans>Learn more</Trans>
|
||||
</ThemedText.LabelSmall>
|
||||
</BannerButton>
|
||||
)}
|
||||
</ButtonRow>
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import walletBannerPhoneImageSrc from 'assets/images/wallet_banner_phone_image.png'
|
||||
import { BaseButton } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Row from 'components/Row'
|
||||
import { X } from 'react-feather'
|
||||
import styled from 'styled-components'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
export const PopupContainer = styled.div<{ show: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
${({ show }) => !show && 'display: none'};
|
||||
|
||||
background: url(${walletBannerPhoneImageSrc});
|
||||
background-repeat: no-repeat;
|
||||
background-position: top 18px right 15px;
|
||||
background-size: 166px;
|
||||
|
||||
:hover {
|
||||
background-size: 170px;
|
||||
}
|
||||
transition: background-size ${({ theme }) => theme.transition.duration.medium}
|
||||
${({ theme }) => theme.transition.timing.inOut};
|
||||
|
||||
background-color: ${({ theme }) => theme.chain_84531};
|
||||
color: ${({ theme }) => theme.neutral1};
|
||||
position: fixed;
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
|
||||
padding: 24px 16px 16px;
|
||||
|
||||
border-radius: 20px;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 390px;
|
||||
height: 164px;
|
||||
|
||||
border: 1px solid ${({ theme }) => theme.surface3};
|
||||
|
||||
box-shadow: ${({ theme }) => theme.deprecated_deepShadow};
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
||||
bottom: 62px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
background-position: top 32px right -10px;
|
||||
width: unset;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
height: 144px;
|
||||
}
|
||||
|
||||
user-select: none;
|
||||
`
|
||||
|
||||
export const BaseBackgroundImage = styled.img`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 138px;
|
||||
width: 138px;
|
||||
`
|
||||
export const ButtonRow = styled(Row)`
|
||||
gap: 16px;
|
||||
`
|
||||
export const StyledXButton = styled(X)`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 21px;
|
||||
right: 17px;
|
||||
|
||||
color: ${({ theme }) => theme.white};
|
||||
${OpacityHoverState};
|
||||
`
|
||||
|
||||
export const BannerButton = styled(BaseButton)`
|
||||
height: 40px;
|
||||
border-radius: 16px;
|
||||
padding: 10px;
|
||||
${OpacityHoverState};
|
||||
`
|
@ -52,11 +52,11 @@ interface ChartDeltaProps {
|
||||
|
||||
function ChartDelta({ startingPrice, endingPrice, noColor }: ChartDeltaProps) {
|
||||
const delta = calculateDelta(startingPrice.value, endingPrice.value)
|
||||
const { formatPercent } = useFormatter()
|
||||
const { formatDelta } = useFormatter()
|
||||
|
||||
return (
|
||||
<DeltaContainer>
|
||||
{formatPercent(delta)}
|
||||
{formatDelta(delta)}
|
||||
<DeltaArrow delta={delta} noColor={noColor} />
|
||||
</DeltaContainer>
|
||||
)
|
||||
|
@ -18,11 +18,13 @@ const FiatLoadingBubble = styled(LoadingBubble)`
|
||||
export function FiatValue({
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
testId,
|
||||
}: {
|
||||
fiatValue: { data?: number; isLoading: boolean }
|
||||
priceImpact?: Percent
|
||||
testId?: string
|
||||
}) {
|
||||
const { formatNumber, formatPriceImpact } = useFormatter()
|
||||
const { formatNumber, formatPercent } = useFormatter()
|
||||
|
||||
const priceImpactColor = useMemo(() => {
|
||||
if (!priceImpact) return undefined
|
||||
@ -39,7 +41,7 @@ export function FiatValue({
|
||||
|
||||
return (
|
||||
<Row gap="sm">
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<ThemedText.BodySmall color="neutral2" data-testid={testId}>
|
||||
{fiatValue.data ? (
|
||||
formatNumber({
|
||||
input: fiatValue.data,
|
||||
@ -54,7 +56,7 @@ export function FiatValue({
|
||||
<MouseoverTooltip
|
||||
text={<Trans>The estimated difference between the USD values of input and output amounts.</Trans>}
|
||||
>
|
||||
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
|
||||
(<Trans>{formatPercent(priceImpact.multiply(-1))}</Trans>)
|
||||
</MouseoverTooltip>
|
||||
</ThemedText.BodySmall>
|
||||
)}
|
||||
|
@ -382,7 +382,9 @@ const SwapCurrencyInputPanel = forwardRef<HTMLInputElement, SwapCurrencyInputPan
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
{fiatValue && <FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />}
|
||||
{fiatValue && (
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} testId={`fiat-value-${id}`} />
|
||||
)}
|
||||
</LoadingOpacityContainer>
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '16px' }}>
|
||||
|
@ -12,7 +12,7 @@ import { ReactNode, useCallback, useState } from 'react'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
@ -212,6 +212,7 @@ export default function CurrencyInputPanel({
|
||||
const { account, chainId } = useWeb3React()
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const { formatCurrencyAmount } = useFormatter()
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
@ -297,7 +298,13 @@ export default function CurrencyInputPanel({
|
||||
>
|
||||
{Boolean(!hideBalance && currency && selectedCurrencyBalance) &&
|
||||
(renderBalance?.(selectedCurrencyBalance as CurrencyAmount<Currency>) || (
|
||||
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)}</Trans>
|
||||
<Trans>
|
||||
Balance:{' '}
|
||||
{formatCurrencyAmount({
|
||||
amount: selectedCurrencyBalance,
|
||||
type: NumberType.TokenNonTx,
|
||||
})}
|
||||
</Trans>
|
||||
))}
|
||||
</ThemedText.DeprecatedBody>
|
||||
{Boolean(showMaxButton && selectedCurrencyBalance) && (
|
||||
|
@ -5,19 +5,17 @@ import { DynamicConfigName } from 'featureFlags/dynamicConfig'
|
||||
import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains'
|
||||
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
|
||||
import { useFallbackProviderEnabledFlag } from 'featureFlags/flags/fallbackProvider'
|
||||
import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments'
|
||||
import { useInfoExploreFlag } from 'featureFlags/flags/infoExplore'
|
||||
import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews'
|
||||
import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage'
|
||||
import { useInfoTDPFlag } from 'featureFlags/flags/infoTDP'
|
||||
import { useLimitsEnabledFlag } from 'featureFlags/flags/limits'
|
||||
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
|
||||
import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2'
|
||||
import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefault'
|
||||
import { useUniswapXEthOutputFlag } from 'featureFlags/flags/uniswapXEthOutput'
|
||||
import { useUniswapXExactOutputFlag } from 'featureFlags/flags/uniswapXExactOutput'
|
||||
import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||
import { useFeesEnabledFlag } from 'featureFlags/flags/useFees'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
@ -267,6 +265,18 @@ export default function FeatureFlagModal() {
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useFeesEnabledFlag()}
|
||||
featureFlag={FeatureFlag.feesEnabled}
|
||||
label="Enable Swap Fees"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useLimitsEnabledFlag()}
|
||||
featureFlag={FeatureFlag.limitsEnabled}
|
||||
label="Enable Limits"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useFallbackProviderEnabledFlag()}
|
||||
@ -285,12 +295,6 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.multichainUX}
|
||||
label="Updated Multichain UX"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useFotAdjustmentsFlag()}
|
||||
featureFlag={FeatureFlag.fotAdjustedmentsEnabled}
|
||||
label="Enable fee-on-transfer UI and slippage adjustments"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useProgressIndicatorV2Flag()}
|
||||
@ -319,24 +323,6 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.uniswapXSyntheticQuote}
|
||||
label="Force synthetic quotes for UniswapX"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useUniswapXEthOutputFlag()}
|
||||
featureFlag={FeatureFlag.uniswapXEthOutputEnabled}
|
||||
label="Enable eth output for UniswapX orders"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useUniswapXExactOutputFlag()}
|
||||
featureFlag={FeatureFlag.uniswapXExactOutputEnabled}
|
||||
label="Enable exact output for UniswapX orders"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useUniswapXDefaultEnabledFlag()}
|
||||
featureFlag={FeatureFlag.uniswapXDefaultEnabled}
|
||||
label="Enable UniswapX by default"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Info Site Migration">
|
||||
<FeatureFlagOption
|
||||
|
@ -7,6 +7,7 @@ import { PoolState } from 'hooks/usePools'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { FeeTierPercentageBadge } from './FeeTierPercentageBadge'
|
||||
import { FEE_AMOUNT_DETAIL } from './shared'
|
||||
@ -30,12 +31,14 @@ interface FeeOptionProps {
|
||||
}
|
||||
|
||||
export function FeeOption({ feeAmount, active, poolState, distributions, onClick }: FeeOptionProps) {
|
||||
const { formatDelta } = useFormatter()
|
||||
|
||||
return (
|
||||
<ButtonRadioChecked active={active} onClick={onClick}>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="6px">
|
||||
<ResponsiveText>
|
||||
<Trans>{FEE_AMOUNT_DETAIL[feeAmount].label}%</Trans>
|
||||
<Trans>{formatDelta(parseFloat(FEE_AMOUNT_DETAIL[feeAmount].label))}</Trans>
|
||||
</ResponsiveText>
|
||||
<ThemedText.DeprecatedMain fontWeight={485} fontSize="12px" textAlign="left">
|
||||
{FEE_AMOUNT_DETAIL[feeAmount].description}
|
||||
|
@ -16,6 +16,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Box } from 'rebass'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { FeeOption } from './FeeOption'
|
||||
import { FeeTierPercentageBadge } from './FeeTierPercentageBadge'
|
||||
@ -62,6 +63,7 @@ export default function FeeSelector({
|
||||
}) {
|
||||
const { chainId } = useWeb3React()
|
||||
const trace = useTrace()
|
||||
const { formatDelta } = useFormatter()
|
||||
|
||||
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
|
||||
|
||||
@ -161,7 +163,7 @@ export default function FeeSelector({
|
||||
) : (
|
||||
<>
|
||||
<ThemedText.DeprecatedLabel className="selected-fee-label">
|
||||
<Trans>{FEE_AMOUNT_DETAIL[feeAmount].label}% fee tier</Trans>
|
||||
<Trans>{formatDelta(parseFloat(FEE_AMOUNT_DETAIL[feeAmount].label))} fee tier</Trans>
|
||||
</ThemedText.DeprecatedLabel>
|
||||
<Box style={{ width: 'fit-content', marginTop: '8px' }} className="selected-fee-percentage">
|
||||
{distributions && (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import {
|
||||
MATIC,
|
||||
MATIC_MAINNET,
|
||||
USDC_ARBITRUM,
|
||||
USDC_MAINNET,
|
||||
USDC_OPTIMISM,
|
||||
@ -32,7 +32,7 @@ describe('getDefaultCurrencyCode', () => {
|
||||
expect(getDefaultCurrencyCode('NATIVE', 'polygon')).toBe('matic_polygon')
|
||||
})
|
||||
it('MATIC/ethereum should return the correct currency code', () => {
|
||||
expect(getDefaultCurrencyCode(MATIC.address, 'ethereum')).toBe('polygon')
|
||||
expect(getDefaultCurrencyCode(MATIC_MAINNET.address, 'ethereum')).toBe('polygon')
|
||||
})
|
||||
it('USDC/arbitrum should return the correct currency code', () => {
|
||||
expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'arbitrum')).toBe('usdc_arbitrum')
|
||||
@ -56,7 +56,7 @@ describe('getDefaultCurrencyCode', () => {
|
||||
expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'ethereum')).toBe('eth')
|
||||
expect(getDefaultCurrencyCode(USDC_OPTIMISM.address, 'ethereum')).toBe('eth')
|
||||
expect(getDefaultCurrencyCode(USDC_POLYGON.address, 'ethereum')).toBe('eth')
|
||||
expect(getDefaultCurrencyCode(MATIC.address, 'arbitrum')).toBe('eth')
|
||||
expect(getDefaultCurrencyCode(MATIC_MAINNET.address, 'arbitrum')).toBe('eth')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import {
|
||||
MATIC,
|
||||
MATIC_MAINNET,
|
||||
USDC_ARBITRUM,
|
||||
USDC_MAINNET,
|
||||
USDC_OPTIMISM,
|
||||
@ -28,7 +28,7 @@ const CURRENCY_CODES: {
|
||||
[USDC_MAINNET.address.toLowerCase()]: 'usdc',
|
||||
[USDT.address.toLowerCase()]: 'usdt',
|
||||
[WBTC.address.toLowerCase()]: 'wbtc',
|
||||
[MATIC.address.toLowerCase()]: 'polygon',
|
||||
[MATIC_MAINNET.address.toLowerCase()]: 'polygon',
|
||||
native: 'eth',
|
||||
},
|
||||
[Chain.Arbitrum]: {
|
||||
|
11
src/components/Icons/ExplorerIcon.tsx
Normal file
11
src/components/Icons/ExplorerIcon.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { ComponentProps } from 'react'
|
||||
|
||||
export const ExplorerIcon = (props: ComponentProps<'svg'>) => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
id="box-search_2"
|
||||
d="M6.32064 7.43328C6.4873 7.50661 6.66065 7.55329 6.83398 7.59329V13.94C6.72732 13.9133 6.62067 13.8733 6.52067 13.8266L2.52067 12.0466C1.80067 11.7266 1.33398 11.0133 1.33398 10.2199V5.77996C1.33398 5.59996 1.36064 5.41995 1.40731 5.25329L6.32064 7.43328ZM12.754 4.37328C12.5807 4.19995 12.3806 4.05328 12.1473 3.95328L8.1473 2.17329C7.6273 1.93996 7.04067 1.93996 6.52067 2.17329L2.52067 3.95328C2.28734 4.05328 2.08731 4.19995 1.91398 4.37328L6.72062 6.51328C7.10729 6.68661 7.55401 6.68661 7.94735 6.51328L12.754 4.37328ZM11.1347 7.92862C11.8227 7.76662 12.4766 7.81863 13.0646 8.02129C13.1966 8.06663 13.334 7.97729 13.334 7.83729V5.77996C13.334 5.59996 13.3073 5.41995 13.2607 5.25329L8.34733 7.43328C8.18066 7.49995 8.00732 7.55329 7.83398 7.59329V13.94C7.84598 13.948 7.84599 13.9479 7.85799 13.9559L8.98665 13.452C9.10132 13.4006 9.13533 13.262 9.066 13.158C8.584 12.4373 8.37864 11.522 8.57397 10.5586C8.83464 9.27263 9.85802 8.22929 11.1347 7.92862ZM15.0207 14.3526C14.9233 14.4499 14.7953 14.4993 14.6673 14.4993C14.5393 14.4993 14.4113 14.4506 14.314 14.3526L13.2807 13.3193C12.896 13.5713 12.4386 13.7213 11.9453 13.7213C10.5973 13.7213 9.50065 12.6246 9.50065 11.2766C9.50065 9.92862 10.5973 8.83194 11.9453 8.83194C13.2926 8.83194 14.3893 9.92862 14.3893 11.2766C14.3893 11.77 14.24 12.228 13.988 12.6119L15.0213 13.6453C15.216 13.8406 15.216 14.1573 15.0207 14.3526ZM13.3893 11.2773C13.3893 10.4806 12.7413 9.83261 11.9453 9.83261C11.1486 9.83261 10.5007 10.4806 10.5007 11.2773C10.5007 12.0739 11.1486 12.722 11.9453 12.722C12.7413 12.7213 13.3893 12.0733 13.3893 11.2773Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
)
|
@ -3,7 +3,6 @@ import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { AutoColumn, ColumnCenter } from 'components/Column'
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import { format } from 'd3'
|
||||
import { useColor } from 'hooks/useColor'
|
||||
import { saturate } from 'polished'
|
||||
import { ReactNode, useCallback, useMemo } from 'react'
|
||||
@ -12,6 +11,7 @@ import { batch } from 'react-redux'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { Chart } from './Chart'
|
||||
import { useDensityChartData } from './hooks'
|
||||
@ -142,6 +142,7 @@ export default function LiquidityChartRangeInput({
|
||||
: undefined
|
||||
}, [isSorted, priceLower, priceUpper])
|
||||
|
||||
const { formatDelta } = useFormatter()
|
||||
const brushLabelValue = useCallback(
|
||||
(d: 'w' | 'e', x: number) => {
|
||||
if (!price) return ''
|
||||
@ -151,9 +152,9 @@ export default function LiquidityChartRangeInput({
|
||||
|
||||
const percent = (x < price ? -1 : 1) * ((Math.max(x, price) - Math.min(x, price)) / price) * 100
|
||||
|
||||
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
|
||||
return price ? `${(Math.sign(percent) < 0 ? '-' : '') + formatDelta(percent)}` : ''
|
||||
},
|
||||
[isSorted, price, ticksAtLimit]
|
||||
[formatDelta, isSorted, price, ticksAtLimit]
|
||||
)
|
||||
|
||||
const isUninitialized = !currencyA || !currencyB || (formattedData === undefined && !isLoading)
|
||||
|
@ -1,8 +0,0 @@
|
||||
export const AppleLogo = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
<svg viewBox="0 0 814 1000" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
@ -79,6 +79,7 @@ export default function AssetLogo({
|
||||
onLoad={() => void setImgLoaded(true)}
|
||||
onError={nextSrc}
|
||||
imgLoaded={imgLoaded}
|
||||
loading="lazy"
|
||||
/>
|
||||
</LogoImageWrapper>
|
||||
) : (
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
|
||||
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const ChainSelector = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
borderRadius: '20',
|
||||
height: '36',
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
color: 'neutral1',
|
||||
background: 'none',
|
||||
}),
|
||||
])
|
@ -2,6 +2,7 @@ import { t } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { showTestnetsAtom } from 'components/AccountDrawer/TestnetsToggle'
|
||||
import { BaseButton } from 'components/Button'
|
||||
import { ChainLogo } from 'components/Logo/ChainLogo'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { getConnection } from 'connection'
|
||||
@ -13,24 +14,38 @@ import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useSelectChain from 'hooks/useSelectChain'
|
||||
import useSyncChainQuery from 'hooks/useSyncChainQuery'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { Column } from 'nft/components/Flex'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { useTheme } from 'styled-components'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { getSupportedChainIdsFromWalletConnectSession } from 'utils/getSupportedChainIdsFromWalletConnectSession'
|
||||
|
||||
import * as styles from './ChainSelector.css'
|
||||
import ChainSelectorRow from './ChainSelectorRow'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
|
||||
const NETWORK_SELECTOR_CHAINS = [...L1_CHAIN_IDS, ...L2_CHAIN_IDS]
|
||||
|
||||
interface ChainSelectorProps {
|
||||
leftAlign?: boolean
|
||||
}
|
||||
const ChainSelectorWrapper = styled.div`
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const ChainSelectorButton = styled(BaseButton)<{ isOpen: boolean }>`
|
||||
display: flex;
|
||||
background: ${({ theme, isOpen }) => (isOpen ? theme.accent2 : 'none')};
|
||||
padding: 10px 8px;
|
||||
gap: 4px;
|
||||
border-radius: 12px;
|
||||
height: 40px;
|
||||
color: ${({ theme }) => theme.neutral1};
|
||||
transition: ${({ theme }) =>
|
||||
`${theme.transition.duration.medium} ${theme.transition.timing.ease} background-color, ${theme.transition.duration.medium} ${theme.transition.timing.ease} margin`};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.deprecated_stateOverlayHover};
|
||||
}
|
||||
`
|
||||
|
||||
function useWalletSupportedChains(): ChainId[] {
|
||||
const { connector } = useWeb3React()
|
||||
@ -45,7 +60,7 @@ function useWalletSupportedChains(): ChainId[] {
|
||||
}
|
||||
}
|
||||
|
||||
export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
|
||||
export const ChainSelector = ({ leftAlign }: { leftAlign?: boolean }) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||
const isMobile = useIsMobile()
|
||||
@ -133,25 +148,18 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position="relative" ref={ref}>
|
||||
<ChainSelectorWrapper ref={ref}>
|
||||
<MouseoverTooltip text={t`Your wallet's current network is unsupported.`} disabled={isSupported}>
|
||||
<Row
|
||||
data-testid="chain-selector"
|
||||
as="button"
|
||||
gap="8"
|
||||
className={styles.ChainSelector}
|
||||
background={isOpen ? 'accent2' : 'none'}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<ChainSelectorButton data-testid="chain-selector" onClick={() => setIsOpen(!isOpen)} isOpen={isOpen}>
|
||||
{!isSupported ? (
|
||||
<AlertTriangle size={20} color={theme.neutral2} />
|
||||
) : (
|
||||
<ChainLogo chainId={chainId} size={24} testId="chain-selector-logo" />
|
||||
<ChainLogo chainId={chainId} size={20} testId="chain-selector-logo" />
|
||||
)}
|
||||
{isOpen ? <ChevronUp {...chevronProps} /> : <ChevronDown {...chevronProps} />}
|
||||
</Row>
|
||||
</ChainSelectorButton>
|
||||
</MouseoverTooltip>
|
||||
{isOpen && (isMobile ? <Portal>{dropdown}</Portal> : <>{dropdown}</>)}
|
||||
</Box>
|
||||
</ChainSelectorWrapper>
|
||||
)
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ export const MenuRow = style([
|
||||
{
|
||||
lineHeight: '24px',
|
||||
textDecoration: 'none',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -20,10 +20,11 @@ import { ReactNode, useReducer, useRef } from 'react'
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom'
|
||||
import { useToggleModal } from 'state/application/hooks'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
|
||||
import { openDownloadApp } from 'utils/openDownloadApp'
|
||||
|
||||
import { ReactComponent as AppleLogo } from '../../assets/svg/apple_logo.svg'
|
||||
import { ReactComponent as UniswapAppLogo } from '../../assets/svg/uniswap_app_logo.svg'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import * as styles from './MenuDropdown.css'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
@ -138,7 +139,7 @@ export const MenuDropdown = () => {
|
||||
|
||||
{isOpen && (
|
||||
<NavDropdown top={{ sm: 'unset', lg: '56' }} bottom={{ sm: '50', lg: 'unset' }} right="0">
|
||||
<Column gap="16">
|
||||
<Column gap="8">
|
||||
<Column paddingX="8" gap="4">
|
||||
<Box display={{ sm: 'none', lg: 'flex', xxl: 'none' }}>
|
||||
<PrimaryMenuRow to="/pool" close={toggleOpen}>
|
||||
@ -150,22 +151,6 @@ export const MenuDropdown = () => {
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Box>
|
||||
<Box
|
||||
onClick={() =>
|
||||
openDownloadApp({
|
||||
element: InterfaceElementName.UNISWAP_WALLET_MODAL_DOWNLOAD_BUTTON,
|
||||
})
|
||||
}
|
||||
>
|
||||
<PrimaryMenuRow close={toggleOpen}>
|
||||
<Icon>
|
||||
<AppleLogo width="24px" height="24px" fill={theme.neutral1} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Download Uniswap Wallet</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Box>
|
||||
<PrimaryMenuRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIcon width={24} height={24} color={theme.neutral1} />
|
||||
@ -182,6 +167,29 @@ export const MenuDropdown = () => {
|
||||
<Trans>View more analytics</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<Box
|
||||
onClick={() =>
|
||||
openDownloadApp({
|
||||
element: InterfaceElementName.UNISWAP_WALLET_NAVBAR_MENU_DOWNLOAD_BUTTON,
|
||||
})
|
||||
}
|
||||
>
|
||||
<PrimaryMenuRow close={toggleOpen}>
|
||||
<>
|
||||
<Icon>
|
||||
<UniswapAppLogo width="24px" height="24px" />
|
||||
</Icon>
|
||||
<div>
|
||||
<ThemedText.BodyPrimary>
|
||||
<Trans>Download Uniswap</Trans>
|
||||
</ThemedText.BodyPrimary>
|
||||
<ThemedText.LabelSmall>
|
||||
<Trans>Available on iOS and Android</Trans>
|
||||
</ThemedText.LabelSmall>
|
||||
</div>
|
||||
</>
|
||||
</PrimaryMenuRow>
|
||||
</Box>
|
||||
</Column>
|
||||
<Separator />
|
||||
<Box
|
||||
|
@ -4,6 +4,7 @@ import clsx from 'clsx'
|
||||
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
|
||||
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { checkSearchTokenWarning } from 'constants/tokenSafety'
|
||||
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
||||
import { Chain, TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import { getTokenDetailsURL } from 'graphql/data/util'
|
||||
@ -12,13 +13,11 @@ import { Column, Row } from 'nft/components/Flex'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
import { vars } from 'nft/css/sprinkles.css'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
import { ethNumberStandardFormatter } from 'nft/utils/currency'
|
||||
import { putCommas } from 'nft/utils/putCommas'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { useFormatter } from 'utils/formatNumbers'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { DeltaArrow, DeltaText } from '../Tokens/TokenDetails/Delta'
|
||||
import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets'
|
||||
@ -48,6 +47,8 @@ export const CollectionRow = ({
|
||||
index,
|
||||
eventProperties,
|
||||
}: CollectionRowProps) => {
|
||||
const { formatNumberOrString } = useFormatter()
|
||||
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
|
||||
@ -101,13 +102,17 @@ export const CollectionRow = ({
|
||||
<Box className={styles.primaryText}>{collection.name}</Box>
|
||||
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection?.stats?.total_supply ?? 0)} items</Box>
|
||||
<Box className={styles.secondaryText}>
|
||||
{formatNumberOrString({ input: collection?.stats?.total_supply, type: NumberType.WholeNumber })} items
|
||||
</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
{collection.stats?.floor_price ? (
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.stats?.floor_price)} ETH</Box>
|
||||
<Box className={styles.primaryText}>
|
||||
{formatNumberOrString({ input: collection.stats?.floor_price, type: NumberType.NFTToken })} ETH
|
||||
</Box>
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>Floor</Box>
|
||||
</Column>
|
||||
@ -128,7 +133,7 @@ interface TokenRowProps {
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
|
||||
const addRecentlySearchedAsset = useAddRecentlySearchedAsset()
|
||||
const navigate = useNavigate()
|
||||
const { formatFiatPrice, formatPercent } = useFormatter()
|
||||
const { formatFiatPrice, formatDelta } = useFormatter()
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
const address = !token.address && token.standard === TokenStandard.Native ? 'NATIVE' : token.address
|
||||
@ -138,7 +143,9 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
sendAnalyticsEvent(InterfaceEventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
|
||||
}, [addRecentlySearchedAsset, token, toggleOpen, eventProperties])
|
||||
|
||||
const tokenDetailsPath = getTokenDetailsURL(token)
|
||||
const isInfoExplorePageEnabled = useInfoExplorePageEnabled()
|
||||
|
||||
const tokenDetailsPath = getTokenDetailsURL({ ...token, isInfoExplorePageEnabled })
|
||||
// Close the modal on escape
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
@ -191,7 +198,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
<DeltaArrow delta={token.market?.pricePercentChange?.value} />
|
||||
<ThemedText.BodySmall>
|
||||
<DeltaText delta={token.market?.pricePercentChange?.value}>
|
||||
{formatPercent(Math.abs(token.market?.pricePercentChange?.value ?? 0))}
|
||||
{formatDelta(Math.abs(token.market?.pricePercentChange?.value ?? 0))}
|
||||
</DeltaText>
|
||||
</ThemedText.BodySmall>
|
||||
</PriceChangeContainer>
|
||||
|
@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
@ -61,15 +62,22 @@ export const PageTabs = () => {
|
||||
const isNftPage = useIsNftPage()
|
||||
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
const infoExplorePageEnabled = useInfoExplorePageEnabled()
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
|
||||
<Trans>Swap</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}>
|
||||
<Trans>Tokens</Trans>
|
||||
</MenuItem>
|
||||
{infoExplorePageEnabled ? (
|
||||
<MenuItem href={`/explore/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/explore')}>
|
||||
<Trans>Explore</Trans>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}>
|
||||
<Trans>Tokens</Trans>
|
||||
</MenuItem>
|
||||
)}
|
||||
{!shouldDisableNFTRoutes && (
|
||||
<MenuItem dataTestId="nft-nav" href="/nfts" isActive={isNftPage}>
|
||||
<Trans>NFTs</Trans>
|
||||
|
@ -1,9 +1,36 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import store from 'state'
|
||||
import { addSerializedToken } from 'state/user/reducer'
|
||||
import { act, render, screen } from 'test-utils/render'
|
||||
|
||||
import { PoolDetailsHeader } from './PoolDetailsHeader'
|
||||
|
||||
describe('PoolDetailsHeader', () => {
|
||||
beforeEach(() => {
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
symbol: 'USDC',
|
||||
name: 'USD Coin',
|
||||
decimals: 6,
|
||||
},
|
||||
})
|
||||
)
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||
symbol: 'WETH',
|
||||
name: 'Wrapped Ether',
|
||||
decimals: 18,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const mockProps = {
|
||||
chainId: 1,
|
||||
poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640',
|
||||
@ -13,6 +40,13 @@ describe('PoolDetailsHeader', () => {
|
||||
toggleReversed: jest.fn(),
|
||||
}
|
||||
|
||||
it('loading skeleton is shown', () => {
|
||||
const { asFragment } = render(<PoolDetailsHeader {...mockProps} loading={true} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
||||
expect(screen.getByTestId('pdp-header-loading-skeleton')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders header text correctly', () => {
|
||||
const { asFragment } = render(<PoolDetailsHeader {...mockProps} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
62
src/pages/PoolDetails/PoolDetailsHeader.tsx → src/components/Pools/PoolDetails/PoolDetailsHeader.tsx
62
src/pages/PoolDetails/PoolDetailsHeader.tsx → src/components/Pools/PoolDetails/PoolDetailsHeader.tsx
@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg'
|
||||
import Column from 'components/Column'
|
||||
import { ChainLogo } from 'components/Logo/ChainLogo'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { BIPS_BASE } from 'constants/misc'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
@ -15,6 +16,7 @@ import { ClickableStyle, ThemedText } from 'theme/components'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
import { ReversedArrowsIcon } from './icons'
|
||||
import { DetailBubble } from './shared'
|
||||
|
||||
const HeaderColumn = styled(Column)`
|
||||
gap: 36px;
|
||||
@ -35,6 +37,12 @@ const ToggleReverseArrows = styled(ReversedArrowsIcon)`
|
||||
${ClickableStyle}
|
||||
`
|
||||
|
||||
const IconBubble = styled(LoadingBubble)`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
interface Token {
|
||||
id: string
|
||||
symbol: string
|
||||
@ -47,6 +55,7 @@ interface PoolDetailsHeaderProps {
|
||||
token1?: Token
|
||||
feeTier?: number
|
||||
toggleReversed: React.DispatchWithoutAction
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export function PoolDetailsHeader({
|
||||
@ -56,10 +65,25 @@ export function PoolDetailsHeader({
|
||||
token1,
|
||||
feeTier,
|
||||
toggleReversed,
|
||||
loading,
|
||||
}: PoolDetailsHeaderProps) {
|
||||
const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined]
|
||||
const chainName = chainIdToBackendName(chainId)
|
||||
const origin = `/tokens/${chainName}`
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<HeaderColumn data-testid="pdp-header-loading-skeleton">
|
||||
<DetailBubble $width={300} />
|
||||
<Column gap="sm">
|
||||
<Row gap="8px">
|
||||
<IconBubble />
|
||||
<DetailBubble $width={137} />
|
||||
</Row>
|
||||
</Column>
|
||||
</HeaderColumn>
|
||||
)
|
||||
|
||||
return (
|
||||
<HeaderColumn>
|
||||
<Row>
|
||||
@ -101,7 +125,6 @@ const StyledLogoParentContainer = styled.div`
|
||||
top: 0;
|
||||
left: 0;
|
||||
`
|
||||
|
||||
function DoubleCurrencyAndChainLogo({
|
||||
chainId,
|
||||
currencies,
|
||||
@ -141,37 +164,45 @@ function SquareL2Logo({ chainId }: { chainId: ChainId }) {
|
||||
)
|
||||
}
|
||||
|
||||
function DoubleCurrencyLogo({ chainId, currencies }: { chainId: number; currencies: Array<Currency | undefined> }) {
|
||||
export function DoubleCurrencyLogo({
|
||||
chainId,
|
||||
currencies,
|
||||
small,
|
||||
}: {
|
||||
chainId: number
|
||||
currencies: Array<Currency | undefined>
|
||||
small?: boolean
|
||||
}) {
|
||||
const [src, nextSrc] = useTokenLogoSource(currencies?.[0]?.wrapped.address, chainId, currencies?.[0]?.isNative)
|
||||
const [src2, nextSrc2] = useTokenLogoSource(currencies?.[1]?.wrapped.address, chainId, currencies?.[1]?.isNative)
|
||||
|
||||
return <DoubleLogo logo1={src} onError1={nextSrc} logo2={src2} onError2={nextSrc2} />
|
||||
return <DoubleLogo logo1={src} onError1={nextSrc} logo2={src2} onError2={nextSrc2} small={small} />
|
||||
}
|
||||
|
||||
const DoubleLogoContainer = styled.div`
|
||||
const DoubleLogoContainer = styled.div<{ small?: boolean }>`
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
img {
|
||||
width: 16px;
|
||||
height: 32px;
|
||||
width: ${({ small }) => (small ? '10px' : '16px')};
|
||||
height: ${({ small }) => (small ? '20px' : '32px')};
|
||||
object-fit: cover;
|
||||
}
|
||||
img:first-child {
|
||||
border-radius: 16px 0 0 16px;
|
||||
border-radius: ${({ small }) => (small ? '10px 0 0 10px' : '16px 0 0 16px')};
|
||||
object-position: 0 0;
|
||||
}
|
||||
img:last-child {
|
||||
border-radius: 0 16px 16px 0;
|
||||
border-radius: ${({ small }) => (small ? '0 10px 10px 0' : '0 16px 16px 0')};
|
||||
object-position: 100% 0;
|
||||
}
|
||||
`
|
||||
|
||||
const CircleLogoImage = styled.img`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
const CircleLogoImage = styled.img<{ small?: boolean }>`
|
||||
width: ${({ small }) => (small ? '10px' : '16px')};
|
||||
height: ${({ small }) => (small ? '20px' : '32px')};
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
@ -180,13 +211,14 @@ interface DoubleLogoProps {
|
||||
logo2?: string
|
||||
onError1?: () => void
|
||||
onError2?: () => void
|
||||
small?: boolean
|
||||
}
|
||||
|
||||
function DoubleLogo({ logo1, onError1, logo2, onError2 }: DoubleLogoProps) {
|
||||
function DoubleLogo({ logo1, onError1, logo2, onError2, small }: DoubleLogoProps) {
|
||||
return (
|
||||
<DoubleLogoContainer>
|
||||
<CircleLogoImage src={logo1 ?? blankTokenUrl} onError={onError1} />
|
||||
<CircleLogoImage src={logo2 ?? blankTokenUrl} onError={onError2} />
|
||||
<DoubleLogoContainer small={small}>
|
||||
<CircleLogoImage src={logo1 ?? blankTokenUrl} onError={onError1} small={small} />
|
||||
<CircleLogoImage src={logo2 ?? blankTokenUrl} onError={onError2} small={small} />
|
||||
</DoubleLogoContainer>
|
||||
)
|
||||
}
|
63
src/components/Pools/PoolDetails/PoolDetailsLink.test.tsx
Normal file
63
src/components/Pools/PoolDetails/PoolDetailsLink.test.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { USDC_MAINNET } from 'constants/tokens'
|
||||
import store from 'state'
|
||||
import { addSerializedToken } from 'state/user/reducer'
|
||||
import { usdcWethPoolAddress, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
|
||||
import { render, screen } from 'test-utils/render'
|
||||
|
||||
import { PoolDetailsLink } from './PoolDetailsLink'
|
||||
|
||||
describe('PoolDetailsHeader', () => {
|
||||
beforeEach(() => {
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
symbol: 'USDC',
|
||||
name: 'USD Coin',
|
||||
decimals: 6,
|
||||
},
|
||||
})
|
||||
)
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||
symbol: 'WETH',
|
||||
name: 'Wrapped Ether',
|
||||
decimals: 18,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renders link for pool address', async () => {
|
||||
const { asFragment } = render(
|
||||
<PoolDetailsLink
|
||||
address={usdcWethPoolAddress}
|
||||
chainId={ChainId.MAINNET}
|
||||
tokens={[validPoolToken0, validPoolToken1]}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
||||
expect(screen.getByText('USDC / WETH')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('pdp-pool-logo-USDC-WETH')).toBeInTheDocument()
|
||||
expect(screen.getByTestId(`copy-address-${usdcWethPoolAddress}`)).toBeInTheDocument()
|
||||
expect(screen.getByTestId(`explorer-url-https://etherscan.io/address/${usdcWethPoolAddress}`)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders link for token address', async () => {
|
||||
const { asFragment } = render(
|
||||
<PoolDetailsLink address={USDC_MAINNET.address} chainId={ChainId.MAINNET} tokens={[validPoolToken0]} />
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
||||
expect(screen.getByText('USDC')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('pdp-token-logo-USDC')).toBeInTheDocument()
|
||||
expect(screen.getByTestId(`copy-address-${USDC_MAINNET.address}`)).toBeInTheDocument()
|
||||
expect(screen.getByTestId(`explorer-url-https://etherscan.io/token/${USDC_MAINNET.address}`)).toBeInTheDocument()
|
||||
})
|
||||
})
|
165
src/components/Pools/PoolDetails/PoolDetailsLink.tsx
Normal file
165
src/components/Pools/PoolDetails/PoolDetailsLink.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { EtherscanLogo } from 'components/Icons/Etherscan'
|
||||
import { ExplorerIcon } from 'components/Icons/ExplorerIcon'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Row from 'components/Row'
|
||||
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
||||
import { chainIdToBackendName, getTokenDetailsURL } from 'graphql/data/util'
|
||||
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import { useCallback } from 'react'
|
||||
import { ChevronRight, Copy } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
import { ClickableStyle, EllipsisStyle, ExternalLink, ThemedText } from 'theme/components'
|
||||
import { shortenAddress } from 'utils'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { DoubleCurrencyLogo } from './PoolDetailsHeader'
|
||||
import { DetailBubble, SmallDetailBubble } from './shared'
|
||||
|
||||
const TokenName = styled(ThemedText.BodyPrimary)`
|
||||
display: none;
|
||||
|
||||
@media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.xs - 1}px) {
|
||||
display: block;
|
||||
}
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
const TokenTextWrapper = styled(Row)<{ isClickable?: boolean }>`
|
||||
gap: 8px;
|
||||
margin-right: 12px;
|
||||
${({ isClickable }) => isClickable && ClickableStyle}
|
||||
`
|
||||
|
||||
const SymbolText = styled(ThemedText.BodyPrimary)`
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.xs - 1}px) {
|
||||
color: ${({ theme }) => theme.neutral2};
|
||||
}
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
const CopyAddress = styled(Row)`
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme }) => theme.surface3};
|
||||
font-size: 14px;
|
||||
font-weight: 535;
|
||||
line-height: 16px;
|
||||
width: max-content;
|
||||
flex-shrink: 0;
|
||||
${ClickableStyle}
|
||||
`
|
||||
const StyledCopyIcon = styled(Copy)`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: ${({ theme }) => theme.neutral2};
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
const ExplorerWrapper = styled.div`
|
||||
padding: 8px;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme }) => theme.surface3};
|
||||
display: flex;
|
||||
${ClickableStyle}
|
||||
`
|
||||
|
||||
const ButtonsRow = styled(Row)`
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
width: max-content;
|
||||
`
|
||||
|
||||
interface PoolDetailsLinkProps {
|
||||
address?: string
|
||||
chainId?: number
|
||||
tokens: (Token | undefined)[]
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export function PoolDetailsLink({ address, chainId, tokens, loading }: PoolDetailsLinkProps) {
|
||||
const theme = useTheme()
|
||||
const currencies = [
|
||||
useCurrency(tokens[0]?.id, chainId) ?? undefined,
|
||||
useCurrency(tokens[1]?.id, chainId) ?? undefined,
|
||||
]
|
||||
const [, setCopied] = useCopyClipboard()
|
||||
const copy = useCallback(() => {
|
||||
address && setCopied(address)
|
||||
}, [address, setCopied])
|
||||
|
||||
const isPool = tokens.length === 2
|
||||
const explorerUrl =
|
||||
address && chainId && getExplorerLink(chainId, address, isPool ? ExplorerDataType.ADDRESS : ExplorerDataType.TOKEN)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const isInfoExplorePageEnabled = useInfoExplorePageEnabled()
|
||||
const chainName = chainIdToBackendName(chainId)
|
||||
const handleTokenTextClick = useCallback(() => {
|
||||
if (!isPool) {
|
||||
navigate(getTokenDetailsURL({ address: tokens[0]?.id, chain: chainName, isInfoExplorePageEnabled }))
|
||||
}
|
||||
}, [navigate, tokens, isPool, chainName, isInfoExplorePageEnabled])
|
||||
|
||||
if (loading || !address || !chainId) {
|
||||
return (
|
||||
<Row gap="8px" padding="6px 0px">
|
||||
<SmallDetailBubble />
|
||||
<DetailBubble $width={117} />
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Row align="space-between">
|
||||
<TokenTextWrapper
|
||||
data-testid={
|
||||
isPool ? `pdp-pool-logo-${tokens[0]?.symbol}-${tokens[1]?.symbol}` : `pdp-token-logo-${tokens[0]?.symbol}`
|
||||
}
|
||||
isClickable={!isPool}
|
||||
onClick={handleTokenTextClick}
|
||||
>
|
||||
{isPool ? (
|
||||
<DoubleCurrencyLogo chainId={chainId} currencies={currencies} small />
|
||||
) : (
|
||||
<CurrencyLogo currency={currencies[0]} size="20px" />
|
||||
)}
|
||||
<TokenName>{isPool ? <Trans>Pool</Trans> : tokens[0]?.name}</TokenName>
|
||||
<SymbolText>
|
||||
{isPool ? (
|
||||
`${tokens[0]?.symbol} / ${tokens[1]?.symbol}`
|
||||
) : (
|
||||
<Row gap="4px">
|
||||
{tokens[0]?.symbol} <ChevronRight size={16} color={theme.neutral2} />
|
||||
</Row>
|
||||
)}
|
||||
</SymbolText>
|
||||
</TokenTextWrapper>
|
||||
<ButtonsRow>
|
||||
<CopyAddress data-testid={`copy-address-${address}`} onClick={copy}>
|
||||
{shortenAddress(address)}
|
||||
<StyledCopyIcon />
|
||||
</CopyAddress>
|
||||
{explorerUrl && (
|
||||
<ExternalLink href={explorerUrl} data-testid={`explorer-url-${explorerUrl}`}>
|
||||
<ExplorerWrapper>
|
||||
{chainId === ChainId.MAINNET ? (
|
||||
<EtherscanLogo width="16px" height="16px" fill={theme.neutral2} />
|
||||
) : (
|
||||
<ExplorerIcon width="16px" height="16px" stroke={theme.darkMode ? 'none' : theme.neutral2} />
|
||||
)}
|
||||
</ExplorerWrapper>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</ButtonsRow>
|
||||
</Row>
|
||||
)
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
import { enableNetConnect } from 'nock'
|
||||
import store from 'state'
|
||||
import { addSerializedToken } from 'state/user/reducer'
|
||||
import { validPoolDataResponse } from 'test-utils/pools/fixtures'
|
||||
import { act, render, screen } from 'test-utils/render'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
@ -15,6 +17,28 @@ describe('PoolDetailsStats', () => {
|
||||
beforeEach(() => {
|
||||
// Enable network connections for retrieving token logos
|
||||
enableNetConnect()
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
symbol: 'USDC',
|
||||
name: 'USD Coin',
|
||||
decimals: 6,
|
||||
},
|
||||
})
|
||||
)
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||
symbol: 'WETH',
|
||||
name: 'Wrapped Ether',
|
||||
decimals: 18,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renders stats text correctly', async () => {
|
79
src/pages/PoolDetails/PoolDetailsStats.tsx → src/components/Pools/PoolDetails/PoolDetailsStats.tsx
79
src/pages/PoolDetails/PoolDetailsStats.tsx → src/components/Pools/PoolDetails/PoolDetailsStats.tsx
@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
|
||||
import { PoolData } from 'graphql/thegraph/PoolData'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
@ -15,6 +16,8 @@ import { colors } from 'theme/colors'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { DetailBubble } from './shared'
|
||||
|
||||
const HeaderText = styled(Text)`
|
||||
font-weight: 485;
|
||||
font-size: 24px;
|
||||
@ -90,13 +93,25 @@ const BalanceChartSide = styled.div<{ percent: number; $color: string; isLeft: b
|
||||
${({ isLeft }) => (isLeft ? leftBarChartStyles : rightBarChartStyles)}
|
||||
`
|
||||
|
||||
const StatSectionBubble = styled(LoadingBubble)`
|
||||
width: 180px;
|
||||
height: 40px;
|
||||
`
|
||||
|
||||
const StatHeaderBubble = styled(LoadingBubble)`
|
||||
width: 116px;
|
||||
height: 24px;
|
||||
border-radius: 8px;
|
||||
`
|
||||
|
||||
interface PoolDetailsStatsProps {
|
||||
poolData: PoolData
|
||||
isReversed: boolean
|
||||
poolData?: PoolData
|
||||
isReversed?: boolean
|
||||
chainId?: number
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsStatsProps) {
|
||||
export function PoolDetailsStats({ poolData, isReversed, chainId, loading }: PoolDetailsStatsProps) {
|
||||
const isScreenSize = useScreenSize()
|
||||
const screenIsNotLarge = isScreenSize['lg']
|
||||
const { formatNumber } = useFormatter()
|
||||
@ -112,26 +127,46 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS
|
||||
}
|
||||
|
||||
const [token0, token1] = useMemo(() => {
|
||||
const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1
|
||||
const token0FullData = {
|
||||
...poolData?.token0,
|
||||
price: poolData?.token0Price,
|
||||
tvl: poolData?.tvlToken0,
|
||||
color: color0,
|
||||
percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth,
|
||||
currency: currency0,
|
||||
if (poolData) {
|
||||
const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1
|
||||
const token0FullData = {
|
||||
...poolData?.token0,
|
||||
price: poolData?.token0Price,
|
||||
tvl: poolData?.tvlToken0,
|
||||
color: color0,
|
||||
percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth,
|
||||
currency: currency0,
|
||||
}
|
||||
const token1FullData = {
|
||||
...poolData?.token1,
|
||||
price: poolData?.token1Price,
|
||||
tvl: poolData?.tvlToken1,
|
||||
color: color1,
|
||||
percent: poolData?.tvlToken1 / fullWidth,
|
||||
currency: currency1,
|
||||
}
|
||||
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
|
||||
} else {
|
||||
return [undefined, undefined]
|
||||
}
|
||||
const token1FullData = {
|
||||
...poolData?.token1,
|
||||
price: poolData?.token1Price,
|
||||
tvl: poolData?.tvlToken1,
|
||||
color: color1,
|
||||
percent: poolData?.tvlToken1 / fullWidth,
|
||||
currency: currency1,
|
||||
}
|
||||
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
|
||||
}, [color0, color1, currency0, currency1, isReversed, poolData])
|
||||
|
||||
if (loading || !token0 || !token1 || !poolData) {
|
||||
return (
|
||||
<StatsWrapper>
|
||||
<HeaderText>
|
||||
<StatHeaderBubble />
|
||||
</HeaderText>
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Column gap="md" key={`loading-info-row-${i}`}>
|
||||
<DetailBubble />
|
||||
<StatSectionBubble />
|
||||
</Column>
|
||||
))}
|
||||
</StatsWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<StatsWrapper>
|
||||
<HeaderText>
|
||||
@ -204,7 +239,7 @@ const StatItemText = styled(Text)`
|
||||
`
|
||||
|
||||
function StatItem({ title, value, delta }: { title: ReactNode; value: number; delta?: number }) {
|
||||
const { formatNumber, formatPercent } = useFormatter()
|
||||
const { formatNumber, formatDelta } = useFormatter()
|
||||
|
||||
return (
|
||||
<StatItemColumn>
|
||||
@ -219,7 +254,7 @@ function StatItem({ title, value, delta }: { title: ReactNode; value: number; de
|
||||
{!!delta && (
|
||||
<Row width="max-content" padding="4px 0px">
|
||||
<DeltaArrow delta={delta} />
|
||||
<ThemedText.BodySecondary>{formatPercent(delta)}</ThemedText.BodySecondary>
|
||||
<ThemedText.BodySecondary>{formatDelta(delta)}</ThemedText.BodySecondary>
|
||||
</Row>
|
||||
)}
|
||||
</StatsTextContainer>
|
@ -1,5 +1,7 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
|
||||
import store from 'state'
|
||||
import { addSerializedToken } from 'state/user/reducer'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { useMultiChainPositionsReturnValue, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
|
||||
import { act, render, screen } from 'test-utils/render'
|
||||
@ -24,6 +26,35 @@ describe('PoolDetailsStatsButton', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
symbol: 'USDC',
|
||||
name: 'USD Coin',
|
||||
decimals: 6,
|
||||
},
|
||||
})
|
||||
)
|
||||
store.dispatch(
|
||||
addSerializedToken({
|
||||
serializedToken: {
|
||||
chainId: 1,
|
||||
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||
symbol: 'WETH',
|
||||
name: 'Wrapped Ether',
|
||||
decimals: 18,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('loading skeleton shown correctly', () => {
|
||||
const { asFragment } = render(<PoolDetailsStatsButtons {...mockProps} loading={true} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
||||
expect(screen.getByTestId('pdp-buttons-loading-skeleton')).toBeVisible()
|
||||
})
|
||||
|
||||
it('renders both buttons correctly', () => {
|
||||
@ -57,7 +88,7 @@ describe('PoolDetailsStatsButton', () => {
|
||||
|
||||
await act(() => userEvent.click(screen.getByTestId('pool-details-add-liquidity-button')))
|
||||
expect(global.window.location.href).toContain(
|
||||
'/increase/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/500'
|
||||
'/add/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/500'
|
||||
)
|
||||
})
|
||||
})
|
@ -4,6 +4,7 @@ import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache
|
||||
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import { useSwitchChain } from 'hooks/useSwitchChain'
|
||||
@ -26,11 +27,18 @@ const PoolButton = styled(ThemeButton)`
|
||||
width: 50%;
|
||||
`
|
||||
|
||||
const ButtonBubble = styled(LoadingBubble)`
|
||||
height: 44px;
|
||||
width: 175px;
|
||||
border-radius: 900px;
|
||||
`
|
||||
|
||||
interface PoolDetailsStatsButtonsProps {
|
||||
chainId?: number
|
||||
token0?: Token
|
||||
token1?: Token
|
||||
feeTier?: number
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) {
|
||||
@ -45,7 +53,7 @@ function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?
|
||||
)
|
||||
}
|
||||
|
||||
export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: PoolDetailsStatsButtonsProps) {
|
||||
export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier, loading }: PoolDetailsStatsButtonsProps) {
|
||||
const { chainId: walletChainId, connector, account } = useWeb3React()
|
||||
const { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined)
|
||||
const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier)
|
||||
@ -60,11 +68,19 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po
|
||||
navigate(
|
||||
toSwap
|
||||
? `/swap?inputCurrency=${currencyId(currency0)}&outputCurrency=${currencyId(currency1)}`
|
||||
: `/increase/${currencyId(currency0)}/${currencyId(currency1)}/${feeTier}${tokenId ? `/${tokenId}` : ''}`
|
||||
: `/add/${currencyId(currency0)}/${currencyId(currency1)}/${feeTier}${tokenId ? `/${tokenId}` : ''}`
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!currency0 || !currency1) return null
|
||||
|
||||
if (loading || !currency0 || !currency1)
|
||||
return (
|
||||
<PoolDetailsStatsButtonsRow data-testid="pdp-buttons-loading-skeleton">
|
||||
<ButtonBubble />
|
||||
<ButtonBubble />
|
||||
</PoolDetailsStatsButtonsRow>
|
||||
)
|
||||
|
||||
return (
|
||||
<PoolDetailsStatsButtonsRow>
|
||||
<PoolButton
|
||||
@ -75,7 +91,6 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po
|
||||
>
|
||||
<Trans>Add liquidity</Trans>
|
||||
</PoolButton>
|
||||
|
||||
<PoolButton
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.highSoft}
|
@ -0,0 +1,98 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import { ScrollBarStyles } from 'components/Common'
|
||||
import Row from 'components/Row'
|
||||
import { ArrowDown } from 'react-feather'
|
||||
import styled from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
|
||||
import { DetailBubble, SmallDetailBubble } from './shared'
|
||||
|
||||
const Table = styled(Column)`
|
||||
gap: 24px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid ${({ theme }) => theme.surface3};
|
||||
padding-bottom: 12px;
|
||||
overflow-y: hidden;
|
||||
${ScrollBarStyles}
|
||||
`
|
||||
|
||||
const TableRow = styled(Row)<{ $borderBottom?: boolean }>`
|
||||
justify-content: space-between;
|
||||
border-bottom: ${({ $borderBottom, theme }) => ($borderBottom ? `1px solid ${theme.surface3}` : 'none')}};
|
||||
padding: 12px;
|
||||
min-width: max-content;
|
||||
`
|
||||
|
||||
const TableElement = styled(ThemedText.BodySecondary)<{
|
||||
alignRight?: boolean
|
||||
small?: boolean
|
||||
large?: boolean
|
||||
}>`
|
||||
display: flex;
|
||||
padding: 0px 8px;
|
||||
flex: ${({ small }) => (small ? 'unset' : '1')};
|
||||
width: ${({ small }) => (small ? '44px' : 'auto')};
|
||||
min-width: ${({ large, small }) => (large ? '136px' : small ? 'unset' : '121px')} !important;
|
||||
justify-content: ${({ alignRight }) => (alignRight ? 'flex-end' : 'flex-start')};
|
||||
`
|
||||
{
|
||||
/* TODO(WEB-2735): When making real datatable, merge in this code and deprecate this skeleton file */
|
||||
}
|
||||
export function PoolDetailsTableSkeleton() {
|
||||
return (
|
||||
<Table $isHorizontalScroll>
|
||||
<TableRow $borderBottom>
|
||||
<TableElement large>
|
||||
<Row>
|
||||
<ArrowDown size={16} />
|
||||
<Trans>Time</Trans>
|
||||
</Row>
|
||||
</TableElement>
|
||||
<TableElement>
|
||||
<Trans>Type</Trans>
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<Trans>USD</Trans>
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<Trans>Maker</Trans>
|
||||
</TableElement>
|
||||
<TableElement alignRight small>
|
||||
<Trans>Txn</Trans>
|
||||
</TableElement>
|
||||
</TableRow>
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<TableRow key={`loading-table-row-${i}`}>
|
||||
<TableElement large>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement alignRight>
|
||||
<DetailBubble />
|
||||
</TableElement>
|
||||
<TableElement alignRight small>
|
||||
<SmallDetailBubble />
|
||||
</TableElement>
|
||||
</TableRow>
|
||||
))}
|
||||
</Table>
|
||||
)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user