Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d69e8b3022 | ||
|
|
15a2446ec8 | ||
|
|
dc5775e318 | ||
|
|
95bb70cf4f | ||
|
|
e454521a97 | ||
|
|
6b99656c1b | ||
|
|
e1436c30c5 | ||
|
|
16e4e0aefa | ||
|
|
9262fec093 | ||
|
|
ff3bcc4693 | ||
|
|
34a22ef9f0 | ||
|
|
5e86cf7b29 | ||
|
|
83172dc5ea | ||
|
|
0ca68bb140 | ||
|
|
430356da9a | ||
|
|
1c50460160 | ||
|
|
2ef9e9b61c | ||
|
|
b906cddc6a | ||
|
|
65fe8d4168 | ||
|
|
8956fba629 | ||
|
|
04884fe1b0 | ||
|
|
ef28667d13 | ||
|
|
0ced5f2402 | ||
|
|
252bf32d2f | ||
|
|
01e87657c6 | ||
|
|
55bf30c0e0 | ||
|
|
95f61487e8 | ||
|
|
3ed3ed4994 | ||
|
|
3a0c4ad4db | ||
|
|
803eb46e5f | ||
|
|
a3f0c54f66 | ||
|
|
52796fcc55 | ||
|
|
ca02a6b56a | ||
|
|
b722a20d96 | ||
|
|
9b5261aaeb | ||
|
|
0efb7f51a4 | ||
|
|
bbe42b81de | ||
|
|
e9f469d399 | ||
|
|
4c58258f01 | ||
|
|
f6e6db6430 | ||
|
|
dab445f237 | ||
|
|
da90738ba1 | ||
|
|
e4dbef80eb | ||
|
|
e169c9d900 | ||
|
|
c8a17f5fe9 | ||
|
|
552a38994b | ||
|
|
b313ac9843 | ||
|
|
88aec2c894 | ||
|
|
86677ed193 | ||
|
|
accf0905f8 | ||
|
|
ef5065de48 | ||
|
|
bcb45cded6 | ||
|
|
dfe50b4bee | ||
|
|
90f72e05b9 | ||
|
|
3a0f6920d0 | ||
|
|
07eb9eb9a2 | ||
|
|
a9e8e8b275 | ||
|
|
4708d3d3d7 | ||
|
|
27acddc0e7 | ||
|
|
102a935ff8 | ||
|
|
614c15243e | ||
|
|
082db21308 | ||
|
|
0f75c6a52b | ||
|
|
1c2ed1d9e4 | ||
|
|
0e956fb7c4 | ||
|
|
b49dd03e25 | ||
|
|
82211a888c | ||
|
|
5dd8cbbdc9 | ||
|
|
5640c115de | ||
|
|
7b8114b401 | ||
|
|
8e36361866 | ||
|
|
0a04cff7eb | ||
|
|
d3dd1d4ebd | ||
|
|
50f2e2770a | ||
|
|
830cd07f38 | ||
|
|
326ae58f04 | ||
|
|
25f5a7178f | ||
|
|
e5591e8f06 | ||
|
|
1c4a383a49 | ||
|
|
469a006088 | ||
|
|
c673c9e458 | ||
|
|
dd957d07e4 | ||
|
|
3837ce24ac | ||
|
|
4b87e3d9b8 | ||
|
|
d6759b86e3 | ||
|
|
df55456409 | ||
|
|
f290787b99 | ||
|
|
2f84507a23 | ||
|
|
96c58361a5 | ||
|
|
011136d0e9 | ||
|
|
054d1de88a | ||
|
|
ebab00d7bd | ||
|
|
01dc10d4f3 | ||
|
|
fb3abf275e | ||
|
|
f6ad694200 | ||
|
|
a3d72a4bbc | ||
|
|
1bb750f136 | ||
|
|
5315272694 | ||
|
|
43b9e398b5 | ||
|
|
1247989cf4 | ||
|
|
45a5ca3b88 | ||
|
|
54b4567a81 | ||
|
|
fc45a504fb | ||
|
|
052cc69414 | ||
|
|
5caaaf1b1f | ||
|
|
c0163767ed | ||
|
|
342b0c81f6 | ||
|
|
cb21750b87 | ||
|
|
2e8ef480f9 | ||
|
|
f27bba9ffa | ||
|
|
6528fd136e | ||
|
|
eb802266e1 | ||
|
|
5bec0b78da | ||
|
|
4894460821 | ||
|
|
f3889e326e | ||
|
|
2d61c72588 | ||
|
|
1a634c350a | ||
|
|
35b83ab842 | ||
|
|
c0d42ade6f | ||
|
|
0e2344ba85 | ||
|
|
309d03b5e7 | ||
|
|
8d32e315ed | ||
|
|
1c8b3f0339 | ||
|
|
fc83659041 | ||
|
|
e8f5a0e8c8 | ||
|
|
9e213fc396 | ||
|
|
f10ba73529 | ||
|
|
c2a83cabaa | ||
|
|
7a3c51bc90 | ||
|
|
e69a7c2712 | ||
|
|
d149512d93 | ||
|
|
5826ed15c8 | ||
|
|
3db9e1b9a4 | ||
|
|
79a72d6fe2 | ||
|
|
f3bfd4ad41 | ||
|
|
45acf421b3 | ||
|
|
2c5ea67ada | ||
|
|
2e141ac94c | ||
|
|
8b16f454ca | ||
|
|
094664dc7a | ||
|
|
62a6ef00da | ||
|
|
3a739476ca | ||
|
|
04e4335cc2 | ||
|
|
4235b57cd8 | ||
|
|
cb362f1b2c | ||
|
|
a4d61d8eaa | ||
|
|
310623b948 | ||
|
|
b7303fb9c0 | ||
|
|
4fbb8e9117 | ||
|
|
f0502bfc33 | ||
|
|
98f4af55c9 | ||
|
|
08b8bdd769 | ||
|
|
1283199d0d | ||
|
|
c1fff5ea49 | ||
|
|
94adc449a1 | ||
|
|
4b24e5f754 | ||
|
|
e0a531e538 | ||
|
|
8cef1ca0f7 | ||
|
|
088f1d9ae4 | ||
|
|
89a7d98b41 | ||
|
|
0076fdc65b | ||
|
|
48b4a533c3 | ||
|
|
0b66fde26c | ||
|
|
5788385951 | ||
|
|
0891e67528 | ||
|
|
b319acd9c4 | ||
|
|
05977f950b | ||
|
|
5ac36d4156 | ||
|
|
fb998706c2 | ||
|
|
c45492c890 | ||
|
|
41219b435f | ||
|
|
e1321843de | ||
|
|
0baa8a1fff | ||
|
|
f2a3b66357 | ||
|
|
f2af46037e | ||
|
|
20a06c9b5a | ||
|
|
8ef54d41b6 | ||
|
|
1cdddd1321 | ||
|
|
f834af69fe | ||
|
|
08cd4bec41 | ||
|
|
63ac64f470 | ||
|
|
72686f1e32 | ||
|
|
c07359362f | ||
|
|
ed58c39bdc | ||
|
|
5d2254be27 | ||
|
|
83f4b53f55 | ||
|
|
4b5e2f7f16 | ||
|
|
0c5d915638 | ||
|
|
a03231d356 | ||
|
|
72936322b3 | ||
|
|
1bc6eb9a23 | ||
|
|
8954aa792a | ||
|
|
379437b720 | ||
|
|
02c0dee089 | ||
|
|
c55e1af101 | ||
|
|
87cbd1ab38 | ||
|
|
1090e97bb5 | ||
|
|
921c6b105f | ||
|
|
bc08e9263d | ||
|
|
8c8300a5de | ||
|
|
3f169adcf2 | ||
|
|
774368f325 | ||
|
|
f83f15d37a | ||
|
|
d9f1402576 | ||
|
|
d81cb28010 | ||
|
|
b57a5d7ddb |
2
.env
@@ -11,3 +11,5 @@ REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
|
||||
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
|
||||
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
REACT_APP_UNISWAP_API_URL="https://api.uniswap.org/v2"
|
||||
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"
|
||||
|
||||
18
.eslintrc.js
@@ -13,7 +13,6 @@ module.exports = {
|
||||
files: ['**/*'],
|
||||
rules: {
|
||||
'multiline-comment-style': ['error', 'separate-lines'],
|
||||
'rulesdir/enforce-retry-on-import': 'error',
|
||||
'rulesdir/no-undefined-or': 'error',
|
||||
},
|
||||
},
|
||||
@@ -58,5 +57,22 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
excludedFiles: ['src/analytics/*'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: '@uniswap/analytics',
|
||||
message: `Do not import from '@uniswap/analytics' directly. Use 'analytics' instead.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
32
.github/actions/cache-on-main/action.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Cache on main
|
||||
description: caches node_modules/.cache, but only saves from main
|
||||
inputs:
|
||||
path:
|
||||
description: 'A list of files, directories, and wildcard patterns to cache and store'
|
||||
required: true
|
||||
key:
|
||||
description: 'An explicit key for restoring and saving the cache'
|
||||
required: true
|
||||
restore-keys:
|
||||
description: 'An ordered list of keys to use for restoring stale cache if no cache hit occured for key. Note `cache-hit` returns false in this case.'
|
||||
required: false
|
||||
|
||||
# Many build steps have their own caches to improve subsequent build times.
|
||||
# Build tools are configured to cache to node_modules/.cache, so they are cached independently of node_modules.
|
||||
# Caches are saved every run *on main* (by keying on github.run_id), and the most recent available cache is loaded.
|
||||
# Caches are not saved on feature branches because they have limited utility, and extend the runtime of the workflow.
|
||||
# See https://jongleberry.medium.com/speed-up-your-ci-and-dx-with-node-modules-cache-ac8df82b7bb0.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
restore-keys: ${{ inputs.restore-keys }}
|
||||
- if: github.ref_name == 'main'
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
24
.github/actions/setup/action.yml
vendored
@@ -8,9 +8,9 @@ runs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
# cache is intentionally omitted, as it is faster with yarn v1 to cache node_modules.
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
@@ -19,7 +19,7 @@ runs:
|
||||
path: |
|
||||
node_modules
|
||||
!node_modules/.cache
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('yarn.lock') }}
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
shell: bash
|
||||
@@ -40,23 +40,17 @@ runs:
|
||||
run: yarn contracts
|
||||
shell: bash
|
||||
|
||||
# GraphQL is generated from schema. The schema is always fetched, but if unchanged, graphql does not need to be re-generated.
|
||||
- run: yarn graphql:fetch
|
||||
shell: bash
|
||||
- uses: actions/cache@v3
|
||||
id: graphql-cache
|
||||
with:
|
||||
path: src/graphql/**/__generated__
|
||||
key: ${{ runner.os }}-graphql-${{ hashFiles('src/graphql/**/schema.graphql') }}
|
||||
- if: steps.graphql-cache.outputs.cache-hit != 'true'
|
||||
run: yarn graphql:generate
|
||||
# GraphQL is generated from schema and client-side graphql queries. The schema is always fetched and changes to
|
||||
# client-side queries are hard to detect, so it is always re-generated.
|
||||
# TODO(WEB-2498): Cache based on both fetched schema and client-side graphql queries.
|
||||
# This will require some processing: cp all literal graphql tags into a separate file and hash it?
|
||||
- run: yarn graphql
|
||||
shell: bash
|
||||
|
||||
# Messages are extracted from source.
|
||||
# A record of source file content hashes and catalogs is maintained in node_modules/.cache/lingui.
|
||||
# Messages are always extracted, but extraction may short-circuit from the custom extractor's cache.
|
||||
- uses: actions/cache@v3
|
||||
id: i18n-extract-cache
|
||||
- uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-i18n-extract-${{ github.run_id }}
|
||||
|
||||
51
.github/workflows/1-main-to-staging.yml
vendored
@@ -14,19 +14,60 @@ jobs:
|
||||
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 CODEOWNERS file
|
||||
git config user.name 'UL Service Account'
|
||||
git config user.email 'hello-happy-puppy@users.noreply.github.com'
|
||||
|
||||
- name: Add translations
|
||||
run: |
|
||||
echo "@uniswap/web-admins" > CODEOWNERS
|
||||
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"
|
||||
git commit -m 'ci: add global CODEOWNERS'
|
||||
|
||||
- name: Git push
|
||||
run: |
|
||||
git push origin main:releases/staging --force
|
||||
|
||||
19
.github/workflows/2-deploy-to-staging.yml
vendored
@@ -10,27 +10,23 @@ jobs:
|
||||
environment:
|
||||
name: deploy/staging
|
||||
steps:
|
||||
- name: Send Slack message that deploy is starting
|
||||
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Staging deploy started for branch: ${{ github.ref_name }}"
|
||||
"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 prepare
|
||||
- run: yarn build
|
||||
env:
|
||||
REACT_APP_STAGING: 1
|
||||
- name: Setup node@16 (required by Cloudflare Pages)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
id: pages-deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
@@ -42,18 +38,19 @@ jobs:
|
||||
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
|
||||
- name: Send Slack message about deployment outcome
|
||||
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
if: always()
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Staging deploy **${{ steps.pages-deployment.outcome }}** for: ${{ github.ref_name }}"
|
||||
"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
|
||||
|
||||
15
.github/workflows/3-staging-to-prod.yml
vendored
@@ -14,6 +14,21 @@ jobs:
|
||||
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 }}
|
||||
|
||||
21
.github/workflows/4-deploy-to-prod.yml
vendored
@@ -10,21 +10,21 @@ jobs:
|
||||
environment:
|
||||
name: deploy/prod
|
||||
steps:
|
||||
- name: Send Slack message that build is starting
|
||||
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Production deploy started for branch: ${{ github.ref_name }}"
|
||||
"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 prepare
|
||||
- run: yarn build
|
||||
|
||||
- name: Bump and tag
|
||||
id: github-tag-action
|
||||
uses: mathieudutour/github-tag-action@d745f2e74aaf1ee82e747b181f7a0967978abee0
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
with:
|
||||
cidv0: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- name: Release
|
||||
- name: Publish release
|
||||
uses: actions/create-release@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -74,11 +74,6 @@ jobs:
|
||||
|
||||
${{ steps.github-tag-action.outputs.changelog }}
|
||||
|
||||
- name: Setup node@16 (required by Cloudflare Pages)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
id: pages-deployment
|
||||
@@ -91,18 +86,18 @@ jobs:
|
||||
# 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
|
||||
|
||||
- name: Send Slack message about deployment outcome
|
||||
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
continue-on-error: true
|
||||
if: always()
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Production deploy **${{ steps.pages-deployment.outcome }}** for: ${{ github.ref_name }}"
|
||||
"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
|
||||
|
||||
33
.github/workflows/crowdin-sync.yaml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Crowdin Download
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Download translations every hour.
|
||||
# This is not done as part of the build so that builds remain reproducible.
|
||||
- cron: '0 * * * *'
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
download-translations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn i18n:extract
|
||||
|
||||
- name: Download Crowdin translations
|
||||
uses: crowdin/github-action@1.4.9
|
||||
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'
|
||||
create_pull_request: true
|
||||
pull_request_title: 'chore(i18n): new Crowdin translations'
|
||||
localization_branch_name: l10n_crowdin
|
||||
commit_message: 'chore(i18n): synchronize translations from crowdin [skip ci]'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.github/workflows/crowdin.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- run: yarn i18n:extract
|
||||
|
||||
- name: Upload Crowdin sources
|
||||
uses: crowdin/github-action@1.1.0
|
||||
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
|
||||
with:
|
||||
upload_sources: true
|
||||
download_translations: false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Slack notifications for releases/* merges
|
||||
name: Slack notification on pushes to releases/*
|
||||
|
||||
# This CI job will push notifications to Slack whenever code is merged into any releases/* branch
|
||||
#
|
||||
@@ -25,7 +25,6 @@ on:
|
||||
|
||||
jobs:
|
||||
notify-slack:
|
||||
name: 'Emit Slack notification(s)'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: notify/releases
|
||||
@@ -45,9 +44,7 @@ jobs:
|
||||
| awk '{print substr($0,0,3000);}' \
|
||||
> /tmp/parsed_github_context
|
||||
echo "SLACK_COMMITS=$(cat /tmp/parsed_github_context)" >> "$GITHUB_OUTPUT"
|
||||
- name: Send custom JSON data to Slack workflow
|
||||
id: slack
|
||||
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
|
||||
125
.github/workflows/release.yaml
vendored
@@ -1,125 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 16 * * 1-4' # every day 16:00 UTC Monday-Thursday
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
wait-on-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: unit-tests
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
checkName: unit-tests
|
||||
- id: cypress-tests
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
checkName: cypress-tests
|
||||
- if: steps.unit-tests.outputs.conclusion != 'success' || steps.cypress-tests.outputs.conclusion != 'success'
|
||||
run: exit 1
|
||||
|
||||
tag:
|
||||
needs: wait-on-tests
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
new_tag: ${{ steps.github-tag-action.outputs.new_tag }}
|
||||
changelog: ${{ steps.github-tag-action.outputs.changelog }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Bump and tag
|
||||
id: github-tag-action
|
||||
uses: mathieudutour/github-tag-action@v6.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_branches: .*
|
||||
default_bump: patch
|
||||
|
||||
release:
|
||||
needs: tag
|
||||
if: ${{ needs.tag.outputs.new_tag != null }}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn build
|
||||
|
||||
- name: Pin to IPFS
|
||||
id: pinata
|
||||
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
|
||||
with:
|
||||
pin-name: Uniswap ${{ needs.tag.outputs.new_tag }}
|
||||
path: './build'
|
||||
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
|
||||
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
|
||||
|
||||
- name: Pin to Crust
|
||||
uses: crustio/ipfs-crust-action@v2.0.3
|
||||
continue-on-error: true
|
||||
timeout-minutes: 2
|
||||
with:
|
||||
cid: ${{ steps.pinata.outputs.hash }}
|
||||
seeds: ${{ secrets.CRUST_SEEDS }}
|
||||
|
||||
- name: Convert CIDv0 to CIDv1
|
||||
id: convert-cidv0
|
||||
uses: uniswap/convert-cidv0-cidv1@v1.0.0
|
||||
with:
|
||||
cidv0: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- name: Release
|
||||
uses: actions/create-release@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ needs.tag.outputs.new_tag }}
|
||||
release_name: Release ${{ needs.tag.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 accessible via our alias to the Cloudflare IPFS gateway at [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
You can also access the Uniswap Interface directly 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 alias to 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 }}/)
|
||||
|
||||
${{ needs.tag.outputs.changelog }}
|
||||
|
||||
- name: Setup node@16 (required by Cloudflare Pages)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
directory: build
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- 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'
|
||||
94
.github/workflows/test.yml
vendored
@@ -1,7 +1,7 @@
|
||||
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 cache to node_modules/.cache, so this is cached independently of node_modules.
|
||||
# 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.
|
||||
|
||||
@@ -9,9 +9,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- releases/staging
|
||||
pull_request:
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -19,12 +18,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
id: eslint-cache
|
||||
- uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-
|
||||
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
|
||||
@@ -37,12 +35,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
id: tsc-cache
|
||||
- uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-
|
||||
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
|
||||
@@ -67,18 +64,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
id: jest-cache
|
||||
- uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-
|
||||
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
|
||||
verbose: true
|
||||
flags: unit-tests
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
@@ -91,16 +86,15 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
id: build-e2e-cache
|
||||
- uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: node_modules/.cache
|
||||
key: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-
|
||||
key: ${{ runner.os }}-build-e2e-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-build-e2e-
|
||||
- run: yarn build:e2e
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-e2e
|
||||
path: build
|
||||
@@ -115,7 +109,6 @@ jobs:
|
||||
cypress-test-matrix:
|
||||
needs: [build-e2e, cypress-rerun]
|
||||
runs-on: ubuntu-latest
|
||||
container: cypress/browsers:node-18.14.1-chrome-111.0.5563.64-1-ff-111.0-edge-111.0.1661.43-1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -123,8 +116,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
id: cypress-cache
|
||||
- uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: /root/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
|
||||
@@ -137,8 +129,7 @@ jobs:
|
||||
name: build-e2e
|
||||
path: build
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: hardhat-cache
|
||||
- uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: cache
|
||||
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
|
||||
@@ -151,8 +142,9 @@ jobs:
|
||||
parallel: true
|
||||
start: yarn serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
browser: chrome
|
||||
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 }}
|
||||
@@ -166,25 +158,55 @@ jobs:
|
||||
COMMIT_INFO_TIMESTAMP: ${{ github.event.pull_request.updated_at || github.event.head_commit.timestamp }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number }}
|
||||
CYPRESS_PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }}
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
flags: e2e-tests
|
||||
|
||||
- if: failure() && github.ref_name == 'main'
|
||||
uses: ./.github/actions/report
|
||||
with:
|
||||
name: Cypress tests
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
# Included as a single job to check for cypress-test-matrix success, as a matrix cannot be checked.
|
||||
cypress-tests:
|
||||
if: always()
|
||||
needs: [cypress-test-matrix]
|
||||
pre:
|
||||
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: needs.cypress-test-matrix.result != 'success'
|
||||
run: exit 1
|
||||
- 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]
|
||||
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' }}
|
||||
|
||||
4
.gitignore
vendored
@@ -5,8 +5,7 @@
|
||||
/src/types/v3
|
||||
/src/abis/types
|
||||
/src/locales/**/*.js
|
||||
/src/locales/**/en-US.po
|
||||
/src/locales/**/pseudo.po
|
||||
/src/locales/**/*.po
|
||||
|
||||
# generated files
|
||||
/src/**/__generated__
|
||||
@@ -47,6 +46,7 @@ notes.txt
|
||||
|
||||
package-lock.json
|
||||
|
||||
cypress/downloads
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
|
||||
|
||||
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
@uniswap/web-admins
|
||||
21
codecov.yml
@@ -9,17 +9,38 @@ ignore:
|
||||
- "**/constants/**/*"
|
||||
- "constants/**/*"
|
||||
|
||||
coverage:
|
||||
status:
|
||||
# Omit merging unit/e2e reports into the defaults, as it is nonsensical.
|
||||
project: off
|
||||
patch: off
|
||||
|
||||
flag_management:
|
||||
default_rules:
|
||||
statuses:
|
||||
- type: project
|
||||
target: auto
|
||||
threshold: 1%
|
||||
# Adjust the base when removing code to avoid penalizing tech debt payback / dead code removal.
|
||||
removed_code_behavior: adjust_base
|
||||
if_ci_failed: error
|
||||
- type: patch
|
||||
target: 80%
|
||||
individual_flags:
|
||||
- name: unit-tests
|
||||
- name: e2e-tests
|
||||
# Wait until all machines have reported coverage - e2e tests run across 4 machines.
|
||||
after_n_builds: 4
|
||||
statuses:
|
||||
- type: patch
|
||||
target: 0%
|
||||
|
||||
comment:
|
||||
layout: flags
|
||||
# Wait until all machines have reported coverage - e2e tests run across 4 machines + unit tests across 1.
|
||||
after_n_builds: 5
|
||||
hide_comment_details: false
|
||||
|
||||
github_checks:
|
||||
# Turn off GitHub Check annotations, as they make it more difficult to review code.
|
||||
annotations: false
|
||||
|
||||
@@ -6,6 +6,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const path = require('path')
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
||||
const { DefinePlugin, IgnorePlugin, ProvidePlugin } = require('webpack')
|
||||
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
|
||||
|
||||
const commitHash = execSync('git rev-parse HEAD').toString().trim()
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
@@ -93,6 +94,16 @@ module.exports = {
|
||||
// See https://vanilla-extract.style/documentation/integrations/webpack/#identifiers for docs.
|
||||
// See https://github.com/vanilla-extract-css/vanilla-extract/issues/771#issuecomment-1249524366.
|
||||
new VanillaExtractPlugin({ identifiers: 'short' }),
|
||||
new RetryChunkLoadPlugin({
|
||||
cacheBust: `function() {
|
||||
return 'cache-bust=' + Date.now();
|
||||
}`,
|
||||
// Retries with exponential backoff (500ms, 1000ms, 2000ms).
|
||||
retryDelay: `function(retryAttempt) {
|
||||
return 2 ** (retryAttempt - 1) * 500;
|
||||
}`,
|
||||
maxRetries: 3,
|
||||
}),
|
||||
],
|
||||
configure: (webpackConfig) => {
|
||||
// Configure webpack plugins:
|
||||
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
experimentalMemoryManagement: true, // better memory management, see https://github.com/cypress-io/cypress/pull/25462
|
||||
retries: { runMode: 2 },
|
||||
videoCompression: false,
|
||||
e2e: {
|
||||
async setupNodeEvents(on, config) {
|
||||
await setupHardhatEvents(on, config)
|
||||
@@ -24,14 +25,9 @@ export default defineConfig({
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
...config,
|
||||
// Only enable Chrome.
|
||||
// Electron (the default) has issues injecting window.ethereum before pageload, so it is not viable.
|
||||
browsers: config.browsers.filter(({ name }) => name === 'chrome'),
|
||||
}
|
||||
return config
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||
specPattern: 'cypress/{e2e,staging}/**/*.test.ts',
|
||||
},
|
||||
})
|
||||
|
||||
202
cypress/README.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# e2e testing with Cypress
|
||||
|
||||
End-to-end tests are run through [Cypress](https://docs.cypress.io/api/table-of-contents/), which runs tests in a real browser. Cypress is a little different than other testing frameworks, and e2e tests are a little different than unit tests, so this directory has its own set of patterns, idioms, and best practices. Not only that, but we're testing against a forked blockchain, not just against typical Web APIs, so we have unique flows that you may not have seen elsewhere.
|
||||
|
||||
## Running your first e2e tests
|
||||
|
||||
Cypress tests run against a local server, so you'll need to run the application locally at the same time. The fastest way to run e2e tests is to use your dev server: `yarn start`.
|
||||
|
||||
Open cypress at the same time with `yarn cypress:open`. You should do this from another window or tab, so that you can continue to see any typechecking/linting warnings from `yarn start`.
|
||||
|
||||
Cypress opens its own instance of Chrome, with a list of "E2E specs" for you to select. When you're developing locally, you usually only want to run one spec file at a time. Select your spec by clicking on the filename and it will run.
|
||||
|
||||
## Glossary
|
||||
|
||||
#### spec
|
||||
Cypress considers each file a separate spec, or collection of tests.
|
||||
Specs are always run as a whole through `yarn cypress:open` or on the same machine through CI.
|
||||
|
||||
#### Thenable
|
||||
Cypress queues commands to run in the browser using [Thenables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables), not Promises.
|
||||
For this reason, you should not use `async/await` syntax in Cypress unless it is wholly-contained in a `cy.then` function argument.
|
||||
|
||||
## Writing your first e2e test
|
||||
|
||||
_For an excellent treatment on tests, check out the [Cypress Fundamentals](https://learn.cypress.io/cypress-fundamentals/how-to-write-a-test) course._
|
||||
_While some of that will be paraphrased here, this should be sufficient to get you started:_
|
||||
|
||||
### What is a test?
|
||||
|
||||
Cypress tests are just like any other test: you should set up an initial state, execute an action, and verify the action's consequence. This is codified in the AAA (Arrange-Act-Assert) pattern, and you'll see this in most of our tests. In _our_ case, it plays out as:
|
||||
|
||||
1. Arrange: Visit a page, eg `cy.visit('/swap')`, and set up the state, on the blockchain and the page.
|
||||
2. Act: Initiate your action under test, eg `initiateSwap()`
|
||||
3. Assert: Verify that the action has occured, eg `// Verify swap has occured`
|
||||
|
||||
You'll usually see the setup, followed by a newline, followed by assertions with comments stating what they are asserting.
|
||||
Because Cypress tests are translated into user actions, it may be hard to follow the action being described. You should use comments liberally to describe what you are doing and what you intend to test, to make tests easier to read and maintain in the future.
|
||||
|
||||
### Thinking about tests: queuing up a sequence of commands
|
||||
|
||||
Cypress uses `Thenable`s to achieve "command chaining". A test is described as a series of commands, which are only executed once the previous command in the chain has executed.
|
||||
|
||||
```
|
||||
cy.visit('/swap')
|
||||
cy.contains('Select token').click()
|
||||
cy.contains('DAI').click()
|
||||
```
|
||||
|
||||
In this example, `cy.contains('Select token').click()` is queued up right away (all the code is synchronous), but it will not execute until `/swap` has loaded (all the commands are chained); and `click()` will not execute until `Select token` has been found.
|
||||
|
||||
This becomes more relevant as you work with data on the blockchain, as you'll need to load it at the correct time, _after_ it's been modified by the application:
|
||||
|
||||
```
|
||||
cy.hardhat().then(async (hardhat) => {
|
||||
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
// wait for the transaction to be executed
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
|
||||
// BAD: This will get the balance _before_ the other queued actions have executed.
|
||||
const balance = await hardhat.getBalance(hardhat.wallet, USDC_MAINNET)
|
||||
cy.wrap(balance).should('deep.equal', expectedBalance)
|
||||
})
|
||||
```
|
||||
|
||||
```
|
||||
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
// wait for the transaction to be executed
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
|
||||
// GOOD: cy.then chains the command so that it runs _after_ executing the swap
|
||||
cy.hardhat()
|
||||
.then((hardhat) => hardhat.getBalance(hardhat.wallet, USDC_MAINNET))
|
||||
.should('deep.equal', expectedBalance)
|
||||
})
|
||||
```
|
||||
|
||||
### Working with the blockchain (ie hardhat)
|
||||
|
||||
Our tests use a local hardhat node to simulate blockchain transactions. This can be accessed with `cy.hardhat().then((hardhat) => ...)`.
|
||||
Currently, tests using hardhat must opt-in in when they load the page: `cy.visit('/swap', { ethereum: 'hardhat' })`. This will not be necessary once we've totally migrated to hardhat.
|
||||
|
||||
By default, automining is turned on, so that any transaction that you send to the blockchain is mined immediately. If you want to assert on intermediate states (between sending a transaction and mining it), you can turn off automining: `cy.hardhat({ automine: false })`.
|
||||
|
||||
The hardhat integration has built-in utilities to let you modify and assert on balances, approvals, and permits, and should be fully typed. Check it out at [Uniswap/cypress-hardhat](https://github.com/Uniswap/cypress-hardhat).
|
||||
|
||||
### Asserting on wallet methods
|
||||
|
||||
Wallet methods to hardhat are all aliased. If you'd like to assert that a method was sent to the wallet, you can do so using the method name, prefixed with `@`:
|
||||
|
||||
```
|
||||
// Asserts that `eth_sendRawTransaction` was sent to the wallet.
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
```
|
||||
|
||||
Sometimes, you may want a method to _fail_. In this case, you can stub it, but you should disable logging to avoid spamming the test:
|
||||
|
||||
```
|
||||
// Stub calls to eth_signTypedData_v4 and fail them
|
||||
cy.hardhat().then((hardhat) => {
|
||||
// Note the closure to keep signTypedDataStub in scope. Using closures instead of variables (eg let) helps prevent misuse of chaining.
|
||||
const signTypedDataStub = cy.stub(hardhat.provider, 'send').log(false)
|
||||
signTypedDataStub.withArgs('eth_signTypedData_v4).rejects(USER_REJECTION)
|
||||
signTypedDataStub.callThrough() // allws other methods to call through to hardhat
|
||||
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
// Verify the call occured
|
||||
// Note the call to cy.wrap to correctly queue the chained command. Without this, the test would occur before the stub is called.
|
||||
cy.wrap(permitApprovalStub).should('be.calledWith', 'eth_signTypedData_v4')
|
||||
|
||||
// Restore the stub
|
||||
// note the call to cy.then to correctly queue the chained command. Without this, the stub would be restored immediately.
|
||||
cy.then(() => permitApprovalStub.restore())
|
||||
})
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
<!-- Best practices should all be labeled using H3, with the rationale italicized at the end of the section. -->
|
||||
<!-- Best practice 🤣 is to also include an example before your rationale. -->
|
||||
|
||||
### Spec / test grouping
|
||||
|
||||
Each spec should be specific to one route, _not_ one functional behavior.
|
||||
For example, `token-details.test.ts` is separated from `swap.test.ts`.
|
||||
|
||||
If a route has different functional behaviors, that route should become a directory name, and its spec should be split.
|
||||
For example, `swap.test.ts` may be split into `swap/swap.test.ts`, `swap/wrap.test.ts`, `swap/permit2.test.ts`.
|
||||
|
||||
_This prevents specs from growing too large, which is important because they are always run as a whole locally and on the same machine through CI. If a spec grows too large, it will have a longer local feedback loop, and it will become the bottleneck for CI test runtime._
|
||||
|
||||
_Similarly, avoid actions outside the scope of your spec, as it will cause total testing time to increase._
|
||||
|
||||
### Use closures instead of variables
|
||||
|
||||
Avoid usage of `let`, instead assigning a constant. In practice, this means using closures for your variables:
|
||||
|
||||
```javascript
|
||||
let badVariable
|
||||
|
||||
cy.hardhat({ automine: false })
|
||||
.then((hardhat) => cy.then(() => hardhat.provider.getBalance(hardhat.wallet.address)))
|
||||
.then((initialBalance) => {
|
||||
// Do not assign to a variable outside of your closure!
|
||||
badVariable = initialBalance // <-- bad!
|
||||
|
||||
// Use initial balance here, within the closure.
|
||||
})
|
||||
|
||||
cy.get('.class-name').then((el) => {
|
||||
// Do not use badVariable here! It may have changed value due to the queued async nature of Cypress.
|
||||
expect(el).should('contain', badVariable) // <-- bad!
|
||||
})
|
||||
```
|
||||
|
||||
_This prevents misuse of a not-yet-initialized variable, or a variable that has changed as the test progresses._
|
||||
|
||||
### Prefer selecting elements using on-screen text over data-testid attributes
|
||||
|
||||
When selecting components (eg with `cy.get`), prefer defining your selector with visible UI. Sometimes this is not possible (eg if the text is duplicated on-screen), and you'll need to add a `data-testid` property.
|
||||
|
||||
_Defining tests using visual fields helps ensure that we don't break them. `data-testid` may select an element that is only selectable programmatically, and should be used only when necessary, as its use may cover up UI breakages._
|
||||
|
||||
_You'll still want to use `data-testid` in cases where the text is rendered in multiple containers and you need to select the correct one, or where the component doesn't render predictable text output._
|
||||
|
||||
### Avoid branching logic
|
||||
|
||||
Do not write tests that rely on if-statements or conditionals. Do not create helper methods which do more than one thing, and rely on branching logic to apply to different but similar situations.
|
||||
|
||||
_Tests should be readable and simple. Branching logic makes it harder to reason about tests, and may hide otherwise flaky or ill-defined behaviors._
|
||||
|
||||
_Similarly, you should avoid complicated for-loops. Sometimes, for simple repetition, for-loops are ok._
|
||||
|
||||
### Avoid spamming the console
|
||||
|
||||
It is ok to include logging while you are developing a test, but that logging should be removed if it is not needed to debug (potential) errors.
|
||||
|
||||
For example, stubbing a wallet method will result in dumping a hex string (the calldata) to the log. Instead, suppress logging from methods which you know will flood the log.
|
||||
|
||||
```javascript
|
||||
cy.stub(hardhat.wallet, 'sendTransaction')
|
||||
.log(false) // <-- suppresses logs from this stub
|
||||
.rejects(new Error('user cancelled'))
|
||||
```
|
||||
|
||||
_Unnecessary logs it makes it harder to reason about a test overall._
|
||||
|
||||
### Name helper methods using transitive verbs
|
||||
|
||||
Name helper methods using "action verbs": `expectsThisToHappen`, not `expectThisToHappen`; `selectsToken(token: string)`, not `selectAToken(token: string)`.
|
||||
|
||||
_This makes your tests read more naturally, and makes it easier to follow given existing `should` syntax._
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FeatureFlag } from '../../src/featureFlags'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Buy Crypto Modal', () => {
|
||||
it('should open and close', () => {
|
||||
cy.visit('/')
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.fiatOnRampButtonOnSwap] })
|
||||
|
||||
// Open the fiat onramp modal
|
||||
cy.get(getTestSelector('buy-fiat-button')).click()
|
||||
@@ -15,7 +16,7 @@ describe('Buy Crypto Modal', () => {
|
||||
|
||||
it('should open and close, mobile viewport', () => {
|
||||
cy.viewport('iphone-6')
|
||||
cy.visit('/')
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.fiatOnRampButtonOnSwap] })
|
||||
|
||||
// Open the fiat onramp modal
|
||||
cy.get(getTestSelector('buy-fiat-button')).click()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
||||
import { CONNECTED_WALLET_USER_STATE, DISCONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
||||
|
||||
describe('Landing Page', () => {
|
||||
it('shows landing page when no user state exists', () => {
|
||||
cy.visit('/', { userState: {} })
|
||||
cy.visit('/', { userState: DISCONNECTED_WALLET_USER_STATE })
|
||||
cy.get(getTestSelector('landing-page'))
|
||||
cy.screenshot()
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
describe('Link', () => {
|
||||
it('should update route', () => {
|
||||
cy.viewport(2000, 1600)
|
||||
cy.visit('/')
|
||||
cy.visit('/swap')
|
||||
cy.contains('Pool').click()
|
||||
cy.get('[data-cy="join-pool-button"]').should('exist')
|
||||
})
|
||||
|
||||
@@ -28,6 +28,27 @@ describe('Mini Portfolio account drawer', () => {
|
||||
cy.get('@gqlSpy').should('have.been.calledOnce')
|
||||
})
|
||||
|
||||
it('fetches account information', () => {
|
||||
// Open the mini portfolio
|
||||
cy.intercept(/graphql/, { fixture: 'mini-portfolio/tokens.json' })
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
|
||||
// Verify that wallet state loads correctly
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Tokens')
|
||||
cy.get(getTestSelector('mini-portfolio-page')).contains('Hidden (201)')
|
||||
|
||||
cy.intercept(/graphql/, { fixture: 'mini-portfolio/nfts.json' })
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
|
||||
cy.get(getTestSelector('mini-portfolio-page')).contains('I Got Plenty')
|
||||
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Pools').click()
|
||||
cy.get(getTestSelector('mini-portfolio-page')).contains('No pools yet')
|
||||
|
||||
cy.intercept(/graphql/, { fixture: 'mini-portfolio/activity.json' })
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
|
||||
cy.get(getTestSelector('mini-portfolio-page')).contains('Contract Interaction')
|
||||
})
|
||||
|
||||
it('refetches balances when account changes', () => {
|
||||
cy.hardhat().then((hardhat) => {
|
||||
const accountA = hardhat.wallets[0].address
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('mini-portfolio activity history', () => {
|
||||
|
||||
// Check activity history tab.
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('mini-portfolio-nav-activity')).click()
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
|
||||
|
||||
// Assert that the local pending transaction is replaced by a remote transaction with the same nonce.
|
||||
cy.contains('Swapping').should('not.exist')
|
||||
|
||||
@@ -4,11 +4,8 @@ const PUDGY_COLLECTION_ADDRESS = '0xbd3531da5cf5857e7cfaa92426877b022e612cf8'
|
||||
const BONSAI_COLLECTION_ADDRESS = '0xec9c519d49856fd2f8133a0741b4dbe002ce211b'
|
||||
|
||||
describe('Testing nfts', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('should load nft leaderboard', () => {
|
||||
cy.visit('/')
|
||||
cy.get(getTestSelector('nft-nav')).first().click()
|
||||
cy.get(getTestSelector('nft-nav')).first().should('exist')
|
||||
cy.get(getTestSelector('nft-nav')).first().click()
|
||||
@@ -49,15 +46,11 @@ describe('Testing nfts', () => {
|
||||
cy.get(getTestSelector('nft-bag')).should('exist')
|
||||
})
|
||||
|
||||
it('should navigate to the owned nfts page', () => {
|
||||
it('should navigate to and from the owned nfts page', () => {
|
||||
cy.visit('/')
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('nft-view-self-nfts')).click()
|
||||
})
|
||||
|
||||
it('should close the sidebar when navigating to NFT details', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('mini-portfolio-nav-nfts')).click()
|
||||
cy.get(getTestSelector('mini-portfolio-nft')).first().click()
|
||||
cy.contains('Buy crypto').should('not.be.visible')
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
|
||||
cy.get(getTestSelector('mini-portfolio-nft')).click()
|
||||
cy.get(getTestSelector('mini-portfolio-navbar')).should('not.be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,216 +1,258 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { MaxUint160, MaxUint256 } from '@uniswap/permit2-sdk'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
|
||||
import { DAI, USDC_MAINNET } from '../../src/constants/tokens'
|
||||
import { DAI, USDC_MAINNET, USDT } from '../../src/constants/tokens'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
/** Initiates a swap. */
|
||||
function initiateSwap() {
|
||||
// The swap button is re-rendered once enable, so we must wait until the original button is not disabled to re-select the appropriate button.
|
||||
// The swap button is re-rendered once enabled, so we must wait until the original button is not disabled to re-select the appropriate button.
|
||||
cy.get('#swap-button').should('not.be.disabled')
|
||||
// Completes the swap.
|
||||
cy.get('#swap-button').click()
|
||||
cy.get(getTestSelector('confirm-swap-button')).click()
|
||||
cy.contains('Confirm swap').click()
|
||||
}
|
||||
|
||||
describe('Permit2', () => {
|
||||
// The same tokens & swap-amount combination is used for all permit2 tests.
|
||||
const INPUT_TOKEN = DAI
|
||||
const OUTPUT_TOKEN = USDC_MAINNET
|
||||
const TEST_BALANCE_INCREMENT = 0.01
|
||||
|
||||
beforeEach(() => {
|
||||
// Sets up a swap between INPUT_TOKEN and OUTPUT_TOKEN.
|
||||
cy.visit(`/swap/?inputCurrency=${INPUT_TOKEN.address}&outputCurrency=${OUTPUT_TOKEN.address}`, {
|
||||
function setupInputs(inputToken: Token, outputToken: Token) {
|
||||
// Sets up a swap between inputToken and outputToken.
|
||||
cy.visit(`/swap/?inputCurrency=${inputToken.address}&outputCurrency=${outputToken.address}`, {
|
||||
ethereum: 'hardhat',
|
||||
})
|
||||
cy.get('#swap-currency-input .token-amount-input').type(TEST_BALANCE_INCREMENT.toString())
|
||||
})
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.01')
|
||||
}
|
||||
|
||||
/** Asserts permit2 has a max approval for spend of the input token on-chain. */
|
||||
function expectTokenAllowanceForPermit2ToBeMax() {
|
||||
function expectTokenAllowanceForPermit2ToBeMax(inputToken: Token) {
|
||||
// check token approval
|
||||
return cy
|
||||
.hardhat()
|
||||
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }))
|
||||
.should('deep.equal', MaxUint256)
|
||||
cy.hardhat()
|
||||
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: inputToken }))
|
||||
.then((allowance) => {
|
||||
Cypress.log({ name: `Token allowace: ${allowance.toString()}` })
|
||||
cy.wrap(allowance).should('deep.equal', MaxUint256)
|
||||
})
|
||||
}
|
||||
|
||||
/** Asserts the universal router has a max permit2 approval for spend of the input token on-chain. */
|
||||
function expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime: number) {
|
||||
return cy
|
||||
.hardhat()
|
||||
.then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN }))
|
||||
function expectPermit2AllowanceForUniversalRouterToBeMax(inputToken: Token) {
|
||||
cy.hardhat()
|
||||
.then(({ approval, wallet }) => approval.getPermit2Allowance({ owner: wallet, token: inputToken }))
|
||||
.then((allowance) => {
|
||||
cy.wrap(MaxUint160.eq(allowance.amount)).should('eq', true)
|
||||
Cypress.log({ name: `Permit2 allowace: ${allowance.amount.toString()}` })
|
||||
cy.wrap(allowance.amount).should('deep.equal', MaxUint160)
|
||||
// Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds.
|
||||
const expected = Math.floor((approvalTime + 2_592_000_000) / 1000)
|
||||
const THIRTY_DAYS_SECONDS = 2_592_000
|
||||
const expected = Math.floor(Date.now() / 1000 + THIRTY_DAYS_SECONDS)
|
||||
cy.wrap(allowance.expiration).should('be.closeTo', expected, 40)
|
||||
})
|
||||
}
|
||||
|
||||
it('swaps when user has already approved token and permit2', () => {
|
||||
cy.hardhat().then(({ approval, wallet }) => {
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN })
|
||||
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN })
|
||||
beforeEach(() =>
|
||||
cy.hardhat().then(async (hardhat) => {
|
||||
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(DAI, 1e18))
|
||||
await hardhat.mine()
|
||||
})
|
||||
)
|
||||
|
||||
describe('approval process (with intermediate screens)', () => {
|
||||
// Turn off automine so that intermediate screens are available to assert on.
|
||||
beforeEach(() => cy.hardhat({ automine: false }))
|
||||
|
||||
it('swaps after completing full permit2 approval process', () => {
|
||||
setupInputs(DAI, USDC_MAINNET)
|
||||
initiateSwap()
|
||||
|
||||
// verify that the modal retains its state when the window loses focus
|
||||
cy.window().trigger('blur')
|
||||
|
||||
// Verify token approval
|
||||
cy.contains('Enable spending DAI on Uniswap')
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.get(getTestSelector('popups')).contains('Approved')
|
||||
expectTokenAllowanceForPermit2ToBeMax(DAI)
|
||||
|
||||
// Verify permit2 approval
|
||||
cy.contains('Allow DAI to be used for swapping')
|
||||
cy.wait('@eth_signTypedData_v4')
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.contains('Success')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
|
||||
})
|
||||
|
||||
it('swaps with existing permit approval and missing token approval', () => {
|
||||
setupInputs(DAI, USDC_MAINNET)
|
||||
cy.hardhat().then(async (hardhat) => {
|
||||
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: DAI })
|
||||
await hardhat.mine()
|
||||
})
|
||||
initiateSwap()
|
||||
|
||||
// Verify token approval
|
||||
cy.contains('Enable spending DAI on Uniswap')
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.get(getTestSelector('popups')).contains('Approved')
|
||||
expectTokenAllowanceForPermit2ToBeMax(DAI)
|
||||
|
||||
// Verify transaction
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.contains('Success')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
})
|
||||
|
||||
/**
|
||||
* On mainnet, you have to revoke USDT approval before increasing it.
|
||||
* From the token contract:
|
||||
* To change the approve amount you first have to reduce the addresses`
|
||||
* allowance to zero by calling `approve(_spender, 0)` if it is not
|
||||
* already 0 to mitigate the race condition described here:
|
||||
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
|
||||
*/
|
||||
it('swaps USDT with existing permit, and existing but insufficient token approval', () => {
|
||||
cy.hardhat().then(async (hardhat) => {
|
||||
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDT, 2e6))
|
||||
await hardhat.mine()
|
||||
await hardhat.approval.setTokenAllowanceForPermit2({ owner: hardhat.wallet, token: USDT }, 1e6)
|
||||
await hardhat.mine()
|
||||
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: USDT })
|
||||
await hardhat.mine()
|
||||
})
|
||||
setupInputs(USDT, USDC_MAINNET)
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('2')
|
||||
initiateSwap()
|
||||
|
||||
// Verify allowance revocation
|
||||
cy.contains('Reset USDT')
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.hardhat()
|
||||
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: USDT }))
|
||||
.should('deep.equal', BigNumber.from(0))
|
||||
|
||||
// Verify token approval
|
||||
cy.contains('Enable spending USDT on Uniswap')
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.get(getTestSelector('popups')).contains('Approved')
|
||||
expectTokenAllowanceForPermit2ToBeMax(USDT)
|
||||
|
||||
// Verify transaction
|
||||
cy.wait('@eth_sendRawTransaction')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.contains('Success')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
})
|
||||
initiateSwap()
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
// Verifies that there is a successful swap notification.
|
||||
cy.contains('Swapped').should('exist')
|
||||
})
|
||||
|
||||
it('swaps after completing full permit2 approval process', () => {
|
||||
cy.hardhat().then(({ provider }) => {
|
||||
cy.spy(provider, 'send').as('permitApprovalSpy')
|
||||
})
|
||||
it('swaps when user has already approved token and permit2', () => {
|
||||
cy.hardhat().then(({ approval, wallet }) =>
|
||||
Promise.all([
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }),
|
||||
approval.setPermit2Allowance({ owner: wallet, token: DAI }),
|
||||
])
|
||||
)
|
||||
setupInputs(DAI, USDC_MAINNET)
|
||||
initiateSwap()
|
||||
cy.contains('Enable spending limits for DAI on Uniswap').should('exist')
|
||||
cy.contains('Approved').should('exist')
|
||||
|
||||
cy.contains('Allow DAI to be used for swapping').should('exist')
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
|
||||
cy.then(() => {
|
||||
const approvalTime = Date.now()
|
||||
|
||||
cy.contains('Swapped').should('exist')
|
||||
|
||||
expectTokenAllowanceForPermit2ToBeMax()
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
|
||||
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4')
|
||||
})
|
||||
// Verify transaction
|
||||
cy.contains('Success')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
})
|
||||
|
||||
it('swaps after handling user rejection of both approval and signature', () => {
|
||||
setupInputs(DAI, USDC_MAINNET)
|
||||
const USER_REJECTION = { code: 4001 }
|
||||
cy.hardhat().then((hardhat) => {
|
||||
const tokenApprovalStub = cy.stub(hardhat.wallet, 'sendTransaction')
|
||||
tokenApprovalStub.rejects(USER_REJECTION) // reject token approval
|
||||
const permitApprovalStub = cy.stub(hardhat.provider, 'send')
|
||||
permitApprovalStub.withArgs('eth_signTypedData_v4').rejects(USER_REJECTION) // reject permit approval
|
||||
permitApprovalStub.callThrough() // allows non-eth_signTypedData_v4 send calls to return non-stubbed values
|
||||
|
||||
// Reject token approval
|
||||
const tokenApprovalStub = cy.stub(hardhat.wallet, 'sendTransaction').log(false)
|
||||
tokenApprovalStub.rejects(USER_REJECTION) // rejects token approval
|
||||
initiateSwap()
|
||||
|
||||
// tokenApprovalStub should reject here, and the modal should revert to the review state.
|
||||
cy.contains('Review swap').should('be.visible')
|
||||
|
||||
cy.then(() => {
|
||||
// The user is now allowing approval, but the permit2 signature will be rejected by the user (permitApprovalStub).
|
||||
tokenApprovalStub.restore() // allow token approval
|
||||
})
|
||||
|
||||
cy.get(getTestSelector('confirm-swap-button')).click()
|
||||
cy.contains('Enable spending limits for DAI on Uniswap').should('exist')
|
||||
cy.contains('Approved').should('exist')
|
||||
|
||||
// permitApprovalStub should reject here, and the modal should revert to the review state.
|
||||
// Verify token approval rejection
|
||||
cy.wrap(tokenApprovalStub).should('be.calledOnce')
|
||||
cy.contains('Review swap')
|
||||
.should('be.visible')
|
||||
.then(() => {
|
||||
permitApprovalStub.restore() // allow permit approval
|
||||
})
|
||||
|
||||
cy.get(getTestSelector('confirm-swap-button')).click()
|
||||
// Allow token approval
|
||||
cy.then(() => tokenApprovalStub.restore())
|
||||
|
||||
// The swap should now be able to proceed, as the permit2 signature will be accepted by the user.
|
||||
const approvalTime = Date.now()
|
||||
// Reject permit2 approval
|
||||
const permitApprovalStub = cy.stub(hardhat.provider, 'send').log(false)
|
||||
permitApprovalStub.withArgs('eth_signTypedData_v4').rejects(USER_REJECTION) // rejects permit approval
|
||||
permitApprovalStub.callThrough() // allows non-eth_signTypedData_v4 send calls to return non-stubbed values
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.contains('Swapped').should('exist')
|
||||
// Verify token approval
|
||||
cy.get(getTestSelector('popups')).contains('Approved')
|
||||
expectTokenAllowanceForPermit2ToBeMax(DAI)
|
||||
|
||||
expectTokenAllowanceForPermit2ToBeMax()
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
|
||||
})
|
||||
})
|
||||
// Verify permit2 approval rejection
|
||||
cy.wrap(permitApprovalStub).should('be.calledWith', 'eth_signTypedData_v4')
|
||||
cy.contains('Review swap')
|
||||
|
||||
it('swaps with existing token approval and missing permit approval', () => {
|
||||
cy.hardhat().then(({ approval, wallet, provider }) => {
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN })
|
||||
cy.spy(provider, 'send').as('permitApprovalSpy')
|
||||
})
|
||||
cy.then(() => initiateSwap())
|
||||
cy.then(() => {
|
||||
const approvalTime = Date.now()
|
||||
// Allow permit2 approval
|
||||
cy.then(() => permitApprovalStub.restore())
|
||||
cy.contains('Confirm swap').click()
|
||||
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.contains('Swapped').should('exist')
|
||||
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
|
||||
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4')
|
||||
})
|
||||
})
|
||||
|
||||
it('swaps with existing permit approval and missing token approval', () => {
|
||||
cy.hardhat().then(({ approval, wallet }) => approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }))
|
||||
cy.then(() => {
|
||||
initiateSwap()
|
||||
})
|
||||
cy.then(() => {
|
||||
const approvalTime = Date.now()
|
||||
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.contains('Swapped').should('exist')
|
||||
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
|
||||
})
|
||||
})
|
||||
|
||||
it('prompts signature when existing permit approval is expired', () => {
|
||||
const expiredAllowance = { expiration: Math.floor((Date.now() - 1) / 1000) }
|
||||
|
||||
cy.hardhat().then(({ approval, wallet, provider }) => {
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN })
|
||||
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, expiredAllowance)
|
||||
cy.spy(provider, 'send').as('permitApprovalSpy')
|
||||
})
|
||||
cy.then(() => {
|
||||
initiateSwap()
|
||||
})
|
||||
cy.then(() => {
|
||||
const approvalTime = Date.now()
|
||||
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.contains('Swapped').should('exist')
|
||||
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
|
||||
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4')
|
||||
})
|
||||
})
|
||||
|
||||
it('prompts signature when existing permit approval amount is too low', () => {
|
||||
const smallAllowance = { amount: 1 }
|
||||
|
||||
cy.hardhat().then(({ approval, wallet, provider }) => {
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN })
|
||||
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, smallAllowance)
|
||||
cy.spy(provider, 'send').as('permitApprovalSpy')
|
||||
initiateSwap()
|
||||
const approvalTime = Date.now()
|
||||
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.contains('Swapped').should('exist')
|
||||
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
|
||||
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4')
|
||||
// Verify permit2 approval
|
||||
cy.contains('Success')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
|
||||
})
|
||||
})
|
||||
|
||||
it('prompts token approval when existing approval amount is too low', () => {
|
||||
cy.hardhat()
|
||||
.then(({ approval, wallet }) => {
|
||||
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN })
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }, 1)
|
||||
})
|
||||
.then(() => {
|
||||
initiateSwap()
|
||||
const approvalTime = Date.now()
|
||||
cy.contains('Enable spending limits for DAI on Uniswap').should('exist')
|
||||
setupInputs(DAI, USDC_MAINNET)
|
||||
cy.hardhat().then(({ approval, wallet }) =>
|
||||
Promise.all([
|
||||
approval.setPermit2Allowance({ owner: wallet, token: DAI }),
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }, 1),
|
||||
])
|
||||
)
|
||||
initiateSwap()
|
||||
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.contains('Swapped').should('exist')
|
||||
// Verify token approval
|
||||
cy.get(getTestSelector('popups')).contains('Approved')
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
|
||||
})
|
||||
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
|
||||
})
|
||||
it('prompts signature when existing permit approval is expired', () => {
|
||||
setupInputs(DAI, USDC_MAINNET)
|
||||
const expiredAllowance = { expiration: Math.floor((Date.now() - 1) / 1000) }
|
||||
cy.hardhat().then(({ approval, wallet }) =>
|
||||
Promise.all([
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }),
|
||||
approval.setPermit2Allowance({ owner: wallet, token: DAI }, expiredAllowance),
|
||||
])
|
||||
)
|
||||
initiateSwap()
|
||||
|
||||
// Verify permit2 approval
|
||||
cy.wait('@eth_signTypedData_v4')
|
||||
cy.contains('Success')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
|
||||
})
|
||||
|
||||
it('prompts signature when existing permit approval amount is too low', () => {
|
||||
setupInputs(DAI, USDC_MAINNET)
|
||||
const smallAllowance = { amount: 1 }
|
||||
cy.hardhat().then(({ approval, wallet }) =>
|
||||
Promise.all([
|
||||
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }),
|
||||
approval.setPermit2Allowance({ owner: wallet, token: DAI }, smallAllowance),
|
||||
])
|
||||
)
|
||||
initiateSwap()
|
||||
|
||||
// Verify permit2 approval
|
||||
cy.wait('@eth_signTypedData_v4')
|
||||
cy.contains('Success')
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
|
||||
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||
import { getBalance, getTestSelector } from '../../utils'
|
||||
|
||||
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
|
||||
const UNI_MAINNET = UNI[ChainId.MAINNET]
|
||||
|
||||
describe('Swap errors', () => {
|
||||
it('wallet rejection', () => {
|
||||
@@ -13,15 +14,18 @@ describe('Swap errors', () => {
|
||||
// Stub the wallet to reject any transaction.
|
||||
cy.stub(hardhat.wallet, 'sendTransaction').log(false).rejects(new Error('user cancelled'))
|
||||
|
||||
// Attempt to swap.
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('1').should('have.value', '1')
|
||||
// Enter amount to swap
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
|
||||
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
|
||||
cy.contains('Review swap').should('exist')
|
||||
cy.get('body').click('topRight')
|
||||
cy.contains('Review swap').should('not.exist')
|
||||
// Submit transaction
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_estimateGas')
|
||||
|
||||
// Verify rejection
|
||||
cy.contains('Review swap')
|
||||
cy.contains('Confirm swap')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,37 +33,38 @@ describe('Swap errors', () => {
|
||||
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
|
||||
cy.hardhat({ automine: false })
|
||||
getBalance(USDC_MAINNET).then((initialBalance) => {
|
||||
// Set deadline to minimum. (1 minute)
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.get(getTestSelector('transaction-deadline-settings')).click()
|
||||
cy.get(getTestSelector('deadline-input')).clear().type('1') // 1 minute
|
||||
|
||||
// Click outside of modal to dismiss it.
|
||||
cy.get('body').click('topRight')
|
||||
cy.get(getTestSelector('deadline-input')).should('not.exist')
|
||||
|
||||
// Attempt to swap.
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('1').should('have.value', '1')
|
||||
// Enter amount to swap
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
|
||||
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
|
||||
|
||||
// Submit transaction
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.contains('Swap submitted')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
|
||||
// The pending transaction indicator should reflect the state.
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
cy.hardhat().then((hardhat) => hardhat.mine(1, /* 10 minutes */ 1000 * 60 * 10)) // mines past the deadline
|
||||
|
||||
// Mine transaction
|
||||
cy.hardhat().then(async (hardhat) => {
|
||||
// Remove the transaction from the mempool, so that it doesn't fail but it is past the deadline.
|
||||
// This should result in it being removed from pending transactions, without a failure notificiation.
|
||||
const transactions = await hardhat.send('eth_pendingTransactions', [])
|
||||
await hardhat.send('hardhat_dropTransaction', [transactions[0].hash])
|
||||
// Mine past the deadline
|
||||
await hardhat.mine(1, DEFAULT_DEADLINE_FROM_NOW + 1)
|
||||
})
|
||||
cy.wait('@eth_getTransactionReceipt')
|
||||
|
||||
// Verify transaction did not occur
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
|
||||
// TODO(WEB-2085): Fix this test - transaction popups are flakey.
|
||||
// cy.get(getTestSelector('transaction-popup')).contains('Swap failed')
|
||||
|
||||
// Verify the balance is unchanged.
|
||||
cy.get('#swap-currency-output [data-testid="balance-text"]').should('have.text', `Balance: ${initialBalance}`)
|
||||
cy.get(getTestSelector('popups')).should('not.contain', 'Swap failed')
|
||||
cy.get('#swap-currency-output').contains(`Balance: ${initialBalance}`)
|
||||
getBalance(USDC_MAINNET).should('eq', initialBalance)
|
||||
})
|
||||
})
|
||||
|
||||
it('slippage failure', () => {
|
||||
it.skip('slippage failure', () => {
|
||||
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${UNI_MAINNET.address}`, { ethereum: 'hardhat' })
|
||||
cy.hardhat({ automine: false })
|
||||
getBalance(USDC_MAINNET).then((initialBalance) => {
|
||||
@@ -74,43 +79,29 @@ describe('Swap errors', () => {
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.get(getTestSelector('max-slippage-settings')).click()
|
||||
cy.get(getTestSelector('slippage-input')).clear().type('0.01')
|
||||
|
||||
// Click outside of modal to dismiss it.
|
||||
cy.get('body').click('topRight')
|
||||
cy.get('body').click('topRight') // close modal
|
||||
cy.get(getTestSelector('slippage-input')).should('not.exist')
|
||||
|
||||
// Swap 2 times.
|
||||
const AMOUNT_TO_SWAP = 200
|
||||
cy.get('#swap-currency-input .token-amount-input')
|
||||
.clear()
|
||||
.type(AMOUNT_TO_SWAP.toString())
|
||||
.should('have.value', AMOUNT_TO_SWAP.toString())
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
|
||||
cy.get('#swap-currency-input .token-amount-input')
|
||||
.clear()
|
||||
.type(AMOUNT_TO_SWAP.toString())
|
||||
.should('have.value', AMOUNT_TO_SWAP.toString())
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.contains('Confirm Swap').should('exist')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
|
||||
// The pending transaction indicator should reflect the state.
|
||||
// Submit 2 transactions
|
||||
for (let i = 0; i < 2; i++) {
|
||||
cy.get('#swap-currency-input .token-amount-input').type('200').should('have.value', '200')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.contains('Swap submitted')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
}
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '2 Pending')
|
||||
|
||||
// Mine transactions
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.wait('@eth_getTransactionReceipt')
|
||||
|
||||
// Verify transaction did not occur
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
|
||||
// TODO(WEB-2085): Fix this test - transaction popups are flakey.
|
||||
// cy.get(getTestSelector('transaction-popup')).contains('Swap failed')
|
||||
|
||||
// Assert that the transactions were unsuccessful by checking on-chain balance.
|
||||
getBalance(UNI_MAINNET).should('equal', initialBalance)
|
||||
cy.get(getTestSelector('popups')).contains('Swap failed')
|
||||
getBalance(UNI_MAINNET).should('eq', initialBalance)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { FeatureFlag } from '../../../src/featureFlags'
|
||||
import { getTestSelector } from '../../utils'
|
||||
|
||||
describe('Swap settings', () => {
|
||||
it('Opens and closes the settings menu', () => {
|
||||
cy.visit('/swap')
|
||||
cy.visit('/swap', { featureFlags: [FeatureFlag.uniswapXEnabled], ethereum: 'hardhat' })
|
||||
cy.contains('Settings').should('not.exist')
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.contains('Max slippage').should('exist')
|
||||
cy.contains('Transaction deadline').should('exist')
|
||||
cy.contains('Auto Router API').should('exist')
|
||||
cy.contains('UniswapX').should('exist')
|
||||
cy.contains('Local routing').should('exist')
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.contains('Settings').should('not.exist')
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||
import { getBalance, getTestSelector } from '../../utils'
|
||||
|
||||
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
|
||||
const UNI_MAINNET = UNI[ChainId.MAINNET]
|
||||
|
||||
describe('Swap', () => {
|
||||
describe('Swap on main page', () => {
|
||||
@@ -37,33 +37,53 @@ describe('Swap', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
|
||||
})
|
||||
|
||||
it('resets the dependent input when the independent input is cleared', () => {
|
||||
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${UNI_MAINNET.address}`)
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
|
||||
cy.get(`#swap-currency-output .token-amount-input`).should('have.value', '')
|
||||
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.01').should('have.value', '0.01')
|
||||
cy.get(`#swap-currency-output .token-amount-input`).should('not.have.value', '')
|
||||
cy.get('#swap-currency-input .token-amount-input').clear()
|
||||
cy.get(`#swap-currency-output .token-amount-input`).should('not.have.value')
|
||||
|
||||
cy.window().trigger('blur')
|
||||
cy.get(`#swap-currency-output .token-amount-input`).should('not.have.value')
|
||||
})
|
||||
|
||||
it('swaps ETH for USDC', () => {
|
||||
cy.visit('/swap', { ethereum: 'hardhat' })
|
||||
cy.hardhat({ automine: false })
|
||||
getBalance(USDC_MAINNET).then((initialBalance) => {
|
||||
// Select USDC
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get(getTestSelector('token-search-input')).clear().type(USDC_MAINNET.address)
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('1').should('have.value', '1')
|
||||
cy.get(getTestSelector('token-search-input')).type(USDC_MAINNET.address)
|
||||
cy.get(getTestSelector('common-base-USDC')).click()
|
||||
|
||||
// Enter amount to swap
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
|
||||
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
|
||||
|
||||
// Submit transaction
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.contains('Review swap')
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.contains('Swap submitted')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
|
||||
// The pending transaction indicator should reflect the state.
|
||||
cy.contains('Swap submitted').should('not.exist')
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
|
||||
// Mine transaction
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.wait('@eth_getTransactionReceipt')
|
||||
|
||||
// Verify transaction
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
|
||||
// TODO(WEB-2085): Fix this test - transaction popups are flakey.
|
||||
// cy.get(getTestSelector('transaction-popup')).contains('Swapped')
|
||||
|
||||
// Verify the balance is updated.
|
||||
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
|
||||
'have.text',
|
||||
`Balance: ${initialBalance + 1}`
|
||||
)
|
||||
getBalance(USDC_MAINNET).should('eq', initialBalance + 1)
|
||||
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||
const finalBalance = initialBalance + 1
|
||||
cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`)
|
||||
getBalance(USDC_MAINNET).should('eq', finalBalance)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CurrencyAmount, SupportedChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { ChainId, CurrencyAmount, WETH9 } from '@uniswap/sdk-core'
|
||||
|
||||
import { getBalance, getTestSelector } from '../../utils'
|
||||
|
||||
const WETH = WETH9[SupportedChainId.MAINNET]
|
||||
const WETH = WETH9[ChainId.MAINNET]
|
||||
|
||||
describe('Swap wrap', () => {
|
||||
beforeEach(() => {
|
||||
@@ -12,7 +12,7 @@ describe('Swap wrap', () => {
|
||||
})
|
||||
|
||||
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('0.01').should('have.value', '0.01')
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.01').should('have.value', '0.01')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01')
|
||||
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('0.02').should('have.value', '0.02')
|
||||
@@ -20,31 +20,28 @@ describe('Swap wrap', () => {
|
||||
})
|
||||
|
||||
it('should be able to wrap ETH', () => {
|
||||
getBalance(WETH).then((initialWethBalance) => {
|
||||
getBalance(WETH).then((initialBalance) => {
|
||||
cy.contains('Enter ETH amount')
|
||||
|
||||
// Enter the amount to wrap.
|
||||
cy.get('#swap-currency-output .token-amount-input').click().type('1').should('have.value', 1)
|
||||
// This also ensures we don't click "Wrap" before the UI has caught up.
|
||||
// Enter amount to wrap
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', 1)
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', 1)
|
||||
|
||||
// Click the wrap button.
|
||||
// Submit transaction
|
||||
cy.contains('Wrap').click()
|
||||
|
||||
// The pending transaction indicator should reflect the state.
|
||||
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
|
||||
// Mine transaction
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.wait('@eth_getTransactionReceipt')
|
||||
|
||||
// Verify transaction
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
|
||||
// TODO(WEB-2085): Fix this test - transaction popups are flakey.
|
||||
// cy.get(getTestSelector('transaction-popup')).contains('Wrapped')
|
||||
// cy.get(getTestSelector('transaction-popup')).contains('1.00 ETH for 1.00 WETH')
|
||||
|
||||
// The UI balance should have increased.
|
||||
cy.get('#swap-currency-output').should('contain', `Balance: ${initialWethBalance + 1}`)
|
||||
|
||||
// The user's WETH account balance should have increased
|
||||
getBalance(WETH).should('equal', initialWethBalance + 1)
|
||||
cy.get(getTestSelector('popups')).contains('Wrapped')
|
||||
const finalBalance = initialBalance + 1
|
||||
cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`)
|
||||
getBalance(WETH).should('equal', finalBalance)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,33 +51,30 @@ describe('Swap wrap', () => {
|
||||
await hardhat.mine()
|
||||
})
|
||||
|
||||
getBalance(WETH).then((initialWethBalance) => {
|
||||
// Swap input/output to unwrap WETH.
|
||||
getBalance(WETH).then((initialBalance) => {
|
||||
// Swap input/output to unwrap WETH
|
||||
cy.get(getTestSelector('swap-currency-button')).click()
|
||||
cy.contains('Enter WETH amount')
|
||||
|
||||
// Enter the amount to unwrap.
|
||||
cy.get('#swap-currency-output .token-amount-input').click().type('1').should('have.value', 1)
|
||||
// This also ensures we don't click "Wrap" before the UI has caught up.
|
||||
// Enter the amount to unwrap
|
||||
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', 1)
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', 1)
|
||||
|
||||
// Click the unwrap button.
|
||||
// Submit transaction
|
||||
cy.contains('Unwrap').click()
|
||||
|
||||
// The pending transaction indicator should reflect the state.
|
||||
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
|
||||
// Mine transaction
|
||||
cy.hardhat().then((hardhat) => hardhat.mine())
|
||||
cy.wait('@eth_getTransactionReceipt')
|
||||
|
||||
// Verify transaction
|
||||
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
|
||||
|
||||
// TODO(WEB-2085): Fix this test - transaction popups are flakey.
|
||||
// cy.get(getTestSelector('transaction-popup')).contains('Unwrapped')
|
||||
// cy.get(getTestSelector('transaction-popup')).contains('1.00 WETH for 1.00 ETH')
|
||||
|
||||
// The UI balance should have increased.
|
||||
cy.get('#swap-currency-input').should('contain', `Balance: ${initialWethBalance - 1}`)
|
||||
|
||||
// The user's WETH account balance should have increased
|
||||
getBalance(WETH).should('equal', initialWethBalance - 1)
|
||||
cy.get(getTestSelector('popups')).contains('Unwrapped')
|
||||
const finalBalance = initialBalance - 1
|
||||
cy.get('#swap-currency-input').contains(`Balance: ${finalBalance}`)
|
||||
getBalance(WETH).should('equal', finalBalance)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SupportedChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { ChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
|
||||
import { UNI } from '../../src/constants/tokens'
|
||||
import { ARB, UNI } from '../../src/constants/tokens'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
|
||||
const UNI_MAINNET = UNI[ChainId.MAINNET]
|
||||
|
||||
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('Token details', () => {
|
||||
cy.url().should('not.include', `${UNI_MAINNET.address}`)
|
||||
})
|
||||
|
||||
it.only('should not share swap state with the main swap page', () => {
|
||||
it('should not share swap state with the main swap page', () => {
|
||||
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'UNI')
|
||||
cy.get(`#swap-currency-input .open-currency-select-button`).click()
|
||||
cy.contains('WETH').click()
|
||||
@@ -149,8 +149,9 @@ describe('Token details', () => {
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).click()
|
||||
cy.get(getTestSelector('tokens-network-filter-option-arbitrum')).click()
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Arbitrum')
|
||||
cy.get(getTestSelector('token-table-row-ARB')).click()
|
||||
cy.get(getTestSelector(`token-table-row-${ARB.address.toLowerCase()}`)).click()
|
||||
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'ARB')
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).should('be.disabled')
|
||||
cy.contains('Connect to Arbitrum').should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,83 +1,24 @@
|
||||
describe.skip('Token explore filter', () => {
|
||||
before(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('should filter correctly by uni search term', () => {
|
||||
describe('Token explore filter', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/tokens')
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
const filteredByUni = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('uni'))
|
||||
cy.wrap(filteredByUni).as('filteredByUni')
|
||||
})
|
||||
|
||||
cy.get('[data-cy="explore-tokens-search-input"]')
|
||||
.clear()
|
||||
.type('uni')
|
||||
.type('{enter}')
|
||||
.then(() => {
|
||||
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
|
||||
cy.get('@filteredByUni').then((filteredByUni) => {
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
expect(tokenNames.length).to.equal(filteredByUni.length)
|
||||
tokenNames.forEach((tokenName) => {
|
||||
expect(filteredByUni).to.include(tokenName)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function aliasFilteredTokens(filter: string) {
|
||||
cy.get('[data-cy="token-name"]').then((tokens) => {
|
||||
cy.wrap(Array.from(tokens).filter((token) => token.innerText.toLowerCase().includes(filter))).as('filteredTokens')
|
||||
})
|
||||
}
|
||||
|
||||
function searchFor(filter: string) {
|
||||
cy.get('[data-cy="explore-tokens-search-input"]').clear().type(filter).type('{enter}')
|
||||
}
|
||||
|
||||
it('should filter correctly by dao search term', () => {
|
||||
cy.visit('/tokens')
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
const filteredByDao = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('dao'))
|
||||
cy.wrap(filteredByDao).as('filteredByDao')
|
||||
aliasFilteredTokens('dao')
|
||||
searchFor('dao')
|
||||
|
||||
cy.get('@filteredTokens').then((filteredTokens) => {
|
||||
cy.get('[data-cy="token-name"]').should('deep.equal', filteredTokens)
|
||||
})
|
||||
|
||||
cy.get('[data-cy="explore-tokens-search-input"]')
|
||||
.clear()
|
||||
.type('dao')
|
||||
.type('{enter}')
|
||||
.then(() => {
|
||||
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
|
||||
cy.get('@filteredByDao').then((filteredByDao) => {
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
expect(tokenNames.length).to.equal(filteredByDao.length)
|
||||
tokenNames.forEach((tokenName) => {
|
||||
expect(filteredByDao).to.include(tokenName)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should filter correctly by ax search term', () => {
|
||||
cy.visit('/tokens')
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
const filteredByAx = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('ax'))
|
||||
cy.wrap(filteredByAx).as('filteredByAx')
|
||||
})
|
||||
|
||||
cy.get('[data-cy="explore-tokens-search-input"]')
|
||||
.clear()
|
||||
.type('ax')
|
||||
.type('{enter}')
|
||||
.then(() => {
|
||||
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
|
||||
cy.get('@filteredByAx').then((filteredByAx) => {
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
expect(tokenNames.length).to.equal(filteredByAx.length)
|
||||
tokenNames.forEach((tokenName) => {
|
||||
expect(filteredByAx).to.include(tokenName)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,11 +10,11 @@ describe('Token explore', () => {
|
||||
cy.get(getTestSelectorStartsWith('token-table')).its('length').should('be.greaterThan', 0)
|
||||
// check sorted svg icon is present in volume cell, since tokens are sorted by volume by default
|
||||
cy.get(getTestSelector('header-row')).find(getTestSelector('volume-cell')).find('svg').should('exist')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('name-cell')).should('include.text', 'Ether')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('volume-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('price-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('tvl-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-ETH'))
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('name-cell')).should('include.text', 'Ether')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('volume-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('price-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('tvl-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('percent-change-cell'))
|
||||
.should('include.text', '%')
|
||||
cy.get(getTestSelector('header-row')).find(getTestSelector('price-cell')).click()
|
||||
@@ -24,14 +24,14 @@ describe('Token explore', () => {
|
||||
it('should update when time window toggled', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelector('time-selector')).should('contain', '1D')
|
||||
cy.get(getTestSelector('token-table-row-ETH'))
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('volume-cell'))
|
||||
.then(function ($elem) {
|
||||
cy.wrap($elem.text()).as('dailyEthVol')
|
||||
})
|
||||
cy.get(getTestSelector('time-selector')).click()
|
||||
cy.get(getTestSelector('1Y')).click()
|
||||
cy.get(getTestSelector('token-table-row-ETH'))
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('volume-cell'))
|
||||
.then(function ($elem) {
|
||||
cy.wrap($elem.text()).as('yearlyEthVol')
|
||||
@@ -41,7 +41,7 @@ describe('Token explore', () => {
|
||||
|
||||
it('should navigate to token detail page when row clicked', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).click()
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).click()
|
||||
cy.get(getTestSelector('token-details-about-section')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-info-container')).should('exist')
|
||||
@@ -53,13 +53,15 @@ describe('Token explore', () => {
|
||||
it('should update when global network changed', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Ethereum')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).should('exist')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).should('exist')
|
||||
|
||||
// note: cannot switch global chain via UI because we cannot approve the network switch
|
||||
// in metamask modal using plain cypress. this is a workaround.
|
||||
cy.visit('/tokens/polygon')
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Polygon')
|
||||
cy.get(getTestSelector('token-table-row-MATIC')).should('exist')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('name-cell'))
|
||||
.should('include.text', 'Polygon Matic')
|
||||
})
|
||||
|
||||
it('should update when token explore table network changed', () => {
|
||||
|
||||
@@ -1,38 +1,54 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Universal search bar', () => {
|
||||
before(() => {
|
||||
function openSearch() {
|
||||
// can't just type "/" because on mobile it doesn't respond to that
|
||||
cy.get('[data-cy="magnifying-icon"]').parent().eq(1).click()
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="magnifying-icon"]')
|
||||
.parent()
|
||||
.then(($navIcon) => {
|
||||
$navIcon.click()
|
||||
})
|
||||
openSearch()
|
||||
})
|
||||
|
||||
function getSearchBar() {
|
||||
return cy.get('[data-cy="search-bar-input"]').last()
|
||||
}
|
||||
|
||||
it('should yield clickable result for regular token or nft collection search term', () => {
|
||||
// Search for uni token by name.
|
||||
cy.get('[data-cy="search-bar-input"]').last().clear().type('uni')
|
||||
getSearchBar().clear().type('uni')
|
||||
cy.get('[data-cy="searchbar-token-row-UNI"]')
|
||||
.should('contain.text', 'Uniswap')
|
||||
.and('contain.text', 'UNI')
|
||||
.and('contain.text', '$')
|
||||
.and('contain.text', '%')
|
||||
cy.get('[data-cy="searchbar-token-row-UNI"]').first().click()
|
||||
cy.location('hash').should('equal', '#/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
|
||||
})
|
||||
|
||||
cy.get('div').contains('Uniswap').should('exist')
|
||||
// Stats should have: TVL, 24H Volume, 52W low, 52W high.
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).within(() => {
|
||||
cy.get('[data-cy="tvl"]').should('include.text', '$')
|
||||
cy.get('[data-cy="volume-24h"]').should('include.text', '$')
|
||||
cy.get('[data-cy="52w-low"]').should('include.text', '$')
|
||||
cy.get('[data-cy="52w-high"]').should('include.text', '$')
|
||||
})
|
||||
it('should go to the selected result when recent results are shown', () => {
|
||||
// Search for uni token by name.
|
||||
getSearchBar().type('uni')
|
||||
cy.get('[data-cy="searchbar-token-row-UNI"]')
|
||||
|
||||
// About section should have description of token.
|
||||
cy.get(getTestSelector('token-details-about-section')).should('exist')
|
||||
cy.contains('UNI is the governance token for Uniswap').should('exist')
|
||||
// Clear search
|
||||
getSearchBar().clear()
|
||||
|
||||
// Close search
|
||||
getSearchBar().type('{esc}')
|
||||
|
||||
openSearch()
|
||||
|
||||
// Search a different token by name.
|
||||
getSearchBar().type('eth')
|
||||
|
||||
// Validate ETH result now exists.
|
||||
cy.get('[data-cy="searchbar-token-row-ETH"]')
|
||||
|
||||
// Hit enter
|
||||
getSearchBar().type('{enter}')
|
||||
|
||||
// Validate we went to ethereum address
|
||||
cy.url().should('contain', 'tokens/ethereum/NATIVE')
|
||||
})
|
||||
|
||||
it.skip('should show recent tokens and popular tokens with empty search term', () => {
|
||||
@@ -42,7 +58,7 @@ describe('Universal search bar', () => {
|
||||
$navIcon.click()
|
||||
})
|
||||
// Recently searched UNI token should exist.
|
||||
cy.get('[data-cy="search-bar-input"]').last().clear()
|
||||
getSearchBar().clear()
|
||||
cy.get('[data-cy="searchbar-dropdown"]')
|
||||
.contains('[data-cy="searchbar-dropdown"]', 'Recent searches')
|
||||
.find('[data-cy="searchbar-token-row-UNI"]')
|
||||
@@ -58,7 +74,7 @@ describe('Universal search bar', () => {
|
||||
|
||||
it.skip('should show blocked badge when blocked token is searched for', () => {
|
||||
// Search for mTSLA, which is a blocked token.
|
||||
cy.get('[data-cy="search-bar-input"]').last().clear().type('mtsla')
|
||||
getSearchBar().clear().type('mtsla')
|
||||
cy.get('[data-cy="searchbar-token-row-mTSLA"]').find('[data-cy="blocked-icon"]').should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
41
cypress/e2e/wallet-connection/connect.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { getTestSelector } from '../../utils'
|
||||
import { DISCONNECTED_WALLET_USER_STATE } from '../../utils/user-state'
|
||||
|
||||
describe('disconnect wallet', () => {
|
||||
it('should clear state', () => {
|
||||
cy.visit('/swap', { ethereum: 'hardhat' })
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('1')
|
||||
|
||||
// Verify wallet is connected
|
||||
cy.hardhat().then((hardhat) => cy.contains(hardhat.wallet.address.substring(0, 6)))
|
||||
cy.contains('Balance:')
|
||||
|
||||
// Disconnect the wallet
|
||||
cy.hardhat().then((hardhat) => cy.contains(hardhat.wallet.address.substring(0, 6)).click())
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-disconnect')).contains('Disconnect')
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
|
||||
// Verify wallet has disconnected
|
||||
cy.contains('Connect a wallet').should('exist')
|
||||
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect')
|
||||
cy.contains('Connect Wallet')
|
||||
|
||||
// Verify swap input is cleared
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
|
||||
})
|
||||
})
|
||||
|
||||
describe('connect wallet', () => {
|
||||
it('should load state', () => {
|
||||
cy.visit('/swap', { ethereum: 'hardhat', userState: DISCONNECTED_WALLET_USER_STATE })
|
||||
|
||||
// Connect the wallet
|
||||
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect').click()
|
||||
cy.contains('MetaMask').click()
|
||||
|
||||
// Verify wallet is connected
|
||||
cy.hardhat().then((hardhat) => cy.contains(hardhat.wallet.address.substring(0, 6)))
|
||||
cy.contains('Balance:')
|
||||
})
|
||||
})
|
||||
144
cypress/e2e/wallet-connection/switch-network.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { createDeferredPromise } from '../../../src/test-utils/promise'
|
||||
import { getTestSelector } from '../../utils'
|
||||
|
||||
function waitsForActiveChain(chain: string) {
|
||||
cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', chain)
|
||||
}
|
||||
|
||||
function switchChain(chain: string) {
|
||||
cy.get(getTestSelector('chain-selector')).eq(1).click()
|
||||
cy.contains(chain).click()
|
||||
}
|
||||
|
||||
describe('network switching', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/swap', { ethereum: 'hardhat' })
|
||||
cy.get(getTestSelector('web3-status-connected'))
|
||||
})
|
||||
|
||||
function rejectsNetworkSwitchWith(rejection: unknown) {
|
||||
cy.hardhat().then((hardhat) => {
|
||||
// Reject network switch
|
||||
const sendStub = cy.stub(hardhat.provider, 'send').log(false).as('switch')
|
||||
sendStub.withArgs('wallet_switchEthereumChain').rejects(rejection)
|
||||
sendStub.callThrough() // allows other calls to return non-stubbed values
|
||||
})
|
||||
|
||||
switchChain('Polygon')
|
||||
|
||||
// Verify rejected network switch
|
||||
cy.get('@switch').should('have.been.calledWith', 'wallet_switchEthereumChain')
|
||||
waitsForActiveChain('Ethereum')
|
||||
cy.get(getTestSelector('web3-status-connected'))
|
||||
}
|
||||
|
||||
it('should not display message on user rejection', () => {
|
||||
const USER_REJECTION = { code: 4001 }
|
||||
rejectsNetworkSwitchWith(USER_REJECTION)
|
||||
cy.get(getTestSelector('popups')).should('not.contain', 'Failed to switch networks')
|
||||
})
|
||||
|
||||
it('should display message on unknown error', () => {
|
||||
rejectsNetworkSwitchWith(new Error('Unknown error'))
|
||||
cy.get(getTestSelector('popups')).contains('Failed to switch networks')
|
||||
})
|
||||
|
||||
it('should add missing chain', () => {
|
||||
cy.hardhat().then((hardhat) => {
|
||||
// https://docs.metamask.io/guide/rpc-api.html#unrestricted-methods
|
||||
const CHAIN_NOT_ADDED = { code: 4902 } // missing message in useSelectChain
|
||||
|
||||
// Reject network switch with CHAIN_NOT_ADDED
|
||||
const sendStub = cy.stub(hardhat.provider, 'send').log(false).as('switch')
|
||||
let added = false
|
||||
sendStub
|
||||
.withArgs('wallet_switchEthereumChain')
|
||||
.callsFake(() => (added ? Promise.resolve(null) : Promise.reject(CHAIN_NOT_ADDED)))
|
||||
sendStub.withArgs('wallet_addEthereumChain').callsFake(() => {
|
||||
added = true
|
||||
return Promise.resolve(null)
|
||||
})
|
||||
sendStub.callThrough() // allows other calls to return non-stubbed values
|
||||
})
|
||||
|
||||
switchChain('Polygon')
|
||||
|
||||
// Verify the network was added
|
||||
cy.get('@switch').should('have.been.calledWith', 'wallet_switchEthereumChain')
|
||||
cy.get('@switch').should('have.been.calledWith', 'wallet_addEthereumChain', [
|
||||
{
|
||||
blockExplorerUrls: ['https://polygonscan.com/'],
|
||||
chainId: '0x89',
|
||||
chainName: 'Polygon',
|
||||
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
|
||||
rpcUrls: ['https://polygon-rpc.com/'],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should not disconnect while switching', () => {
|
||||
const promise = createDeferredPromise()
|
||||
|
||||
cy.hardhat().then((hardhat) => {
|
||||
// Reject network switch with CHAIN_NOT_ADDED
|
||||
const sendStub = cy.stub(hardhat.provider, 'send').log(false).as('switch')
|
||||
sendStub.withArgs('wallet_switchEthereumChain').returns(promise)
|
||||
sendStub.callThrough() // allows other calls to return non-stubbed values
|
||||
})
|
||||
|
||||
switchChain('Polygon')
|
||||
|
||||
// Verify there is no disconnection
|
||||
cy.get('@switch').should('have.been.calledWith', 'wallet_switchEthereumChain')
|
||||
cy.contains('Connecting to Polygon')
|
||||
cy.get(getTestSelector('web3-status-connected')).should('be.disabled')
|
||||
promise.resolve()
|
||||
})
|
||||
|
||||
it('should switch networks', () => {
|
||||
// Select an output currency
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.contains('USDC').click()
|
||||
|
||||
// Populate input/output fields
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('1')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
|
||||
|
||||
// Switch network
|
||||
switchChain('Polygon')
|
||||
|
||||
// Verify network switch
|
||||
cy.wait('@wallet_switchEthereumChain')
|
||||
waitsForActiveChain('Polygon')
|
||||
cy.get(getTestSelector('web3-status-connected'))
|
||||
cy.url().should('contain', 'chain=polygon')
|
||||
|
||||
// Verify that the input/output fields were reset
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
|
||||
cy.get(`#swap-currency-input .token-symbol-container`).should('contain.text', 'MATIC')
|
||||
cy.get(`#swap-currency-output .token-amount-input`).should('not.have.value')
|
||||
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'Select token')
|
||||
})
|
||||
})
|
||||
|
||||
describe('network switching from URL param', () => {
|
||||
it('should switch network from URL param', () => {
|
||||
cy.visit('/swap?chain=polygon', { ethereum: 'hardhat' })
|
||||
cy.get(getTestSelector('web3-status-connected'))
|
||||
cy.wait('@wallet_switchEthereumChain')
|
||||
waitsForActiveChain('Polygon')
|
||||
})
|
||||
|
||||
it('should be able to switch network after loading from URL param', () => {
|
||||
cy.visit('/swap?chain=polygon', { ethereum: 'hardhat' })
|
||||
cy.get(getTestSelector('web3-status-connected'))
|
||||
cy.wait('@wallet_switchEthereumChain')
|
||||
waitsForActiveChain('Polygon')
|
||||
|
||||
// switching to another chain clears query param
|
||||
switchChain('Ethereum')
|
||||
cy.wait('@wallet_switchEthereumChain')
|
||||
waitsForActiveChain('Ethereum')
|
||||
cy.url().should('not.contain', 'chain=polygon')
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Wallet Dropdown', () => {
|
||||
function itShouldChangeTheTheme() {
|
||||
it('should change the theme', () => {
|
||||
function itChangesTheme() {
|
||||
it('should change theme', () => {
|
||||
cy.get(getTestSelector('theme-lightmode')).click()
|
||||
|
||||
cy.get(getTestSelector('theme-lightmode')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
|
||||
@@ -21,13 +21,17 @@ describe('Wallet Dropdown', () => {
|
||||
})
|
||||
}
|
||||
|
||||
function itShouldChangeTheLanguage() {
|
||||
it('should select a language', () => {
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Deutsch').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Sprache')
|
||||
function itChangesLocale() {
|
||||
it('should change locale', () => {
|
||||
cy.contains('Uniswap available in: English').should('not.exist')
|
||||
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.location('hash').should('match', /\?lng=af-ZA$/)
|
||||
cy.contains('Uniswap available in: English')
|
||||
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
cy.location('hash').should('match', /\?lng=en-US$/)
|
||||
cy.contains('Uniswap available in: English').should('not.exist')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,19 +41,39 @@ describe('Wallet Dropdown', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-settings')).click()
|
||||
})
|
||||
itShouldChangeTheTheme()
|
||||
itShouldChangeTheLanguage()
|
||||
itChangesTheme()
|
||||
itChangesLocale()
|
||||
})
|
||||
|
||||
describe('testnet toggle', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
it('should toggle testnet visibility', () => {
|
||||
cy.get(getTestSelector('chain-selector')).last().click()
|
||||
cy.get(getTestSelector('chain-selector-options')).should('not.contain.text', 'Sepolia')
|
||||
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-settings')).click()
|
||||
cy.get('#testnets-toggle').click()
|
||||
cy.get(getTestSelector('close-account-drawer')).click()
|
||||
cy.get(getTestSelector('chain-selector')).last().click()
|
||||
cy.get(getTestSelector('chain-selector-options')).should('contain.text', 'Sepolia')
|
||||
})
|
||||
})
|
||||
|
||||
describe('disconnected', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
// click twice, first time to show confirmation, second to confirm
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-disconnect')).should('contain', 'Disconnect')
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-settings')).click()
|
||||
})
|
||||
itShouldChangeTheTheme()
|
||||
itShouldChangeTheLanguage()
|
||||
itChangesTheme()
|
||||
itChangesLocale()
|
||||
})
|
||||
|
||||
describe('with color theme', () => {
|
||||
|
||||
2498
cypress/fixtures/mini-portfolio/activity.json
Normal file
1
cypress/fixtures/mini-portfolio/nfts.json
Normal file
1
cypress/fixtures/mini-portfolio/tokens.json
Normal file
19
cypress/staging/t9n.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('translations', () => {
|
||||
it('loads locale from the query param', () => {
|
||||
cy.visit('/?lng=fr-FR')
|
||||
cy.contains('Échanger')
|
||||
cy.contains('Uniswap disponible en : English')
|
||||
})
|
||||
|
||||
it('loads locale from menu', () => {
|
||||
cy.visit('/')
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-settings')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('français').click({ force: true })
|
||||
cy.location('hash').should('match', /\?lng=fr-FR$/)
|
||||
cy.contains('Échanger')
|
||||
cy.contains('Uniswap disponible en : English')
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import 'cypress-hardhat/lib/browser'
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
import { FeatureFlag } from '../../src/featureFlags'
|
||||
import { UserState } from '../../src/state/user/reducer'
|
||||
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
||||
import { injected } from './ethereum'
|
||||
@@ -58,7 +58,7 @@ Cypress.Commands.overwrite(
|
||||
// Set initial user state.
|
||||
win.localStorage.setItem(
|
||||
'redux_localstorage_simple_user', // storage key for the user reducer using 'redux-localstorage-simple'
|
||||
JSON.stringify(options?.userState ?? CONNECTED_WALLET_USER_STATE)
|
||||
JSON.stringify({ ...CONNECTED_WALLET_USER_STATE, ...(options?.userState ?? {}) })
|
||||
)
|
||||
|
||||
// Set feature flags, if configured.
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
*/
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
|
||||
import { SupportedChainId } from '../../src/constants/chains'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
// todo: figure out how env vars actually work in CI
|
||||
// const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
|
||||
@@ -15,7 +13,7 @@ const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e77
|
||||
|
||||
// address of the above key
|
||||
const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address
|
||||
const CHAIN_ID = SupportedChainId.GOERLI
|
||||
const CHAIN_ID = ChainId.GOERLI
|
||||
const HEXLIFIED_CHAIN_ID = `0x${CHAIN_ID.toString(16)}`
|
||||
|
||||
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 5)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-ignore
|
||||
import TokenListJSON from '@uniswap/default-token-list'
|
||||
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
||||
|
||||
beforeEach(() => {
|
||||
// Many API calls enforce that requests come from our app, so we must mock Origin and Referer.
|
||||
@@ -16,6 +17,9 @@ beforeEach(() => {
|
||||
req.continue()
|
||||
})
|
||||
|
||||
// Log requests to hardhat.
|
||||
cy.intercept(/:8545/, logJsonRpc)
|
||||
|
||||
// Mock analytics responses to avoid analytics in tests.
|
||||
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
|
||||
const requestBody = JSON.stringify(req.body)
|
||||
@@ -39,3 +43,21 @@ beforeEach(() => {
|
||||
// This resets the fork, as well as options like automine.
|
||||
cy.hardhat().then((hardhat) => hardhat.reset())
|
||||
})
|
||||
|
||||
function logJsonRpc(req: CyHttpMessages.IncomingHttpRequest) {
|
||||
req.alias = req.body.method
|
||||
const log = Cypress.log({
|
||||
autoEnd: false,
|
||||
name: req.body.method,
|
||||
message: req.body.params?.map((param: unknown) =>
|
||||
typeof param === 'object' ? '{...}' : param?.toString().substring(0, 10)
|
||||
),
|
||||
})
|
||||
req.on('after:response', (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
log.end()
|
||||
} else {
|
||||
log.error(new Error(`${res.statusCode}: ${res.statusMessage}`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"strict": true,
|
||||
"target": "ES5",
|
||||
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo
|
||||
"types": ["cypress", "node"]
|
||||
"types": ["cypress", "node"],
|
||||
"jsx": "react"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["**/*.ts"],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { UserState } from '../../src/state/user/reducer'
|
||||
|
||||
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: 'INJECTED' }
|
||||
|
||||
export const DISCONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: undefined }
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/* eslint-env node */
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'enforce use of retry() for dynamic imports',
|
||||
category: 'Best Practices',
|
||||
recommended: false,
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
ImportExpression(node) {
|
||||
const grandParent = node.parent.parent
|
||||
if (
|
||||
!(
|
||||
grandParent &&
|
||||
grandParent.type === 'CallExpression' &&
|
||||
// Technically, we are only checking that a function named `retry` wraps the dynamic import.
|
||||
// We do not go as far as enforcing that it is import('utils/retry').retry
|
||||
grandParent.callee.name === 'retry' &&
|
||||
grandParent.arguments.length === 1 &&
|
||||
grandParent.arguments[0].type === 'ArrowFunctionExpression'
|
||||
)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
message: 'Dynamic import should be wrapped in retry (see `utils/retry.ts`): `retry(() => import(...))`',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
403
functions/__snapshots__/collection.test.ts.snap
Normal file
@@ -0,0 +1,403 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should inject metadata for valid collections 1`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Azuki on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="twitter:image:alt" content="Azuki on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid collections 2`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid collections 3`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="twitter:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
403
functions/__snapshots__/nft.test.ts.snap
Normal file
@@ -0,0 +1,403 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should inject metadata for valid assets 1`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Azuki #2550"/><meta property="og:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki #2550"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki #2550"/><meta property="twitter:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="twitter:image:alt" content="Azuki #2550"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid assets 2`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club #3735"/><meta property="og:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club #3735"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club #3735"/><meta property="twitter:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club #3735"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid assets 3`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="CryptoPunk #3947"/><meta property="og:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CryptoPunk #3947"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CryptoPunk #3947"/><meta property="twitter:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="twitter:image:alt" content="CryptoPunk #3947"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
671
functions/__snapshots__/token.test.ts.snap
Normal file
@@ -0,0 +1,671 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should inject metadata for valid tokens 1`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get USDC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get USDC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get USDC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="twitter:image:alt" content="Get USDC on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid tokens 2`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get ETH on Uniswap"/><meta property="og:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get ETH on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get ETH on Uniswap"/><meta property="twitter:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="twitter:image:alt" content="Get ETH on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid tokens 3`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get MATIC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get MATIC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/polygon/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get MATIC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="twitter:image:alt" content="Get MATIC on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid tokens 4`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get FUC on Uniswap"/><meta property="og:image" content="https://assets.coingecko.com/coins/images/30081/large/fuc.png?1683016112"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get FUC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/arbitrum/0x1f52145666c862ed3e2f1da213d479e61b2892af"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get FUC on Uniswap"/><meta property="twitter:image" content="https://assets.coingecko.com/coins/images/30081/large/fuc.png?1683016112"/><meta property="twitter:image:alt" content="Get FUC on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`should inject metadata for valid tokens 5`] = `
|
||||
"<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
. will be replaced with the URL of the \`public\` folder during build.
|
||||
Only files inside the \`public\` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
|
||||
/>
|
||||
|
||||
<!--
|
||||
Apple Smart App Banner for Safari on iOS
|
||||
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
|
||||
-->
|
||||
<meta name="apple-itunes-app" content="app-id=6443944476">
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(./fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get PEPE on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get PEPE on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get PEPE on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="twitter:image:alt" content="Get PEPE on Uniswap"/></head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
`;
|
||||
1
functions/babel.config.js
Normal file
@@ -0,0 +1 @@
|
||||
export const presets = ['@babel/preset-env']
|
||||
20
functions/client.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ApolloClient, InMemoryCache } from '@apollo/client'
|
||||
const GRAPHQL_ENDPOINT = 'https://api.uniswap.org/v1/graphql'
|
||||
|
||||
//TODO: Figure out how to make ApolloClient global variable
|
||||
export default new ApolloClient({
|
||||
connectToDevTools: true,
|
||||
uri: GRAPHQL_ENDPOINT,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://app.uniswap.org',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.110 Safari/537.36',
|
||||
},
|
||||
cache: new InMemoryCache(),
|
||||
defaultOptions: {
|
||||
watchQuery: {
|
||||
fetchPolicy: 'cache-first',
|
||||
},
|
||||
},
|
||||
})
|
||||
63
functions/collection.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
const collections = [
|
||||
{
|
||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||
collectionName: 'Azuki',
|
||||
image:
|
||||
'https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format',
|
||||
},
|
||||
{
|
||||
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
||||
collectionName: 'Bored Ape Yacht Club',
|
||||
image:
|
||||
'https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format',
|
||||
},
|
||||
{
|
||||
address: '0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
|
||||
collectionName: 'CLONE X - X TAKASHI MURAKAMI',
|
||||
image:
|
||||
'https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format',
|
||||
},
|
||||
]
|
||||
|
||||
test.each(collections)('should inject metadata for valid collections', async (collection) => {
|
||||
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).toMatchSnapshot()
|
||||
expect(body).toContain(`<meta property="og:title" content="${collection.collectionName} on Uniswap"/>`)
|
||||
expect(body).toContain(`<meta property="og:image" content="${collection.image}"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:width" content="1200"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:height" content="630"/>`)
|
||||
expect(body).toContain(`<meta property="og:type" content="website"/>`)
|
||||
expect(body).toContain(`<meta property="og:url" content="${url}"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:alt" content="${collection.collectionName} on Uniswap"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:card" content="summary_large_image"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:title" content="${collection.collectionName} on Uniswap"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:image" content="${collection.image}"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:image:alt" content="${collection.collectionName} on Uniswap"/>`)
|
||||
})
|
||||
|
||||
const invalidCollections = [
|
||||
'http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
'http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545/10',
|
||||
'http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545//',
|
||||
'http://127.0.0.1:3000/nfts/collection',
|
||||
]
|
||||
|
||||
test.each(invalidCollections)(
|
||||
'should not inject metadata for invalid urls',
|
||||
async (url) => {
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).not.toContain('og:title')
|
||||
expect(body).not.toContain('og:image')
|
||||
expect(body).not.toContain('og:image:width')
|
||||
expect(body).not.toContain('og:image:height')
|
||||
expect(body).not.toContain('og:type')
|
||||
expect(body).not.toContain('og:url')
|
||||
expect(body).not.toContain('og:image:alt')
|
||||
expect(body).not.toContain('twitter:card')
|
||||
expect(body).not.toContain('twitter:title')
|
||||
expect(body).not.toContain('twitter:image')
|
||||
expect(body).not.toContain('twitter:image:alt')
|
||||
},
|
||||
50000
|
||||
)
|
||||
41
functions/components/metaTagInjector.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
type MetaTagInjectorInput = {
|
||||
title: string
|
||||
image?: string
|
||||
url: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener class for Cloudflare's HTMLRewriter {@link https://developers.cloudflare.com/workers/runtime-apis/html-rewriter}
|
||||
* to inject meta tags into the <head> of an HTML document.
|
||||
*/
|
||||
export class MetaTagInjector {
|
||||
constructor(private input: MetaTagInjectorInput) {}
|
||||
|
||||
append(element, property: string, content: string) {
|
||||
element.append(`<meta property="${property}" content="${content}"/>`, { html: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for ElementHandler {@link https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#element-handlers}
|
||||
*/
|
||||
element(element) {
|
||||
//Open Graph Tags
|
||||
this.append(element, 'og:title', this.input.title)
|
||||
if (this.input.image) {
|
||||
this.append(element, 'og:image', this.input.image)
|
||||
this.append(element, 'og:image:width', '1200')
|
||||
this.append(element, 'og:image:height', '630')
|
||||
this.append(element, 'og:image:alt', this.input.title)
|
||||
}
|
||||
this.append(element, 'og:type', 'website')
|
||||
this.append(element, 'og:url', this.input.url)
|
||||
|
||||
//Twitter Tags
|
||||
this.append(element, 'twitter:card', 'summary_large_image')
|
||||
this.append(element, 'twitter:title', this.input.title)
|
||||
if (this.input.image) {
|
||||
this.append(element, 'twitter:image', this.input.image)
|
||||
this.append(element, 'twitter:image:alt', this.input.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
9
functions/global-setup.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { setup } from 'jest-dev-server'
|
||||
|
||||
module.exports = async function globalSetup() {
|
||||
globalThis.servers = await setup({
|
||||
command: `yarn start:cloud`,
|
||||
port: 3000,
|
||||
launchTimeout: 50000,
|
||||
})
|
||||
}
|
||||
5
functions/global-teardown.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { teardown } from 'jest-dev-server'
|
||||
|
||||
module.exports = async function globalTeardown() {
|
||||
await teardown(globalThis.servers)
|
||||
}
|
||||
6
functions/global.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setup } from 'jest-dev-server'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var servers: Awaited<ReturnType<typeof setup>>
|
||||
}
|
||||
10
functions/jest.config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"globalSetup": "<rootDir>/global-setup.ts",
|
||||
"globalTeardown": "<rootDir>/global-teardown.ts",
|
||||
"preset": "ts-jest",
|
||||
"transform": {
|
||||
"'^.+\\.(ts|tsx)?$'": "ts-jest",
|
||||
"^.+\\.(js|jsx)$": "babel-jest"
|
||||
},
|
||||
"testTimeout": 50000
|
||||
}
|
||||
64
functions/nft.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
const assets = [
|
||||
{
|
||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||
assetId: '2550',
|
||||
collectionName: 'Azuki',
|
||||
image:
|
||||
'https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png',
|
||||
},
|
||||
{
|
||||
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
||||
assetId: '3735',
|
||||
collectionName: 'Bored Ape Yacht Club',
|
||||
image:
|
||||
'https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png',
|
||||
},
|
||||
{
|
||||
address: '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb',
|
||||
assetId: '3947',
|
||||
collectionName: 'CryptoPunk',
|
||||
image:
|
||||
'https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png',
|
||||
},
|
||||
]
|
||||
|
||||
test.each(assets)('should inject metadata for valid assets', async (nft) => {
|
||||
const url = 'http://127.0.0.1:3000/nfts/asset/' + nft.address + '/' + nft.assetId
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).toMatchSnapshot()
|
||||
expect(body).toContain(`<meta property="og:title" content="${nft.collectionName} #${nft.assetId}"/>`)
|
||||
expect(body).toContain(`<meta property="og:image" content="${nft.image}"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:width" content="1200"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:height" content="630"/>`)
|
||||
expect(body).toContain(`<meta property="og:type" content="website"/>`)
|
||||
expect(body).toContain(`<meta property="og:url" content="${url}"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:alt" content="${nft.collectionName} #${nft.assetId}"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:card" content="summary_large_image"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:title" content="${nft.collectionName} #${nft.assetId}"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:image" content="${nft.image}"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:image:alt" content="${nft.collectionName} #${nft.assetId}"/>`)
|
||||
})
|
||||
|
||||
const invalidAssets = [
|
||||
'http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/100000',
|
||||
'http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||
'http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||
'http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/-1',
|
||||
'http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544//',
|
||||
'http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544//2550',
|
||||
]
|
||||
|
||||
test.each(invalidAssets)('should not inject metadata for invalid calls', async (url) => {
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).not.toContain('og:title')
|
||||
expect(body).not.toContain('og:image')
|
||||
expect(body).not.toContain('og:image:width')
|
||||
expect(body).not.toContain('og:image:height')
|
||||
expect(body).not.toContain('og:type')
|
||||
expect(body).not.toContain('og:url')
|
||||
expect(body).not.toContain('og:image:alt')
|
||||
expect(body).not.toContain('twitter:card')
|
||||
expect(body).not.toContain('twitter:title')
|
||||
expect(body).not.toContain('twitter:image')
|
||||
expect(body).not.toContain('twitter:image:alt')
|
||||
})
|
||||
20
functions/nfts/asset/[[index]].ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/* eslint-disable import/no-unused-modules */
|
||||
import { MetaTagInjector } from '../../components/metaTagInjector'
|
||||
import getAsset from '../../utils/getAsset'
|
||||
|
||||
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
||||
const { index } = params
|
||||
const collectionAddress = index[0]?.toString()
|
||||
const tokenId = index[1]?.toString()
|
||||
const assetPromise = getAsset(collectionAddress, tokenId, request.url)
|
||||
const resPromise = next()
|
||||
try {
|
||||
const [data, res] = await Promise.all([assetPromise, resPromise])
|
||||
if (!data) {
|
||||
return resPromise
|
||||
}
|
||||
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(res)
|
||||
} catch (e) {
|
||||
return resPromise
|
||||
}
|
||||
}
|
||||
19
functions/nfts/collection/[index].ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-disable import/no-unused-modules */
|
||||
import { MetaTagInjector } from '../../components/metaTagInjector'
|
||||
import getCollection from '../../utils/getCollection'
|
||||
|
||||
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
||||
const { index } = params
|
||||
const collectionAddress = index?.toString()
|
||||
const collectionPromise = getCollection(collectionAddress, request.url)
|
||||
const resPromise = next()
|
||||
try {
|
||||
const [data, res] = await Promise.all([collectionPromise, resPromise])
|
||||
if (!data) {
|
||||
return resPromise
|
||||
}
|
||||
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(res)
|
||||
} catch (e) {
|
||||
return resPromise
|
||||
}
|
||||
}
|
||||
76
functions/token.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
const tokens = [
|
||||
{
|
||||
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
network: 'ethereum',
|
||||
symbol: 'USDC',
|
||||
image:
|
||||
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
|
||||
},
|
||||
{
|
||||
address: 'NATIVE',
|
||||
network: 'ethereum',
|
||||
symbol: 'ETH',
|
||||
image: 'https://token-icons.s3.amazonaws.com/eth.png',
|
||||
},
|
||||
{
|
||||
address: 'NATIVE',
|
||||
network: 'polygon',
|
||||
symbol: 'MATIC',
|
||||
image:
|
||||
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png',
|
||||
},
|
||||
{
|
||||
address: '0x1f52145666c862ed3e2f1da213d479e61b2892af',
|
||||
network: 'arbitrum',
|
||||
symbol: 'FUC',
|
||||
image: 'https://assets.coingecko.com/coins/images/30081/large/fuc.png?1683016112',
|
||||
},
|
||||
{
|
||||
address: '0x6982508145454ce325ddbe47a25d4ec3d2311933',
|
||||
network: 'ethereum',
|
||||
symbol: 'PEPE',
|
||||
image:
|
||||
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png',
|
||||
},
|
||||
]
|
||||
|
||||
test.each(tokens)('should inject metadata for valid tokens', async (token) => {
|
||||
const url = 'http://127.0.0.1:3000/tokens/' + token.network + '/' + token.address
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).toMatchSnapshot()
|
||||
expect(body).toContain(`<meta property="og:title" content="Get ${token.symbol} on Uniswap"/>`)
|
||||
expect(body).toContain(`<meta property="og:image" content="${token.image}"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:width" content="1200"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:height" content="630"/>`)
|
||||
expect(body).toContain(`<meta property="og:type" content="website"/>`)
|
||||
expect(body).toContain(`<meta property="og:url" content="${url}"/>`)
|
||||
expect(body).toContain(`<meta property="og:image:alt" content="Get ${token.symbol} on Uniswap"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:card" content="summary_large_image"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:title" content="Get ${token.symbol} on Uniswap"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:image" content="${token.image}"/>`)
|
||||
expect(body).toContain(`<meta property="twitter:image:alt" content="Get ${token.symbol} on Uniswap"/>`)
|
||||
})
|
||||
|
||||
const invalidTokens = [
|
||||
'http://127.0.0.1:3000/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb49',
|
||||
'http://127.0.0.1:3000/tokens/ethereum',
|
||||
'http://127.0.0.1:3000/tokens/ethereun',
|
||||
'http://127.0.0.1:3000/tokens/ethereum/0x0',
|
||||
'http://127.0.0.1:3000/tokens/ethereum//',
|
||||
'http://127.0.0.1:3000/tokens/potato/?potato=1',
|
||||
]
|
||||
|
||||
test.each(invalidTokens)('should not inject metadata for invalid tokens', async (url) => {
|
||||
const body = await fetch(new Request(url)).then((res) => res.text())
|
||||
expect(body).not.toContain('og:title')
|
||||
expect(body).not.toContain('og:image')
|
||||
expect(body).not.toContain('og:image:width')
|
||||
expect(body).not.toContain('og:image:height')
|
||||
expect(body).not.toContain('og:type')
|
||||
expect(body).not.toContain('og:url')
|
||||
expect(body).not.toContain('og:image:alt')
|
||||
expect(body).not.toContain('twitter:card')
|
||||
expect(body).not.toContain('twitter:title')
|
||||
expect(body).not.toContain('twitter:image')
|
||||
expect(body).not.toContain('twitter:image:alt')
|
||||
})
|
||||
27
functions/tokens/[[index]].ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/* eslint-disable import/no-unused-modules */
|
||||
import { MetaTagInjector } from '../components/metaTagInjector'
|
||||
import getToken from '../utils/getToken'
|
||||
|
||||
const convertTokenAddress = (tokenAddress: string) => {
|
||||
return tokenAddress && tokenAddress === 'NATIVE' ? '0x0000000000000000000000000000000000000000' : tokenAddress
|
||||
}
|
||||
|
||||
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
||||
const { index } = params
|
||||
const networkName = index[0]?.toString().toUpperCase()
|
||||
const tokenAddress = convertTokenAddress(index[1]?.toString())
|
||||
if (!tokenAddress) {
|
||||
return next()
|
||||
}
|
||||
const tokenPromise = getToken(networkName, tokenAddress, request.url)
|
||||
const resPromise = next()
|
||||
try {
|
||||
const [data, res] = await Promise.all([tokenPromise, resPromise])
|
||||
if (!data) {
|
||||
return resPromise
|
||||
}
|
||||
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(res)
|
||||
} catch (e) {
|
||||
return resPromise
|
||||
}
|
||||
}
|
||||
19
functions/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"incremental": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"target": "ES6",
|
||||
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/functions", // avoid clobbering the build tsbuildinfo
|
||||
"types": ["jest", "node"],
|
||||
"jsx": "react",
|
||||
"moduleResolution": "NodeNext",
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["**/*.ts"],
|
||||
"watchOptions": {
|
||||
"excludeDirectories": ["node_modules"]
|
||||
}
|
||||
}
|
||||
38
functions/utils/getAsset.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { AssetDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||
import client from '../client'
|
||||
|
||||
function formatTitleName(name: string, collectionName: string, tokenId: string) {
|
||||
if (name) {
|
||||
return name
|
||||
}
|
||||
if (collectionName && tokenId) {
|
||||
return collectionName + ' #' + tokenId
|
||||
}
|
||||
if (tokenId) {
|
||||
return 'Asset #' + tokenId
|
||||
}
|
||||
return 'View NFT on Uniswap'
|
||||
}
|
||||
|
||||
export default async function getAsset(collectionAddress: string, tokenId: string, url: string) {
|
||||
const { data } = await client.query({
|
||||
query: AssetDocument,
|
||||
variables: {
|
||||
address: collectionAddress,
|
||||
filter: {
|
||||
tokenIds: [tokenId],
|
||||
},
|
||||
},
|
||||
})
|
||||
const asset = data?.nftAssets?.edges[0]?.node
|
||||
if (!asset) {
|
||||
return undefined
|
||||
}
|
||||
const title = formatTitleName(asset.name, asset.collection?.name, asset.tokenId)
|
||||
const formattedAsset = {
|
||||
title,
|
||||
image: asset.image?.url,
|
||||
url,
|
||||
}
|
||||
return formattedAsset
|
||||
}
|
||||
21
functions/utils/getCollection.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { CollectionDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||
import client from '../client'
|
||||
|
||||
export default async function getCollection(collectionAddress: string, url: string) {
|
||||
const { data } = await client.query({
|
||||
query: CollectionDocument,
|
||||
variables: {
|
||||
addresses: collectionAddress,
|
||||
},
|
||||
})
|
||||
const collection = data?.nftCollections?.edges[0]?.node
|
||||
if (!collection || !collection.name) {
|
||||
return undefined
|
||||
}
|
||||
const formattedAsset = {
|
||||
title: collection.name + ' on Uniswap',
|
||||
image: collection.image?.url,
|
||||
url,
|
||||
}
|
||||
return formattedAsset
|
||||
}
|
||||
33
functions/utils/getToken.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { TokenDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||
import client from '../client'
|
||||
|
||||
function formatTitleName(symbol: string, name: string) {
|
||||
if (symbol) {
|
||||
return 'Get ' + symbol + ' on Uniswap'
|
||||
}
|
||||
if (name) {
|
||||
return 'Get ' + name + ' on Uniswap'
|
||||
}
|
||||
return 'View Token on Uniswap'
|
||||
}
|
||||
|
||||
export default async function getToken(networkName: string, tokenAddress: string, url: string) {
|
||||
const { data } = await client.query({
|
||||
query: TokenDocument,
|
||||
variables: {
|
||||
chain: networkName,
|
||||
address: tokenAddress,
|
||||
},
|
||||
})
|
||||
const asset = data?.token
|
||||
if (!asset) {
|
||||
return undefined
|
||||
}
|
||||
const title = formatTitleName(asset.symbol, asset.name)
|
||||
const formattedAsset = {
|
||||
title,
|
||||
image: asset.project?.logoUrl,
|
||||
url,
|
||||
}
|
||||
return formattedAsset
|
||||
}
|
||||
@@ -15,6 +15,7 @@ const config: CodegenConfig = {
|
||||
withHooks: true,
|
||||
// This avoid all generated schemas being wrapped in Maybe https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#maybevalue-string-default-value-t--null
|
||||
maybeValue: 'T',
|
||||
immutableTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
/* eslint-env node */
|
||||
require('dotenv').config()
|
||||
|
||||
// Block selection is arbitrary, as e2e tests will build up their own state.
|
||||
// The only requirement is that all infrastructure under test (eg Permit2 contracts) are already deployed.
|
||||
const BLOCK_NUMBER = 17023328
|
||||
// TODO(WEB-2187): Make more dynamic to avoid manually updating
|
||||
const BLOCK_NUMBER = 17693163
|
||||
const POLYGON_BLOCK_NUMBER = 43600000
|
||||
|
||||
const mainnetFork = {
|
||||
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
blockNumber: BLOCK_NUMBER,
|
||||
const forkingConfig = {
|
||||
httpHeaders: {
|
||||
Origin: 'localhost:3000', // infura allowlists requests by origin
|
||||
},
|
||||
}
|
||||
|
||||
const forks = {
|
||||
[ChainId.MAINNET]: {
|
||||
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
blockNumber: BLOCK_NUMBER,
|
||||
...forkingConfig,
|
||||
},
|
||||
[ChainId.POLYGON]: {
|
||||
url: `https://polygon-mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
blockNumber: POLYGON_BLOCK_NUMBER,
|
||||
...forkingConfig,
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
forks,
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1,
|
||||
forking: mainnetFork,
|
||||
chainId: ChainId.MAINNET,
|
||||
forking: forks[ChainId.MAINNET],
|
||||
accounts: {
|
||||
count: 2,
|
||||
},
|
||||
|
||||
@@ -115,13 +115,11 @@ const linguiConfig = {
|
||||
'vi-VN',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
'pseudo',
|
||||
],
|
||||
orderBy: 'messageId',
|
||||
rootDir: '.',
|
||||
runtimeConfigModule: ['@lingui/core', 'i18n'],
|
||||
sourceLocale: 'en-US',
|
||||
pseudoLocale: 'pseudo',
|
||||
extractors: [cachingExtractor],
|
||||
}
|
||||
|
||||
|
||||
55
package.json
@@ -15,11 +15,11 @@
|
||||
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
|
||||
"graphql": "yarn graphql:fetch && yarn graphql:generate",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo",
|
||||
"i18n:compile": "lingui compile",
|
||||
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
|
||||
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
|
||||
"start": "craco start",
|
||||
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --node-compat --proxy=3001 --port=3000 -- yarn start",
|
||||
"build": "craco build",
|
||||
"build:e2e": "REACT_APP_CSP_ALLOW_UNSAFE_EVAL=true REACT_APP_ADD_COVERAGE_INSTRUMENTATION=true craco build",
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js' --only-mapped",
|
||||
@@ -27,6 +27,7 @@
|
||||
"lint": "yarn eslint --ignore-path .gitignore --cache --cache-location node_modules/.cache/eslint/ .",
|
||||
"typecheck": "tsc",
|
||||
"test": "craco test",
|
||||
"test:cloud": "NODE_OPTIONS=--experimental-vm-modules yarn jest functions --watch --config=functions/jest.config.json",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest"
|
||||
@@ -67,6 +68,9 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.22.7",
|
||||
"@cloudflare/workers-types": "^4.20230518.0",
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@lingui/cli": "^3.9.0",
|
||||
@@ -98,35 +102,42 @@
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@uniswap/default-token-list": "^9.4.0",
|
||||
"@uniswap/default-token-list": "^11.2.0",
|
||||
"@uniswap/eslint-config": "^1.2.0",
|
||||
"@vanilla-extract/babel-plugin": "^1.1.7",
|
||||
"@vanilla-extract/jest-transform": "^1.1.1",
|
||||
"@vanilla-extract/webpack-plugin": "^2.1.11",
|
||||
"@walletconnect/types": "^2.8.6",
|
||||
"babel-jest": "^29.6.1",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"buffer": "^6.0.3",
|
||||
"concurrently": "^8.0.1",
|
||||
"cypress": "12.12.0",
|
||||
"cypress-hardhat": "^2.3.0",
|
||||
"cypress-hardhat": "^2.5.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-plugin-import": "^2.27",
|
||||
"eslint-plugin-rulesdir": "^0.2.2",
|
||||
"hardhat": "^2.14.0",
|
||||
"jest": "^29.6.1",
|
||||
"jest-dev-server": "^9.0.0",
|
||||
"jest-fail-on-console": "^3.1.1",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"ms.macro": "^2.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.8",
|
||||
"process": "^0.11.10",
|
||||
"react-scripts": "^5.0.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"serve": "^11.3.2",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-transform-graphql-tag": "^0.2.1",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
||||
"wrangler": "https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/4925945367/npm-package-wrangler-3048",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -157,24 +168,25 @@
|
||||
"@sentry/types": "^7.45.0",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "^1.3.1",
|
||||
"@uniswap/analytics-events": "^2.10.0",
|
||||
"@uniswap/conedison": "^1.4.0",
|
||||
"@uniswap/analytics-events": "^2.14.0",
|
||||
"@uniswap/conedison": "^1.8.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/permit2-sdk": "1.2.0",
|
||||
"@uniswap/merkle-distributor": "^1.0.1",
|
||||
"@uniswap/permit2-sdk": "^1.2.0",
|
||||
"@uniswap/redux-multicall": "^1.1.8",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.2.2",
|
||||
"@uniswap/smart-order-router": "^3.6.1",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.31",
|
||||
"@uniswap/universal-router-sdk": "^1.3.8",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/router-sdk": "^1.6.0",
|
||||
"@uniswap/sdk-core": "^4.0.3",
|
||||
"@uniswap/smart-order-router": "^3.13.7",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.33",
|
||||
"@uniswap/uniswapx-sdk": "^1.1.0",
|
||||
"@uniswap/universal-router-sdk": "^1.5.4",
|
||||
"@uniswap/v2-core": "^1.0.1",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v2-sdk": "^3.2.0",
|
||||
"@uniswap/v3-core": "^1.0.1",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/v3-sdk": "^3.10.0",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -186,17 +198,16 @@
|
||||
"@visx/react-spring": "^2.12.2",
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "^1.8.0",
|
||||
"@web3-react/coinbase-wallet": "^8.2.0",
|
||||
"@web3-react/core": "^8.2.0",
|
||||
"@web3-react/eip1193": "^8.2.0",
|
||||
"@web3-react/empty": "^8.2.0",
|
||||
"@web3-react/gnosis-safe": "^8.2.0",
|
||||
"@web3-react/gnosis-safe": "^8.2.1",
|
||||
"@web3-react/metamask": "^8.2.0",
|
||||
"@web3-react/network": "^8.2.0",
|
||||
"@web3-react/types": "^8.2.0",
|
||||
"@web3-react/url": "^8.2.0",
|
||||
"@web3-react/walletconnect": "^8.2.0",
|
||||
"@web3-react/walletconnect-v2": "^8.3.7",
|
||||
"ajv": "^8.11.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
@@ -248,7 +259,7 @@
|
||||
"styled-components": "^5.3.5",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"use-resize-observer": "^9.0.2",
|
||||
"use-resize-observer": "^9.1.0",
|
||||
"uuid": "^8.3.2",
|
||||
"video-extensions": "^1.2.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
@@ -261,7 +272,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"npm": "please-use-yarn",
|
||||
"node": "14",
|
||||
"node": "18.x",
|
||||
"yarn": ">=1.22"
|
||||
}
|
||||
}
|
||||
|
||||
1
public/CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
@uniswap/web-admins
|
||||
30
public/apple-app-site-association
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"applinks": {
|
||||
"details": [
|
||||
{
|
||||
"appIDs": [
|
||||
"JH3UHGZD75.com.uniswap.mobile",
|
||||
"JH3UHGZD75.com.uniswap.mobile.dev"
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"#": "/nfts/asset/*",
|
||||
"comment": "NFT Item"
|
||||
},
|
||||
{
|
||||
"#": "/nfts/collection/*",
|
||||
"comment": "NFT Collection"
|
||||
},
|
||||
{
|
||||
"#": "/tokens/*",
|
||||
"comment": "Token address"
|
||||
},
|
||||
{
|
||||
"#": "/address/*",
|
||||
"comment": "Wallet address"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
public/images/324x74_App_Watermark.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
3
public/images/54x54_Verified_Check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="55" height="55" viewBox="0 0 55 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.48223 46.7073C7.39664 45.6264 6.85526 43.9605 6.85814 41.7096V36.8327C6.8693 36.41 6.71466 35.9999 6.42734 35.69L2.981 32.2403C1.37846 30.6564 0.577148 29.0983 0.577148 27.5661C0.577148 26.0338 1.36838 24.4786 2.95082 22.9004L6.39716 19.4508C6.68527 19.1414 6.84004 18.7309 6.82795 18.3081L6.82795 13.4182C6.82795 11.1501 7.36932 9.47985 8.45205 8.40759C9.53477 7.33532 11.1876 6.79776 13.4105 6.79488L18.3128 6.79488C18.5176 6.80189 18.7217 6.7673 18.9128 6.69319C19.1038 6.61908 19.2778 6.50698 19.4243 6.36368L22.9138 2.91403C24.4991 1.34732 26.0527 0.559659 27.5749 0.551034C29.097 0.54241 30.6508 1.33007 32.2361 2.91403L35.6824 6.36368C35.834 6.50867 36.0132 6.6216 36.2093 6.69569C36.4055 6.76978 36.6145 6.80351 36.824 6.79488H41.6963C43.9421 6.79488 45.605 7.33677 46.6849 8.42054C47.7647 9.5043 48.3061 11.1702 48.309 13.4182V18.2951C48.2969 18.7179 48.4517 19.1285 48.7398 19.4378L52.1861 22.8875C53.7686 24.4686 54.5655 26.0238 54.577 27.5531C54.5885 29.0825 53.7915 30.6362 52.1861 32.2145L48.7398 35.6641C48.4525 35.974 48.2978 36.3842 48.309 36.8068V41.6837C48.309 43.9289 47.7633 45.5948 46.6719 46.6814C45.5806 47.768 43.922 48.3099 41.6963 48.3071H36.824C36.6144 48.2977 36.4052 48.3311 36.2089 48.4052C36.0127 48.4793 35.8336 48.5927 35.6824 48.7383L32.2361 52.1879C30.6508 53.7546 29.097 54.5423 27.5749 54.5509C26.0527 54.5595 24.4991 53.7719 22.9138 52.1879L19.4243 48.7383C19.2782 48.5943 19.1042 48.4818 18.9131 48.4077C18.7219 48.3335 18.5177 48.2993 18.3128 48.3071H13.4105C11.2077 48.3243 9.56496 47.791 8.48223 46.7073ZM25.225 37.9942C26.0971 37.9942 26.848 37.5581 27.3567 36.783L38.1848 19.8505C38.4997 19.3418 38.7904 18.7604 38.7904 18.2033C38.7904 17.0163 37.7245 16.2169 36.586 16.2169C35.8593 16.2169 35.2052 16.6045 34.6965 17.4281L25.1281 32.786L20.6225 27.045C20.0411 26.294 19.4597 26.0276 18.733 26.0276C17.5461 26.0276 16.6255 26.9723 16.6255 28.1835C16.6255 28.7649 16.8436 29.2978 17.2554 29.8065L22.9722 36.783C23.6262 37.6308 24.3287 37.9942 25.225 37.9942Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -19,9 +19,9 @@
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
<% if (process.env.REACT_APP_CSP_ALLOW_UNSAFE_EVAL) { %>
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval'"
|
||||
content="script-src 'self' 'unsafe-inline' 'unsafe-eval'"
|
||||
<% } else { %>
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
content="script-src 'self' 'unsafe-inline'"
|
||||
<% } %>
|
||||
/>
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
|
||||
@@ -10,7 +10,7 @@ const thegraphConfig = require('../graphql.thegraph.config')
|
||||
const exec = promisify(child_process.exec)
|
||||
|
||||
function fetchSchema(url, outputFile) {
|
||||
exec(`npx get-graphql-schema --h Origin=https://app.uniswap.org ${url}`)
|
||||
exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`)
|
||||
.then(({ stderr, stdout }) => {
|
||||
if (stderr) {
|
||||
throw new Error(stderr)
|
||||
|
||||
47
src/analytics/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
sendAnalyticsEvent as sendAnalyticsTraceEvent,
|
||||
Trace as AnalyticsTrace,
|
||||
TraceEvent as AnalyticsEvent,
|
||||
} from '@uniswap/analytics'
|
||||
import { atomWithStorage, useAtomValue } from 'jotai/utils'
|
||||
import { memo } from 'react'
|
||||
|
||||
export { getDeviceId, initializeAnalytics, OriginApplication, user, useTrace } from '@uniswap/analytics'
|
||||
|
||||
const allowAnalyticsAtomKey = 'allow_analytics'
|
||||
export const allowAnalyticsAtom = atomWithStorage<boolean>(allowAnalyticsAtomKey, true)
|
||||
|
||||
export const Trace = memo((props: React.ComponentProps<typeof AnalyticsTrace>) => {
|
||||
const allowAnalytics = useAtomValue(allowAnalyticsAtom)
|
||||
const shouldLogImpression = allowAnalytics ? props.shouldLogImpression : false
|
||||
|
||||
return <AnalyticsTrace {...props} shouldLogImpression={shouldLogImpression} />
|
||||
})
|
||||
|
||||
Trace.displayName = 'Trace'
|
||||
|
||||
export const TraceEvent = memo((props: React.ComponentProps<typeof AnalyticsEvent>) => {
|
||||
const allowAnalytics = useAtomValue(allowAnalyticsAtom)
|
||||
const shouldLogImpression = allowAnalytics ? props.shouldLogImpression : false
|
||||
|
||||
return <AnalyticsEvent {...props} shouldLogImpression={shouldLogImpression} />
|
||||
})
|
||||
|
||||
TraceEvent.displayName = 'TraceEvent'
|
||||
|
||||
export const sendAnalyticsEvent: typeof sendAnalyticsTraceEvent = (event, properties) => {
|
||||
let allowAnalytics = true
|
||||
|
||||
try {
|
||||
const value = localStorage.getItem(allowAnalyticsAtomKey)
|
||||
|
||||
if (typeof value === 'string') {
|
||||
allowAnalytics = JSON.parse(value)
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
|
||||
if (allowAnalytics) {
|
||||
sendAnalyticsTraceEvent(event, properties)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_12510_20348)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.99872 0H19.0003C21.7622 0 24 2.40768 24 5.37792V18.6221C24 21.5923 21.7622 24 19.0013 24H4.99872C2.23776 24 0 21.5923 0 18.6221V5.37792C0 2.40768 2.23776 0 4.99872 0Z" fill="#0052FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9994 3.47656C16.7073 3.47656 20.5233 7.29256 20.5233 12.0004C20.5233 16.7082 16.7073 20.5242 11.9994 20.5242C7.29159 20.5242 3.47559 16.7082 3.47559 12.0004C3.47559 7.29256 7.29159 3.47656 11.9994 3.47656Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.90035 9.27539H14.0984C14.444 9.27539 14.7234 9.57683 14.7234 9.94739V14.0514C14.7234 14.4229 14.4431 14.7234 14.0984 14.7234H9.90035C9.55475 14.7234 9.27539 14.422 9.27539 14.0514V9.94739C9.27539 9.57683 9.55571 9.27539 9.90035 9.27539Z" fill="#0052FF"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_12510_20348">
|
||||
<rect width="24" height="24" rx="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,15 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" rx="8" fill="#F7F9FB"/>
|
||||
<path d="M19.6128 4L13.2335 8.73803L14.4132 5.94266L19.6128 4Z" fill="#E2761B" stroke="#E2761B" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.88603 4L11.2141 8.78291L10.0921 5.94266L4.88603 4ZM17.3177 14.9827L15.6187 17.5858L19.254 18.586L20.2991 15.0404L17.3177 14.9827ZM4.21283 15.0404L5.25148 18.586L8.88675 17.5858L7.18772 14.9827L4.21283 15.0404Z" fill="#E4761B" stroke="#E4761B" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.68189 10.5847L7.66888 12.117L11.2785 12.2773L11.1503 8.3984L8.68189 10.5847ZM15.8178 10.5847L13.3173 8.35352L13.234 12.2773L16.8372 12.117L15.8178 10.5847ZM8.88705 17.5859L11.0541 16.5281L9.18198 15.0663L8.88705 17.5859ZM13.4456 16.5281L15.619 17.5859L15.3177 15.0663L13.4456 16.5281Z" fill="#E4761B" stroke="#E4761B" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.6191 17.5857L13.4456 16.5278L13.6187 17.9448L13.5995 18.541L15.6191 17.5857ZM8.88708 17.5857L10.9067 18.541L10.8939 17.9448L11.0541 16.5278L8.88708 17.5857Z" fill="#D7C1B3" stroke="#D7C1B3" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.9392 14.1297L9.13123 13.5976L10.4071 13.0142L10.9392 14.1297ZM13.5615 14.1297L14.0937 13.0142L15.3759 13.5976L13.5615 14.1297Z" fill="#233447" stroke="#233447" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.88652 17.5856L9.19427 14.9826L7.1875 15.0403L8.88652 17.5856ZM15.3108 14.9826L15.6185 17.5856L17.3175 15.0403L15.3108 14.9826ZM16.8367 12.1167L13.2335 12.277L13.5669 14.1299L14.099 13.0143L15.3813 13.5977L16.8367 12.1167ZM9.13016 13.5977L10.4124 13.0143L10.9382 14.1299L11.278 12.277L7.66836 12.1167L9.13016 13.5977Z" fill="#CD6116" stroke="#CD6116" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.66888 12.1167L9.18198 15.0659L9.13069 13.5977L7.66888 12.1167ZM15.3818 13.5977L15.3177 15.0659L16.8372 12.1167L15.3818 13.5977ZM11.2785 12.277L10.9387 14.1299L11.3619 16.3162L11.458 13.4374L11.2785 12.277ZM13.234 12.277L13.0609 13.431L13.1378 16.3162L13.5674 14.1299L13.234 12.277Z" fill="#E4751F" stroke="#E4751F" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5679 14.1298L13.1384 16.3161L13.4461 16.5277L15.3182 15.0659L15.3824 13.5977L13.5679 14.1298ZM9.13123 13.5977L9.18252 15.0659L11.0546 16.5277L11.3624 16.3161L10.9392 14.1298L9.13123 13.5977Z" fill="#F6851B" stroke="#F6851B" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5995 18.5412L13.6187 17.945L13.4584 17.8039H11.0413L10.8939 17.945L10.9067 18.5412L8.88708 17.5859L9.59234 18.163L11.0221 19.1567H13.4777L14.9138 18.163L15.6191 17.5859L13.5995 18.5412Z" fill="#C0AD9E" stroke="#C0AD9E" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.4455 16.528L13.1377 16.3164H11.3618L11.054 16.528L10.8937 17.9449L11.0412 17.8039H13.4583L13.6186 17.9449L13.4455 16.528Z" fill="#161616" stroke="#161616" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.8824 9.04578L20.4273 6.42992L19.6131 4L13.4453 8.57775L15.8175 10.5845L19.1707 11.5655L19.9144 10.6999L19.5939 10.4691L20.1068 10.0011L19.7093 9.69333L20.2222 9.30224L19.8824 9.04578ZM4.07825 6.42992L4.62322 9.04578L4.277 9.30224L4.78991 9.69333L4.39882 10.0011L4.91173 10.4691L4.59116 10.6999L5.32847 11.5655L8.68164 10.5845L11.0539 8.57775L4.88608 4L4.07825 6.42992Z" fill="#763D16" stroke="#763D16" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.1706 11.5657L15.8175 10.5847L16.8369 12.1171L15.3174 15.0663L17.3177 15.0407H20.2991L19.1706 11.5657ZM8.68158 10.5847L5.32841 11.5657L4.21283 15.0407H7.18772L9.18167 15.0663L7.66858 12.1171L8.68158 10.5847ZM13.2337 12.2773L13.4453 8.57796L14.4198 5.94287H10.0921L11.0538 8.57796L11.2782 12.2773L11.3551 13.4442L11.3616 16.3165H13.1375L13.1503 13.4442L13.2337 12.2773Z" fill="#F6851B" stroke="#F6851B" stroke-width="0.0641141" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" rx="8" fill="#3396FF"/>
|
||||
<path d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24Z" fill="#3396FF"/>
|
||||
<path d="M7.33957 8.93036C9.91346 6.42035 14.0867 6.42035 16.6606 8.93036L16.9704 9.23244C17.0991 9.35791 17.0991 9.5614 16.9704 9.68687L15.9107 10.7203C15.8463 10.783 15.742 10.783 15.6777 10.7203L15.2514 10.3046C13.4557 8.55352 10.5444 8.55352 8.74877 10.3046L8.29223 10.7497C8.22787 10.8125 8.12357 10.8125 8.05921 10.7497L6.99954 9.71635C6.87082 9.59087 6.87082 9.38739 6.99954 9.26191L7.33957 8.93036ZM18.8522 11.0674L19.7953 11.9871C19.924 12.1126 19.924 12.3161 19.7953 12.4416L15.5426 16.5886C15.414 16.7141 15.2053 16.7141 15.0766 16.5886L12.0584 13.6454C12.0262 13.614 11.974 13.614 11.9419 13.6454L8.92363 16.5886C8.79497 16.7141 8.5863 16.7141 8.45758 16.5886L4.20486 12.4415C4.07616 12.316 4.07616 12.1126 4.20486 11.9871L5.14799 11.0674C5.27669 10.9419 5.48534 10.9419 5.61404 11.0674L8.63232 14.0107C8.6645 14.0421 8.71665 14.0421 8.74883 14.0107L11.767 11.0674C11.8957 10.9419 12.1043 10.9419 12.233 11.0674L15.2513 14.0107C15.2835 14.0421 15.3357 14.0421 15.3678 14.0107L18.3861 11.0674C18.5148 10.9419 18.7234 10.9419 18.8522 11.0674Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
11
src/assets/svg/avax_logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13871_12533)">
|
||||
<path d="M12.9341 2.74118H3.05524V11.7259H12.9341V2.74118Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9947 8C15.9947 12.4154 12.4154 15.9947 7.99999 15.9947C3.58465 15.9947 0.00531006 12.4154 0.00531006 8C0.00531006 3.58466 3.58465 0.00532532 7.99999 0.00532532C12.4154 0.00532532 15.9947 3.58466 15.9947 8ZM5.73452 11.1815H4.18298C3.85696 11.1815 3.69591 11.1815 3.59772 11.1187C3.49166 11.0499 3.42685 10.936 3.41899 10.8103C3.4131 10.6945 3.49363 10.553 3.65467 10.2702L7.48562 3.51761C7.64864 3.23086 7.73112 3.08749 7.83521 3.03447C7.94715 2.97751 8.08071 2.97751 8.19266 3.03447C8.29675 3.08749 8.37924 3.23086 8.54224 3.51761L9.32981 4.89239L9.33382 4.89941C9.50989 5.20703 9.59917 5.36303 9.63815 5.52675C9.68135 5.70548 9.68135 5.89402 9.63815 6.07274C9.59887 6.23771 9.51049 6.39484 9.33177 6.70711L7.31946 10.2643L7.31426 10.2734C7.13703 10.5836 7.04722 10.7408 6.92274 10.8594C6.78722 10.989 6.62421 11.0832 6.44549 11.1363C6.28247 11.1815 6.09983 11.1815 5.73452 11.1815ZM9.65268 11.1815H11.8759C12.2038 11.1815 12.3689 11.1815 12.4671 11.1168C12.5731 11.048 12.6399 10.9321 12.6458 10.8064C12.6515 10.6943 12.5727 10.5584 12.4184 10.292C12.413 10.283 12.4077 10.2737 12.4023 10.2643L11.2887 8.35928L11.276 8.33783C11.1195 8.07321 11.0405 7.93958 10.9391 7.88793C10.8272 7.83096 10.6955 7.83096 10.5836 7.88793C10.4815 7.94095 10.399 8.0804 10.236 8.36124L9.12633 10.2663L9.12253 10.2729C8.96009 10.5533 8.87891 10.6934 8.88477 10.8084C8.89262 10.9341 8.95743 11.0499 9.06348 11.1187C9.15973 11.1815 9.3247 11.1815 9.65268 11.1815Z" fill="#E84142"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_13871_12533">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
11
src/assets/svg/avax_square_logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_298_130193)">
|
||||
<path d="M12.9346 2.74121H3.05566V11.7259H12.9346V2.74121Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9998 0C15.9998 4.41538 15.9998 15.9947 15.9998 15.9947C11.5844 15.9947 0.00590861 15.9947 0.00590861 15.9947L0.00488281 5.43594e-05C4.42027 5.43594e-05 15.9998 0 15.9998 0ZM5.73493 11.1815H4.18339C3.85736 11.1815 3.69632 11.1815 3.59813 11.1187C3.49207 11.0499 3.42726 10.936 3.4194 10.8103C3.41351 10.6945 3.49404 10.553 3.65508 10.2702L7.48603 3.51765C7.64905 3.2309 7.73153 3.08753 7.83562 3.03451C7.94756 2.97755 8.08112 2.97755 8.19307 3.03451C8.29716 3.08753 8.37965 3.2309 8.54265 3.51765L9.33022 4.89243L9.33423 4.89945C9.51029 5.20707 9.59958 5.36307 9.63856 5.52679C9.68176 5.70552 9.68176 5.89406 9.63856 6.07278C9.59928 6.23775 9.5109 6.39488 9.33218 6.70715L7.31987 10.2643L7.31466 10.2734C7.13744 10.5836 7.04762 10.7408 6.92315 10.8594C6.78763 10.9891 6.62462 11.0833 6.44589 11.1364C6.28288 11.1815 6.10024 11.1815 5.73493 11.1815ZM9.65309 11.1815H11.8763C12.2043 11.1815 12.3693 11.1815 12.4675 11.1168C12.5735 11.048 12.6403 10.9321 12.6463 10.8065C12.6519 10.6944 12.5731 10.5584 12.4188 10.2921C12.4134 10.283 12.4081 10.2738 12.4027 10.2644L11.2891 8.35932L11.2764 8.33787C11.1199 8.07325 11.0409 7.93962 10.9395 7.88797C10.8276 7.831 10.6959 7.831 10.584 7.88797C10.4819 7.94099 10.3994 8.08044 10.2364 8.36128L9.12674 10.2663L9.12294 10.2729C8.9605 10.5533 8.87932 10.6934 8.88518 10.8084C8.89303 10.9341 8.95784 11.0499 9.06389 11.1187C9.16014 11.1815 9.32511 11.1815 9.65309 11.1815Z" fill="#E84142"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_298_130193">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
9
src/assets/svg/bolt.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="10" height="14" viewBox="0 0 10 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.97119 6.19815C9.91786 6.07749 9.79854 6.00016 9.66654 6.00016H6.66654V1.00016C6.66654 0.862156 6.58189 0.738159 6.45255 0.688826C6.32255 0.638826 6.17787 0.674818 6.0852 0.776818L0.0852016 7.44349C-0.00279838 7.54149 -0.025439 7.68149 0.028561 7.80216C0.0818943 7.92283 0.201208 8.00016 0.333208 8.00016H3.33321V13.0002C3.33321 13.1382 3.41786 13.2622 3.5472 13.3115C3.58653 13.3262 3.62654 13.3335 3.66654 13.3335C3.75921 13.3335 3.84988 13.2948 3.91455 13.2228L9.91455 6.55616C10.0025 6.45882 10.0245 6.31815 9.97119 6.19815Z" fill="url(#paint0_linear_1816_1801)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1816_1801" x1="-10.1808" y1="-12.0005" x2="10.6572" y2="-11.6015" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4673FA"/>
|
||||
<stop offset="1" stop-color="#9646FA"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 917 B |
@@ -1,77 +0,0 @@
|
||||
<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.75" y="0.75" width="40.5" height="40.5" rx="8.25" fill="#293249" />
|
||||
<g clip-path="url(#clip0_304_15139)">
|
||||
<rect x="3" y="3" width="17" height="17" rx="6" fill="#3375BB" />
|
||||
<g clip-path="url(#clip1_304_15139)">
|
||||
<mask id="mask0_304_15139" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="3" y="2"
|
||||
width="17" height="18">
|
||||
<path d="M19.843 2.99976H3.10449V19.7382H19.843V2.99976Z" fill="white" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_304_15139)">
|
||||
<path
|
||||
d="M11.4737 19.7382C16.0959 19.7382 19.843 15.9912 19.843 11.369C19.843 6.74679 16.0959 2.99976 11.4737 2.99976C6.85153 2.99976 3.10449 6.74679 3.10449 11.369C3.10449 15.9912 6.85153 19.7382 11.4737 19.7382Z"
|
||||
fill="#3375BB" />
|
||||
<path
|
||||
d="M11.533 6.66125C13.187 8.0426 15.0837 7.95741 15.6257 7.95741C15.5071 15.8135 14.6039 14.2556 11.533 16.4585C8.46215 14.2556 7.5646 15.8135 7.44604 7.95741C7.98233 7.95741 9.87903 8.0426 11.533 6.66125Z"
|
||||
stroke="white" stroke-width="1.04615" stroke-miterlimit="10" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="3" y="22" width="17" height="17" rx="6" fill="white" />
|
||||
<path
|
||||
d="M17.9393 30.9431C18.4082 29.8912 16.0762 26.938 13.8455 25.7213C12.4386 24.7707 10.981 24.8975 10.6769 25.3157C10.0305 26.2283 12.8315 27.0141 14.7073 27.914C14.3017 28.0914 13.9215 28.4083 13.706 28.8012C13.0089 28.0407 11.4753 27.3816 9.67558 27.914C8.45883 28.2688 7.45756 29.118 7.06465 30.3855C6.97593 30.3474 6.86186 30.3221 6.76046 30.3221C6.34221 30.3221 6 30.6643 6 31.0826C6 31.5008 6.34221 31.843 6.76046 31.843C6.83651 31.843 7.07732 31.7923 7.07732 31.7923L10.981 31.8177C9.42209 34.3019 8.18 34.6567 8.18 35.0877C8.18 35.5186 9.35872 35.4045 9.80232 35.2398C11.9316 34.4793 14.213 32.0838 14.6059 31.3994C16.2536 31.6149 17.6351 31.6276 17.9393 30.9431Z"
|
||||
fill="url(#paint0_linear_304_15139)" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M14.7201 27.9138C14.8088 27.8758 14.7961 27.7491 14.7708 27.6477C14.7201 27.4195 13.7695 26.4816 12.8823 26.0634C11.6656 25.493 10.7784 25.5184 10.6516 25.7845C10.8924 26.2915 12.0458 26.7605 13.2372 27.2674C13.7315 27.4702 14.2511 27.6857 14.7201 27.9138Z"
|
||||
fill="url(#paint1_linear_304_15139)" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M13.1737 33.0091C12.9328 32.9204 12.654 32.8317 12.3371 32.7556C12.6667 32.1599 12.7427 31.26 12.4259 30.7024C11.9823 29.9165 11.4246 29.4983 10.1191 29.4983C9.40936 29.4983 7.48285 29.7391 7.44482 31.3488C7.44482 31.5135 7.44482 31.6656 7.4575 31.8177H10.981C10.512 32.5655 10.0684 33.1232 9.67552 33.5414C10.1445 33.6555 10.5247 33.7569 10.8796 33.8583C11.2091 33.947 11.526 34.023 11.8428 34.1118C12.3245 33.7569 12.7808 33.3767 13.1737 33.0091Z"
|
||||
fill="url(#paint2_linear_304_15139)" />
|
||||
<path
|
||||
d="M7.01397 31.6276C7.15338 32.8443 7.85048 33.326 9.27001 33.4654C10.6895 33.6048 11.5007 33.5161 12.578 33.6048C13.4779 33.6809 14.2891 34.1498 14.5806 33.985C14.8468 33.8456 14.6947 33.326 14.3398 32.9964C13.8708 32.5655 13.2244 32.274 12.0964 32.1599C12.3245 31.5389 12.2612 30.6644 11.9063 30.1954C11.3993 29.511 10.4614 29.2068 9.27001 29.3335C8.02792 29.4856 6.83652 30.1067 7.01397 31.6276Z"
|
||||
fill="url(#paint3_linear_304_15139)" />
|
||||
<rect x="22" y="3" width="17" height="36" rx="6" fill="#4C82FB" fill-opacity="0.24" />
|
||||
<g clip-path="url(#clip2_304_15139)">
|
||||
<path
|
||||
d="M30.5001 25.0833C33.0314 25.0833 35.0834 23.0313 35.0834 20.5C35.0834 17.9687 33.0314 15.9166 30.5001 15.9166C27.9688 15.9166 25.9167 17.9687 25.9167 20.5C25.9167 23.0313 27.9688 25.0833 30.5001 25.0833Z"
|
||||
stroke="#4C82FB" stroke-width="0.825" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M25.9167 20.5H35.0834" stroke="#4C82FB" stroke-width="0.825" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M30.5001 15.9166C31.6465 17.1717 32.298 18.8005 32.3334 20.5C32.298 22.1994 31.6465 23.8282 30.5001 25.0833C29.3537 23.8282 28.7022 22.1994 28.6667 20.5C28.7022 18.8005 29.3537 17.1717 30.5001 15.9166V15.9166Z"
|
||||
stroke="#4C82FB" stroke-width="0.825" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</g>
|
||||
<rect x="0.75" y="0.75" width="40.5" height="40.5" rx="8.25" stroke="#1B2236" stroke-width="0.5" />
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_304_15139" x1="9.54664" y1="29.9616" x2="17.8542" y2="32.3175"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8797FF" />
|
||||
<stop offset="1" stop-color="#AAA8FF" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_304_15139" x1="16.147" y1="30.1475" x2="10.1527" y2="24.1385"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3B22A0" />
|
||||
<stop offset="1" stop-color="#5156D8" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_304_15139" x1="13.3391" y1="33.2263" x2="7.58086" y2="29.9157"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3B1E8F" />
|
||||
<stop offset="1" stop-color="#6A6FFB" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_304_15139" x1="9.14535" y1="30.6449" x2="13.0375" y2="35.5903"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8898FF" />
|
||||
<stop offset="0.9839" stop-color="#5F47F1" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_304_15139">
|
||||
<rect x="3" y="3" width="17" height="17" rx="6" fill="white" />
|
||||
</clipPath>
|
||||
<clipPath id="clip1_304_15139">
|
||||
<rect width="17" height="16.7385" fill="white" transform="translate(3 2.99988)" />
|
||||
</clipPath>
|
||||
<clipPath id="clip2_304_15139">
|
||||
<rect width="11" height="11" fill="white" transform="translate(25 15)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.7 KiB |
@@ -1,78 +0,0 @@
|
||||
<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="1" width="40" height="40" rx="8" fill="#E8ECFB" />
|
||||
<g clip-path="url(#clip0_304_15192)">
|
||||
<rect x="3" y="3" width="17" height="17" rx="6" fill="#3375BB" />
|
||||
<g clip-path="url(#clip1_304_15192)">
|
||||
<mask id="mask0_304_15192" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="3" y="2"
|
||||
width="17" height="18">
|
||||
<path d="M19.843 2.99976H3.10449V19.7382H19.843V2.99976Z" fill="white" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_304_15192)">
|
||||
<path
|
||||
d="M11.4737 19.7382C16.0959 19.7382 19.843 15.9912 19.843 11.369C19.843 6.74679 16.0959 2.99976 11.4737 2.99976C6.85153 2.99976 3.10449 6.74679 3.10449 11.369C3.10449 15.9912 6.85153 19.7382 11.4737 19.7382Z"
|
||||
fill="#3375BB" />
|
||||
<path
|
||||
d="M11.533 6.66125C13.187 8.0426 15.0837 7.95741 15.6257 7.95741C15.5071 15.8135 14.6039 14.2556 11.533 16.4585C8.46215 14.2556 7.5646 15.8135 7.44604 7.95741C7.98233 7.95741 9.87903 8.0426 11.533 6.66125Z"
|
||||
stroke="white" stroke-width="1.04615" stroke-miterlimit="10" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="3" y="22" width="17" height="17" rx="6" fill="white" />
|
||||
<path
|
||||
d="M17.9393 30.9431C18.4082 29.8912 16.0762 26.938 13.8455 25.7213C12.4386 24.7707 10.981 24.8975 10.6769 25.3157C10.0305 26.2283 12.8315 27.0141 14.7073 27.914C14.3017 28.0914 13.9215 28.4083 13.706 28.8012C13.0089 28.0407 11.4753 27.3816 9.67558 27.914C8.45883 28.2688 7.45756 29.118 7.06465 30.3855C6.97593 30.3474 6.86186 30.3221 6.76046 30.3221C6.34221 30.3221 6 30.6643 6 31.0826C6 31.5008 6.34221 31.843 6.76046 31.843C6.83651 31.843 7.07732 31.7923 7.07732 31.7923L10.981 31.8177C9.42209 34.3019 8.18 34.6567 8.18 35.0877C8.18 35.5186 9.35872 35.4045 9.80232 35.2398C11.9316 34.4793 14.213 32.0838 14.6059 31.3994C16.2536 31.6149 17.6351 31.6276 17.9393 30.9431Z"
|
||||
fill="url(#paint0_linear_304_15192)" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M14.7201 27.9138C14.8088 27.8758 14.7961 27.7491 14.7708 27.6477C14.7201 27.4195 13.7695 26.4816 12.8823 26.0634C11.6656 25.493 10.7784 25.5184 10.6516 25.7845C10.8924 26.2915 12.0458 26.7605 13.2372 27.2674C13.7315 27.4702 14.2511 27.6857 14.7201 27.9138Z"
|
||||
fill="url(#paint1_linear_304_15192)" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M13.1737 33.0091C12.9328 32.9204 12.654 32.8317 12.3371 32.7556C12.6667 32.1599 12.7427 31.26 12.4259 30.7024C11.9823 29.9165 11.4246 29.4983 10.1191 29.4983C9.40936 29.4983 7.48285 29.7391 7.44482 31.3488C7.44482 31.5135 7.44482 31.6656 7.4575 31.8177H10.981C10.512 32.5655 10.0684 33.1232 9.67552 33.5414C10.1445 33.6555 10.5247 33.7569 10.8796 33.8583C11.2091 33.947 11.526 34.023 11.8428 34.1118C12.3245 33.7569 12.7808 33.3767 13.1737 33.0091Z"
|
||||
fill="url(#paint2_linear_304_15192)" />
|
||||
<path
|
||||
d="M7.01397 31.6276C7.15338 32.8443 7.85048 33.326 9.27001 33.4654C10.6895 33.6048 11.5007 33.5161 12.578 33.6048C13.4779 33.6809 14.2891 34.1498 14.5806 33.985C14.8468 33.8456 14.6947 33.326 14.3398 32.9964C13.8708 32.5655 13.2244 32.274 12.0964 32.1599C12.3245 31.5389 12.2612 30.6644 11.9063 30.1954C11.3993 29.511 10.4614 29.2068 9.27001 29.3335C8.02792 29.4856 6.83652 30.1067 7.01397 31.6276Z"
|
||||
fill="url(#paint3_linear_304_15192)" />
|
||||
<rect x="22" y="3" width="17" height="36" rx="6" fill="#FB118E" fill-opacity="0.12" />
|
||||
<g clip-path="url(#clip2_304_15192)">
|
||||
<path
|
||||
d="M30.5001 25.0833C33.0314 25.0833 35.0834 23.0313 35.0834 20.5C35.0834 17.9687 33.0314 15.9166 30.5001 15.9166C27.9688 15.9166 25.9167 17.9687 25.9167 20.5C25.9167 23.0313 27.9688 25.0833 30.5001 25.0833Z"
|
||||
stroke="#FB118E" stroke-width="0.825" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M25.9167 20.5H35.0834" stroke="#FB118E" stroke-width="0.825" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M30.5001 15.9166C31.6465 17.1717 32.298 18.8005 32.3334 20.5C32.298 22.1994 31.6465 23.8282 30.5001 25.0833C29.3537 23.8282 28.7022 22.1994 28.6667 20.5C28.7022 18.8005 29.3537 17.1717 30.5001 15.9166V15.9166Z"
|
||||
stroke="#FB118E" stroke-width="0.825" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</g>
|
||||
<rect x="0.75" y="0.75" width="40.5" height="40.5" rx="8.25" stroke="#5D6785"
|
||||
stroke-opacity="0.24" stroke-width="0.5" />
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_304_15192" x1="9.54664" y1="29.9616" x2="17.8542" y2="32.3175"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8797FF" />
|
||||
<stop offset="1" stop-color="#AAA8FF" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_304_15192" x1="16.147" y1="30.1475" x2="10.1527" y2="24.1385"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3B22A0" />
|
||||
<stop offset="1" stop-color="#5156D8" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_304_15192" x1="13.3391" y1="33.2263" x2="7.58086" y2="29.9157"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3B1E8F" />
|
||||
<stop offset="1" stop-color="#6A6FFB" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_304_15192" x1="9.14535" y1="30.6449" x2="13.0375" y2="35.5903"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8898FF" />
|
||||
<stop offset="0.9839" stop-color="#5F47F1" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_304_15192">
|
||||
<rect x="3" y="3" width="17" height="17" rx="6" fill="white" />
|
||||
</clipPath>
|
||||
<clipPath id="clip1_304_15192">
|
||||
<rect width="17" height="16.7385" fill="white" transform="translate(3 2.99988)" />
|
||||
</clipPath>
|
||||
<clipPath id="clip2_304_15192">
|
||||
<rect width="11" height="11" fill="white" transform="translate(25 15)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.7 KiB |
1
src/assets/svg/moonpay.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="36" height="36" rx="18" fill="#7D00FF"/><path d="M24.933 14.14a3.07 3.07 0 0 0 0-6.14 3.07 3.07 0 0 0 0 6.14ZM15.5 28A7.495 7.495 0 0 1 8 20.493a7.495 7.495 0 0 1 7.5-7.506c4.149 0 7.5 3.354 7.5 7.506A7.495 7.495 0 0 1 15.5 28Z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 356 B |
24
src/assets/svg/phantom-icon.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13559_129580)">
|
||||
<rect width="40" height="40" rx="8" fill="url(#paint0_linear_13559_129580)"/>
|
||||
<path d="M20 40C31.0457 40 40 31.0457 40 20C40 8.9543 31.0457 0 20 0C8.9543 0 0 8.9543 0 20C0 31.0457 8.9543 40 20 40Z" fill="url(#paint1_linear_13559_129580)"/>
|
||||
<path d="M34.5576 20.2857H30.982C30.982 13.0516 25.0542 7.1875 17.7415 7.1875C10.5192 7.1875 4.64749 12.908 4.5038 20.0182C4.35511 27.3678 11.3254 33.75 18.7559 33.75H19.6906C26.2415 33.75 35.0217 28.6771 36.3936 22.4961C36.647 21.3567 35.737 20.2857 34.5576 20.2857ZM12.4279 20.6079C12.4279 21.5753 11.6281 22.3665 10.6502 22.3665C9.67227 22.3665 8.87249 21.575 8.87249 20.6079V17.7629C8.87249 16.7955 9.67227 16.0043 10.6502 16.0043C11.6281 16.0043 12.4279 16.7955 12.4279 17.7629V20.6079ZM18.6009 20.6079C18.6009 21.5753 17.8011 22.3665 16.8232 22.3665C15.8453 22.3665 15.0455 21.575 15.0455 20.6079V17.7629C15.0455 16.7955 15.8456 16.0043 16.8232 16.0043C17.8011 16.0043 18.6009 16.7955 18.6009 17.7629V20.6079Z" fill="url(#paint2_linear_13559_129580)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_13559_129580" x1="20" y1="0" x2="20" y2="40" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#534BB1"/>
|
||||
<stop offset="1" stop-color="#551BF9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_13559_129580" x1="20" y1="0" x2="20" y2="40" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#534BB1"/>
|
||||
<stop offset="1" stop-color="#551BF9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_13559_129580" x1="20.4688" y1="7.1875" x2="20.4688" y2="33.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.82"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_13559_129580">
|
||||
<rect width="40" height="40" rx="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
37
src/assets/svg/uniswapx_error.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg width="74" height="72" viewBox="0 0 74 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.3747 21.5043C24.671 21.5043 24.9274 21.7105 24.991 21.9998C25.1664 22.7982 25.1791 24.1098 24.6141 25.1601C24.322 25.703 23.8683 26.1865 23.2031 26.4658C22.5432 26.7429 21.7375 26.7931 20.7823 26.5871C19.5609 26.3237 18.3363 25.6271 17.1951 24.7335C16.0477 23.8349 14.9472 22.7077 13.9724 21.5319C12.9967 20.3551 12.137 19.1177 11.4739 17.9903C10.8173 16.8739 10.3306 15.8269 10.132 15.0366C10.059 14.7459 10.2005 14.444 10.4706 14.3141C10.7407 14.1843 11.0649 14.2624 11.2463 14.501C12.087 15.6072 14.0213 17.3714 16.4539 18.8598C18.8877 20.3489 21.7272 21.5043 24.3747 21.5043Z" fill="white" stroke="black" stroke-width="1.26191" stroke-linejoin="round"/>
|
||||
<mask id="path-2-outside-1_1930_115608" maskUnits="userSpaceOnUse" x="0.0805664" y="18.0469" width="56" height="43" fill="black">
|
||||
<rect fill="white" x="0.0805664" y="18.0469" width="56" height="43"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.4887 52.6749C51.8537 49.2159 53.8899 44.7033 53.8899 39.7671C53.8899 28.8759 43.9775 20.0469 31.7499 20.0469C19.5224 20.0469 9.60998 28.8759 9.60998 39.7671C9.60998 40.805 9.69999 41.8241 9.87343 42.8186C5.25341 44.2149 2.08057 47.1039 2.08057 50.4413C2.08057 55.1444 8.38116 58.9569 16.1533 58.9569C18.6361 58.9569 20.9687 58.5679 22.9937 57.885C25.6793 58.9161 28.6397 59.4874 31.7499 59.4874C38.4356 59.4874 44.4292 56.8479 48.4887 52.6749Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.4887 52.6749C51.8537 49.2159 53.8899 44.7033 53.8899 39.7671C53.8899 28.8759 43.9775 20.0469 31.7499 20.0469C19.5224 20.0469 9.60998 28.8759 9.60998 39.7671C9.60998 40.805 9.69999 41.8241 9.87343 42.8186C5.25341 44.2149 2.08057 47.1039 2.08057 50.4413C2.08057 55.1444 8.38116 58.9569 16.1533 58.9569C18.6361 58.9569 20.9687 58.5679 22.9937 57.885C25.6793 58.9161 28.6397 59.4874 31.7499 59.4874C38.4356 59.4874 44.4292 56.8479 48.4887 52.6749Z" fill="white"/>
|
||||
<path d="M48.4887 52.6749L49.3932 53.5549L48.4887 52.6749ZM9.87343 42.8186L10.2385 44.0265L11.3086 43.7031L11.1166 42.6018L9.87343 42.8186ZM22.9937 57.885L23.446 56.707L23.0214 56.544L22.5905 56.6893L22.9937 57.885ZM52.628 39.7671C52.628 44.3432 50.743 48.548 47.5842 51.795L49.3932 53.5549C52.9645 49.8838 55.1518 45.0634 55.1518 39.7671H52.628ZM31.7499 21.3088C43.4219 21.3088 52.628 29.7062 52.628 39.7671H55.1518C55.1518 28.0457 44.5332 18.785 31.7499 18.785V21.3088ZM10.8719 39.7671C10.8719 29.7062 20.078 21.3088 31.7499 21.3088V18.785C18.9667 18.785 8.34807 28.0457 8.34807 39.7671H10.8719ZM11.1166 42.6018C10.9555 41.6784 10.8719 40.7318 10.8719 39.7671H8.34807C8.34807 40.8781 8.44444 41.9697 8.63029 43.0354L11.1166 42.6018ZM3.34248 50.4413C3.34248 47.9707 5.77784 45.3747 10.2385 44.0265L9.50835 41.6106C4.72899 43.0551 0.818657 46.2371 0.818657 50.4413H3.34248ZM16.1533 57.695C12.4585 57.695 9.17438 56.7862 6.85568 55.3831C4.5135 53.9658 3.34248 52.1807 3.34248 50.4413H0.818657C0.818657 53.405 2.79793 55.8776 5.54909 57.5424C8.32373 59.2214 12.076 60.2188 16.1533 60.2188V57.695ZM22.5905 56.6893C20.7028 57.3258 18.5071 57.695 16.1533 57.695V60.2188C18.7651 60.2188 21.2345 59.8099 23.3969 59.0808L22.5905 56.6893ZM31.7499 58.2255C28.7946 58.2255 25.9875 57.6827 23.446 56.707L22.5414 59.0631C25.3711 60.1496 28.4849 60.7493 31.7499 60.7493V58.2255ZM47.5842 51.795C43.7692 55.7165 38.1051 58.2255 31.7499 58.2255V60.7493C38.7662 60.7493 45.0891 57.9792 49.3932 53.5549L47.5842 51.795Z" fill="black" mask="url(#path-2-outside-1_1930_115608)"/>
|
||||
<path d="M39.0719 21.5043C38.7762 21.5043 38.5201 21.7097 38.456 21.9984C38.2783 22.7979 38.2654 24.1112 38.8376 25.1627C39.1334 25.7063 39.592 26.1886 40.2622 26.4668C40.9266 26.7426 41.7385 26.793 42.7026 26.5874C43.9355 26.3246 45.1727 25.6291 46.3268 24.7356C47.487 23.8374 48.6 22.7104 49.586 21.5347C50.5728 20.3579 51.4423 19.1205 52.1131 17.993C52.7773 16.8766 53.2698 15.8293 53.4709 15.0382C53.5446 14.7481 53.4043 14.4461 53.135 14.3153C52.8657 14.1845 52.5416 14.2609 52.3592 14.4982C51.5085 15.6046 49.5517 17.3692 47.0903 18.8581C44.6282 20.3475 41.7536 21.5043 39.0719 21.5043Z" fill="white" stroke="black" stroke-width="1.26191" stroke-linejoin="round"/>
|
||||
<path d="M9.27317 46.8448L9.50259 46.4782C9.93019 45.7949 9.38096 45.0323 8.64133 45.3527C7.81405 45.7111 6.82196 46.2689 5.87019 47.1255C5.44297 47.51 5.12141 47.8905 4.88077 48.2545C3.88061 49.7674 5.42411 51.2381 7.23768 51.2541C8.19162 51.2626 9.28694 51.2174 10.3799 51.0555C11.8292 50.8408 13.0434 50.1513 13.916 49.4901C14.561 49.0012 14.1621 48.1301 13.3534 48.1626L10.1134 48.2925C9.35325 48.323 8.8696 47.4897 9.27317 46.8448Z" fill="black"/>
|
||||
<path d="M22.3604 55.8761C23.8648 55.9852 25.2756 55.8261 26.3472 55.4561C26.8819 55.2714 27.362 55.024 27.7263 54.7048C28.0935 54.3831 28.3749 53.9571 28.4127 53.4357C28.4505 52.9142 28.2335 52.4521 27.9166 52.0807C27.6022 51.7123 27.1628 51.3982 26.6604 51.1384C25.6534 50.6176 24.2803 50.2565 22.7758 50.1474C21.2714 50.0383 19.8606 50.1974 18.789 50.5675C18.2544 50.7521 17.7742 50.9995 17.4099 51.3187C17.0428 51.6404 16.7613 52.0665 16.7235 52.5879C16.6857 53.1093 16.9027 53.5714 17.2196 53.9428C17.534 54.3112 17.9735 54.6253 18.4759 54.8852C19.4829 55.4059 20.8559 55.767 22.3604 55.8761Z" fill="white" stroke="black" stroke-width="1.26191"/>
|
||||
<line y1="-0.630955" x2="10.458" y2="-0.630955" transform="matrix(0.99738 0.0723377 -0.0723344 0.99738 17.2944 53.4453)" stroke="black" stroke-width="1.26191"/>
|
||||
<line y1="-0.630955" x2="5.19898" y2="-0.630955" transform="matrix(-0.0723344 0.99738 -0.99738 -0.0723377 23.6921 50.0703)" stroke="black" stroke-width="1.26191"/>
|
||||
<line y1="-0.630955" x2="5.19898" y2="-0.630955" transform="matrix(-0.0723344 0.99738 -0.99738 -0.0723377 19.9366 50.5742)" stroke="black" stroke-width="1.26191"/>
|
||||
<path d="M23.6113 12.1879C23.4573 11.9346 23.1478 11.8226 22.8674 11.9188C22.587 12.015 22.4114 12.2934 22.4453 12.5878L23.4514 21.3197C23.618 22.7653 24.9556 23.7808 26.3928 23.5526C28.1885 23.2675 29.1214 21.2523 28.1769 19.6986L23.6113 12.1879Z" fill="url(#paint0_linear_1930_115608)" stroke="black" stroke-width="1.26191" stroke-linejoin="round"/>
|
||||
<mask id="path-11-outside-2_1930_115608" maskUnits="userSpaceOnUse" x="53.3934" y="34.8259" width="22.6706" height="24.8274" fill="black">
|
||||
<rect fill="white" x="53.3934" y="34.8259" width="22.6706" height="24.8274"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M57.6261 54.012C57.135 53.9721 56.7516 53.8134 56.5049 53.5249C55.2906 52.1043 57.8521 48.0728 62.2261 44.5205C66.6002 40.9681 71.1305 39.24 72.3449 40.6606C73.078 41.5183 72.435 43.3274 70.8479 45.4082C71.665 45.4935 72.234 45.7574 72.4517 46.2109C73.1324 47.6291 70.1196 50.3641 65.7223 52.3195C62.4196 53.7883 59.2752 54.3996 57.6261 54.012Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M57.6261 54.012C57.135 53.9721 56.7516 53.8134 56.5049 53.5249C55.2906 52.1043 57.8521 48.0728 62.2261 44.5205C66.6002 40.9681 71.1305 39.24 72.3449 40.6606C73.078 41.5183 72.435 43.3274 70.8479 45.4082C71.665 45.4935 72.234 45.7574 72.4517 46.2109C73.1324 47.6291 70.1196 50.3641 65.7223 52.3195C62.4196 53.7883 59.2752 54.3996 57.6261 54.012Z" fill="url(#paint1_linear_1930_115608)"/>
|
||||
<path d="M57.6261 54.012L57.9148 52.7835C57.8534 52.7691 57.791 52.7593 57.7282 52.7542L57.6261 54.012ZM56.5049 53.5249L55.5457 54.3449L55.5457 54.3449L56.5049 53.5249ZM62.2261 44.5205L61.4306 43.5409L62.2261 44.5205ZM72.3449 40.6606L71.3857 41.4806L71.3857 41.4806L72.3449 40.6606ZM70.8479 45.4082L69.8445 44.6428C69.5678 45.0056 69.5085 45.4895 69.6893 45.9084C69.8702 46.3272 70.2631 46.6159 70.7169 46.6633L70.8479 45.4082ZM72.4517 46.2109L71.314 46.757L72.4517 46.2109ZM65.7223 52.3195L66.2351 53.4726L65.7223 52.3195ZM57.7282 52.7542C57.5875 52.7428 57.5078 52.7177 57.4715 52.7021C57.44 52.6886 57.4475 52.6855 57.4642 52.705L55.5457 54.3449C56.0816 54.9718 56.8316 55.2135 57.524 55.2697L57.7282 52.7542ZM57.4642 52.705C57.5255 52.7768 57.4073 52.7335 57.5045 52.2864C57.5986 51.8541 57.859 51.248 58.335 50.4988C59.279 49.0131 60.9099 47.2151 63.0217 45.5L61.4306 43.5409C59.1683 45.3782 57.3314 47.3721 56.2048 49.1453C55.6454 50.0257 55.2198 50.9159 55.0384 51.75C54.8602 52.5693 54.8772 53.5627 55.5457 54.3449L57.4642 52.705ZM63.0217 45.5C65.1329 43.7855 67.244 42.5441 68.9202 41.9047C69.7645 41.5827 70.4326 41.4388 70.9004 41.4263C71.3906 41.4132 71.4384 41.5422 71.3857 41.4806L73.3041 39.8407C72.6442 39.0687 71.6703 38.881 70.8329 38.9034C69.973 38.9264 69.0075 39.1702 68.0207 39.5466C66.0346 40.3042 63.6935 41.7031 61.4306 43.5409L63.0217 45.5ZM71.3857 41.4806C71.3132 41.3958 71.4919 41.4945 71.2668 42.1842C71.0603 42.8169 70.5933 43.6612 69.8445 44.6428L71.8512 46.1735C72.6896 45.0744 73.3376 43.9737 73.6661 42.9673C73.976 42.0178 74.1096 40.7831 73.3041 39.8407L71.3857 41.4806ZM70.7169 46.6633C71.0333 46.6963 71.2227 46.7572 71.3185 46.8042C71.4057 46.8471 71.3576 46.8477 71.314 46.757L73.5893 45.6649C73.0698 44.5825 71.8893 44.2481 70.9789 44.1531L70.7169 46.6633ZM71.314 46.757C71.2372 46.5968 71.3566 46.582 71.1885 46.902C71.0285 47.2068 70.6953 47.6281 70.1431 48.1294C69.05 49.1216 67.321 50.2275 65.2095 51.1665L66.2351 53.4726C68.5208 52.4561 70.4969 51.2168 71.8394 49.9981C72.5049 49.394 73.0722 48.7434 73.423 48.0755C73.7658 47.4227 74.0066 46.5342 73.5893 45.6649L71.314 46.757ZM65.2095 51.1665C63.6302 51.8688 62.1053 52.3583 60.8007 52.6257C59.4622 52.9 58.4759 52.9154 57.9148 52.7835L57.3374 55.2404C58.4254 55.4961 59.8358 55.3997 61.3075 55.0981C62.813 54.7896 64.5117 54.239 66.2351 53.4726L65.2095 51.1665Z" fill="black" mask="url(#path-11-outside-2_1930_115608)"/>
|
||||
<ellipse cx="14.8446" cy="37.3595" rx="3.31248" ry="4.96883" fill="black"/>
|
||||
<ellipse cx="14.9235" cy="37.3605" rx="1.4985" ry="2.60272" fill="white"/>
|
||||
<ellipse cx="27.7795" cy="37.3595" rx="3.31248" ry="4.96883" fill="black"/>
|
||||
<ellipse cx="27.8581" cy="37.3605" rx="1.4985" ry="2.60272" fill="white"/>
|
||||
<path d="M34.2463 54.3156C34.2463 51.2381 35.3257 47.5016 37.9302 45.6216C40.1969 43.9853 43.0541 43.828 45.7611 44.284" stroke="black" stroke-width="1.26191"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1930_115608" x1="27.4989" y1="4.1016" x2="19.0152" y2="20.2113" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF24A7"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1930_115608" x1="66.7203" y1="38.9751" x2="63.2492" y2="55.4808" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF24A7"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
18
src/assets/wallets/brave-icon.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.3668 12.5808L33.0988 10.7617C33.0988 10.7617 32.1671 9.75101 31.0359 8.60559C29.9046 7.46023 27.509 8.13398 27.509 8.13398L24.7807 5H19.9895H15.1984L12.47 8.13398C12.47 8.13398 10.0744 7.46023 8.94319 8.60559C7.81191 9.75101 6.8803 10.7617 6.8803 10.7617L7.61228 12.5808L6.68066 15.2758C6.68066 15.2758 9.42061 25.7834 9.74169 27.0666C10.3739 29.5932 10.8064 30.5701 12.6031 31.8503C14.3998 33.1304 17.6604 35.3538 18.1929 35.6907C18.7252 36.0276 19.3906 36.6014 19.9895 36.6014C20.5884 36.6014 21.2539 36.0276 21.7862 35.6907C22.3185 35.3538 25.5793 33.1304 27.3759 31.8503C29.1726 30.5701 29.6052 29.5932 30.2374 27.0666C30.5584 25.7834 33.2984 15.2758 33.2984 15.2758L32.3668 12.5808Z" fill="url(#paint0_linear_13571_129901)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.7438 10.1216C26.7438 10.1216 30.253 14.4167 30.253 15.3347C30.253 16.2527 29.8116 16.4951 29.3678 16.9722C28.9238 17.4495 26.9853 19.5337 26.7364 19.8013C26.4875 20.069 25.9693 20.4748 26.2741 21.2052C26.5789 21.9357 27.0286 22.8651 26.5286 23.8078C26.0284 24.7506 25.1717 25.3797 24.6227 25.2757C24.0737 25.1717 22.7843 24.4895 22.3103 24.1778C21.8361 23.8662 20.3334 22.6114 20.3334 22.1313C20.3334 21.6512 21.8868 20.7892 22.1737 20.5934C22.4608 20.3976 23.7697 19.6396 23.7965 19.342C23.8233 19.0444 23.8131 18.9572 23.4268 18.222C23.0405 17.4867 22.3448 16.5056 22.4607 15.8527C22.5764 15.2 23.6983 14.8606 24.499 14.5544C25.2995 14.2483 26.841 13.6702 27.0336 13.5803C27.2262 13.4903 27.1764 13.4047 26.593 13.3487C26.0098 13.2927 24.3544 13.0702 23.6081 13.2809C22.8618 13.4915 21.5868 13.8119 21.4836 13.9819C21.3803 14.1518 21.2892 14.1575 21.3953 14.7437C21.5013 15.3298 22.0473 18.1425 22.1003 18.6421C22.1533 19.1417 22.257 19.472 21.725 19.5952C21.1929 19.7183 20.2973 19.9322 19.9895 19.9322C19.6818 19.9322 18.7861 19.7183 18.254 19.5952C17.7219 19.472 17.8256 19.1417 17.8787 18.6421C17.9317 18.1425 18.4777 15.3298 18.5838 14.7437C18.6898 14.1575 18.5987 14.1518 18.4954 13.9819C18.3922 13.8119 17.1171 13.4915 16.3708 13.2809C15.6245 13.0702 13.9693 13.2927 13.3859 13.3487C12.8026 13.4047 12.7528 13.4903 12.9454 13.5803C13.138 13.6702 14.6795 14.2483 15.48 14.5544C16.2806 14.8606 17.4026 15.2 17.5184 15.8527C17.6342 16.5056 16.9384 17.4867 16.5523 18.222C16.1659 18.9572 16.1557 19.0444 16.1825 19.342C16.2093 19.6396 17.5183 20.3976 17.8053 20.5934C18.0923 20.7892 19.6455 21.6512 19.6455 22.1313C19.6455 22.6114 18.1429 23.8662 17.6688 24.1778C17.1947 24.4895 15.9053 25.1717 15.3563 25.2757C14.8073 25.3797 13.9506 24.7506 13.4505 23.8078C12.9504 22.8651 13.4001 21.9357 13.7048 21.2052C14.0097 20.4748 13.4915 20.069 13.2425 19.8013C12.9937 19.5337 11.0551 17.4495 10.6113 16.9722C10.1673 16.4951 9.72601 16.2527 9.72601 15.3347C9.72601 14.4167 13.2353 10.1216 13.2353 10.1216C13.2353 10.1216 16.1965 10.6942 16.5958 10.6942C16.995 10.6942 17.8601 10.3574 18.6586 10.0879C19.4572 9.81841 19.9895 9.81641 19.9895 9.81641C19.9895 9.81641 20.5218 9.81841 21.3204 10.0879C22.1189 10.3574 22.984 10.6942 23.3833 10.6942C23.7825 10.6942 26.7438 10.1216 26.7438 10.1216ZM24.113 26.5516C24.3302 26.6893 24.1977 26.9489 23.9998 27.0905C23.8019 27.2322 21.1428 29.317 20.8848 29.5475C20.6266 29.778 20.2473 30.1586 19.9895 30.1586C19.7317 30.1586 19.3523 29.778 19.0943 29.5475C18.8362 29.317 16.177 27.2322 15.9792 27.0905C15.7813 26.9489 15.6488 26.6893 15.866 26.5516C16.0833 26.4139 16.7629 26.0665 17.7007 25.5751C18.6384 25.0838 19.8071 24.6661 19.9895 24.6661C20.1719 24.6661 21.3405 25.0838 22.2783 25.5751C23.2161 26.0665 23.8957 26.4139 24.113 26.5516Z" fill="white"/>
|
||||
<path d="M27.509 8.13398L24.7807 5H19.9895H15.1983L12.47 8.13398C12.47 8.13398 10.0744 7.46023 8.94318 8.60558C8.94318 8.60558 12.1373 8.31367 13.2353 10.1216C13.2353 10.1216 16.1965 10.6942 16.5958 10.6942C16.995 10.6942 17.8601 10.3574 18.6586 10.0879C19.4572 9.81841 19.9895 9.81641 19.9895 9.81641C19.9895 9.81641 20.5218 9.81841 21.3204 10.0879C22.1189 10.3574 22.984 10.6942 23.3833 10.6942C23.7825 10.6942 26.7438 10.1216 26.7438 10.1216C27.8418 8.31367 31.0358 8.60558 31.0358 8.60558C29.9046 7.46023 27.509 8.13398 27.509 8.13398Z" fill="url(#paint1_linear_13571_129901)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_13571_129901" x1="6.68066" y1="1607.37" x2="2668.45" y2="1607.37" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF5500"/>
|
||||
<stop offset="0.409877" stop-color="#FF5500"/>
|
||||
<stop offset="0.581981" stop-color="#FF2000"/>
|
||||
<stop offset="1" stop-color="#FF2000"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_13571_129901" x1="56.4079" y1="293.731" x2="2218.22" y2="293.731" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF452A"/>
|
||||
<stop offset="1" stop-color="#FF2000"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
4
src/assets/wallets/browser-wallet-dark.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="#4C82FB" fill-opacity="0.24"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.454 11.6305C14.6973 12.4936 11.8284 15.6822 11.4439 19.5998H15.6425C15.8586 16.7339 16.8329 13.9847 18.454 11.6305ZM22.546 11.6305C24.1671 13.9847 25.1413 16.7339 25.3574 19.5998H29.5561C29.1715 15.6822 26.3027 12.4936 22.546 11.6305ZM23.5517 19.5998C23.3127 16.7852 22.2508 14.103 20.5 11.8882C18.7491 14.103 17.6872 16.7852 17.4483 19.5998H23.5517ZM17.4483 21.3998H23.5516C23.3126 24.2143 22.2507 26.8963 20.5 29.111C18.7492 26.8963 17.6873 24.2143 17.4483 21.3998ZM15.6426 21.3998H11.4439C11.8286 25.3172 14.6974 28.5056 18.454 29.3687C16.833 27.0146 15.8587 24.2656 15.6426 21.3998ZM22.546 29.3687C24.167 27.0146 25.1412 24.2656 25.3574 21.3998H29.556C29.1713 25.3172 26.3026 28.5056 22.546 29.3687ZM20.5 31.3996C14.4801 31.3996 9.59998 26.5195 9.59998 20.4996C9.59998 14.4797 14.4801 9.59961 20.5 9.59961C26.5199 9.59961 31.4 14.4797 31.4 20.4996V20.4998C31.3999 26.5196 26.5198 31.3996 20.5 31.3996Z" fill="#4C82FB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
5
src/assets/wallets/browser-wallet-light.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="1" width="40" height="40" fill="#FB118E" fill-opacity="0.12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.454 12.6305C15.6973 13.4936 12.8284 16.6822 12.4439 20.5998H16.6425C16.8586 17.7339 17.8329 14.9847 19.454 12.6305ZM23.546 12.6305C25.1671 14.9847 26.1413 17.7339 26.3574 20.5998H30.5561C30.1715 16.6822 27.3027 13.4936 23.546 12.6305ZM24.5517 20.5998C24.3127 17.7852 23.2508 15.103 21.5 12.8882C19.7491 15.103 18.6872 17.7852 18.4483 20.5998H24.5517ZM18.4483 22.3998H24.5516C24.3126 25.2143 23.2507 27.8963 21.5 30.111C19.7492 27.8963 18.6873 25.2143 18.4483 22.3998ZM16.6426 22.3998H12.4439C12.8286 26.3172 15.6974 29.5056 19.454 30.3687C17.833 28.0146 16.8587 25.2656 16.6426 22.3998ZM23.546 30.3687C25.167 28.0146 26.1412 25.2656 26.3574 22.3998H30.556C30.1713 26.3172 27.3026 29.5056 23.546 30.3687ZM21.5 32.3996C15.4801 32.3996 10.6 27.5195 10.6 21.4996C10.6 15.4797 15.4801 10.5996 21.5 10.5996C27.5199 10.5996 32.4 15.4797 32.4 21.4996V21.4998C32.3999 27.5196 27.5198 32.3996 21.5 32.3996Z" fill="#FB118E"/>
|
||||
<rect x="0.75" y="0.75" width="40.5" height="40.5" stroke="#5D6785" stroke-opacity="0.24" stroke-width="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
13
src/assets/wallets/coinbase-icon.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13571_129878)">
|
||||
<rect width="40" height="40" fill="#0052FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.3312 0H31.6672C36.2704 0 40 4.0128 40 8.9632V31.0368C40 35.9872 36.2704 40 31.6688 40H8.3312C3.7296 40 0 35.9872 0 31.0368V8.9632C0 4.0128 3.7296 0 8.3312 0Z" fill="#0052FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.9989 5.79443C27.8453 5.79443 34.2053 12.1544 34.2053 20.0008C34.2053 27.8472 27.8453 34.2072 19.9989 34.2072C12.1525 34.2072 5.79254 27.8472 5.79254 20.0008C5.79254 12.1544 12.1525 5.79443 19.9989 5.79443Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5005 15.459H23.4973C24.0733 15.459 24.5389 15.9614 24.5389 16.579V23.419C24.5389 24.0382 24.0717 24.539 23.4973 24.539H16.5005C15.9245 24.539 15.4589 24.0366 15.4589 23.419V16.579C15.4589 15.9614 15.9261 15.459 16.5005 15.459Z" fill="#0052FF"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_13571_129878">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
11
src/assets/wallets/ledger-icon.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="black"/>
|
||||
<g clip-path="url(#clip0_13605_128411)">
|
||||
<path d="M8 24.9635V30.8974H17.0274V29.5815H9.31532V24.9635H8ZM30.6847 24.9635V29.5815H22.9726V30.8971H32V24.9635H30.6847ZM17.0405 15.9334V24.9632H22.9726V23.7765H18.3559V15.9334H17.0405ZM8 9.99951V15.9334H9.31532V11.3152H17.0274V9.99951H8ZM22.9726 9.99951V11.3152H30.6847V15.9334H32V9.99951H22.9726Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_13605_128411">
|
||||
<rect width="24" height="20.8979" fill="white" transform="translate(8 10)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 649 B |
15
src/assets/wallets/metamask-icon.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="#F7F9FB"/>
|
||||
<path d="M32.6877 6.6665L22.0555 14.5632L24.0216 9.90427L32.6877 6.6665Z" fill="#E2761B" stroke="#E2761B" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.1433 6.6665L18.6901 14.638L16.8201 9.90427L8.1433 6.6665ZM28.8628 24.9711L26.0311 29.3095L32.0899 30.9764L33.8317 25.0672L28.8628 24.9711ZM7.0213 25.0672L8.75238 30.9764L14.8112 29.3095L11.9795 24.9711L7.0213 25.0672Z" fill="#E4761B" stroke="#E4761B" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.4697 17.641L12.7814 20.1949L18.7974 20.462L18.5837 13.9972L14.4697 17.641ZM26.3629 17.641L22.1955 13.9224L22.0565 20.462L28.0619 20.1949L26.3629 17.641ZM14.8117 29.3097L18.4234 27.5466L15.3032 25.1103L14.8117 29.3097ZM22.4092 27.5466L26.0316 29.3097L25.5294 25.1103L22.4092 27.5466Z" fill="#E4761B" stroke="#E4761B" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M26.0316 29.3095L22.4091 27.5464L22.6976 29.9079L22.6656 30.9017L26.0316 29.3095ZM14.8116 29.3095L18.1776 30.9017L18.1562 29.9079L18.4233 27.5464L14.8116 29.3095Z" fill="#D7C1B3" stroke="#D7C1B3" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.2319 23.5497L15.2185 22.6628L17.345 21.6904L18.2319 23.5497ZM22.6023 23.5497L23.4892 21.6904L25.6264 22.6628L22.6023 23.5497Z" fill="#233447" stroke="#233447" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.8106 29.3092L15.3236 24.9708L11.9789 25.067L14.8106 29.3092ZM25.5177 24.9708L26.0306 29.3092L28.8623 25.067L25.5177 24.9708ZM28.0609 20.1943L22.0555 20.4615L22.6112 23.5496L23.4981 21.6903L25.6352 22.6627L28.0609 20.1943ZM15.2167 22.6627L17.3538 21.6903L18.2301 23.5496L18.7964 20.4615L12.7804 20.1943L15.2167 22.6627Z" fill="#CD6116" stroke="#CD6116" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.7814 20.1943L15.3032 25.1097L15.2177 22.6627L12.7814 20.1943ZM25.6362 22.6627L25.5294 25.1097L28.0619 20.1943L25.6362 22.6627ZM18.7974 20.4615L18.2311 23.5496L18.9363 27.1935L19.0966 22.3956L18.7974 20.4615ZM22.0565 20.4615L21.768 22.3849L21.8963 27.1935L22.6122 23.5496L22.0565 20.4615Z" fill="#E4751F" stroke="#E4751F" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.613 23.5495L21.8971 27.1933L22.41 27.546L25.5302 25.1096L25.637 22.6626L22.613 23.5495ZM15.2185 22.6626L15.304 25.1096L18.4242 27.546L18.9371 27.1933L18.2319 23.5495L15.2185 22.6626Z" fill="#F6851B" stroke="#F6851B" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.6656 30.9022L22.6976 29.9085L22.4305 29.6734H18.402L18.1562 29.9085L18.1776 30.9022L14.8116 29.3101L15.987 30.2718L18.3699 31.9281H22.4625L24.8561 30.2718L26.0316 29.3101L22.6656 30.9022Z" fill="#C0AD9E" stroke="#C0AD9E" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.409 27.5465L21.8961 27.1938H18.9362L18.4233 27.5465L18.1561 29.908L18.4019 29.6729H22.4304L22.6975 29.908L22.409 27.5465Z" fill="#161616" stroke="#161616" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M33.1371 15.0761L34.0454 10.7164L32.6883 6.6665L22.4087 14.2961L26.3624 17.6407L31.951 19.2756L33.1905 17.833L32.6562 17.4484L33.5111 16.6683L32.8486 16.1554L33.7034 15.5036L33.1371 15.0761ZM6.79688 10.7164L7.70516 15.0761L7.12813 15.5036L7.98299 16.1554L7.33116 16.6683L8.18601 17.4484L7.65173 17.833L8.88058 19.2756L14.4692 17.6407L18.4229 14.2961L8.14327 6.6665L6.79688 10.7164Z" fill="#763D16" stroke="#763D16" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M31.951 19.2761L26.3624 17.6412L28.0614 20.1951L25.5289 25.1105L28.8628 25.0678H33.8317L31.951 19.2761ZM14.4692 17.6412L8.88061 19.2761L7.0213 25.0678H11.9795L15.3027 25.1105L12.7809 20.1951L14.4692 17.6412ZM22.0561 20.4622L22.4087 14.2966L24.0329 9.90479H16.8201L18.4229 14.2966L18.7969 20.4622L18.9252 22.407L18.9358 27.1942H21.8958L21.9171 22.407L22.0561 20.4622Z" fill="#F6851B" stroke="#F6851B" stroke-width="0.106857" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
25
src/assets/wallets/rabby-icon.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<path d="M35.1513 21.043C36.2547 18.5678 30.7674 11.6192 25.5187 8.75633C22.2085 6.51967 18.7789 6.81789 18.0632 7.80202C16.5423 9.94921 23.133 11.7982 27.5466 13.9155C26.5923 14.3331 25.6977 15.0786 25.1907 16.0031C23.5505 14.2138 19.942 12.663 15.7073 13.9155C12.8443 14.7506 10.4884 16.7486 9.5639 19.7309C9.35514 19.6414 9.08674 19.5817 8.84817 19.5817C7.86404 19.5817 7.05884 20.3869 7.05884 21.3711C7.05884 22.3552 7.86404 23.1604 8.84817 23.1604C9.0271 23.1604 9.59372 23.0411 9.59372 23.0411L18.7789 23.1008C15.1108 28.9459 12.1882 29.7809 12.1882 30.7949C12.1882 31.8088 14.9617 31.5404 16.0055 31.1527C21.0156 29.3634 26.3836 23.727 27.3081 22.1166C31.1849 22.6236 34.4356 22.6534 35.1513 21.043Z" fill="url(#paint0_linear_13571_129896)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.5765 13.915C27.7853 13.8256 27.7554 13.5273 27.6958 13.2888C27.5765 12.752 25.3398 10.5451 23.2523 9.56101C20.3894 8.21901 18.3018 8.27865 18.0036 8.90492C18.5702 10.0978 21.284 11.2012 24.0873 12.3941C25.2504 12.8713 26.4731 13.3782 27.5765 13.915Z" fill="url(#paint1_linear_13571_129896)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.9382 25.9043C23.3715 25.6955 22.7155 25.4868 21.9699 25.3078C22.7453 23.9062 22.9242 21.7888 22.1787 20.4767C21.1349 18.6277 19.8227 17.6436 16.751 17.6436C15.081 17.6436 10.548 18.2102 10.4586 21.9976C10.4586 22.3853 10.4586 22.7431 10.4884 23.101H18.7789C17.6755 24.8605 16.6317 26.1727 15.7073 27.1568C16.8107 27.4252 17.7053 27.6638 18.5404 27.9024C19.3157 28.1111 20.0613 28.29 20.8068 28.4988C21.9401 27.6638 23.0137 26.7691 23.9382 25.9043Z" fill="url(#paint2_linear_13571_129896)"/>
|
||||
<path d="M9.44462 22.6539C9.77266 25.5168 11.4129 26.6501 14.753 26.9781C18.093 27.3062 20.0017 27.0974 22.5365 27.3062C24.6539 27.4851 26.5625 28.5885 27.2484 28.2008C27.8747 27.8728 27.5168 26.6501 26.6818 25.8747C25.5784 24.8607 24.0575 24.1748 21.4033 23.9064C21.9401 22.4452 21.791 20.3874 20.956 19.284C19.7631 17.6736 17.5562 16.9579 14.753 17.2561C11.8304 17.614 9.02711 19.0753 9.44462 22.6539Z" fill="url(#paint3_linear_13571_129896)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_13571_129896" x1="15.4039" y1="18.7336" x2="34.9512" y2="24.2768" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8797FF"/>
|
||||
<stop offset="1" stop-color="#AAA8FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_13571_129896" x1="30.9339" y1="19.1707" x2="16.8297" y2="5.03193" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3B22A0"/>
|
||||
<stop offset="1" stop-color="#5156D8" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_13571_129896" x1="24.3273" y1="26.4153" x2="10.7786" y2="18.6257" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3B1E8F"/>
|
||||
<stop offset="1" stop-color="#6A6FFB" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_13571_129896" x1="14.4596" y1="20.3417" x2="23.6175" y2="31.9778" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8898FF"/>
|
||||
<stop offset="0.9839" stop-color="#5F47F1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
17
src/assets/wallets/trustwallet-icon.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13571_129905)">
|
||||
<rect width="40" height="40" fill="#3375BB"/>
|
||||
<mask id="mask0_13571_129905" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="-1" width="40" height="41">
|
||||
<path d="M39.6306 -0.000488281H0.246033V39.3841H39.6306V-0.000488281Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_13571_129905)">
|
||||
<path d="M19.9383 39.3841C30.8141 39.3841 39.6306 30.5676 39.6306 19.6918C39.6306 8.81607 30.8141 -0.000488281 19.9383 -0.000488281C9.06258 -0.000488281 0.246033 8.81607 0.246033 19.6918C0.246033 30.5676 9.06258 39.3841 19.9383 39.3841Z" fill="#3375BB"/>
|
||||
<path d="M20.0783 8.61572C23.97 11.8659 28.4329 11.6655 29.708 11.6655C29.4291 30.1503 27.3039 26.4848 20.0783 31.668C12.8527 26.4848 10.7408 30.1503 10.4619 11.6655C11.7237 11.6655 16.1865 11.8659 20.0783 8.61572Z" stroke="white" stroke-width="2.46154" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_13571_129905">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |