Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
78cb58329e | ||
|
0e561f80ef | ||
|
51e3bee414 | ||
|
57fc9e1eb7 | ||
|
f6660bef03 | ||
|
ae0bedf24b | ||
|
206c999835 | ||
|
3bd0b1c9be | ||
|
3cc7cecf6a | ||
|
9620365349 | ||
|
8e4e8a90ab |
.env.env.production
.github
CODEOWNERScypress/e2e
package.jsonpublic
scripts
src
assets/svg
components
AccountDrawer
Banner/AndroidAnnouncementBanner
FeatureFlagModal
Logo
NavBar
Pools/PoolDetails
PoolDetailsHeader.test.tsxPoolDetailsLink.test.tsxPoolDetailsStats.test.tsxPoolDetailsStatsButtons.test.tsxPoolDetailsStatsButtons.tsx
__snapshots__
Popover
PrefetchBalancesWrapper
SearchModal
Settings/RouterPreferenceSettings
Tokens
TokenDetails
BalanceSummary.tsxChartSection.tsxMobileBalanceSummaryFooter.tsxSkeleton.tsxTimeSelector.tsxindex.tsx
TokenTable
WalletModal
swap
connection
constants
featureFlags
flags
androidGALaunch.tsfotAdjustments.tslimits.tsuniswapXDefault.tsuniswapXEthOutput.tsuniswapXExactOutput.ts
index.tsxgraphql/data
hooks
lib
locales
10
.env
10
.env
@ -1,8 +1,8 @@
|
|||||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||||
ESLINT_NO_DEV_ERRORS=true
|
ESLINT_NO_DEV_ERRORS=true
|
||||||
REACT_APP_AMPLITUDE_PROXY_URL="https://null.null"
|
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||||
REACT_APP_AWS_API_REGION="us-east-2"
|
REACT_APP_AWS_API_REGION="us-east-2"
|
||||||
REACT_APP_AWS_API_ENDPOINT="https://null.null"
|
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||||
REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf"
|
REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf"
|
||||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||||
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://magical-alien-tab.quiknode.pro/669e87e569a8277d3fbd9e202f9df93189f19f4c"
|
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_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=staging"
|
||||||
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
|
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
|
||||||
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
|
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
|
||||||
REACT_APP_STATSIG_PROXY_URL="https://null.null"
|
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||||
REACT_APP_TEMP_API_URL="https://null.null"
|
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||||
REACT_APP_UNISWAP_API_URL="https://null.null"
|
REACT_APP_UNISWAP_API_URL="https://api.uniswap.org/v2"
|
||||||
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"
|
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.
|
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||||
REACT_APP_AMPLITUDE_PROXY_URL="https://null.null"
|
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||||
REACT_APP_AWS_API_ENDPOINT="https://null.null"
|
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
|
||||||
REACT_APP_BNB_RPC_URL="https://old-wispy-arrow.bsc.quiknode.pro/f5c060177236065c1058531a0615ab4f7a34a2fd"
|
REACT_APP_BNB_RPC_URL="https://old-wispy-arrow.bsc.quiknode.pro/f5c060177236065c1058531a0615ab4f7a34a2fd"
|
||||||
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
||||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||||
REACT_APP_MOONPAY_API="https://api.moonpay.com"
|
REACT_APP_MOONPAY_API="https://api.moonpay.com"
|
||||||
REACT_APP_MOONPAY_LINK="https://null.null"
|
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=production"
|
||||||
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
|
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
|
||||||
REACT_APP_SENTRY_ENABLED=true
|
REACT_APP_SENTRY_ENABLED=true
|
||||||
REACT_APP_SENTRY_TRACES_SAMPLE_RATE=0.00003
|
REACT_APP_SENTRY_TRACES_SAMPLE_RATE=0.00003
|
||||||
REACT_APP_STATSIG_PROXY_URL="https://null.null"
|
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||||
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://ultra-blue-flower.quiknode.pro/770b22d5f362c537bc8fe19b034c45b22958f880"
|
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"
|
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3?source=uniswap"
|
||||||
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* @uniswap/web-reviewers
|
22
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
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
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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
Normal file
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
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
Normal file
48
.github/actions/report/action.yml
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
name: Report
|
||||||
|
description: Report test failures via Slack
|
||||||
|
inputs:
|
||||||
|
name:
|
||||||
|
description: The name of the failing test
|
||||||
|
required: true
|
||||||
|
SLACK_WEBHOOK_URL:
|
||||||
|
description: The webhook URL to send the report to
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": "${{ inputs.name }} failing on `${{ github.ref_name }}`",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"text": {
|
||||||
|
"type": "mrkdwn",
|
||||||
|
"text": "*${{ inputs.name }} failing on `${{ github.ref_name }}`:* <https://github.com/${{ github.repository}}/actions/runs/${{ github.run_id }}|view failing action>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"text": {
|
||||||
|
"type": "mrkdwn",
|
||||||
|
"text": "_This is blocking pull requests and branch promotions._\n_Please prioritize fixing the build._"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
|
||||||
|
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||||
|
# The !oncall bot requires its own message:
|
||||||
|
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": "!oncall web"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
|
||||||
|
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
49
.github/actions/setup/action.yml
vendored
Normal file
49
.github/actions/setup/action.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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
Normal file
12
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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
Normal file
52
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!-- 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
Normal file
73
.github/workflows/1-main-to-staging.yml
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
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
Normal file
64
.github/workflows/2-deploy-to-staging.yml
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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
Normal file
42
.github/workflows/3-staging-to-prod.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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
Normal file
111
.github/workflows/4-deploy-to-prod.yml
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
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
Normal file
17
.github/workflows/check-pr-title.yaml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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
Normal file
26
.github/workflows/crowdin.yaml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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 }}
|
91
.github/workflows/notify-slack-on-release-merge.yml
vendored
Normal file
91
.github/workflows/notify-slack-on-release-merge.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
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
|
24
.github/workflows/semgrep.yml
vendored
Normal file
24
.github/workflows/semgrep.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: Semgrep
|
||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
pull_request: {}
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- .github/workflows/semgrep.yml
|
||||||
|
schedule:
|
||||||
|
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
|
||||||
|
- cron: '2 11 * * *'
|
||||||
|
jobs:
|
||||||
|
semgrep:
|
||||||
|
name: semgrep/ci
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
env:
|
||||||
|
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
||||||
|
container:
|
||||||
|
image: returntocorp/semgrep
|
||||||
|
if: (github.actor != 'dependabot[bot]')
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- run: semgrep ci
|
278
.github/workflows/test.yml
vendored
Normal file
278
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
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' }}
|
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@ -0,0 +1 @@
|
|||||||
|
* @uniswap/web-admins
|
@ -16,16 +16,6 @@ describe('Add Liquidity', () => {
|
|||||||
cy.contains('0.05% fee tier')
|
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', () => {
|
it('does not crash if token is duplicated', () => {
|
||||||
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
|
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { BigNumber } from '@ethersproject/bignumber'
|
import { BigNumber } from '@ethersproject/bignumber'
|
||||||
import { InterfaceSectionName } from '@uniswap/analytics-events'
|
import { InterfaceSectionName } from '@uniswap/analytics-events'
|
||||||
import { CurrencyAmount } from '@uniswap/sdk-core'
|
import { CurrencyAmount } from '@uniswap/sdk-core'
|
||||||
|
import { FeatureFlag } from 'featureFlags'
|
||||||
|
|
||||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
|
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
|
||||||
import { DAI, USDC_MAINNET } from '../../../src/constants/tokens'
|
import { DAI, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||||
@ -64,7 +65,9 @@ describe('Swap errors', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('slippage failure', () => {
|
it('slippage failure', () => {
|
||||||
cy.visit(`/swap?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
cy.visit(`/swap?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
||||||
|
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||||
|
})
|
||||||
cy.hardhat({ automine: false }).then(async (hardhat) => {
|
cy.hardhat({ automine: false }).then(async (hardhat) => {
|
||||||
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 500e6))
|
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 500e6))
|
||||||
await hardhat.mine()
|
await hardhat.mine()
|
||||||
|
@ -120,7 +120,10 @@ describe.skip('Swap with fees', () => {
|
|||||||
describe('UniswapX swaps', () => {
|
describe('UniswapX swaps', () => {
|
||||||
it('displays UniswapX fee in UI', () => {
|
it('displays UniswapX fee in UI', () => {
|
||||||
cy.visit('/swap', {
|
cy.visit('/swap', {
|
||||||
featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }],
|
featureFlags: [
|
||||||
|
{ name: FeatureFlag.feesEnabled, value: true },
|
||||||
|
{ name: FeatureFlag.uniswapXDefaultEnabled, value: true },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
// Intercept the trade quote
|
// Intercept the trade quote
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
|
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
|
||||||
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
||||||
|
import { FeatureFlag } from 'featureFlags'
|
||||||
|
|
||||||
import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens'
|
import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||||
import { getTestSelector } from '../../utils'
|
import { getTestSelector } from '../../utils'
|
||||||
@ -45,7 +46,9 @@ function stubSwapTxReceipt() {
|
|||||||
describe.skip('UniswapX Toggle', () => {
|
describe.skip('UniswapX Toggle', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
|
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
|
||||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
||||||
|
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('displays uniswapx ui when setting is on', () => {
|
it('displays uniswapx ui when setting is on', () => {
|
||||||
@ -53,9 +56,39 @@ describe.skip('UniswapX Toggle', () => {
|
|||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
cy.wait('@quote')
|
cy.wait('@quote')
|
||||||
|
|
||||||
|
// UniswapX UI should not be visible
|
||||||
|
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist')
|
||||||
|
|
||||||
|
// Opt-in to UniswapX
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// UniswapX UI should be visible
|
// UniswapX UI should be visible
|
||||||
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('exist')
|
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')
|
||||||
|
cy.wait('@quote')
|
||||||
|
|
||||||
|
// 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.skip('UniswapX Orders', () => {
|
describe.skip('UniswapX Orders', () => {
|
||||||
@ -67,7 +100,9 @@ describe.skip('UniswapX Orders', () => {
|
|||||||
stubSwapTxReceipt()
|
stubSwapTxReceipt()
|
||||||
|
|
||||||
cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)))
|
cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)))
|
||||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
||||||
|
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can swap exact-in trades using uniswapX', () => {
|
it('can swap exact-in trades using uniswapX', () => {
|
||||||
@ -75,6 +110,8 @@ describe.skip('UniswapX Orders', () => {
|
|||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
cy.wait('@quote')
|
cy.wait('@quote')
|
||||||
|
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Submit uniswapx order signature
|
// Submit uniswapx order signature
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
cy.contains('Confirm swap').click()
|
cy.contains('Confirm swap').click()
|
||||||
@ -94,6 +131,8 @@ describe.skip('UniswapX Orders', () => {
|
|||||||
cy.get('#swap-currency-output .token-amount-input').type('300')
|
cy.get('#swap-currency-output .token-amount-input').type('300')
|
||||||
cy.wait('@quote')
|
cy.wait('@quote')
|
||||||
|
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Submit uniswapx order signature
|
// Submit uniswapx order signature
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
cy.contains('Confirm swap').click()
|
cy.contains('Confirm swap').click()
|
||||||
@ -113,6 +152,8 @@ describe.skip('UniswapX Orders', () => {
|
|||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
cy.wait('@quote')
|
cy.wait('@quote')
|
||||||
|
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Submit uniswapx order signature
|
// Submit uniswapx order signature
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
cy.contains('Confirm swap').click()
|
cy.contains('Confirm swap').click()
|
||||||
@ -129,6 +170,8 @@ describe.skip('UniswapX Orders', () => {
|
|||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
cy.wait('@quote')
|
cy.wait('@quote')
|
||||||
|
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Submit uniswapx order signature
|
// Submit uniswapx order signature
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
cy.contains('Confirm swap').click()
|
cy.contains('Confirm swap').click()
|
||||||
@ -155,7 +198,9 @@ describe.skip('UniswapX Eth Input', () => {
|
|||||||
|
|
||||||
stubSwapTxReceipt()
|
stubSwapTxReceipt()
|
||||||
|
|
||||||
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`)
|
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`, {
|
||||||
|
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can swap using uniswapX with ETH as input', () => {
|
it('can swap using uniswapX with ETH as input', () => {
|
||||||
@ -163,6 +208,7 @@ describe.skip('UniswapX Eth Input', () => {
|
|||||||
cy.get('#swap-currency-input .token-amount-input').type('1')
|
cy.get('#swap-currency-input .token-amount-input').type('1')
|
||||||
|
|
||||||
cy.wait('@quote')
|
cy.wait('@quote')
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Prompt ETH wrap to use for order
|
// Prompt ETH wrap to use for order
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
@ -196,6 +242,8 @@ describe.skip('UniswapX Eth Input', () => {
|
|||||||
cy.get('#swap-currency-input .token-amount-input').type('1')
|
cy.get('#swap-currency-input .token-amount-input').type('1')
|
||||||
cy.wait('@quote')
|
cy.wait('@quote')
|
||||||
|
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Prompt ETH wrap and confirm
|
// Prompt ETH wrap and confirm
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
cy.contains('Confirm swap').click()
|
cy.contains('Confirm swap').click()
|
||||||
@ -255,12 +303,15 @@ describe.skip('UniswapX activity history', () => {
|
|||||||
cy.hardhat().then(async (hardhat) => {
|
cy.hardhat().then(async (hardhat) => {
|
||||||
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))
|
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))
|
||||||
})
|
})
|
||||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
||||||
|
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can view UniswapX order status progress in activity', () => {
|
it('can view UniswapX order status progress in activity', () => {
|
||||||
// Setup a swap
|
// Setup a swap
|
||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Submit uniswapx order signature
|
// Submit uniswapx order signature
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
@ -288,6 +339,7 @@ describe.skip('UniswapX activity history', () => {
|
|||||||
it('can view UniswapX order status progress in activity upon expiry', () => {
|
it('can view UniswapX order status progress in activity upon expiry', () => {
|
||||||
// Setup a swap
|
// Setup a swap
|
||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Submit uniswapx order signature
|
// Submit uniswapx order signature
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
@ -314,6 +366,7 @@ describe.skip('UniswapX activity history', () => {
|
|||||||
it('deduplicates remote vs local uniswapx orders', () => {
|
it('deduplicates remote vs local uniswapx orders', () => {
|
||||||
// Setup a swap
|
// Setup a swap
|
||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
// Submit uniswapx order signature
|
// Submit uniswapx order signature
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
@ -345,6 +398,7 @@ describe.skip('UniswapX activity history', () => {
|
|||||||
it('balances should refetch after uniswapx swap', () => {
|
it('balances should refetch after uniswapx swap', () => {
|
||||||
// Setup a swap
|
// Setup a swap
|
||||||
cy.get('#swap-currency-input .token-amount-input').type('300')
|
cy.get('#swap-currency-input .token-amount-input').type('300')
|
||||||
|
cy.contains('Try it now').click()
|
||||||
|
|
||||||
const gqlSpy = cy.spy().as('gqlSpy')
|
const gqlSpy = cy.spy().as('gqlSpy')
|
||||||
cy.intercept(/graphql/, (req) => {
|
cy.intercept(/graphql/, (req) => {
|
||||||
|
@ -202,7 +202,7 @@
|
|||||||
"@uniswap/merkle-distributor": "^1.0.1",
|
"@uniswap/merkle-distributor": "^1.0.1",
|
||||||
"@uniswap/permit2-sdk": "^1.2.0",
|
"@uniswap/permit2-sdk": "^1.2.0",
|
||||||
"@uniswap/redux-multicall": "^1.1.8",
|
"@uniswap/redux-multicall": "^1.1.8",
|
||||||
"@uniswap/router-sdk": "^1.7.1",
|
"@uniswap/router-sdk": "^1.6.0",
|
||||||
"@uniswap/sdk-core": "4.0.7",
|
"@uniswap/sdk-core": "4.0.7",
|
||||||
"@uniswap/smart-order-router": "^3.15.0",
|
"@uniswap/smart-order-router": "^3.15.0",
|
||||||
"@uniswap/token-lists": "^1.0.0-beta.33",
|
"@uniswap/token-lists": "^1.0.0-beta.33",
|
||||||
|
@ -72,6 +72,12 @@
|
|||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.6</priority>
|
<priority>0.6</priority>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://app.uniswap.org/increase</loc>
|
||||||
|
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.6</priority>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://app.uniswap.org/migrate/v2</loc>
|
<loc>https://app.uniswap.org/migrate/v2</loc>
|
||||||
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
# *
|
# *
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Disallow: /static/js/
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Host
|
||||||
|
Host: https://app.uniswap.org
|
||||||
|
|
||||||
# Sitemaps
|
# Sitemaps
|
||||||
Sitemap: https://app.uniswap.org/sitemap.xml
|
Sitemap: https://app.uniswap.org/sitemap.xml
|
||||||
|
@ -9,13 +9,19 @@ const thegraphConfig = require('../graphql.thegraph.config')
|
|||||||
|
|
||||||
const exec = promisify(child_process.exec)
|
const exec = promisify(child_process.exec)
|
||||||
|
|
||||||
async function fetchSchema(url, outputFile) {
|
function fetchSchema(url, outputFile) {
|
||||||
try {
|
exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`)
|
||||||
const { stdout } = await exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`);
|
.then(({ stderr, stdout }) => {
|
||||||
await fs.writeFile(outputFile, stdout);
|
if (stderr) {
|
||||||
} catch(err){
|
throw new Error(stderr)
|
||||||
console.error(`Failed to fetch schema from ${url}`)
|
} else {
|
||||||
}
|
fs.writeFile(outputFile, stdout)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
console.error(`Failed to fetch schema from ${url}`)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchSchema(process.env.THE_GRAPH_SCHEMA_ENDPOINT, thegraphConfig.schema)
|
fetchSchema(process.env.THE_GRAPH_SCHEMA_ENDPOINT, thegraphConfig.schema)
|
||||||
|
77
src/assets/svg/apple_logo.svg
Normal file
77
src/assets/svg/apple_logo.svg
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?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>
|
After (image error) Size: 1.5 KiB |
@ -1,4 +1,5 @@
|
|||||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||||
|
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
|
||||||
import { PropsWithChildren, useCallback } from 'react'
|
import { PropsWithChildren, useCallback } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { ClickableStyle } from 'theme/components'
|
import { ClickableStyle } from 'theme/components'
|
||||||
@ -41,11 +42,12 @@ export function DownloadButton({
|
|||||||
text?: string
|
text?: string
|
||||||
element: InterfaceElementName
|
element: InterfaceElementName
|
||||||
}) {
|
}) {
|
||||||
|
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
|
||||||
const onButtonClick = useCallback(() => {
|
const onButtonClick = useCallback(() => {
|
||||||
// handles any actions required by the parent, i.e. cancelling wallet connection attempt or dismissing an ad
|
// handles any actions required by the parent, i.e. cancelling wallet connection attempt or dismissing an ad
|
||||||
onClick?.()
|
onClick?.()
|
||||||
openDownloadApp({ element })
|
openDownloadApp({ element, isAndroidGALaunched })
|
||||||
}, [element, onClick])
|
}, [element, isAndroidGALaunched, onClick])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseButton branded onClick={onButtonClick}>
|
<BaseButton branded onClick={onButtonClick}>
|
||||||
|
@ -90,7 +90,7 @@ const DescriptionText = styled(ThemedText.LabelMicro)`
|
|||||||
|
|
||||||
function useOrderAmounts(
|
function useOrderAmounts(
|
||||||
orderDetails?: UniswapXOrderDetails
|
orderDetails?: UniswapXOrderDetails
|
||||||
): Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> | undefined {
|
): Pick<InterfaceTrade, 'inputAmount' | 'postTaxOutputAmount'> | undefined {
|
||||||
const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId)
|
const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId)
|
||||||
const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId)
|
const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId)
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ function useOrderAmounts(
|
|||||||
if (swapInfo.tradeType === TradeType.EXACT_INPUT) {
|
if (swapInfo.tradeType === TradeType.EXACT_INPUT) {
|
||||||
return {
|
return {
|
||||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw),
|
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw),
|
||||||
outputAmount: CurrencyAmount.fromRawAmount(
|
postTaxOutputAmount: CurrencyAmount.fromRawAmount(
|
||||||
outputCurrency,
|
outputCurrency,
|
||||||
swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw
|
swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw
|
||||||
),
|
),
|
||||||
@ -114,7 +114,7 @@ function useOrderAmounts(
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw),
|
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw),
|
||||||
outputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
|
postTaxOutputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ const ActiveDot = styled.span<{ closed: boolean; outOfRange: boolean }>`
|
|||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
`
|
`
|
||||||
|
|
||||||
function calculateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) {
|
function calculcateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) {
|
||||||
if (!price0 || !price1) return undefined
|
if (!price0 || !price1) return undefined
|
||||||
|
|
||||||
const value0 = parseFloat(position.amount0.toExact()) * price0
|
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 { chainId, position, pool, details, inRange, closed } = positionInfo
|
||||||
|
|
||||||
const { priceA, priceB, fees: feeValue } = useFeeValues(positionInfo)
|
const { priceA, priceB, fees: feeValue } = useFeeValues(positionInfo)
|
||||||
const liquidityValue = calculateLiquidityValue(priceA, priceB, position)
|
const liquidityValue = calculcateLiquidityValue(priceA, priceB, position)
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||||
|
@ -36,9 +36,7 @@ const DoubleLogoContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const LogoContainer = styled.div`
|
const StyledLogoParentContainer = styled.div`
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -59,9 +57,9 @@ const CircleLogoImage = styled.img<{ size: string }>`
|
|||||||
const L2LogoContainer = styled.div`
|
const L2LogoContainer = styled.div`
|
||||||
border-radius: ${getDefaultBorderRadius(16)}px;
|
border-radius: ${getDefaultBorderRadius(16)}px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
left: 70%;
|
left: 60%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 70%;
|
top: 60%;
|
||||||
outline: 2px solid ${({ theme }) => theme.surface1};
|
outline: 2px solid ${({ theme }) => theme.surface1};
|
||||||
width: 16px;
|
width: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -157,10 +155,10 @@ function SquareL2Logo({ chainId }: { chainId: ChainId }) {
|
|||||||
*/
|
*/
|
||||||
export function PortfolioLogo(props: PortfolioLogoProps) {
|
export function PortfolioLogo(props: PortfolioLogoProps) {
|
||||||
return (
|
return (
|
||||||
<LogoContainer style={props.style}>
|
<StyledLogoParentContainer style={props.style}>
|
||||||
{getLogo(props)}
|
{getLogo(props)}
|
||||||
<SquareL2Logo chainId={props.chainId} />
|
<SquareL2Logo chainId={props.chainId} />
|
||||||
</LogoContainer>
|
</StyledLogoParentContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,7 @@ import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrap
|
|||||||
import Row from 'components/Row'
|
import Row from 'components/Row'
|
||||||
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
|
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
|
||||||
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
||||||
import { PortfolioTokenBalancePartsFragment } from 'graphql/data/__generated__/types-and-hooks'
|
import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks'
|
||||||
import { PortfolioToken } from 'graphql/data/portfolios'
|
|
||||||
import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
|
import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
||||||
@ -29,7 +28,7 @@ export default function Tokens({ account }: { account: string }) {
|
|||||||
|
|
||||||
const { data } = useCachedPortfolioBalancesQuery({ account })
|
const { data } = useCachedPortfolioBalancesQuery({ account })
|
||||||
|
|
||||||
const tokenBalances = data?.portfolios?.[0].tokenBalances
|
const tokenBalances = data?.portfolios?.[0].tokenBalances as TokenBalance[] | undefined
|
||||||
|
|
||||||
const { visibleTokens, hiddenTokens } = useMemo(
|
const { visibleTokens, hiddenTokens } = useMemo(
|
||||||
() => splitHiddenTokens(tokenBalances ?? [], { hideSmallBalances }),
|
() => splitHiddenTokens(tokenBalances ?? [], { hideSmallBalances }),
|
||||||
@ -70,12 +69,9 @@ const TokenNameText = styled(ThemedText.SubHeader)`
|
|||||||
${EllipsisStyle}
|
${EllipsisStyle}
|
||||||
`
|
`
|
||||||
|
|
||||||
function TokenRow({
|
type PortfolioToken = NonNullable<TokenBalance['token']>
|
||||||
token,
|
|
||||||
quantity,
|
function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) {
|
||||||
denominatedValue,
|
|
||||||
tokenProjectMarket,
|
|
||||||
}: PortfolioTokenBalancePartsFragment & { token: PortfolioToken }) {
|
|
||||||
const { formatDelta } = useFormatter()
|
const { formatDelta } = useFormatter()
|
||||||
const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
|
const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
|
||||||
|
|
||||||
|
@ -32,14 +32,6 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c0 {
|
.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;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -54,9 +46,9 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
|
|||||||
.c3 {
|
.c3 {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
left: 70%;
|
left: 60%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 70%;
|
top: 60%;
|
||||||
outline: 2px solid #FFFFFF;
|
outline: 2px solid #FFFFFF;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
@ -158,14 +150,6 @@ exports[`PortfolioLogo renders without L2 icon 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c0 {
|
.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;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -9,6 +9,7 @@ import { uniwalletWCV2ConnectConnection } from 'connection'
|
|||||||
import { ActivationStatus, useActivationState } from 'connection/activate'
|
import { ActivationStatus, useActivationState } from 'connection/activate'
|
||||||
import { ConnectionType } from 'connection/types'
|
import { ConnectionType } from 'connection/types'
|
||||||
import { UniwalletConnect as UniwalletConnectV2 } from 'connection/WalletConnectV2'
|
import { UniwalletConnect as UniwalletConnectV2 } from 'connection/WalletConnectV2'
|
||||||
|
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
|
||||||
import { QRCodeSVG } from 'qrcode.react'
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import styled, { useTheme } from 'styled-components'
|
import styled, { useTheme } from 'styled-components'
|
||||||
@ -42,8 +43,9 @@ export default function UniwalletModal() {
|
|||||||
const { activationState, cancelActivation } = useActivationState()
|
const { activationState, cancelActivation } = useActivationState()
|
||||||
const [uri, setUri] = useState<string>()
|
const [uri, setUri] = useState<string>()
|
||||||
|
|
||||||
|
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
|
||||||
// Displays the modal if not on iOS/Android, 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 onLaunchedMobilePlatform = isIOS || (isAndroidGALaunched && isAndroid)
|
||||||
const open =
|
const open =
|
||||||
!onLaunchedMobilePlatform &&
|
!onLaunchedMobilePlatform &&
|
||||||
activationState.status === ActivationStatus.PENDING &&
|
activationState.status === ActivationStatus.PENDING &&
|
||||||
@ -103,6 +105,8 @@ const InfoSectionWrapper = styled(RowBetween)`
|
|||||||
`
|
`
|
||||||
|
|
||||||
function InfoSection() {
|
function InfoSection() {
|
||||||
|
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfoSectionWrapper>
|
<InfoSectionWrapper>
|
||||||
<AutoColumn gap="4px">
|
<AutoColumn gap="4px">
|
||||||
@ -110,7 +114,13 @@ function InfoSection() {
|
|||||||
<Trans>Don't have a Uniswap wallet?</Trans>
|
<Trans>Don't have a Uniswap wallet?</Trans>
|
||||||
</ThemedText.SubHeaderSmall>
|
</ThemedText.SubHeaderSmall>
|
||||||
<ThemedText.BodySmall color="neutral2">
|
<ThemedText.BodySmall color="neutral2">
|
||||||
<Trans>Safely store and swap tokens with the Uniswap app. Available on iOS and Android.</Trans>
|
{isAndroidGALaunched ? (
|
||||||
|
<Trans>Safely store and swap tokens with the Uniswap app. Available on iOS and Android.</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
Download in the App Store to safely store your tokens and NFTs, swap tokens, and connect to crypto apps.
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
</ThemedText.BodySmall>
|
</ThemedText.BodySmall>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
<Column>
|
<Column>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||||
|
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
|
||||||
import { useScreenSize } from 'hooks/useScreenSize'
|
import { useScreenSize } from 'hooks/useScreenSize'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { useHideAndroidAnnouncementBanner } from 'state/user/hooks'
|
import { useHideAndroidAnnouncementBanner } from 'state/user/hooks'
|
||||||
@ -30,12 +31,14 @@ export default function AndroidAnnouncementBanner() {
|
|||||||
const shouldDisplay = Boolean(!hideAndroidAnnouncementBanner && !isLandingScreen)
|
const shouldDisplay = Boolean(!hideAndroidAnnouncementBanner && !isLandingScreen)
|
||||||
const isDarkMode = useIsDarkMode()
|
const isDarkMode = useIsDarkMode()
|
||||||
|
|
||||||
|
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
|
||||||
const onClick = () =>
|
const onClick = () =>
|
||||||
openDownloadApp({
|
openDownloadApp({
|
||||||
element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON,
|
element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON,
|
||||||
|
isAndroidGALaunched,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isMobileSafari) return null
|
if (!isAndroidGALaunched || isMobileSafari) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopupContainer show={shouldDisplay}>
|
<PopupContainer show={shouldDisplay}>
|
||||||
|
@ -3,17 +3,21 @@ import Column from 'components/Column'
|
|||||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateConfig, useUpdateFlag } from 'featureFlags'
|
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateConfig, useUpdateFlag } from 'featureFlags'
|
||||||
import { DynamicConfigName } from 'featureFlags/dynamicConfig'
|
import { DynamicConfigName } from 'featureFlags/dynamicConfig'
|
||||||
import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains'
|
import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains'
|
||||||
|
import { useAndroidGALaunchFlag } from 'featureFlags/flags/androidGALaunch'
|
||||||
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
|
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
|
||||||
import { useFallbackProviderEnabledFlag } from 'featureFlags/flags/fallbackProvider'
|
import { useFallbackProviderEnabledFlag } from 'featureFlags/flags/fallbackProvider'
|
||||||
|
import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments'
|
||||||
import { useInfoExploreFlag } from 'featureFlags/flags/infoExplore'
|
import { useInfoExploreFlag } from 'featureFlags/flags/infoExplore'
|
||||||
import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews'
|
import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews'
|
||||||
import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage'
|
import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage'
|
||||||
import { useInfoTDPFlag } from 'featureFlags/flags/infoTDP'
|
import { useInfoTDPFlag } from 'featureFlags/flags/infoTDP'
|
||||||
import { useLimitsEnabledFlag } from 'featureFlags/flags/limits'
|
|
||||||
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
|
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
|
||||||
import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2'
|
import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2'
|
||||||
import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet'
|
import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet'
|
||||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
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 { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||||
import { useFeesEnabledFlag } from 'featureFlags/flags/useFees'
|
import { useFeesEnabledFlag } from 'featureFlags/flags/useFees'
|
||||||
import { useUpdateAtom } from 'jotai/utils'
|
import { useUpdateAtom } from 'jotai/utils'
|
||||||
@ -271,12 +275,6 @@ export default function FeatureFlagModal() {
|
|||||||
featureFlag={FeatureFlag.feesEnabled}
|
featureFlag={FeatureFlag.feesEnabled}
|
||||||
label="Enable Swap Fees"
|
label="Enable Swap Fees"
|
||||||
/>
|
/>
|
||||||
<FeatureFlagOption
|
|
||||||
variant={BaseVariant}
|
|
||||||
value={useLimitsEnabledFlag()}
|
|
||||||
featureFlag={FeatureFlag.limitsEnabled}
|
|
||||||
label="Enable Limits"
|
|
||||||
/>
|
|
||||||
<FeatureFlagOption
|
<FeatureFlagOption
|
||||||
variant={BaseVariant}
|
variant={BaseVariant}
|
||||||
value={useFallbackProviderEnabledFlag()}
|
value={useFallbackProviderEnabledFlag()}
|
||||||
@ -295,12 +293,24 @@ export default function FeatureFlagModal() {
|
|||||||
featureFlag={FeatureFlag.multichainUX}
|
featureFlag={FeatureFlag.multichainUX}
|
||||||
label="Updated Multichain UX"
|
label="Updated Multichain UX"
|
||||||
/>
|
/>
|
||||||
|
<FeatureFlagOption
|
||||||
|
variant={BaseVariant}
|
||||||
|
value={useFotAdjustmentsFlag()}
|
||||||
|
featureFlag={FeatureFlag.fotAdjustedmentsEnabled}
|
||||||
|
label="Enable fee-on-transfer UI and slippage adjustments"
|
||||||
|
/>
|
||||||
<FeatureFlagOption
|
<FeatureFlagOption
|
||||||
variant={BaseVariant}
|
variant={BaseVariant}
|
||||||
value={useProgressIndicatorV2Flag()}
|
value={useProgressIndicatorV2Flag()}
|
||||||
featureFlag={FeatureFlag.progressIndicatorV2}
|
featureFlag={FeatureFlag.progressIndicatorV2}
|
||||||
label="Refreshed swap progress indicator"
|
label="Refreshed swap progress indicator"
|
||||||
/>
|
/>
|
||||||
|
<FeatureFlagOption
|
||||||
|
variant={BaseVariant}
|
||||||
|
value={useAndroidGALaunchFlag()}
|
||||||
|
featureFlag={FeatureFlag.androidGALaunch}
|
||||||
|
label="Android Nov 14th GA launch"
|
||||||
|
/>
|
||||||
<FeatureFlagGroup name="Quick routes">
|
<FeatureFlagGroup name="Quick routes">
|
||||||
<FeatureFlagOption
|
<FeatureFlagOption
|
||||||
variant={BaseVariant}
|
variant={BaseVariant}
|
||||||
@ -323,6 +333,24 @@ export default function FeatureFlagModal() {
|
|||||||
featureFlag={FeatureFlag.uniswapXSyntheticQuote}
|
featureFlag={FeatureFlag.uniswapXSyntheticQuote}
|
||||||
label="Force synthetic quotes for UniswapX"
|
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>
|
||||||
<FeatureFlagGroup name="Info Site Migration">
|
<FeatureFlagGroup name="Info Site Migration">
|
||||||
<FeatureFlagOption
|
<FeatureFlagOption
|
||||||
|
8
src/components/Logo/AppleLogo.tsx
Normal file
8
src/components/Logo/AppleLogo.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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>
|
||||||
|
)
|
@ -1,7 +1,9 @@
|
|||||||
import { t, Trans } from '@lingui/macro'
|
import { t, Trans } from '@lingui/macro'
|
||||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||||
|
import { ReactComponent as AppleLogo } from 'assets/svg/apple_logo.svg'
|
||||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||||
|
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
|
||||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||||
import { Box } from 'nft/components/Box'
|
import { Box } from 'nft/components/Box'
|
||||||
import { Column, Row } from 'nft/components/Flex'
|
import { Column, Row } from 'nft/components/Flex'
|
||||||
@ -130,6 +132,8 @@ export const MenuDropdown = () => {
|
|||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||||
|
|
||||||
|
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box position="relative" ref={ref} marginRight="4">
|
<Box position="relative" ref={ref} marginRight="4">
|
||||||
@ -171,23 +175,35 @@ export const MenuDropdown = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
openDownloadApp({
|
openDownloadApp({
|
||||||
element: InterfaceElementName.UNISWAP_WALLET_NAVBAR_MENU_DOWNLOAD_BUTTON,
|
element: InterfaceElementName.UNISWAP_WALLET_NAVBAR_MENU_DOWNLOAD_BUTTON,
|
||||||
|
isAndroidGALaunched,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PrimaryMenuRow close={toggleOpen}>
|
<PrimaryMenuRow close={toggleOpen}>
|
||||||
<>
|
{isAndroidGALaunched ? (
|
||||||
<Icon>
|
<>
|
||||||
<UniswapAppLogo width="24px" height="24px" />
|
<Icon>
|
||||||
</Icon>
|
<UniswapAppLogo width="24px" height="24px" />
|
||||||
<div>
|
</Icon>
|
||||||
<ThemedText.BodyPrimary>
|
<div>
|
||||||
<Trans>Download Uniswap</Trans>
|
<ThemedText.BodyPrimary>
|
||||||
</ThemedText.BodyPrimary>
|
<Trans>Download Uniswap</Trans>
|
||||||
<ThemedText.LabelSmall>
|
</ThemedText.BodyPrimary>
|
||||||
<Trans>Available on iOS and Android</Trans>
|
<ThemedText.LabelSmall>
|
||||||
</ThemedText.LabelSmall>
|
<Trans>Available on iOS and Android</Trans>
|
||||||
</div>
|
</ThemedText.LabelSmall>
|
||||||
</>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon>
|
||||||
|
<AppleLogo width="24px" height="24px" fill={theme.neutral1} />
|
||||||
|
</Icon>
|
||||||
|
<PrimaryMenuRow.Text>
|
||||||
|
<Trans>Download Uniswap app</Trans>
|
||||||
|
</PrimaryMenuRow.Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</PrimaryMenuRow>
|
</PrimaryMenuRow>
|
||||||
</Box>
|
</Box>
|
||||||
</Column>
|
</Column>
|
||||||
|
@ -1,36 +1,9 @@
|
|||||||
import userEvent from '@testing-library/user-event'
|
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 { act, render, screen } from 'test-utils/render'
|
||||||
|
|
||||||
import { PoolDetailsHeader } from './PoolDetailsHeader'
|
import { PoolDetailsHeader } from './PoolDetailsHeader'
|
||||||
|
|
||||||
describe('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 = {
|
const mockProps = {
|
||||||
chainId: 1,
|
chainId: 1,
|
||||||
poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640',
|
poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640',
|
||||||
|
@ -1,38 +1,11 @@
|
|||||||
import { ChainId } from '@uniswap/sdk-core'
|
import { ChainId } from '@uniswap/sdk-core'
|
||||||
import { USDC_MAINNET } from 'constants/tokens'
|
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 { usdcWethPoolAddress, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
|
||||||
import { render, screen } from 'test-utils/render'
|
import { render, screen } from 'test-utils/render'
|
||||||
|
|
||||||
import { PoolDetailsLink } from './PoolDetailsLink'
|
import { PoolDetailsLink } from './PoolDetailsLink'
|
||||||
|
|
||||||
describe('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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders link for pool address', async () => {
|
it('renders link for pool address', async () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<PoolDetailsLink
|
<PoolDetailsLink
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { enableNetConnect } from 'nock'
|
import { enableNetConnect } from 'nock'
|
||||||
import store from 'state'
|
|
||||||
import { addSerializedToken } from 'state/user/reducer'
|
|
||||||
import { validPoolDataResponse } from 'test-utils/pools/fixtures'
|
import { validPoolDataResponse } from 'test-utils/pools/fixtures'
|
||||||
import { act, render, screen } from 'test-utils/render'
|
import { act, render, screen } from 'test-utils/render'
|
||||||
import { BREAKPOINTS } from 'theme'
|
import { BREAKPOINTS } from 'theme'
|
||||||
@ -17,28 +15,6 @@ describe('PoolDetailsStats', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Enable network connections for retrieving token logos
|
// Enable network connections for retrieving token logos
|
||||||
enableNetConnect()
|
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 () => {
|
it('renders stats text correctly', async () => {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
|
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 { mocked } from 'test-utils/mocked'
|
||||||
import { useMultiChainPositionsReturnValue, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
|
import { useMultiChainPositionsReturnValue, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
|
||||||
import { act, render, screen } from 'test-utils/render'
|
import { act, render, screen } from 'test-utils/render'
|
||||||
@ -26,28 +24,6 @@ describe('PoolDetailsStatsButton', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
|
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', () => {
|
it('loading skeleton shown correctly', () => {
|
||||||
@ -88,7 +64,7 @@ describe('PoolDetailsStatsButton', () => {
|
|||||||
|
|
||||||
await act(() => userEvent.click(screen.getByTestId('pool-details-add-liquidity-button')))
|
await act(() => userEvent.click(screen.getByTestId('pool-details-add-liquidity-button')))
|
||||||
expect(global.window.location.href).toContain(
|
expect(global.window.location.href).toContain(
|
||||||
'/add/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/500'
|
'/increase/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/500'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -68,7 +68,7 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier, load
|
|||||||
navigate(
|
navigate(
|
||||||
toSwap
|
toSwap
|
||||||
? `/swap?inputCurrency=${currencyId(currency0)}&outputCurrency=${currencyId(currency1)}`
|
? `/swap?inputCurrency=${currencyId(currency0)}&outputCurrency=${currencyId(currency1)}`
|
||||||
: `/add/${currencyId(currency0)}/${currencyId(currency1)}/${feeTier}${tokenId ? `/${tokenId}` : ''}`
|
: `/increase/${currencyId(currency0)}/${currencyId(currency1)}/${feeTier}${tokenId ? `/${tokenId}` : ''}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -541,7 +541,7 @@ exports[`PoolDetailsHeader renders link for token address 1`] = `
|
|||||||
class="c5"
|
class="c5"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="USDC logo"
|
alt="UNKNOWN logo"
|
||||||
class="c6"
|
class="c6"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
||||||
|
@ -253,7 +253,7 @@ exports[`PoolDetailsStats pool balance chart not visible on mobile 1`] = `
|
|||||||
class="c10"
|
class="c10"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="USDC logo"
|
alt="UNKNOWN logo"
|
||||||
class="c11"
|
class="c11"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
||||||
|
@ -20,7 +20,7 @@ const ReferenceElement = styled.div`
|
|||||||
height: inherit;
|
height: inherit;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Arrow = styled.div`
|
export const Arrow = styled.div`
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
z-index: 9998;
|
z-index: 9998;
|
||||||
|
@ -3,7 +3,6 @@ import { usePortfolioBalancesLazyQuery, usePortfolioBalancesQuery } from 'graphq
|
|||||||
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
|
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
|
||||||
import usePrevious from 'hooks/usePrevious'
|
import usePrevious from 'hooks/usePrevious'
|
||||||
import { atom, useAtom } from 'jotai'
|
import { atom, useAtom } from 'jotai'
|
||||||
import ms from 'ms'
|
|
||||||
import { PropsWithChildren, useCallback, useEffect } from 'react'
|
import { PropsWithChildren, useCallback, useEffect } from 'react'
|
||||||
|
|
||||||
import { usePendingActivity } from '../AccountDrawer/MiniPortfolio/Activity/hooks'
|
import { usePendingActivity } from '../AccountDrawer/MiniPortfolio/Activity/hooks'
|
||||||
@ -32,9 +31,8 @@ const hasUnfetchedBalancesAtom = atom<boolean>(true)
|
|||||||
export default function PrefetchBalancesWrapper({
|
export default function PrefetchBalancesWrapper({
|
||||||
children,
|
children,
|
||||||
shouldFetchOnAccountUpdate,
|
shouldFetchOnAccountUpdate,
|
||||||
shouldFetchOnHover = true,
|
|
||||||
className,
|
className,
|
||||||
}: PropsWithChildren<{ shouldFetchOnAccountUpdate: boolean; shouldFetchOnHover?: boolean; className?: string }>) {
|
}: PropsWithChildren<{ shouldFetchOnAccountUpdate: boolean; className?: string }>) {
|
||||||
const { account } = useWeb3React()
|
const { account } = useWeb3React()
|
||||||
const [prefetchPortfolioBalances] = usePortfolioBalancesLazyQuery()
|
const [prefetchPortfolioBalances] = usePortfolioBalancesLazyQuery()
|
||||||
|
|
||||||
@ -42,13 +40,8 @@ export default function PrefetchBalancesWrapper({
|
|||||||
const [hasUnfetchedBalances, setHasUnfetchedBalances] = useAtom(hasUnfetchedBalancesAtom)
|
const [hasUnfetchedBalances, setHasUnfetchedBalances] = useAtom(hasUnfetchedBalancesAtom)
|
||||||
const fetchBalances = useCallback(() => {
|
const fetchBalances = useCallback(() => {
|
||||||
if (account) {
|
if (account) {
|
||||||
// Backend takes <2sec to get the updated portfolio value after a transaction
|
prefetchPortfolioBalances({ variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS } })
|
||||||
// This timeout is an interim solution while we're working on a websocket that'll ping the client when connected account gets changes
|
setHasUnfetchedBalances(false)
|
||||||
// TODO(WEB-3131): remove this timeout after websocket is implemented
|
|
||||||
setTimeout(() => {
|
|
||||||
prefetchPortfolioBalances({ variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS } })
|
|
||||||
setHasUnfetchedBalances(false)
|
|
||||||
}, ms('3.5s'))
|
|
||||||
}
|
}
|
||||||
}, [account, prefetchPortfolioBalances, setHasUnfetchedBalances])
|
}, [account, prefetchPortfolioBalances, setHasUnfetchedBalances])
|
||||||
|
|
||||||
@ -69,18 +62,12 @@ export default function PrefetchBalancesWrapper({
|
|||||||
}
|
}
|
||||||
}, [account, prevAccount, shouldFetchOnAccountUpdate, fetchBalances, hasUpdatedTx, setHasUnfetchedBalances])
|
}, [account, prevAccount, shouldFetchOnAccountUpdate, fetchBalances, hasUpdatedTx, setHasUnfetchedBalances])
|
||||||
|
|
||||||
// Temporary workaround to fix balances on TDP - this fetches balances if shouldFetchOnAccountUpdate becomes true while hasUnfetchedBalances is true
|
|
||||||
// TODO(WEB-3071) remove this logic once balance provider refactor is done
|
|
||||||
useEffect(() => {
|
|
||||||
if (hasUnfetchedBalances && shouldFetchOnAccountUpdate) fetchBalances()
|
|
||||||
}, [fetchBalances, hasUnfetchedBalances, shouldFetchOnAccountUpdate])
|
|
||||||
|
|
||||||
const onHover = useCallback(() => {
|
const onHover = useCallback(() => {
|
||||||
if (hasUnfetchedBalances) fetchBalances()
|
if (hasUnfetchedBalances) fetchBalances()
|
||||||
}, [fetchBalances, hasUnfetchedBalances])
|
}, [fetchBalances, hasUnfetchedBalances])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onMouseEnter={shouldFetchOnHover ? onHover : undefined} className={className}>
|
<div onMouseEnter={onHover} className={className}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,7 @@ import { ChainId, Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
|||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { Trace } from 'analytics'
|
import { Trace } from 'analytics'
|
||||||
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
|
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
|
||||||
|
import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks'
|
||||||
import { supportedChainIdFromGQLChain } from 'graphql/data/util'
|
import { supportedChainIdFromGQLChain } from 'graphql/data/util'
|
||||||
import useDebounce from 'hooks/useDebounce'
|
import useDebounce from 'hooks/useDebounce'
|
||||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||||
@ -100,7 +101,7 @@ export function CurrencySearch({
|
|||||||
}, [chainId, data?.portfolios])
|
}, [chainId, data?.portfolios])
|
||||||
|
|
||||||
const sortedTokens: Token[] = useMemo(() => {
|
const sortedTokens: Token[] = useMemo(() => {
|
||||||
const portfolioTokenBalances = data?.portfolios?.[0].tokenBalances
|
const portfolioTokenBalances = data?.portfolios?.[0].tokenBalances as TokenBalance[] | undefined
|
||||||
const portfolioTokens = splitHiddenTokens(portfolioTokenBalances ?? [])
|
const portfolioTokens = splitHiddenTokens(portfolioTokenBalances ?? [])
|
||||||
.visibleTokens.map((tokenBalance) => {
|
.visibleTokens.map((tokenBalance) => {
|
||||||
if (!tokenBalance?.token?.chain || !tokenBalance.token?.address || !tokenBalance.token?.decimals) {
|
if (!tokenBalance?.token?.chain || !tokenBalance.token?.address || !tokenBalance.token?.decimals) {
|
||||||
|
@ -3,8 +3,11 @@ import Column from 'components/Column'
|
|||||||
import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark'
|
import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark'
|
||||||
import { RowBetween, RowFixed } from 'components/Row'
|
import { RowBetween, RowFixed } from 'components/Row'
|
||||||
import Toggle from 'components/Toggle'
|
import Toggle from 'components/Toggle'
|
||||||
|
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
||||||
|
import { useAppDispatch } from 'state/hooks'
|
||||||
import { RouterPreference } from 'state/routing/types'
|
import { RouterPreference } from 'state/routing/types'
|
||||||
import { useRouterPreference } from 'state/user/hooks'
|
import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks'
|
||||||
|
import { updateDisabledUniswapX, updateOptedOutOfUniswapX } from 'state/user/reducer'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { ExternalLink, ThemedText } from 'theme/components'
|
import { ExternalLink, ThemedText } from 'theme/components'
|
||||||
|
|
||||||
@ -19,6 +22,12 @@ const InlineLink = styled(ThemedText.BodySmall)`
|
|||||||
|
|
||||||
export default function RouterPreferenceSettings() {
|
export default function RouterPreferenceSettings() {
|
||||||
const [routerPreference, setRouterPreference] = useRouterPreference()
|
const [routerPreference, setRouterPreference] = useRouterPreference()
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
||||||
|
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
||||||
|
const isUniswapXOverrideEnabled = isUniswapXDefaultEnabled && !userOptedOutOfUniswapX
|
||||||
|
|
||||||
|
const uniswapXInEffect = routerPreference === RouterPreference.X || isUniswapXOverrideEnabled
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowBetween gap="sm">
|
<RowBetween gap="sm">
|
||||||
@ -37,9 +46,21 @@ export default function RouterPreferenceSettings() {
|
|||||||
</RowFixed>
|
</RowFixed>
|
||||||
<Toggle
|
<Toggle
|
||||||
id="toggle-uniswap-x-button"
|
id="toggle-uniswap-x-button"
|
||||||
isActive={routerPreference === RouterPreference.X}
|
// If UniswapX-by-default is enabled we need to render this as active even if routerPreference === RouterPreference.API
|
||||||
|
// because we're going to default to the UniswapX quote.
|
||||||
|
// If the user manually toggles it off, this doesn't apply.
|
||||||
|
isActive={uniswapXInEffect}
|
||||||
toggle={() => {
|
toggle={() => {
|
||||||
setRouterPreference(routerPreference === RouterPreference.X ? RouterPreference.API : RouterPreference.X)
|
if (uniswapXInEffect) {
|
||||||
|
if (isUniswapXDefaultEnabled) {
|
||||||
|
// We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes.
|
||||||
|
dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true }))
|
||||||
|
} else {
|
||||||
|
// We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again.
|
||||||
|
dispatch(updateDisabledUniswapX({ disabledUniswapX: true }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setRouterPreference(uniswapXInEffect ? RouterPreference.API : RouterPreference.X)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
|
@ -1,30 +1,22 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { ChainId, Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
import { ChainId, Currency } from '@uniswap/sdk-core'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo'
|
import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo'
|
||||||
import { getChainInfo } from 'constants/chainInfo'
|
import { getChainInfo } from 'constants/chainInfo'
|
||||||
import { asSupportedChain } from 'constants/chains'
|
import { asSupportedChain } from 'constants/chains'
|
||||||
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
|
||||||
import { useInfoTDPEnabled } from 'featureFlags/flags/infoTDP'
|
|
||||||
import { Chain, PortfolioTokenBalancePartsFragment } from 'graphql/data/__generated__/types-and-hooks'
|
|
||||||
import { getTokenDetailsURL, gqlToCurrency, supportedChainIdFromGQLChain } from 'graphql/data/util'
|
|
||||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import styled, { useTheme } from 'styled-components'
|
||||||
import styled from 'styled-components'
|
|
||||||
import { ThemedText } from 'theme/components'
|
import { ThemedText } from 'theme/components'
|
||||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||||
|
|
||||||
import { MultiChainMap } from '.'
|
const BalancesCard = styled.div`
|
||||||
|
border-radius: 16px;
|
||||||
const BalancesCard = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
|
||||||
color: ${({ theme }) => theme.neutral1};
|
color: ${({ theme }) => theme.neutral1};
|
||||||
display: flex;
|
display: none;
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
${({ isInfoTDPEnabled }) => !isInfoTDPEnabled && 'padding: 16px;'}
|
padding: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
// 768 hardcoded to match NFT-redesign navbar breakpoints
|
// 768 hardcoded to match NFT-redesign navbar breakpoints
|
||||||
@ -56,13 +48,11 @@ const BalanceContainer = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`
|
`
|
||||||
|
|
||||||
const BalanceAmountsContainer = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
const BalanceAmountsContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
|
||||||
${({ isInfoTDPEnabled }) => isInfoTDPEnabled && 'margin-left: 12px;'}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledNetworkLabel = styled.div`
|
const StyledNetworkLabel = styled.div`
|
||||||
@ -71,187 +61,49 @@ const StyledNetworkLabel = styled.div`
|
|||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
`
|
`
|
||||||
|
|
||||||
interface BalanceProps {
|
export default function BalanceSummary({ token }: { token: Currency }) {
|
||||||
currency?: Currency
|
const { account, chainId } = useWeb3React()
|
||||||
chainId?: ChainId
|
const theme = useTheme()
|
||||||
balance?: CurrencyAmount<Currency> // TODO(WEB-3026): only used for pre-Info-project calculations, should remove after project goes live
|
const { label, color } = getChainInfo(asSupportedChain(chainId) ?? ChainId.MAINNET)
|
||||||
gqlBalance?: PortfolioTokenBalancePartsFragment
|
const balance = useCurrencyBalance(account, token)
|
||||||
onClick?: () => void
|
const { formatCurrencyAmount } = useFormatter()
|
||||||
}
|
|
||||||
const Balance = ({ currency, chainId = ChainId.MAINNET, balance, gqlBalance, onClick }: BalanceProps) => {
|
|
||||||
const { formatCurrencyAmount, formatNumber } = useFormatter()
|
|
||||||
const { label: chainName, color } = getChainInfo(asSupportedChain(chainId) ?? ChainId.MAINNET)
|
|
||||||
const currencies = useMemo(() => [currency], [currency])
|
|
||||||
const isInfoTDPEnabled = useInfoExplorePageEnabled()
|
|
||||||
|
|
||||||
const formattedBalance = formatCurrencyAmount({
|
const formattedBalance = formatCurrencyAmount({
|
||||||
amount: balance,
|
amount: balance,
|
||||||
type: NumberType.TokenNonTx,
|
type: NumberType.TokenNonTx,
|
||||||
})
|
})
|
||||||
const formattedUsdValue = formatCurrencyAmount({
|
const formattedUsdValue = formatCurrencyAmount({
|
||||||
amount: useStablecoinValue(balance),
|
amount: useStablecoinValue(balance),
|
||||||
type: NumberType.PortfolioBalance,
|
type: NumberType.FiatTokenStats,
|
||||||
})
|
|
||||||
const formattedGqlBalance = formatNumber({
|
|
||||||
input: gqlBalance?.quantity,
|
|
||||||
type: NumberType.TokenNonTx,
|
|
||||||
})
|
|
||||||
const formattedUsdGqlValue = formatNumber({
|
|
||||||
input: gqlBalance?.denominatedValue?.value,
|
|
||||||
type: NumberType.PortfolioBalance,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isInfoTDPEnabled) {
|
const currencies = useMemo(() => [token], [token])
|
||||||
return (
|
|
||||||
<BalanceRow onClick={onClick}>
|
|
||||||
<PortfolioLogo currencies={currencies} chainId={chainId} size="2rem" />
|
|
||||||
<BalanceAmountsContainer isInfoTDPEnabled>
|
|
||||||
<BalanceItem>
|
|
||||||
<ThemedText.BodyPrimary>{formattedUsdGqlValue}</ThemedText.BodyPrimary>
|
|
||||||
</BalanceItem>
|
|
||||||
<BalanceItem>
|
|
||||||
<ThemedText.BodySecondary>{formattedGqlBalance}</ThemedText.BodySecondary>
|
|
||||||
</BalanceItem>
|
|
||||||
</BalanceAmountsContainer>
|
|
||||||
</BalanceRow>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<BalanceRow>
|
|
||||||
<PortfolioLogo currencies={currencies} chainId={chainId} size="2rem" />
|
|
||||||
<BalanceContainer>
|
|
||||||
<BalanceAmountsContainer>
|
|
||||||
<BalanceItem>
|
|
||||||
<ThemedText.SubHeader>
|
|
||||||
{formattedBalance} {currency?.symbol}
|
|
||||||
</ThemedText.SubHeader>
|
|
||||||
</BalanceItem>
|
|
||||||
<BalanceItem>
|
|
||||||
<ThemedText.BodyPrimary>{formattedUsdValue}</ThemedText.BodyPrimary>
|
|
||||||
</BalanceItem>
|
|
||||||
</BalanceAmountsContainer>
|
|
||||||
<StyledNetworkLabel color={color}>{chainName}</StyledNetworkLabel>
|
|
||||||
</BalanceContainer>
|
|
||||||
</BalanceRow>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConnectedChainBalanceSummary = ({
|
if (!account || !balance) {
|
||||||
connectedChainBalance,
|
|
||||||
}: {
|
|
||||||
connectedChainBalance?: CurrencyAmount<Currency>
|
|
||||||
}) => {
|
|
||||||
const { chainId: connectedChainId } = useWeb3React()
|
|
||||||
if (!connectedChainId || !connectedChainBalance || !connectedChainBalance.greaterThan(0)) return null
|
|
||||||
const token = connectedChainBalance.currency
|
|
||||||
const { label: chainName } = getChainInfo(asSupportedChain(connectedChainId) ?? ChainId.MAINNET)
|
|
||||||
return (
|
|
||||||
<BalanceSection>
|
|
||||||
<ThemedText.SubHeaderSmall color="neutral1">
|
|
||||||
<Trans>Your balance on {chainName}</Trans>
|
|
||||||
</ThemedText.SubHeaderSmall>
|
|
||||||
<Balance currency={token} chainId={connectedChainId} balance={connectedChainBalance} />
|
|
||||||
</BalanceSection>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PageChainBalanceSummary = ({ pageChainBalance }: { pageChainBalance?: PortfolioTokenBalancePartsFragment }) => {
|
|
||||||
if (!pageChainBalance || !pageChainBalance.token) return null
|
|
||||||
const currency = gqlToCurrency(pageChainBalance.token)
|
|
||||||
return (
|
|
||||||
<BalanceSection>
|
|
||||||
<ThemedText.HeadlineSmall color="neutral1">
|
|
||||||
<Trans>Your balance</Trans>
|
|
||||||
</ThemedText.HeadlineSmall>
|
|
||||||
<Balance currency={currency} chainId={currency?.chainId} gqlBalance={pageChainBalance} />
|
|
||||||
</BalanceSection>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const OtherChainsBalanceSummary = ({
|
|
||||||
otherChainBalances,
|
|
||||||
hasPageChainBalance,
|
|
||||||
}: {
|
|
||||||
otherChainBalances: readonly PortfolioTokenBalancePartsFragment[]
|
|
||||||
hasPageChainBalance: boolean
|
|
||||||
}) => {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const isInfoExplorePageEnabled = useInfoExplorePageEnabled()
|
|
||||||
|
|
||||||
if (!otherChainBalances.length) return null
|
|
||||||
return (
|
|
||||||
<BalanceSection>
|
|
||||||
{hasPageChainBalance ? (
|
|
||||||
<ThemedText.SubHeaderSmall>
|
|
||||||
<Trans>On other networks</Trans>
|
|
||||||
</ThemedText.SubHeaderSmall>
|
|
||||||
) : (
|
|
||||||
<ThemedText.HeadlineSmall>
|
|
||||||
<Trans>Balance on other networks</Trans>
|
|
||||||
</ThemedText.HeadlineSmall>
|
|
||||||
)}
|
|
||||||
{otherChainBalances.map((balance) => {
|
|
||||||
const currency = balance.token && gqlToCurrency(balance.token)
|
|
||||||
const chainId = (balance.token && supportedChainIdFromGQLChain(balance.token.chain)) ?? ChainId.MAINNET
|
|
||||||
return (
|
|
||||||
<Balance
|
|
||||||
key={balance.id}
|
|
||||||
currency={currency}
|
|
||||||
chainId={chainId}
|
|
||||||
gqlBalance={balance}
|
|
||||||
onClick={() =>
|
|
||||||
navigate(
|
|
||||||
getTokenDetailsURL({
|
|
||||||
address: balance.token?.address,
|
|
||||||
chain: balance.token?.chain ?? Chain.Ethereum,
|
|
||||||
isInfoExplorePageEnabled,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</BalanceSection>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BalanceSummary({
|
|
||||||
currency,
|
|
||||||
chain,
|
|
||||||
multiChainMap,
|
|
||||||
}: {
|
|
||||||
currency: Currency
|
|
||||||
chain: Chain
|
|
||||||
multiChainMap: MultiChainMap
|
|
||||||
}) {
|
|
||||||
const { account } = useWeb3React()
|
|
||||||
|
|
||||||
const isInfoTDPEnabled = useInfoTDPEnabled()
|
|
||||||
|
|
||||||
const connectedChainBalance = useCurrencyBalance(account, currency)
|
|
||||||
|
|
||||||
const pageChainBalance = multiChainMap[chain].balance
|
|
||||||
const otherChainBalances: PortfolioTokenBalancePartsFragment[] = []
|
|
||||||
for (const [key, value] of Object.entries(multiChainMap)) {
|
|
||||||
if (key !== chain && value.balance !== undefined) {
|
|
||||||
otherChainBalances.push(value.balance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const hasBalances = pageChainBalance || Boolean(otherChainBalances.length)
|
|
||||||
|
|
||||||
if (!account || !hasBalances) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<BalancesCard isInfoTDPEnabled={isInfoTDPEnabled}>
|
<BalancesCard>
|
||||||
{!isInfoTDPEnabled && <ConnectedChainBalanceSummary connectedChainBalance={connectedChainBalance} />}
|
<BalanceSection>
|
||||||
{isInfoTDPEnabled && (
|
<ThemedText.SubHeaderSmall color={theme.neutral1}>
|
||||||
<>
|
<Trans>Your balance on {label}</Trans>
|
||||||
<PageChainBalanceSummary pageChainBalance={pageChainBalance} />
|
</ThemedText.SubHeaderSmall>
|
||||||
<OtherChainsBalanceSummary otherChainBalances={otherChainBalances} hasPageChainBalance={!!pageChainBalance} />
|
<BalanceRow>
|
||||||
</>
|
<PortfolioLogo currencies={currencies} chainId={token.chainId} size="2rem" />
|
||||||
)}
|
<BalanceContainer>
|
||||||
|
<BalanceAmountsContainer>
|
||||||
|
<BalanceItem>
|
||||||
|
<ThemedText.SubHeader>
|
||||||
|
{formattedBalance} {token.symbol}
|
||||||
|
</ThemedText.SubHeader>
|
||||||
|
</BalanceItem>
|
||||||
|
<BalanceItem>
|
||||||
|
<ThemedText.BodyPrimary>{formattedUsdValue}</ThemedText.BodyPrimary>
|
||||||
|
</BalanceItem>
|
||||||
|
</BalanceAmountsContainer>
|
||||||
|
<StyledNetworkLabel color={color}>{label}</StyledNetworkLabel>
|
||||||
|
</BalanceContainer>
|
||||||
|
</BalanceRow>
|
||||||
|
</BalanceSection>
|
||||||
</BalancesCard>
|
</BalancesCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,15 @@
|
|||||||
import { ParentSize } from '@visx/responsive'
|
import { ParentSize } from '@visx/responsive'
|
||||||
import { LoadingChart } from 'components/Tokens/TokenDetails/Skeleton'
|
import { ChartContainer, LoadingChart } from 'components/Tokens/TokenDetails/Skeleton'
|
||||||
import { TokenPriceQuery } from 'graphql/data/TokenPrice'
|
import { TokenPriceQuery } from 'graphql/data/TokenPrice'
|
||||||
import { isPricePoint, PricePoint } from 'graphql/data/util'
|
import { isPricePoint, PricePoint } from 'graphql/data/util'
|
||||||
|
import { TimePeriod } from 'graphql/data/util'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import { pageTimePeriodAtom } from 'pages/TokenDetails'
|
import { pageTimePeriodAtom } from 'pages/TokenDetails'
|
||||||
import { Suspense, useMemo } from 'react'
|
import { startTransition, Suspense, useMemo } from 'react'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
import { PriceChart } from '../../Charts/PriceChart'
|
import { PriceChart } from '../../Charts/PriceChart'
|
||||||
import TimePeriodSelector from './TimeSelector'
|
import TimePeriodSelector from './TimeSelector'
|
||||||
|
|
||||||
export const ChartContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 436px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
align-items: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
`
|
|
||||||
|
|
||||||
function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined {
|
function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined {
|
||||||
// Appends the current price to the end of the priceHistory array
|
// Appends the current price to the end of the priceHistory array
|
||||||
const priceHistory = useMemo(() => {
|
const priceHistory = useMemo(() => {
|
||||||
@ -34,28 +25,49 @@ function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefi
|
|||||||
|
|
||||||
return priceHistory
|
return priceHistory
|
||||||
}
|
}
|
||||||
export default function ChartSection({ tokenPriceQuery }: { tokenPriceQuery?: TokenPriceQuery }) {
|
export default function ChartSection({
|
||||||
|
tokenPriceQuery,
|
||||||
|
onChangeTimePeriod,
|
||||||
|
}: {
|
||||||
|
tokenPriceQuery?: TokenPriceQuery
|
||||||
|
onChangeTimePeriod: OnChangeTimePeriod
|
||||||
|
}) {
|
||||||
if (!tokenPriceQuery) {
|
if (!tokenPriceQuery) {
|
||||||
return <LoadingChart />
|
return <LoadingChart />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<LoadingChart />}>
|
<Suspense fallback={<LoadingChart />}>
|
||||||
<ChartContainer data-testid="chart-container">
|
<ChartContainer>
|
||||||
<Chart tokenPriceQuery={tokenPriceQuery} />
|
<Chart tokenPriceQuery={tokenPriceQuery} onChangeTimePeriod={onChangeTimePeriod} />
|
||||||
<TimePeriodSelector />
|
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Chart({ tokenPriceQuery }: { tokenPriceQuery: TokenPriceQuery }) {
|
export type OnChangeTimePeriod = (t: TimePeriod) => void
|
||||||
|
function Chart({
|
||||||
|
tokenPriceQuery,
|
||||||
|
onChangeTimePeriod,
|
||||||
|
}: {
|
||||||
|
tokenPriceQuery: TokenPriceQuery
|
||||||
|
onChangeTimePeriod: OnChangeTimePeriod
|
||||||
|
}) {
|
||||||
const prices = usePriceHistory(tokenPriceQuery)
|
const prices = usePriceHistory(tokenPriceQuery)
|
||||||
|
// Initializes time period to global & maintain separate time period for subsequent changes
|
||||||
const timePeriod = useAtomValue(pageTimePeriodAtom)
|
const timePeriod = useAtomValue(pageTimePeriodAtom)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ParentSize>
|
<ChartContainer data-testid="chart-container">
|
||||||
{({ width }) => <PriceChart prices={prices} width={width} height={392} timePeriod={timePeriod} />}
|
<ParentSize>
|
||||||
</ParentSize>
|
{({ width }) => <PriceChart prices={prices} width={width} height={392} timePeriod={timePeriod} />}
|
||||||
|
</ParentSize>
|
||||||
|
<TimePeriodSelector
|
||||||
|
currentTimePeriod={timePeriod}
|
||||||
|
onTimeChange={(t: TimePeriod) => {
|
||||||
|
startTransition(() => onChangeTimePeriod(t))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ChartContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,21 @@ import { Trans } from '@lingui/macro'
|
|||||||
import { Currency } from '@uniswap/sdk-core'
|
import { Currency } from '@uniswap/sdk-core'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||||
import { useInfoTDPEnabled } from 'featureFlags/flags/infoTDP'
|
|
||||||
import { PortfolioTokenBalancePartsFragment } from 'graphql/data/__generated__/types-and-hooks'
|
|
||||||
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
|
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
|
||||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||||
import styled, { css } from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { StyledInternalLink, ThemedText } from 'theme/components'
|
import { StyledInternalLink } from 'theme/components'
|
||||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||||
|
|
||||||
const Wrapper = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
const Wrapper = styled.div`
|
||||||
align-content: center;
|
align-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: ${({ theme }) => theme.surface1};
|
|
||||||
border: 1px solid ${({ theme }) => theme.surface3};
|
border: 1px solid ${({ theme }) => theme.surface3};
|
||||||
|
border-bottom: none;
|
||||||
|
background-color: ${({ theme }) => theme.surface1};
|
||||||
|
border-radius: 20px 20px 0px 0px;
|
||||||
|
bottom: 52px;
|
||||||
color: ${({ theme }) => theme.neutral2};
|
color: ${({ theme }) => theme.neutral2};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -25,24 +26,9 @@ const Wrapper = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
left: 0;
|
left: 0;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
padding: 12px 16px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
${({ isInfoTDPEnabled }) =>
|
|
||||||
isInfoTDPEnabled
|
|
||||||
? css`
|
|
||||||
border-radius: 20px;
|
|
||||||
bottom: 56px;
|
|
||||||
margin: 8px;
|
|
||||||
padding: 12px 32px;
|
|
||||||
width: calc(100vw - 16px);
|
|
||||||
`
|
|
||||||
: css`
|
|
||||||
border-bottom: none;
|
|
||||||
border-radius: 20px 20px 0px 0px;
|
|
||||||
bottom: 52px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
width: 100%;
|
|
||||||
`}
|
|
||||||
|
|
||||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
|
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
@ -51,29 +37,27 @@ const Wrapper = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const BalanceValue = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
const BalanceValue = styled.div`
|
||||||
color: ${({ theme }) => theme.neutral1};
|
color: ${({ theme }) => theme.neutral1};
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: ${({ isInfoTDPEnabled }) => (isInfoTDPEnabled ? '20px' : '28px')};
|
line-height: 28px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
`
|
`
|
||||||
const Balance = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
const Balance = styled.div`
|
||||||
align-items: ${({ isInfoTDPEnabled }) => (isInfoTDPEnabled ? 'flex-end' : 'center')};
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
`
|
`
|
||||||
const BalanceInfo = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
const BalanceInfo = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 10 1 auto;
|
flex: 10 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
${({ isInfoTDPEnabled }) => isInfoTDPEnabled && 'gap: 6px;'}
|
|
||||||
`
|
`
|
||||||
const FiatValue = styled(ThemedText.Caption)<{ isInfoTDPEnabled?: boolean }>`
|
const FiatValue = styled.span`
|
||||||
${({ isInfoTDPEnabled, theme }) => !isInfoTDPEnabled && `color: ${theme.neutral2};`}
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
|
|
||||||
@ -81,15 +65,15 @@ const FiatValue = styled(ThemedText.Caption)<{ isInfoTDPEnabled?: boolean }>`
|
|||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const SwapButton = styled(StyledInternalLink)<{ isInfoTDPEnabled?: boolean }>`
|
const SwapButton = styled(StyledInternalLink)`
|
||||||
background-color: ${({ theme }) => theme.accent1};
|
background-color: ${({ theme }) => theme.accent1};
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: ${({ isInfoTDPEnabled }) => (isInfoTDPEnabled ? '22px' : '12px')};
|
border-radius: 12px;
|
||||||
color: ${({ theme }) => theme.deprecated_accentTextLightPrimary};
|
color: ${({ theme }) => theme.deprecated_accentTextLightPrimary};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
font-size: ${({ isInfoTDPEnabled }) => (isInfoTDPEnabled ? '16px' : '1em')};
|
font-size: 1em;
|
||||||
font-weight: 535;
|
font-weight: 535;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -97,18 +81,10 @@ const SwapButton = styled(StyledInternalLink)<{ isInfoTDPEnabled?: boolean }>`
|
|||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function MobileBalanceSummaryFooter({
|
export default function MobileBalanceSummaryFooter({ token }: { token: Currency }) {
|
||||||
currency,
|
|
||||||
pageChainBalance,
|
|
||||||
}: {
|
|
||||||
currency: Currency
|
|
||||||
pageChainBalance?: PortfolioTokenBalancePartsFragment
|
|
||||||
}) {
|
|
||||||
const isInfoTDPEnabled = useInfoTDPEnabled()
|
|
||||||
|
|
||||||
const { account } = useWeb3React()
|
const { account } = useWeb3React()
|
||||||
const balance = useCurrencyBalance(account, currency)
|
const balance = useCurrencyBalance(account, token)
|
||||||
const { formatCurrencyAmount, formatNumber } = useFormatter()
|
const { formatCurrencyAmount } = useFormatter()
|
||||||
const formattedBalance = formatCurrencyAmount({
|
const formattedBalance = formatCurrencyAmount({
|
||||||
amount: balance,
|
amount: balance,
|
||||||
type: NumberType.TokenNonTx,
|
type: NumberType.TokenNonTx,
|
||||||
@ -117,35 +93,22 @@ export default function MobileBalanceSummaryFooter({
|
|||||||
amount: useStablecoinValue(balance),
|
amount: useStablecoinValue(balance),
|
||||||
type: NumberType.FiatTokenStats,
|
type: NumberType.FiatTokenStats,
|
||||||
})
|
})
|
||||||
const formattedGqlBalance = formatNumber({
|
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
|
||||||
input: pageChainBalance?.quantity,
|
|
||||||
type: NumberType.TokenNonTx,
|
|
||||||
})
|
|
||||||
const formattedUsdGqlValue = formatNumber({
|
|
||||||
input: pageChainBalance?.denominatedValue?.value,
|
|
||||||
type: NumberType.PortfolioBalance,
|
|
||||||
})
|
|
||||||
const chain = CHAIN_ID_TO_BACKEND_NAME[currency.chainId].toLowerCase()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper isInfoTDPEnabled={isInfoTDPEnabled}>
|
<Wrapper>
|
||||||
{Boolean(account && (isInfoTDPEnabled ? pageChainBalance : balance)) && (
|
{Boolean(account && balance) && (
|
||||||
<BalanceInfo isInfoTDPEnabled={isInfoTDPEnabled}>
|
<BalanceInfo>
|
||||||
{isInfoTDPEnabled ? <Trans>Your balance</Trans> : <Trans>Your {currency.symbol} balance</Trans>}
|
<Trans>Your {token.symbol} balance</Trans>
|
||||||
<Balance isInfoTDPEnabled={isInfoTDPEnabled}>
|
<Balance>
|
||||||
<BalanceValue isInfoTDPEnabled={isInfoTDPEnabled}>
|
<BalanceValue>
|
||||||
{isInfoTDPEnabled ? formattedGqlBalance : formattedBalance} {currency.symbol}
|
{formattedBalance} {token.symbol}
|
||||||
</BalanceValue>
|
</BalanceValue>
|
||||||
<FiatValue isInfoTDPEnabled={isInfoTDPEnabled}>
|
<FiatValue>{formattedUsdValue}</FiatValue>
|
||||||
{isInfoTDPEnabled ? `(${formattedUsdGqlValue})` : formattedUsdValue}
|
|
||||||
</FiatValue>
|
|
||||||
</Balance>
|
</Balance>
|
||||||
</BalanceInfo>
|
</BalanceInfo>
|
||||||
)}
|
)}
|
||||||
<SwapButton
|
<SwapButton to={`/swap?chainName=${chain}&outputCurrency=${token.isNative ? NATIVE_CHAIN_ID : token.address}`}>
|
||||||
isInfoTDPEnabled={isInfoTDPEnabled}
|
|
||||||
to={`/swap?chainName=${chain}&outputCurrency=${currency.isNative ? NATIVE_CHAIN_ID : currency.address}`}
|
|
||||||
>
|
|
||||||
<Trans>Swap</Trans>
|
<Trans>Swap</Trans>
|
||||||
</SwapButton>
|
</SwapButton>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
@ -11,7 +11,6 @@ import { textFadeIn } from 'theme/styles'
|
|||||||
import { LoadingBubble } from '../loading'
|
import { LoadingBubble } from '../loading'
|
||||||
import { AboutContainer, AboutHeader } from './About'
|
import { AboutContainer, AboutHeader } from './About'
|
||||||
import { BreadcrumbNav, BreadcrumbNavLink } from './BreadcrumbNavLink'
|
import { BreadcrumbNav, BreadcrumbNavLink } from './BreadcrumbNavLink'
|
||||||
import { ChartContainer } from './ChartSection'
|
|
||||||
import { StatPair, StatsWrapper, StatWrapper } from './StatsSection'
|
import { StatPair, StatsWrapper, StatWrapper } from './StatsSection'
|
||||||
|
|
||||||
const SWAP_COMPONENT_WIDTH = 360
|
const SWAP_COMPONENT_WIDTH = 360
|
||||||
@ -54,6 +53,14 @@ export const RightPanel = styled.div<{ isInfoTDPEnabled?: boolean }>`
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
export const ChartContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 436px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
const LoadingChartContainer = styled.div`
|
const LoadingChartContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useAtom } from 'jotai'
|
import { TimePeriod } from 'graphql/data/util'
|
||||||
import { pageTimePeriodAtom } from 'pages/TokenDetails'
|
import { startTransition, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||||
@ -46,13 +46,26 @@ const TimeButton = styled.button<{ active: boolean }>`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function TimePeriodSelector() {
|
export default function TimePeriodSelector({
|
||||||
const [timePeriod, setTimePeriod] = useAtom(pageTimePeriodAtom)
|
currentTimePeriod,
|
||||||
|
onTimeChange,
|
||||||
|
}: {
|
||||||
|
currentTimePeriod: TimePeriod
|
||||||
|
onTimeChange: (t: TimePeriod) => void
|
||||||
|
}) {
|
||||||
|
const [timePeriod, setTimePeriod] = useState(currentTimePeriod)
|
||||||
return (
|
return (
|
||||||
<TimeOptionsWrapper>
|
<TimeOptionsWrapper>
|
||||||
<TimeOptionsContainer>
|
<TimeOptionsContainer>
|
||||||
{ORDERED_TIMES.map((time) => (
|
{ORDERED_TIMES.map((time) => (
|
||||||
<TimeButton key={DISPLAYS[time]} active={timePeriod === time} onClick={() => setTimePeriod(time)}>
|
<TimeButton
|
||||||
|
key={DISPLAYS[time]}
|
||||||
|
active={timePeriod === time}
|
||||||
|
onClick={() => {
|
||||||
|
startTransition(() => onTimeChange(time))
|
||||||
|
setTimePeriod(time)
|
||||||
|
}}
|
||||||
|
>
|
||||||
{DISPLAYS[time]}
|
{DISPLAYS[time]}
|
||||||
</TimeButton>
|
</TimeButton>
|
||||||
))}
|
))}
|
||||||
|
@ -3,11 +3,12 @@ import { InterfacePageName } from '@uniswap/analytics-events'
|
|||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { Trace } from 'analytics'
|
import { Trace } from 'analytics'
|
||||||
import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo'
|
import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo'
|
||||||
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
|
|
||||||
import { AboutSection } from 'components/Tokens/TokenDetails/About'
|
import { AboutSection } from 'components/Tokens/TokenDetails/About'
|
||||||
import AddressSection from 'components/Tokens/TokenDetails/AddressSection'
|
import AddressSection from 'components/Tokens/TokenDetails/AddressSection'
|
||||||
|
import BalanceSummary from 'components/Tokens/TokenDetails/BalanceSummary'
|
||||||
import { BreadcrumbNav, BreadcrumbNavLink } from 'components/Tokens/TokenDetails/BreadcrumbNavLink'
|
import { BreadcrumbNav, BreadcrumbNavLink } from 'components/Tokens/TokenDetails/BreadcrumbNavLink'
|
||||||
import ChartSection from 'components/Tokens/TokenDetails/ChartSection'
|
import ChartSection from 'components/Tokens/TokenDetails/ChartSection'
|
||||||
|
import MobileBalanceSummaryFooter from 'components/Tokens/TokenDetails/MobileBalanceSummaryFooter'
|
||||||
import ShareButton from 'components/Tokens/TokenDetails/ShareButton'
|
import ShareButton from 'components/Tokens/TokenDetails/ShareButton'
|
||||||
import TokenDetailsSkeleton, {
|
import TokenDetailsSkeleton, {
|
||||||
Hr,
|
Hr,
|
||||||
@ -24,13 +25,8 @@ import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
|||||||
import { checkWarning } from 'constants/tokenSafety'
|
import { checkWarning } from 'constants/tokenSafety'
|
||||||
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
|
||||||
import { useInfoTDPEnabled } from 'featureFlags/flags/infoTDP'
|
import { useInfoTDPEnabled } from 'featureFlags/flags/infoTDP'
|
||||||
import {
|
import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||||
Chain,
|
import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token'
|
||||||
PortfolioTokenBalancePartsFragment,
|
|
||||||
TokenPriceQuery,
|
|
||||||
TokenQuery,
|
|
||||||
} from 'graphql/data/__generated__/types-and-hooks'
|
|
||||||
import { TokenQueryData } from 'graphql/data/Token'
|
|
||||||
import { getTokenDetailsURL, gqlToCurrency, InterfaceGqlChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
|
import { getTokenDetailsURL, gqlToCurrency, InterfaceGqlChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
|
||||||
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
|
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
|
||||||
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
|
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
|
||||||
@ -45,9 +41,8 @@ import { CopyContractAddress } from 'theme/components'
|
|||||||
import { isAddress, shortenAddress } from 'utils'
|
import { isAddress, shortenAddress } from 'utils'
|
||||||
import { addressesAreEquivalent } from 'utils/addressesAreEquivalent'
|
import { addressesAreEquivalent } from 'utils/addressesAreEquivalent'
|
||||||
|
|
||||||
import BalanceSummary from './BalanceSummary'
|
import { OnChangeTimePeriod } from './ChartSection'
|
||||||
import InvalidTokenDetails from './InvalidTokenDetails'
|
import InvalidTokenDetails from './InvalidTokenDetails'
|
||||||
import MobileBalanceSummaryFooter from './MobileBalanceSummaryFooter'
|
|
||||||
import { TokenDescription } from './TokenDescription'
|
import { TokenDescription } from './TokenDescription'
|
||||||
|
|
||||||
const TokenSymbol = styled.span`
|
const TokenSymbol = styled.span`
|
||||||
@ -100,13 +95,14 @@ function useRelevantToken(
|
|||||||
[onChainToken, queryToken]
|
[onChainToken, queryToken]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export type MultiChainMap = { [chain: string]: { address?: string; balance?: PortfolioTokenBalancePartsFragment } }
|
|
||||||
type TokenDetailsProps = {
|
type TokenDetailsProps = {
|
||||||
urlAddress?: string
|
urlAddress?: string
|
||||||
inputTokenAddress?: string
|
inputTokenAddress?: string
|
||||||
chain: InterfaceGqlChain
|
chain: InterfaceGqlChain
|
||||||
tokenQuery: TokenQuery
|
tokenQuery: TokenQuery
|
||||||
tokenPriceQuery?: TokenPriceQuery
|
tokenPriceQuery?: TokenPriceQuery
|
||||||
|
onChangeTimePeriod: OnChangeTimePeriod
|
||||||
}
|
}
|
||||||
export default function TokenDetails({
|
export default function TokenDetails({
|
||||||
urlAddress,
|
urlAddress,
|
||||||
@ -114,6 +110,7 @@ export default function TokenDetails({
|
|||||||
chain,
|
chain,
|
||||||
tokenQuery,
|
tokenQuery,
|
||||||
tokenPriceQuery,
|
tokenPriceQuery,
|
||||||
|
onChangeTimePeriod,
|
||||||
}: TokenDetailsProps) {
|
}: TokenDetailsProps) {
|
||||||
if (!urlAddress) {
|
if (!urlAddress) {
|
||||||
throw new Error('Invalid token details route: tokenAddress param is undefined')
|
throw new Error('Invalid token details route: tokenAddress param is undefined')
|
||||||
@ -123,25 +120,17 @@ export default function TokenDetails({
|
|||||||
[urlAddress]
|
[urlAddress]
|
||||||
)
|
)
|
||||||
|
|
||||||
const { account, chainId: connectedChainId } = useWeb3React()
|
const { chainId: connectedChainId } = useWeb3React()
|
||||||
const pageChainId = supportedChainIdFromGQLChain(chain)
|
const pageChainId = supportedChainIdFromGQLChain(chain)
|
||||||
const tokenQueryData = tokenQuery.token
|
const tokenQueryData = tokenQuery.token
|
||||||
const { data: balanceQuery } = useCachedPortfolioBalancesQuery({ account })
|
const crossChainMap = useMemo(
|
||||||
const multiChainMap = useMemo(() => {
|
() =>
|
||||||
const tokenBalances = balanceQuery?.portfolios?.[0].tokenBalances
|
tokenQueryData?.project?.tokens.reduce((map, current) => {
|
||||||
const tokensAcrossChains = tokenQueryData?.project?.tokens
|
if (current) map[current.chain] = current.address
|
||||||
if (!tokensAcrossChains) return {}
|
return map
|
||||||
return tokensAcrossChains.reduce((map, current) => {
|
}, {} as { [key: string]: string | undefined }) ?? {},
|
||||||
if (current) {
|
[tokenQueryData]
|
||||||
if (!map[current.chain]) {
|
)
|
||||||
map[current.chain] = {}
|
|
||||||
}
|
|
||||||
map[current.chain].address = current.address
|
|
||||||
map[current.chain].balance = tokenBalances?.find((tokenBalance) => tokenBalance.token?.id === current.id)
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}, {} as MultiChainMap)
|
|
||||||
}, [balanceQuery?.portfolios, tokenQueryData?.project?.tokens])
|
|
||||||
|
|
||||||
const { token: detailedToken, didFetchFromChain } = useRelevantToken(address, pageChainId, tokenQueryData)
|
const { token: detailedToken, didFetchFromChain } = useRelevantToken(address, pageChainId, tokenQueryData)
|
||||||
|
|
||||||
@ -157,7 +146,7 @@ export default function TokenDetails({
|
|||||||
const navigateToTokenForChain = useCallback(
|
const navigateToTokenForChain = useCallback(
|
||||||
(update: Chain) => {
|
(update: Chain) => {
|
||||||
if (!address) return
|
if (!address) return
|
||||||
const bridgedAddress = multiChainMap[update].address
|
const bridgedAddress = crossChainMap[update]
|
||||||
if (bridgedAddress) {
|
if (bridgedAddress) {
|
||||||
startTokenTransition(() =>
|
startTokenTransition(() =>
|
||||||
navigate(
|
navigate(
|
||||||
@ -172,7 +161,7 @@ export default function TokenDetails({
|
|||||||
startTokenTransition(() => navigate(getTokenDetailsURL({ address, chain: update, isInfoExplorePageEnabled })))
|
startTokenTransition(() => navigate(getTokenDetailsURL({ address, chain: update, isInfoExplorePageEnabled })))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[address, multiChainMap, didFetchFromChain, detailedToken?.isNative, navigate, isInfoExplorePageEnabled]
|
[address, crossChainMap, didFetchFromChain, detailedToken?.isNative, navigate, isInfoExplorePageEnabled]
|
||||||
)
|
)
|
||||||
useOnGlobalChainSwitch(navigateToTokenForChain)
|
useOnGlobalChainSwitch(navigateToTokenForChain)
|
||||||
|
|
||||||
@ -269,7 +258,7 @@ export default function TokenDetails({
|
|||||||
<ShareButton currency={detailedToken} />
|
<ShareButton currency={detailedToken} />
|
||||||
</TokenActions>
|
</TokenActions>
|
||||||
</TokenInfoContainer>
|
</TokenInfoContainer>
|
||||||
<ChartSection tokenPriceQuery={tokenPriceQuery} />
|
<ChartSection tokenPriceQuery={tokenPriceQuery} onChangeTimePeriod={onChangeTimePeriod} />
|
||||||
|
|
||||||
<StatsSection chainId={pageChainId} address={address} tokenQueryData={tokenQueryData} />
|
<StatsSection chainId={pageChainId} address={address} tokenQueryData={tokenQueryData} />
|
||||||
<Hr />
|
<Hr />
|
||||||
@ -297,7 +286,7 @@ export default function TokenDetails({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />}
|
{tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />}
|
||||||
{detailedToken && <BalanceSummary currency={detailedToken} chain={chain} multiChainMap={multiChainMap} />}
|
{!isInfoTDPEnabled && detailedToken && <BalanceSummary token={detailedToken} />}
|
||||||
{isInfoTDPEnabled && (
|
{isInfoTDPEnabled && (
|
||||||
<TokenDescription
|
<TokenDescription
|
||||||
tokenAddress={address}
|
tokenAddress={address}
|
||||||
@ -307,9 +296,7 @@ export default function TokenDetails({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</RightPanel>
|
</RightPanel>
|
||||||
{detailedToken && (
|
{!isInfoTDPEnabled && detailedToken && <MobileBalanceSummaryFooter token={detailedToken} />}
|
||||||
<MobileBalanceSummaryFooter currency={detailedToken} pageChainBalance={multiChainMap[chain].balance} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TokenSafetyModal
|
<TokenSafetyModal
|
||||||
isOpen={openTokenSafetyModal || !!continueSwap}
|
isOpen={openTokenSafetyModal || !!continueSwap}
|
||||||
|
@ -114,7 +114,7 @@ const ClickableContent = styled.div<{ gap?: number }>`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`
|
`
|
||||||
const ClickableName = styled(ClickableContent)`
|
const ClickableName = styled(ClickableContent)`
|
||||||
gap: 14px;
|
gap: 12px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
`
|
`
|
||||||
const StyledHeaderRow = styled(StyledTokenRow)`
|
const StyledHeaderRow = styled(StyledTokenRow)`
|
||||||
|
@ -3,14 +3,6 @@
|
|||||||
exports[`LoadedRow.tsx renders a row 1`] = `
|
exports[`LoadedRow.tsx renders a row 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
.c7 {
|
.c7 {
|
||||||
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;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -102,7 +94,7 @@ exports[`LoadedRow.tsx renders a row 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c6 {
|
.c6 {
|
||||||
gap: 14px;
|
gap: 12px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { AutoRow } from 'components/Row'
|
|||||||
import { connections, deprecatedNetworkConnection, networkConnection } from 'connection'
|
import { connections, deprecatedNetworkConnection, networkConnection } from 'connection'
|
||||||
import { ActivationStatus, useActivationState } from 'connection/activate'
|
import { ActivationStatus, useActivationState } from 'connection/activate'
|
||||||
import { isSupportedChain } from 'constants/chains'
|
import { isSupportedChain } from 'constants/chains'
|
||||||
|
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
|
||||||
import { useFallbackProviderEnabled } from 'featureFlags/flags/fallbackProvider'
|
import { useFallbackProviderEnabled } from 'featureFlags/flags/fallbackProvider'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -40,6 +41,7 @@ const PrivacyPolicyWrapper = styled.div`
|
|||||||
|
|
||||||
export default function WalletModal({ openSettings }: { openSettings: () => void }) {
|
export default function WalletModal({ openSettings }: { openSettings: () => void }) {
|
||||||
const { connector, chainId } = useWeb3React()
|
const { connector, chainId } = useWeb3React()
|
||||||
|
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
|
||||||
|
|
||||||
const { activationState } = useActivationState()
|
const { activationState } = useActivationState()
|
||||||
const fallbackProviderEnabled = useFallbackProviderEnabled()
|
const fallbackProviderEnabled = useFallbackProviderEnabled()
|
||||||
@ -66,7 +68,7 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
|||||||
<AutoColumn gap="16px">
|
<AutoColumn gap="16px">
|
||||||
<OptionGrid data-testid="option-grid">
|
<OptionGrid data-testid="option-grid">
|
||||||
{connections
|
{connections
|
||||||
.filter((connection) => connection.shouldDisplay())
|
.filter((connection) => connection.shouldDisplay(isAndroidGALaunched))
|
||||||
.map((connection) => (
|
.map((connection) => (
|
||||||
<Option key={connection.getName()} connection={connection} />
|
<Option key={connection.getName()} connection={connection} />
|
||||||
))}
|
))}
|
||||||
|
@ -6,7 +6,7 @@ import { useTheme } from 'styled-components'
|
|||||||
import { ThemedText } from 'theme/components'
|
import { ThemedText } from 'theme/components'
|
||||||
import { useFormatter } from 'utils/formatNumbers'
|
import { useFormatter } from 'utils/formatNumbers'
|
||||||
|
|
||||||
export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> }) {
|
export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmount' | 'postTaxOutputAmount'> }) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const { formatReviewSwapCurrencyAmount } = useFormatter()
|
const { formatReviewSwapCurrencyAmount } = useFormatter()
|
||||||
|
|
||||||
@ -17,9 +17,9 @@ export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmou
|
|||||||
{formatReviewSwapCurrencyAmount(trade.inputAmount)} {trade.inputAmount.currency.symbol}
|
{formatReviewSwapCurrencyAmount(trade.inputAmount)} {trade.inputAmount.currency.symbol}
|
||||||
</ThemedText.LabelSmall>
|
</ThemedText.LabelSmall>
|
||||||
<ArrowRight color={theme.neutral1} size="12px" />
|
<ArrowRight color={theme.neutral1} size="12px" />
|
||||||
<CurrencyLogo currency={trade.outputAmount.currency} size="16px" />
|
<CurrencyLogo currency={trade.postTaxOutputAmount.currency} size="16px" />
|
||||||
<ThemedText.LabelSmall color="neutral1">
|
<ThemedText.LabelSmall color="neutral1">
|
||||||
{formatReviewSwapCurrencyAmount(trade.outputAmount)} {trade.outputAmount.currency.symbol}
|
{formatReviewSwapCurrencyAmount(trade.postTaxOutputAmount)} {trade.postTaxOutputAmount.currency.symbol}
|
||||||
</ThemedText.LabelSmall>
|
</ThemedText.LabelSmall>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
@ -29,18 +29,12 @@ const StyledTextButton = styled(ButtonText)`
|
|||||||
color: ${({ theme }) => theme.neutral2};
|
color: ${({ theme }) => theme.neutral2};
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
font-weight: 485;
|
font-weight: 485;
|
||||||
transition-duration: ${({ theme }) => theme.transition.duration.fast};
|
|
||||||
transition-timing-function: ease-in-out;
|
|
||||||
transition-property: opacity, color, background-color;
|
|
||||||
&:focus {
|
&:focus {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
:hover {
|
|
||||||
opacity: ${({ theme }) => theme.opacity.hover};
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function SwapBuyFiatButton() {
|
export default function SwapBuyFiatButton() {
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
|
|
||||||
import { render, screen } from 'test-utils/render'
|
|
||||||
|
|
||||||
import SwapHeader, { SwapTab } from './SwapHeader'
|
|
||||||
|
|
||||||
jest.mock('../../featureFlags/flags/limits', () => ({ useLimitsEnabled: () => true }))
|
|
||||||
|
|
||||||
describe('SwapHeader.tsx', () => {
|
|
||||||
it('matches base snapshot', () => {
|
|
||||||
const { asFragment } = render(
|
|
||||||
<SwapHeader
|
|
||||||
trade={TEST_TRADE_EXACT_INPUT}
|
|
||||||
selectedTab={SwapTab.Swap}
|
|
||||||
autoSlippage={TEST_ALLOWED_SLIPPAGE}
|
|
||||||
onClickTab={jest.fn()}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
expect(asFragment()).toMatchSnapshot()
|
|
||||||
expect(screen.getByText('Swap')).toBeInTheDocument()
|
|
||||||
expect(screen.getByText('Buy')).toBeInTheDocument()
|
|
||||||
expect(screen.getByText('Limit')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls callback for switching tabs', () => {
|
|
||||||
const onClickTab = jest.fn()
|
|
||||||
render(
|
|
||||||
<SwapHeader
|
|
||||||
trade={TEST_TRADE_EXACT_INPUT}
|
|
||||||
selectedTab={SwapTab.Swap}
|
|
||||||
autoSlippage={TEST_ALLOWED_SLIPPAGE}
|
|
||||||
onClickTab={onClickTab}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
screen.getByText('Limit').click()
|
|
||||||
expect(onClickTab).toHaveBeenCalledWith(SwapTab.Limit)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,9 +1,8 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { Percent } from '@uniswap/sdk-core'
|
import { Percent } from '@uniswap/sdk-core'
|
||||||
import { useLimitsEnabled } from 'featureFlags/flags/limits'
|
|
||||||
import { InterfaceTrade } from 'state/routing/types'
|
import { InterfaceTrade } from 'state/routing/types'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { ButtonText } from 'theme/components'
|
import { ThemedText } from 'theme/components'
|
||||||
|
|
||||||
import { RowBetween, RowFixed } from '../Row'
|
import { RowBetween, RowFixed } from '../Row'
|
||||||
import SettingsTab from '../Settings'
|
import SettingsTab from '../Settings'
|
||||||
@ -19,49 +18,22 @@ const HeaderButtonContainer = styled(RowFixed)`
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledTextButton = styled(ButtonText)<{ $isActive: boolean }>`
|
|
||||||
color: ${({ theme, $isActive }) => ($isActive ? theme.neutral1 : theme.neutral2)};
|
|
||||||
gap: 4px;
|
|
||||||
font-weight: 485;
|
|
||||||
&:focus {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export enum SwapTab {
|
|
||||||
Swap = 'swap',
|
|
||||||
Limit = 'limit',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SwapHeader({
|
export default function SwapHeader({
|
||||||
autoSlippage,
|
autoSlippage,
|
||||||
chainId,
|
chainId,
|
||||||
trade,
|
trade,
|
||||||
selectedTab,
|
|
||||||
onClickTab,
|
|
||||||
}: {
|
}: {
|
||||||
autoSlippage: Percent
|
autoSlippage: Percent
|
||||||
chainId?: number
|
chainId?: number
|
||||||
trade?: InterfaceTrade
|
trade?: InterfaceTrade
|
||||||
selectedTab: SwapTab
|
|
||||||
onClickTab: (tab: SwapTab) => void
|
|
||||||
}) {
|
}) {
|
||||||
const limitsEnabled = useLimitsEnabled()
|
|
||||||
return (
|
return (
|
||||||
<StyledSwapHeader>
|
<StyledSwapHeader>
|
||||||
<HeaderButtonContainer>
|
<HeaderButtonContainer>
|
||||||
<StyledTextButton $isActive={selectedTab === SwapTab.Swap} onClick={() => onClickTab?.(SwapTab.Swap)}>
|
<ThemedText.SubHeader>
|
||||||
<Trans>Swap</Trans>
|
<Trans>Swap</Trans>
|
||||||
</StyledTextButton>
|
</ThemedText.SubHeader>
|
||||||
<SwapBuyFiatButton />
|
<SwapBuyFiatButton />
|
||||||
{limitsEnabled && (
|
|
||||||
<StyledTextButton $isActive={selectedTab === SwapTab.Limit} onClick={() => onClickTab?.(SwapTab.Limit)}>
|
|
||||||
<Trans>Limit</Trans>
|
|
||||||
</StyledTextButton>
|
|
||||||
)}
|
|
||||||
</HeaderButtonContainer>
|
</HeaderButtonContainer>
|
||||||
<RowFixed>
|
<RowFixed>
|
||||||
<SettingsTab autoSlippage={autoSlippage} chainId={chainId} trade={trade} />
|
<SettingsTab autoSlippage={autoSlippage} chainId={chainId} trade={trade} />
|
||||||
|
@ -24,7 +24,7 @@ export default function SwapModalHeader({
|
|||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
}) {
|
}) {
|
||||||
const fiatValueInput = useUSDPrice(trade.inputAmount)
|
const fiatValueInput = useUSDPrice(trade.inputAmount)
|
||||||
const fiatValueOutput = useUSDPrice(trade.outputAmount)
|
const fiatValueOutput = useUSDPrice(trade.postTaxOutputAmount)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderContainer gap="sm">
|
<HeaderContainer gap="sm">
|
||||||
@ -40,7 +40,7 @@ export default function SwapModalHeader({
|
|||||||
<SwapModalHeaderAmount
|
<SwapModalHeaderAmount
|
||||||
field={Field.OUTPUT}
|
field={Field.OUTPUT}
|
||||||
label={<Trans>You receive</Trans>}
|
label={<Trans>You receive</Trans>}
|
||||||
amount={trade.outputAmount}
|
amount={trade.postTaxOutputAmount}
|
||||||
currency={trade.outputAmount.currency}
|
currency={trade.outputAmount.currency}
|
||||||
usdAmount={fiatValueOutput.data}
|
usdAmount={fiatValueOutput.data}
|
||||||
isLoading={isPreviewTrade(trade) && trade.tradeType === TradeType.EXACT_INPUT}
|
isLoading={isPreviewTrade(trade) && trade.tradeType === TradeType.EXACT_INPUT}
|
||||||
|
@ -120,12 +120,6 @@ exports[`SwapBuyFiatButton.tsx matches base snapshot 1`] = `
|
|||||||
color: #7D7D7D;
|
color: #7D7D7D;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
font-weight: 485;
|
font-weight: 485;
|
||||||
-webkit-transition-duration: 125ms;
|
|
||||||
transition-duration: 125ms;
|
|
||||||
-webkit-transition-timing-function: ease-in-out;
|
|
||||||
transition-timing-function: ease-in-out;
|
|
||||||
-webkit-transition-property: opacity,color,background-color;
|
|
||||||
transition-property: opacity,color,background-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.c4:focus {
|
.c4:focus {
|
||||||
@ -138,10 +132,6 @@ exports[`SwapBuyFiatButton.tsx matches base snapshot 1`] = `
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c4:hover {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="c0"
|
class="c0"
|
||||||
>
|
>
|
||||||
|
@ -1,343 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`SwapHeader.tsx matches base snapshot 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
.c0 {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c9 {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
min-width: 0;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
line-height: inherit;
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: inherit;
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 16px;
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
color: white;
|
|
||||||
background-color: primary;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c1 {
|
|
||||||
width: 100%;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-box-pack: start;
|
|
||||||
-webkit-justify-content: flex-start;
|
|
||||||
-ms-flex-pack: start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c2 {
|
|
||||||
-webkit-box-pack: justify;
|
|
||||||
-webkit-justify-content: space-between;
|
|
||||||
-ms-flex-pack: justify;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c4 {
|
|
||||||
width: -webkit-fit-content;
|
|
||||||
width: -moz-fit-content;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c6 {
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
font-size: inherit;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-transition-duration: 125ms;
|
|
||||||
transition-duration: 125ms;
|
|
||||||
-webkit-transition-timing-function: ease-in-out;
|
|
||||||
transition-timing-function: ease-in-out;
|
|
||||||
-webkit-transition-property: opacity,color,background-color;
|
|
||||||
transition-property: opacity,color,background-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c6:hover {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c6:focus {
|
|
||||||
-webkit-text-decoration: underline;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c10 {
|
|
||||||
padding: 16px;
|
|
||||||
width: 100%;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: 535;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 16px;
|
|
||||||
outline: none;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
color: #222222;
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-pack: center;
|
|
||||||
-webkit-justify-content: center;
|
|
||||||
-ms-flex-pack: center;
|
|
||||||
justify-content: center;
|
|
||||||
-webkit-flex-wrap: nowrap;
|
|
||||||
-ms-flex-wrap: nowrap;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
will-change: transform;
|
|
||||||
-webkit-transition: -webkit-transform 450ms ease;
|
|
||||||
-webkit-transition: transform 450ms ease;
|
|
||||||
transition: transform 450ms ease;
|
|
||||||
-webkit-transform: perspective(1px) translateZ(0);
|
|
||||||
-ms-transform: perspective(1px) translateZ(0);
|
|
||||||
transform: perspective(1px) translateZ(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.c10:disabled {
|
|
||||||
opacity: 50%;
|
|
||||||
cursor: auto;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c10 > * {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c10 > a {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c11 {
|
|
||||||
padding: 0;
|
|
||||||
width: -webkit-fit-content;
|
|
||||||
width: -moz-fit-content;
|
|
||||||
width: fit-content;
|
|
||||||
background: none;
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c11:focus {
|
|
||||||
-webkit-text-decoration: underline;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c11:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c11:active {
|
|
||||||
-webkit-text-decoration: underline;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c11:disabled {
|
|
||||||
opacity: 50%;
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c8 {
|
|
||||||
display: inline-block;
|
|
||||||
height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c17 {
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c17 > * {
|
|
||||||
fill: #7D7D7D;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c15 {
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c15:not([disabled]):hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c16 {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c14 {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c12 {
|
|
||||||
color: #7D7D7D;
|
|
||||||
gap: 4px;
|
|
||||||
font-weight: 485;
|
|
||||||
-webkit-transition-duration: 125ms;
|
|
||||||
transition-duration: 125ms;
|
|
||||||
-webkit-transition-timing-function: ease-in-out;
|
|
||||||
transition-timing-function: ease-in-out;
|
|
||||||
-webkit-transition-property: opacity,color,background-color;
|
|
||||||
transition-property: opacity,color,background-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c12:focus {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c12:active {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c12:hover {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c3 {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #7D7D7D;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c5 {
|
|
||||||
padding: 0 12px;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c7 {
|
|
||||||
color: #222222;
|
|
||||||
gap: 4px;
|
|
||||||
font-weight: 485;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c7:focus {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c7:active {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c13 {
|
|
||||||
color: #7D7D7D;
|
|
||||||
gap: 4px;
|
|
||||||
font-weight: 485;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c13:focus {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c13:active {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="c0 c1 c2 c3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="c0 c1 c4 c5"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="c6 c7"
|
|
||||||
>
|
|
||||||
Swap
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
class="c8"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class="c9 c10 c11 c12"
|
|
||||||
data-testid="buy-fiat-button"
|
|
||||||
>
|
|
||||||
Buy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="c6 c13"
|
|
||||||
>
|
|
||||||
Limit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="c0 c1 c4"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="c14"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label="Transaction Settings"
|
|
||||||
class="c15"
|
|
||||||
data-testid="open-settings-dialog-button"
|
|
||||||
disabled=""
|
|
||||||
id="open-settings-dialog-button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="c0 c1 c16"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="c17"
|
|
||||||
fill="none"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20.83 14.6C19.9 14.06 19.33 13.07 19.33 12C19.33 10.93 19.9 9.93999 20.83 9.39999C20.99 9.29999 21.05 9.1 20.95 8.94L19.28 6.06C19.22 5.95 19.11 5.89001 19 5.89001C18.94 5.89001 18.88 5.91 18.83 5.94C18.37 6.2 17.85 6.34 17.33 6.34C16.8 6.34 16.28 6.19999 15.81 5.92999C14.88 5.38999 14.31 4.41 14.31 3.34C14.31 3.15 14.16 3 13.98 3H10.02C9.83999 3 9.69 3.15 9.69 3.34C9.69 4.41 9.12 5.38999 8.19 5.92999C7.72 6.19999 7.20001 6.34 6.67001 6.34C6.15001 6.34 5.63001 6.2 5.17001 5.94C5.01001 5.84 4.81 5.9 4.72 6.06L3.04001 8.94C3.01001 8.99 3 9.05001 3 9.10001C3 9.22001 3.06001 9.32999 3.17001 9.39999C4.10001 9.93999 4.67001 10.92 4.67001 11.99C4.67001 13.07 4.09999 14.06 3.17999 14.6H3.17001C3.01001 14.7 2.94999 14.9 3.04999 15.06L4.72 17.94C4.78 18.05 4.89 18.11 5 18.11C5.06 18.11 5.12001 18.09 5.17001 18.06C6.11001 17.53 7.26 17.53 8.19 18.07C9.11 18.61 9.67999 19.59 9.67999 20.66C9.67999 20.85 9.82999 21 10.02 21H13.98C14.16 21 14.31 20.85 14.31 20.66C14.31 19.59 14.88 18.61 15.81 18.07C16.28 17.8 16.8 17.66 17.33 17.66C17.85 17.66 18.37 17.8 18.83 18.06C18.99 18.16 19.19 18.1 19.28 17.94L20.96 15.06C20.99 15.01 21 14.95 21 14.9C21 14.78 20.94 14.67 20.83 14.6ZM12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
@ -6882,7 +6882,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
|
|||||||
<span
|
<span
|
||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
~-108834.406%
|
~-105566.373%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -7165,14 +7165,14 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="c3 css-obwv3p"
|
class="c3 css-obwv3p"
|
||||||
>
|
>
|
||||||
0.00000000000000098 DEF
|
0.000000000000000952 DEF
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c13"
|
class="c13"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive.
|
If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive.
|
||||||
<a
|
<a
|
||||||
class="c14"
|
class="c14"
|
||||||
href="https://support.uniswap.org/hc/en-us/articles/8643879653261-What-is-Price-Slippage-"
|
href="https://support.uniswap.org/hc/en-us/articles/8643879653261-What-is-Price-Slippage-"
|
||||||
@ -7549,7 +7549,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="c3 c6 css-142zc9n"
|
class="c3 c6 css-142zc9n"
|
||||||
>
|
>
|
||||||
0.00000000000000098 DEF
|
0.000000000000000952 DEF
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -8761,7 +8761,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
|
|||||||
<span
|
<span
|
||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
~-108834.406%
|
~-105566.373%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -9044,14 +9044,14 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="c3 css-obwv3p"
|
class="c3 css-obwv3p"
|
||||||
>
|
>
|
||||||
0.00000000000000098 DEF
|
0.000000000000000952 DEF
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c13"
|
class="c13"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive.
|
If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive.
|
||||||
<a
|
<a
|
||||||
class="c14"
|
class="c14"
|
||||||
href="https://support.uniswap.org/hc/en-us/articles/8643879653261-What-is-Price-Slippage-"
|
href="https://support.uniswap.org/hc/en-us/articles/8643879653261-What-is-Price-Slippage-"
|
||||||
@ -9428,7 +9428,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="c3 c6 css-142zc9n"
|
class="c3 c6 css-142zc9n"
|
||||||
>
|
>
|
||||||
0.00000000000000098 DEF
|
0.000000000000000952 DEF
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,7 @@ import { AlertTriangle } from 'react-feather'
|
|||||||
import styled, { css } from 'styled-components'
|
import styled, { css } from 'styled-components'
|
||||||
import { Z_INDEX } from 'theme/zIndex'
|
import { Z_INDEX } from 'theme/zIndex'
|
||||||
|
|
||||||
|
import { useIsDarkMode } from '../../theme/components/ThemeToggle'
|
||||||
import { AutoColumn } from '../Column'
|
import { AutoColumn } from '../Column'
|
||||||
|
|
||||||
export const PageWrapper = styled.div`
|
export const PageWrapper = styled.div`
|
||||||
@ -60,6 +61,109 @@ const SwapWrapperInner = styled.div`
|
|||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const UniswapPopoverContainer = styled.div`
|
||||||
|
padding: 18px;
|
||||||
|
color: ${({ theme }) => theme.neutral1};
|
||||||
|
font-weight: 485;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
word-break: break-word;
|
||||||
|
background: ${({ theme }) => theme.surface1};
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid ${({ theme }) => theme.surface3};
|
||||||
|
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)};
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
const springDownKeyframes = `@keyframes spring-down {
|
||||||
|
0% { transform: translateY(-80px); }
|
||||||
|
25% { transform: translateY(4px); }
|
||||||
|
50% { transform: translateY(-1px); }
|
||||||
|
75% { transform: translateY(0px); }
|
||||||
|
100% { transform: translateY(0px); }
|
||||||
|
}`
|
||||||
|
|
||||||
|
const backUpKeyframes = `@keyframes back-up {
|
||||||
|
0% { transform: translateY(0px); }
|
||||||
|
100% { transform: translateY(-80px); }
|
||||||
|
}`
|
||||||
|
|
||||||
|
export const UniswapXShine = (props: any) => {
|
||||||
|
const isDarkMode = useIsDarkMode()
|
||||||
|
return <UniswapXShineInner {...props} style={{ opacity: isDarkMode ? 0.15 : 0.05, ...props.style }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const UniswapXShineInner = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
background: linear-gradient(130deg, transparent 20%, ${({ theme }) => theme.accent1}, transparent 80%);
|
||||||
|
opacity: 0.15;
|
||||||
|
`
|
||||||
|
|
||||||
|
// overflow hidden to hide the SwapMustacheShadow
|
||||||
|
export const SwapOptInSmallContainer = styled.div<{ visible: boolean }>`
|
||||||
|
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: -14px;
|
||||||
|
transform: translateY(${({ visible }) => (visible ? 0 : -80)}px);
|
||||||
|
transition: all ease 400ms;
|
||||||
|
animation: ${({ visible }) => (visible ? `spring-down 900ms ease forwards` : 'back-up 200ms ease forwards')};
|
||||||
|
|
||||||
|
${springDownKeyframes}
|
||||||
|
${backUpKeyframes}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const UniswapXOptInLargeContainerPositioner = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 211px;
|
||||||
|
right: ${-320 - 15}px;
|
||||||
|
width: 320px;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 170px;
|
||||||
|
display: flex;
|
||||||
|
pointer-events: none;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const UniswapXOptInLargeContainer = styled.div<{ visible: boolean }>`
|
||||||
|
opacity: ${({ visible }) => (visible ? 1 : 0)};
|
||||||
|
transform: ${({ visible }) => `translateY(${visible ? 0 : -6}px)`};
|
||||||
|
transition: all ease-in 300ms;
|
||||||
|
transition-delay: ${({ visible }) => (visible ? '350ms' : '0')};
|
||||||
|
pointer-events: ${({ visible }) => (visible ? 'auto' : 'none')};
|
||||||
|
`
|
||||||
|
|
||||||
|
export const SwapMustache = styled.main`
|
||||||
|
position: relative;
|
||||||
|
background: ${({ theme }) => theme.surface1};
|
||||||
|
border-radius: 16px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border: 1px solid ${({ theme }) => theme.surface3};
|
||||||
|
border-top-width: 0;
|
||||||
|
padding: 18px;
|
||||||
|
padding-top: calc(12px + 18px);
|
||||||
|
z-index: 0;
|
||||||
|
transition: transform 250ms ease;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const SwapMustacheShadow = styled.main`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
box-shadow: 0 0 20px 20px ${({ theme }) => theme.surface2};
|
||||||
|
background: red;
|
||||||
|
`
|
||||||
|
|
||||||
export const ArrowWrapper = styled.div<{ clickable: boolean }>`
|
export const ArrowWrapper = styled.div<{ clickable: boolean }>`
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
@ -11,7 +11,7 @@ import COINBASE_ICON from 'assets/wallets/coinbase-icon.svg'
|
|||||||
import UNIWALLET_ICON from 'assets/wallets/uniswap-wallet-icon.png'
|
import UNIWALLET_ICON from 'assets/wallets/uniswap-wallet-icon.png'
|
||||||
import WALLET_CONNECT_ICON from 'assets/wallets/walletconnect-icon.svg'
|
import WALLET_CONNECT_ICON from 'assets/wallets/walletconnect-icon.svg'
|
||||||
import { useSyncExternalStore } from 'react'
|
import { useSyncExternalStore } from 'react'
|
||||||
import { isMobile, isNonSupportedPhone } from 'utils/userAgent'
|
import { isMobile, isNonIOSPhone, isNonSupportedPhone } from 'utils/userAgent'
|
||||||
|
|
||||||
import { RPC_URLS } from '../constants/networks'
|
import { RPC_URLS } from '../constants/networks'
|
||||||
import { DEPRECATED_RPC_PROVIDERS, RPC_PROVIDERS } from '../constants/providers'
|
import { DEPRECATED_RPC_PROVIDERS, RPC_PROVIDERS } from '../constants/providers'
|
||||||
@ -149,7 +149,8 @@ export const uniwalletWCV2ConnectConnection: Connection = {
|
|||||||
hooks: web3WCV2UniwalletConnectHooks,
|
hooks: web3WCV2UniwalletConnectHooks,
|
||||||
type: ConnectionType.UNISWAP_WALLET_V2,
|
type: ConnectionType.UNISWAP_WALLET_V2,
|
||||||
getIcon: () => UNIWALLET_ICON,
|
getIcon: () => UNIWALLET_ICON,
|
||||||
shouldDisplay: () => Boolean(!getIsInjectedMobileBrowser() && !isNonSupportedPhone),
|
shouldDisplay: (isAndroidGALaunched) =>
|
||||||
|
Boolean(!getIsInjectedMobileBrowser() && (isAndroidGALaunched ? !isNonSupportedPhone : !isNonIOSPhone)),
|
||||||
}
|
}
|
||||||
|
|
||||||
const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
|
const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
|
||||||
|
@ -26,6 +26,6 @@ export interface Connection {
|
|||||||
hooks: Web3ReactHooks
|
hooks: Web3ReactHooks
|
||||||
type: ConnectionType
|
type: ConnectionType
|
||||||
getIcon?(isDarkMode: boolean): string
|
getIcon?(isDarkMode: boolean): string
|
||||||
shouldDisplay(): boolean
|
shouldDisplay(isAndroidGALaunched?: boolean): boolean
|
||||||
overrideActivate?: (chainId?: ChainId) => boolean
|
overrideActivate?: (chainId?: ChainId) => boolean
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export const UNI_LIST = 'https://cloudflare-ipfs.com/ipns/tokens.uniswap.org'
|
export const UNI_LIST = 'https://cloudflare-ipfs.com/ipns/tokens.uniswap.org'
|
||||||
export const UNI_EXTENDED_LIST = 'https://cloudflare-ipfs.com/ipns/extendedtokens.uniswap.org'
|
export const UNI_EXTENDED_LIST = 'https://cloudflare-ipfs.com/ipns/extendedtokens.uniswap.org'
|
||||||
|
const UNI_UNSUPPORTED_LIST = 'https://cloudflare-ipfs.com/ipns/unsupportedtokens.uniswap.org'
|
||||||
const AAVE_LIST = 'tokenlist.aave.eth'
|
const AAVE_LIST = 'tokenlist.aave.eth'
|
||||||
|
const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json'
|
||||||
// TODO(WEB-2282): Re-enable CMC list once we have a better solution for handling large lists.
|
// TODO(WEB-2282): Re-enable CMC list once we have a better solution for handling large lists.
|
||||||
// const CMC_ALL_LIST = 'https://s3.coinmarketcap.com/generated/dex/tokens/eth-tokens-all.json'
|
// const CMC_ALL_LIST = 'https://s3.coinmarketcap.com/generated/dex/tokens/eth-tokens-all.json'
|
||||||
const COINGECKO_LIST = 'https://tokens.coingecko.com/uniswap/all.json'
|
const COINGECKO_LIST = 'https://tokens.coingecko.com/uniswap/all.json'
|
||||||
@ -25,6 +27,8 @@ export const AVALANCHE_LIST =
|
|||||||
export const BASE_LIST =
|
export const BASE_LIST =
|
||||||
'https://raw.githubusercontent.com/ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'
|
'https://raw.githubusercontent.com/ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'
|
||||||
|
|
||||||
|
export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST, UNI_UNSUPPORTED_LIST]
|
||||||
|
|
||||||
// default lists to be 'active' aka searched across
|
// default lists to be 'active' aka searched across
|
||||||
export const DEFAULT_ACTIVE_LIST_URLS: string[] = [UNI_LIST]
|
export const DEFAULT_ACTIVE_LIST_URLS: string[] = [UNI_LIST]
|
||||||
export const DEFAULT_INACTIVE_LIST_URLS: string[] = [
|
export const DEFAULT_INACTIVE_LIST_URLS: string[] = [
|
||||||
@ -48,7 +52,8 @@ export const DEFAULT_INACTIVE_LIST_URLS: string[] = [
|
|||||||
CELO_LIST,
|
CELO_LIST,
|
||||||
PLASMA_BNB_LIST,
|
PLASMA_BNB_LIST,
|
||||||
AVALANCHE_LIST,
|
AVALANCHE_LIST,
|
||||||
BASE_LIST
|
BASE_LIST,
|
||||||
|
...UNSUPPORTED_LIST_URLS,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DEFAULT_LIST_OF_LISTS: string[] = [...DEFAULT_ACTIVE_LIST_URLS, ...DEFAULT_INACTIVE_LIST_URLS]
|
export const DEFAULT_LIST_OF_LISTS: string[] = [...DEFAULT_ACTIVE_LIST_URLS, ...DEFAULT_INACTIVE_LIST_URLS]
|
||||||
|
@ -126,27 +126,34 @@ export const FALLBACK_URLS = {
|
|||||||
* These are the URLs used by the interface when there is not another available source of chain data.
|
* These are the URLs used by the interface when there is not another available source of chain data.
|
||||||
*/
|
*/
|
||||||
export const RPC_URLS = {
|
export const RPC_URLS = {
|
||||||
[ChainId.MAINNET]: [`https://rpc.mevblocker.io/fast`, QUICKNODE_MAINNET_RPC_URL, ...FALLBACK_URLS[ChainId.MAINNET]],
|
[ChainId.MAINNET]: [
|
||||||
[ChainId.GOERLI]: [`https://ethereum-goerli.publicnode.com`, ...FALLBACK_URLS[ChainId.GOERLI]],
|
`https://mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||||
|
QUICKNODE_MAINNET_RPC_URL,
|
||||||
|
...FALLBACK_URLS[ChainId.MAINNET],
|
||||||
|
],
|
||||||
|
[ChainId.GOERLI]: [`https://goerli.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.GOERLI]],
|
||||||
[ChainId.SEPOLIA]: [`https://sepolia.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.SEPOLIA]],
|
[ChainId.SEPOLIA]: [`https://sepolia.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.SEPOLIA]],
|
||||||
[ChainId.OPTIMISM]: [`https://optimism.llamarpc.com`, ...FALLBACK_URLS[ChainId.OPTIMISM]],
|
[ChainId.OPTIMISM]: [`https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.OPTIMISM]],
|
||||||
[ChainId.OPTIMISM_GOERLI]: [
|
[ChainId.OPTIMISM_GOERLI]: [
|
||||||
`https://optimism-goerli.infura.io/v3/${INFURA_KEY}`,
|
`https://optimism-goerli.infura.io/v3/${INFURA_KEY}`,
|
||||||
...FALLBACK_URLS[ChainId.OPTIMISM_GOERLI],
|
...FALLBACK_URLS[ChainId.OPTIMISM_GOERLI],
|
||||||
],
|
],
|
||||||
[ChainId.ARBITRUM_ONE]: [`https://arbitrum.llamarpc.com`, ...FALLBACK_URLS[ChainId.ARBITRUM_ONE]],
|
[ChainId.ARBITRUM_ONE]: [
|
||||||
|
`https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||||
|
...FALLBACK_URLS[ChainId.ARBITRUM_ONE],
|
||||||
|
],
|
||||||
[ChainId.ARBITRUM_GOERLI]: [
|
[ChainId.ARBITRUM_GOERLI]: [
|
||||||
`https://arbitrum-goerli.infura.io/v3/${INFURA_KEY}`,
|
`https://arbitrum-goerli.infura.io/v3/${INFURA_KEY}`,
|
||||||
...FALLBACK_URLS[ChainId.ARBITRUM_GOERLI],
|
...FALLBACK_URLS[ChainId.ARBITRUM_GOERLI],
|
||||||
],
|
],
|
||||||
[ChainId.POLYGON]: [`https://polygon.llamarpc.com`, ...FALLBACK_URLS[ChainId.POLYGON]],
|
[ChainId.POLYGON]: [`https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.POLYGON]],
|
||||||
[ChainId.POLYGON_MUMBAI]: [
|
[ChainId.POLYGON_MUMBAI]: [
|
||||||
`https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`,
|
`https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`,
|
||||||
...FALLBACK_URLS[ChainId.POLYGON_MUMBAI],
|
...FALLBACK_URLS[ChainId.POLYGON_MUMBAI],
|
||||||
],
|
],
|
||||||
[ChainId.CELO]: FALLBACK_URLS[ChainId.CELO],
|
[ChainId.CELO]: FALLBACK_URLS[ChainId.CELO],
|
||||||
[ChainId.CELO_ALFAJORES]: FALLBACK_URLS[ChainId.CELO_ALFAJORES],
|
[ChainId.CELO_ALFAJORES]: FALLBACK_URLS[ChainId.CELO_ALFAJORES],
|
||||||
[ChainId.BNB]: ['https://bsc.publicnode.com', 'https://binance.llamarpc.com', ...FALLBACK_URLS[ChainId.BNB]],
|
[ChainId.BNB]: [QUICKNODE_BNB_RPC_URL, ...FALLBACK_URLS[ChainId.BNB]],
|
||||||
[ChainId.AVALANCHE]: [`https://avalanche-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.AVALANCHE]],
|
[ChainId.AVALANCHE]: [`https://avalanche-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.AVALANCHE]],
|
||||||
[ChainId.BASE]: [`https://base-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.BASE]],
|
[ChainId.BASE]: [`https://base-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.BASE]],
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import {
|
|||||||
OP,
|
OP,
|
||||||
PORTAL_ETH_CELO,
|
PORTAL_ETH_CELO,
|
||||||
PORTAL_USDC_CELO,
|
PORTAL_USDC_CELO,
|
||||||
TORN_MAINNET,
|
|
||||||
USDC_ARBITRUM,
|
USDC_ARBITRUM,
|
||||||
USDC_ARBITRUM_GOERLI,
|
USDC_ARBITRUM_GOERLI,
|
||||||
USDC_AVALANCHE,
|
USDC_AVALANCHE,
|
||||||
@ -68,10 +67,10 @@ const WRAPPED_NATIVE_CURRENCIES_ONLY: ChainTokenList = Object.fromEntries(
|
|||||||
export const COMMON_BASES: ChainCurrencyList = {
|
export const COMMON_BASES: ChainCurrencyList = {
|
||||||
[ChainId.MAINNET]: [
|
[ChainId.MAINNET]: [
|
||||||
nativeOnChain(ChainId.MAINNET),
|
nativeOnChain(ChainId.MAINNET),
|
||||||
TORN_MAINNET,
|
|
||||||
DAI,
|
DAI,
|
||||||
USDC_MAINNET,
|
USDC_MAINNET,
|
||||||
USDT,
|
USDT,
|
||||||
|
WBTC,
|
||||||
WRAPPED_NATIVE_CURRENCY[ChainId.MAINNET] as Token,
|
WRAPPED_NATIVE_CURRENCY[ChainId.MAINNET] as Token,
|
||||||
],
|
],
|
||||||
[ChainId.GOERLI]: [nativeOnChain(ChainId.GOERLI), WRAPPED_NATIVE_CURRENCY[ChainId.GOERLI] as Token],
|
[ChainId.GOERLI]: [nativeOnChain(ChainId.GOERLI), WRAPPED_NATIVE_CURRENCY[ChainId.GOERLI] as Token],
|
||||||
|
@ -93,6 +93,10 @@ export function checkWarning(tokenAddress: string, chainId?: number | null) {
|
|||||||
return null
|
return null
|
||||||
case TOKEN_LIST_TYPES.UNI_EXTENDED:
|
case TOKEN_LIST_TYPES.UNI_EXTENDED:
|
||||||
return MediumWarning
|
return MediumWarning
|
||||||
|
case TOKEN_LIST_TYPES.UNKNOWN:
|
||||||
|
return StrongWarning
|
||||||
|
case TOKEN_LIST_TYPES.BLOCKED:
|
||||||
|
return BlockedWarning
|
||||||
case TOKEN_LIST_TYPES.BROKEN:
|
case TOKEN_LIST_TYPES.BROKEN:
|
||||||
return BlockedWarning
|
return BlockedWarning
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { TokenInfo } from '@uniswap/token-lists'
|
|||||||
import { ListsState } from 'state/lists/reducer'
|
import { ListsState } from 'state/lists/reducer'
|
||||||
|
|
||||||
import store from '../state'
|
import store from '../state'
|
||||||
import { UNI_EXTENDED_LIST, UNI_LIST} from './lists'
|
import { UNI_EXTENDED_LIST, UNI_LIST, UNSUPPORTED_LIST_URLS } from './lists'
|
||||||
import { COMMON_BASES } from './routing'
|
import { COMMON_BASES } from './routing'
|
||||||
import brokenTokenList from './tokenLists/broken.tokenlist.json'
|
import brokenTokenList from './tokenLists/broken.tokenlist.json'
|
||||||
import { NATIVE_CHAIN_ID } from './tokens'
|
import { NATIVE_CHAIN_ID } from './tokens'
|
||||||
@ -37,6 +37,14 @@ class TokenSafetyLookupTable {
|
|||||||
brokenTokenList.tokens.forEach((token) => {
|
brokenTokenList.tokens.forEach((token) => {
|
||||||
this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BROKEN
|
this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BROKEN
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Initialize blocked tokens from all urls included
|
||||||
|
UNSUPPORTED_LIST_URLS.map((url) => lists.byUrl[url]?.current?.tokens)
|
||||||
|
.filter((x): x is TokenInfo[] => !!x)
|
||||||
|
.flat(1)
|
||||||
|
.forEach((token) => {
|
||||||
|
this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BLOCKED
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
checkToken(address: string, chainId?: number | null) {
|
checkToken(address: string, chainId?: number | null) {
|
||||||
|
@ -15,13 +15,6 @@ export const USDC_MAINNET = new Token(
|
|||||||
'USDC',
|
'USDC',
|
||||||
'USD//C'
|
'USD//C'
|
||||||
)
|
)
|
||||||
export const TORN_MAINNET = new Token(
|
|
||||||
ChainId.MAINNET,
|
|
||||||
'0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
|
|
||||||
18,
|
|
||||||
'TORN',
|
|
||||||
'Tornado Cash'
|
|
||||||
)
|
|
||||||
const USDC_GOERLI = new Token(ChainId.GOERLI, '0x07865c6e87b9f70255377e024ace6630c1eaa37f', 6, 'USDC', 'USD//C')
|
const USDC_GOERLI = new Token(ChainId.GOERLI, '0x07865c6e87b9f70255377e024ace6630c1eaa37f', 6, 'USDC', 'USD//C')
|
||||||
const USDC_SEPOLIA = new Token(ChainId.SEPOLIA, '0x6f14C02Fc1F78322cFd7d707aB90f18baD3B54f5', 6, 'USDC', 'USD//C')
|
const USDC_SEPOLIA = new Token(ChainId.SEPOLIA, '0x6f14C02Fc1F78322cFd7d707aB90f18baD3B54f5', 6, 'USDC', 'USD//C')
|
||||||
export const USDC_OPTIMISM = new Token(
|
export const USDC_OPTIMISM = new Token(
|
||||||
|
10
src/featureFlags/flags/androidGALaunch.ts
Normal file
10
src/featureFlags/flags/androidGALaunch.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||||
|
|
||||||
|
export function useAndroidGALaunchFlag(): BaseVariant {
|
||||||
|
return useBaseFlag(FeatureFlag.androidGALaunch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo(kristiehuang): add statsig flag after staging goes out
|
||||||
|
export function useAndroidGALaunchFlagEnabled(): boolean {
|
||||||
|
return useAndroidGALaunchFlag() === BaseVariant.Enabled
|
||||||
|
}
|
9
src/featureFlags/flags/fotAdjustments.ts
Normal file
9
src/featureFlags/flags/fotAdjustments.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||||
|
|
||||||
|
export function useFotAdjustmentsFlag(): BaseVariant {
|
||||||
|
return useBaseFlag(FeatureFlag.fotAdjustedmentsEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFotAdjustmentsEnabled(): boolean {
|
||||||
|
return useFotAdjustmentsFlag() === BaseVariant.Enabled
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
|
||||||
|
|
||||||
export function useLimitsEnabledFlag(): BaseVariant {
|
|
||||||
return useBaseFlag(FeatureFlag.limitsEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLimitsEnabled(): boolean {
|
|
||||||
return useLimitsEnabledFlag() === BaseVariant.Enabled
|
|
||||||
}
|
|
9
src/featureFlags/flags/uniswapXDefault.ts
Normal file
9
src/featureFlags/flags/uniswapXDefault.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||||
|
|
||||||
|
export function useUniswapXDefaultEnabledFlag(): BaseVariant {
|
||||||
|
return useBaseFlag(FeatureFlag.uniswapXDefaultEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUniswapXDefaultEnabled(): boolean {
|
||||||
|
return useUniswapXDefaultEnabledFlag() === BaseVariant.Enabled
|
||||||
|
}
|
9
src/featureFlags/flags/uniswapXEthOutput.ts
Normal file
9
src/featureFlags/flags/uniswapXEthOutput.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||||
|
|
||||||
|
export function useUniswapXEthOutputFlag(): BaseVariant {
|
||||||
|
return useBaseFlag(FeatureFlag.uniswapXEthOutputEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUniswapXEthOutputEnabled(): boolean {
|
||||||
|
return useUniswapXEthOutputFlag() === BaseVariant.Enabled
|
||||||
|
}
|
9
src/featureFlags/flags/uniswapXExactOutput.ts
Normal file
9
src/featureFlags/flags/uniswapXExactOutput.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||||
|
|
||||||
|
export function useUniswapXExactOutputFlag(): BaseVariant {
|
||||||
|
return useBaseFlag(FeatureFlag.uniswapXExactOutputEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUniswapXExactOutputEnabled(): boolean {
|
||||||
|
return useUniswapXExactOutputFlag() === BaseVariant.Enabled
|
||||||
|
}
|
@ -10,16 +10,20 @@ export enum FeatureFlag {
|
|||||||
debounceSwapQuote = 'debounce_swap_quote',
|
debounceSwapQuote = 'debounce_swap_quote',
|
||||||
fallbackProvider = 'fallback_provider',
|
fallbackProvider = 'fallback_provider',
|
||||||
uniswapXSyntheticQuote = 'uniswapx_synthetic_quote',
|
uniswapXSyntheticQuote = 'uniswapx_synthetic_quote',
|
||||||
|
uniswapXEthOutputEnabled = 'uniswapx_eth_output_enabled',
|
||||||
|
uniswapXExactOutputEnabled = 'uniswapx_exact_output_enabled',
|
||||||
multichainUX = 'multichain_ux',
|
multichainUX = 'multichain_ux',
|
||||||
currencyConversion = 'currency_conversion',
|
currencyConversion = 'currency_conversion',
|
||||||
|
fotAdjustedmentsEnabled = 'fot_dynamic_adjustments_enabled',
|
||||||
infoExplore = 'info_explore',
|
infoExplore = 'info_explore',
|
||||||
infoTDP = 'info_tdp',
|
infoTDP = 'info_tdp',
|
||||||
infoPoolPage = 'info_pool_page',
|
infoPoolPage = 'info_pool_page',
|
||||||
infoLiveViews = 'info_live_views',
|
infoLiveViews = 'info_live_views',
|
||||||
|
uniswapXDefaultEnabled = 'uniswapx_default_enabled',
|
||||||
quickRouteMainnet = 'enable_quick_route_mainnet',
|
quickRouteMainnet = 'enable_quick_route_mainnet',
|
||||||
progressIndicatorV2 = 'progress_indicator_v2',
|
progressIndicatorV2 = 'progress_indicator_v2',
|
||||||
feesEnabled = 'fees_enabled',
|
feesEnabled = 'fees_enabled',
|
||||||
limitsEnabled = 'limits_enabled',
|
androidGALaunch = 'android_ga_launch',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FeatureFlagsContextType {
|
interface FeatureFlagsContextType {
|
||||||
|
@ -97,4 +97,7 @@ gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export type { Chain, TokenQuery } from './__generated__/types-and-hooks'
|
||||||
|
|
||||||
export type TokenQueryData = TokenQuery['token']
|
export type TokenQueryData = TokenQuery['token']
|
||||||
|
@ -49,22 +49,6 @@ export const apolloClient = new ApolloClient({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TokenProject: {
|
|
||||||
fields: {
|
|
||||||
tokens: {
|
|
||||||
// cache data may be lost when replacing the tokens array
|
|
||||||
merge(existing, incoming) {
|
|
||||||
if (!existing) {
|
|
||||||
return incoming
|
|
||||||
} else if (Array.isArray(existing)) {
|
|
||||||
return [...existing, ...incoming]
|
|
||||||
} else {
|
|
||||||
return [existing, ...incoming]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
|
@ -1,39 +1,6 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
import { PortfolioTokenBalancePartsFragment } from './__generated__/types-and-hooks'
|
|
||||||
|
|
||||||
gql`
|
gql`
|
||||||
fragment PortfolioTokenBalanceParts on TokenBalance {
|
|
||||||
id
|
|
||||||
quantity
|
|
||||||
denominatedValue {
|
|
||||||
id
|
|
||||||
currency
|
|
||||||
value
|
|
||||||
}
|
|
||||||
token {
|
|
||||||
id
|
|
||||||
chain
|
|
||||||
address
|
|
||||||
name
|
|
||||||
symbol
|
|
||||||
standard
|
|
||||||
decimals
|
|
||||||
}
|
|
||||||
tokenProjectMarket {
|
|
||||||
id
|
|
||||||
pricePercentChange(duration: DAY) {
|
|
||||||
id
|
|
||||||
value
|
|
||||||
}
|
|
||||||
tokenProject {
|
|
||||||
id
|
|
||||||
logoUrl
|
|
||||||
isSpam
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query PortfolioBalances($ownerAddress: String!, $chains: [Chain!]!) {
|
query PortfolioBalances($ownerAddress: String!, $chains: [Chain!]!) {
|
||||||
portfolios(ownerAddresses: [$ownerAddress], chains: $chains) {
|
portfolios(ownerAddresses: [$ownerAddress], chains: $chains) {
|
||||||
id
|
id
|
||||||
@ -52,10 +19,35 @@ gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokenBalances {
|
tokenBalances {
|
||||||
...PortfolioTokenBalanceParts
|
id
|
||||||
|
quantity
|
||||||
|
denominatedValue {
|
||||||
|
id
|
||||||
|
currency
|
||||||
|
value
|
||||||
|
}
|
||||||
|
tokenProjectMarket {
|
||||||
|
id
|
||||||
|
pricePercentChange(duration: DAY) {
|
||||||
|
id
|
||||||
|
value
|
||||||
|
}
|
||||||
|
tokenProject {
|
||||||
|
id
|
||||||
|
logoUrl
|
||||||
|
isSpam
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token {
|
||||||
|
id
|
||||||
|
chain
|
||||||
|
address
|
||||||
|
name
|
||||||
|
symbol
|
||||||
|
standard
|
||||||
|
decimals
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export type PortfolioToken = NonNullable<PortfolioTokenBalancePartsFragment['token']>
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { Chain } from 'graphql/data/__generated__/types-and-hooks'
|
import { Chain } from 'graphql/data/Token'
|
||||||
import { chainIdToBackendName } from 'graphql/data/util'
|
import { chainIdToBackendName } from 'graphql/data/util'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ export function useIsPoolsPage() {
|
|||||||
pathname.startsWith('/pools') ||
|
pathname.startsWith('/pools') ||
|
||||||
pathname.startsWith('/pool') ||
|
pathname.startsWith('/pool') ||
|
||||||
pathname.startsWith('/add') ||
|
pathname.startsWith('/add') ||
|
||||||
pathname.startsWith('/remove')
|
pathname.startsWith('/remove') ||
|
||||||
|
pathname.startsWith('/increase')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -84,13 +84,13 @@ export function useSwapCallback(
|
|||||||
? {
|
? {
|
||||||
tradeType: TradeType.EXACT_INPUT,
|
tradeType: TradeType.EXACT_INPUT,
|
||||||
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
||||||
expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
|
expectedOutputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(),
|
||||||
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(),
|
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
tradeType: TradeType.EXACT_OUTPUT,
|
tradeType: TradeType.EXACT_OUTPUT,
|
||||||
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(),
|
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(),
|
||||||
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
|
outputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(),
|
||||||
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,12 @@ export function useUniversalRouterSwapCallback(
|
|||||||
|
|
||||||
setTraceData('slippageTolerance', options.slippageTolerance.toFixed(2))
|
setTraceData('slippageTolerance', options.slippageTolerance.toFixed(2))
|
||||||
|
|
||||||
|
// universal-router-sdk reconstructs V2Trade objects, so rather than updating the trade amounts to account for tax, we adjust the slippage tolerance as a workaround
|
||||||
|
// TODO(WEB-2725): update universal-router-sdk to not reconstruct trades
|
||||||
|
const taxAdjustedSlippageTolerance = options.slippageTolerance.add(trade.totalTaxRate)
|
||||||
|
|
||||||
const { calldata: data, value } = SwapRouter.swapERC20CallParameters(trade, {
|
const { calldata: data, value } = SwapRouter.swapERC20CallParameters(trade, {
|
||||||
slippageTolerance: options.slippageTolerance,
|
slippageTolerance: taxAdjustedSlippageTolerance,
|
||||||
deadlineOrPreviousBlockhash: options.deadline?.toString(),
|
deadlineOrPreviousBlockhash: options.deadline?.toString(),
|
||||||
inputTokenPermit: options.permit,
|
inputTokenPermit: options.permit,
|
||||||
fee: options.feeOptions,
|
fee: options.feeOptions,
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react'
|
import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react'
|
||||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||||
|
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
||||||
|
import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput'
|
||||||
|
import { useUniswapXExactOutputEnabled } from 'featureFlags/flags/uniswapXExactOutput'
|
||||||
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||||
import { useFeesEnabled } from 'featureFlags/flags/useFees'
|
import { useFeesEnabled } from 'featureFlags/flags/useFees'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
|
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
|
||||||
import { currencyAddressForSwapQuote } from 'state/routing/utils'
|
import { currencyAddressForSwapQuote } from 'state/routing/utils'
|
||||||
|
import { useUserDisabledUniswapX, useUserOptedOutOfUniswapX } from 'state/user/hooks'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns query arguments for the Routing API query or undefined if the
|
* Returns query arguments for the Routing API query or undefined if the
|
||||||
@ -18,6 +22,8 @@ export function useRoutingAPIArguments({
|
|||||||
amount,
|
amount,
|
||||||
tradeType,
|
tradeType,
|
||||||
routerPreference,
|
routerPreference,
|
||||||
|
inputTax,
|
||||||
|
outputTax,
|
||||||
}: {
|
}: {
|
||||||
account?: string
|
account?: string
|
||||||
tokenIn?: Currency
|
tokenIn?: Currency
|
||||||
@ -25,8 +31,15 @@ export function useRoutingAPIArguments({
|
|||||||
amount?: CurrencyAmount<Currency>
|
amount?: CurrencyAmount<Currency>
|
||||||
tradeType: TradeType
|
tradeType: TradeType
|
||||||
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
||||||
|
inputTax: Percent
|
||||||
|
outputTax: Percent
|
||||||
}): GetQuoteArgs | SkipToken {
|
}): GetQuoteArgs | SkipToken {
|
||||||
const uniswapXForceSyntheticQuotes = useUniswapXSyntheticQuoteEnabled()
|
const uniswapXForceSyntheticQuotes = useUniswapXSyntheticQuoteEnabled()
|
||||||
|
const userDisabledUniswapX = useUserDisabledUniswapX()
|
||||||
|
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
||||||
|
const uniswapXEthOutputEnabled = useUniswapXEthOutputEnabled()
|
||||||
|
const uniswapXExactOutputEnabled = useUniswapXExactOutputEnabled()
|
||||||
|
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
||||||
|
|
||||||
const feesEnabled = useFeesEnabled()
|
const feesEnabled = useFeesEnabled()
|
||||||
// Don't enable fee logic if this is a quote for pricing
|
// Don't enable fee logic if this is a quote for pricing
|
||||||
@ -51,8 +64,31 @@ export function useRoutingAPIArguments({
|
|||||||
tradeType,
|
tradeType,
|
||||||
needsWrapIfUniswapX: tokenIn.isNative,
|
needsWrapIfUniswapX: tokenIn.isNative,
|
||||||
uniswapXForceSyntheticQuotes,
|
uniswapXForceSyntheticQuotes,
|
||||||
|
userDisabledUniswapX,
|
||||||
|
userOptedOutOfUniswapX,
|
||||||
|
uniswapXEthOutputEnabled,
|
||||||
|
uniswapXExactOutputEnabled,
|
||||||
|
isUniswapXDefaultEnabled,
|
||||||
sendPortionEnabled,
|
sendPortionEnabled,
|
||||||
|
inputTax,
|
||||||
|
outputTax,
|
||||||
},
|
},
|
||||||
[account, amount, routerPreference, tokenIn, tokenOut, tradeType, uniswapXForceSyntheticQuotes, sendPortionEnabled]
|
[
|
||||||
|
account,
|
||||||
|
amount,
|
||||||
|
routerPreference,
|
||||||
|
tokenIn,
|
||||||
|
tokenOut,
|
||||||
|
tradeType,
|
||||||
|
uniswapXExactOutputEnabled,
|
||||||
|
uniswapXForceSyntheticQuotes,
|
||||||
|
userDisabledUniswapX,
|
||||||
|
userOptedOutOfUniswapX,
|
||||||
|
uniswapXEthOutputEnabled,
|
||||||
|
isUniswapXDefaultEnabled,
|
||||||
|
sendPortionEnabled,
|
||||||
|
inputTax,
|
||||||
|
outputTax,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -68,12 +68,9 @@ export function useTokenFromActiveNetwork(tokenAddress: string | undefined): Tok
|
|||||||
// If the token is on another chain, we cannot fetch it on-chain, and it is invalid.
|
// If the token is on another chain, we cannot fetch it on-chain, and it is invalid.
|
||||||
if (typeof tokenAddress !== 'string' || !isSupportedChain(chainId) || !formattedAddress) return undefined
|
if (typeof tokenAddress !== 'string' || !isSupportedChain(chainId) || !formattedAddress) return undefined
|
||||||
if (isLoading || !chainId) return null
|
if (isLoading || !chainId) return null
|
||||||
if (!decimals?.result?.[0] && parsedSymbol === UNKNOWN_TOKEN_SYMBOL && parsedName === UNKNOWN_TOKEN_NAME) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Token(chainId, formattedAddress, parsedDecimals, parsedSymbol, parsedName)
|
return new Token(chainId, formattedAddress, parsedDecimals, parsedSymbol, parsedName)
|
||||||
}, [tokenAddress, chainId, formattedAddress, isLoading, decimals?.result, parsedDecimals, parsedSymbol, parsedName])
|
}, [chainId, tokenAddress, formattedAddress, isLoading, parsedDecimals, parsedSymbol, parsedName])
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenMap = { [address: string]: Token }
|
type TokenMap = { [address: string]: Token }
|
||||||
|
@ -108,10 +108,6 @@ export function useTokenBalance(account?: string, token?: Token): CurrencyAmount
|
|||||||
return tokenBalances[token.address]
|
return tokenBalances[token.address]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns balances for tokens on currently-connected chainId via RPC.
|
|
||||||
* See useCachedPortfolioBalancesQuery for multichain portfolio balances via GQL.
|
|
||||||
*/
|
|
||||||
export function useCurrencyBalances(
|
export function useCurrencyBalances(
|
||||||
account?: string,
|
account?: string,
|
||||||
currencies?: (Currency | undefined)[]
|
currencies?: (Currency | undefined)[]
|
||||||
|
@ -51,7 +51,7 @@ export function formatCommonPropertiesForTrade(
|
|||||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
token_out_amount: formatToDecimal(trade.postTaxOutputAmount, trade.outputAmount.currency.decimals),
|
||||||
price_impact_basis_points: isClassicTrade(trade)
|
price_impact_basis_points: isClassicTrade(trade)
|
||||||
? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade))
|
? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade))
|
||||||
: undefined,
|
: undefined,
|
||||||
@ -64,8 +64,6 @@ export function formatCommonPropertiesForTrade(
|
|||||||
allowed_slippage: formatPercentNumber(allowedSlippage),
|
allowed_slippage: formatPercentNumber(allowedSlippage),
|
||||||
method: getQuoteMethod(trade),
|
method: getQuoteMethod(trade),
|
||||||
fee_usd: outputFeeFiatValue,
|
fee_usd: outputFeeFiatValue,
|
||||||
token_out_detected_tax: formatPercentNumber(trade.outputTax),
|
|
||||||
token_in_detected_tax: formatPercentNumber(trade.inputTax),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +101,8 @@ export const formatSwapQuoteReceivedEventProperties = (
|
|||||||
trade: InterfaceTrade,
|
trade: InterfaceTrade,
|
||||||
allowedSlippage: Percent,
|
allowedSlippage: Percent,
|
||||||
swapQuoteLatencyMs: number | undefined,
|
swapQuoteLatencyMs: number | undefined,
|
||||||
|
inputTax: Percent,
|
||||||
|
outputTax: Percent,
|
||||||
outputFeeFiatValue: number | undefined
|
outputFeeFiatValue: number | undefined
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
@ -112,5 +112,7 @@ export const formatSwapQuoteReceivedEventProperties = (
|
|||||||
token_in_amount_max: trade.maximumAmountIn(allowedSlippage).toExact(),
|
token_in_amount_max: trade.maximumAmountIn(allowedSlippage).toExact(),
|
||||||
token_out_amount_min: trade.minimumAmountOut(allowedSlippage).toExact(),
|
token_out_amount_min: trade.minimumAmountOut(allowedSlippage).toExact(),
|
||||||
quote_latency_milliseconds: swapQuoteLatencyMs,
|
quote_latency_milliseconds: swapQuoteLatencyMs,
|
||||||
|
token_out_detected_tax: formatPercentNumber(outputTax),
|
||||||
|
token_in_detected_tax: formatPercentNumber(inputTax),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3816
src/locales/af-ZA.po
Normal file
3816
src/locales/af-ZA.po
Normal file
File diff suppressed because it is too large
Load Diff
3816
src/locales/ar-SA.po
Normal file
3816
src/locales/ar-SA.po
Normal file
File diff suppressed because it is too large
Load Diff
3816
src/locales/ca-ES.po
Normal file
3816
src/locales/ca-ES.po
Normal file
File diff suppressed because it is too large
Load Diff
3816
src/locales/cs-CZ.po
Normal file
3816
src/locales/cs-CZ.po
Normal file
File diff suppressed because it is too large
Load Diff
3816
src/locales/da-DK.po
Normal file
3816
src/locales/da-DK.po
Normal file
File diff suppressed because it is too large
Load Diff
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