Compare commits

..

1 Commits

Author SHA1 Message Date
UL Service Account
fca93af230 ci: add global CODEOWNERS 2023-06-09 19:00:49 +00:00
546 changed files with 13206 additions and 40963 deletions

5
.env
View File

@@ -4,8 +4,6 @@ REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
REACT_APP_AWS_API_REGION="us-east-2"
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf"
REACT_APP_BASE_GOERLI_RPC_URL="https://wiser-compatible-mansion.base-goerli.quiknode.pro/5874f36248e17020a1006149e7f68c63967e1f45/"
REACT_APP_BASE_MAINNET_RPC_URL="https://cool-white-diagram.base-mainnet.quiknode.pro/d8f036f35dfab2c68f32dfa822cd971e7a25a117/"
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_MOONPAY_API="https://api.moonpay.com"
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=staging"
@@ -13,5 +11,4 @@ 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"
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"

View File

@@ -13,6 +13,7 @@ module.exports = {
files: ['**/*'],
rules: {
'multiline-comment-style': ['error', 'separate-lines'],
'rulesdir/enforce-retry-on-import': 'error',
'rulesdir/no-undefined-or': 'error',
},
},
@@ -57,22 +58,5 @@ 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.`,
},
],
},
],
},
},
],
}

View File

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

View File

@@ -10,7 +10,7 @@ runs:
with:
node-version: 18
registry-url: https://registry.npmjs.org
# cache is intentionally omitted, as it is faster with yarn v1 to cache node_modules.
cache: 'yarn'
- 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,17 +40,23 @@ runs:
run: yarn contracts
shell: bash
# GraphQL is generated from schema and client-side graphql queries. The schema is always fetched and changes to
# client-side queries are hard to detect, so it is always re-generated.
# TODO(WEB-2498): Cache based on both fetched schema and client-side graphql queries.
# This will require some processing: cp all literal graphql tags into a separate file and hash it?
- run: yarn graphql
# 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
shell: bash
# Messages are extracted from source.
# A record of source file content hashes and catalogs is maintained in node_modules/.cache/lingui.
# Messages are always extracted, but extraction may short-circuit from the custom extractor's cache.
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
id: i18n-extract-cache
with:
path: node_modules/.cache
key: ${{ runner.os }}-i18n-extract-${{ github.run_id }}

View File

@@ -14,60 +14,19 @@ 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 translations
git config user.name "UL Service Account"
git config user.email "hello-happy-puppy@users.noreply.github.com"
- name: Add CODEOWNERS file
run: |
rm src/locales/en-US.po
git add -f src/locales/*.po
git commit -m 'ci(t9n): download translations from crowdin'
- name: Add CODEOWNERS
run: |
echo '@uniswap/web-admins' > CODEOWNERS
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

View File

@@ -10,23 +10,23 @@ jobs:
environment:
name: deploy/staging
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- name: Send Slack message that deploy is starting
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Deploy _started_ for ${{ github.ref_name }}"
"text": "Staging deploy started for branch: ${{ 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: Update Cloudflare Pages deployment
id: pages-deployment
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
@@ -38,19 +38,18 @@ 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
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- name: Send Slack message about deployment outcome
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
"text": "Staging 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

View File

@@ -14,21 +14,6 @@ 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 }}

View File

@@ -10,21 +10,21 @@ jobs:
environment:
name: deploy/prod
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- name: Send Slack message that build is starting
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Deploy _started_ for ${{ github.ref_name }}"
"text": "Production deploy started for branch: ${{ 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: Publish release
- name: Release
uses: actions/create-release@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -86,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
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- name: Send Slack message about deployment outcome
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
"text": "Production 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 Normal file
View File

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

View File

@@ -14,7 +14,7 @@ jobs:
- run: yarn i18n:extract
- name: Upload Crowdin sources
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
uses: crowdin/github-action@1.1.0
with:
upload_sources: true
download_translations: false

View File

@@ -1,4 +1,4 @@
name: Slack notification on pushes to releases/*
name: Slack notifications for releases/* merges
# This CI job will push notifications to Slack whenever code is merged into any releases/* branch
#
@@ -25,6 +25,7 @@ on:
jobs:
notify-slack:
name: 'Emit Slack notification(s)'
runs-on: ubuntu-latest
environment:
name: notify/releases
@@ -44,7 +45,9 @@ jobs:
| awk '{print substr($0,0,3000);}' \
> /tmp/parsed_github_context
echo "SLACK_COMMITS=$(cat /tmp/parsed_github_context)" >> "$GITHUB_OUTPUT"
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- name: Send custom JSON data to Slack workflow
id: slack
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{

View File

@@ -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 to node_modules/.cache, so they are cached independently of node_modules.
# Build tools are configured to cache cache to node_modules/.cache, so this is 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,8 +9,9 @@ on:
push:
branches:
- main
- releases/staging
pull_request:
# manual trigger
workflow_dispatch:
jobs:
lint:
@@ -18,11 +19,12 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
id: eslint-cache
with:
path: node_modules/.cache
key: ${{ runner.os }}-eslint-${{ github.run_id }}
restore-keys: ${{ runner.os }}-eslint-
key: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-
- run: yarn lint
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
@@ -35,11 +37,12 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
id: tsc-cache
with:
path: node_modules/.cache
key: ${{ runner.os }}-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-tsc-
key: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-
- run: yarn typecheck
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
@@ -64,11 +67,12 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
id: jest-cache
with:
path: node_modules/.cache
key: ${{ runner.os }}-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-jest-
key: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-
- run: yarn test --coverage --maxWorkers=100%
- uses: codecov/codecov-action@v3
with:
@@ -86,11 +90,12 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
id: build-e2e-cache
with:
path: node_modules/.cache
key: ${{ runner.os }}-build-e2e-${{ github.run_id }}
restore-keys: ${{ runner.os }}-build-e2e-
key: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-
- run: yarn build:e2e
env:
NODE_OPTIONS: "--max_old_space_size=4096"
@@ -100,23 +105,6 @@ jobs:
path: build
if-no-files-found: error
cypress-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-cypress-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cypress-tsc-
- run: yarn typecheck:cypress
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# Allows for parallel re-runs of cypress tests without re-building.
cypress-rerun:
runs-on: ubuntu-latest
@@ -126,6 +114,7 @@ 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:
@@ -133,7 +122,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
id: cypress-cache
with:
path: /root/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
@@ -146,7 +136,8 @@ jobs:
name: build-e2e
path: build
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
id: hardhat-cache
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
@@ -159,9 +150,8 @@ jobs:
parallel: true
start: yarn serve
wait-on: 'http://localhost:3000'
browser: electron
browser: chrome
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 }}
@@ -186,79 +176,11 @@ jobs:
name: Cypress tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
cloud-typecheck:
# 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]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-tsc-
- run: yarn typecheck:cloud
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cloud typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# TODO(WEB-2537): Setup CodeCOV
cloud-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-jest-
- run: yarn test:cloud --coverage --maxWorkers=100%
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cloud tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
pre:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6.4.1
with:
script: |
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: 'pending',
context: 'Test / promotion',
description: 'Running tests...',
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
})
post:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
needs: [pre, lint, typecheck, deps-tests, unit-tests, cypress-test-matrix]
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' }}
- if: needs.cypress-test-matrix.result != 'success'
run: exit 1

7
.gitignore vendored
View File

@@ -5,7 +5,8 @@
/src/types/v3
/src/abis/types
/src/locales/**/*.js
/src/locales/**/*.po
/src/locales/**/en-US.po
/src/locales/**/pseudo.po
# generated files
/src/**/__generated__
@@ -19,7 +20,6 @@ schema.graphql
# testing
/coverage
/cache
/functions/coverage
# builds
/build
@@ -47,10 +47,7 @@ notes.txt
package-lock.json
cypress/downloads
cypress/videos
cypress/screenshots
.vercel
.wrangler

View File

@@ -6,7 +6,6 @@ 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'
@@ -94,16 +93,6 @@ 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:

View File

@@ -1,6 +1,7 @@
import codeCoverageTask from '@cypress/code-coverage/task'
import { defineConfig } from 'cypress'
import { setupHardhatEvents } from 'cypress-hardhat'
import { unlinkSync } from 'fs'
export default defineConfig({
projectId: 'yp82ef',
@@ -8,14 +9,30 @@ export default defineConfig({
chromeWebSecurity: false,
experimentalMemoryManagement: true, // better memory management, see https://github.com/cypress-io/cypress/pull/25462
retries: { runMode: 2 },
video: false, // GH provides 2 CPUs, and cypress video eats one up, see https://github.com/cypress-io/cypress/issues/20468#issuecomment-1307608025
videoCompression: false,
e2e: {
async setupNodeEvents(on, config) {
await setupHardhatEvents(on, config)
codeCoverageTask(on, config)
return config
// Delete recorded videos for specs that passed without flakes.
on('after:spec', async (spec, results) => {
if (results && results.video) {
// If there were no failures (including flakes), delete the recorded video.
if (!results.tests?.some((test) => test.attempts.some((attempt) => attempt?.state === 'failed'))) {
unlinkSync(results.video)
}
}
})
return {
...config,
// 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'),
}
},
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/{e2e,staging}/**/*.test.ts',
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
},
})

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import { getTestSelector } from '../utils'
import { CONNECTED_WALLET_USER_STATE, DISCONNECTED_WALLET_USER_STATE } from '../utils/user-state'
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
describe('Landing Page', () => {
it('shows landing page when no user state exists', () => {
cy.visit('/', { userState: DISCONNECTED_WALLET_USER_STATE })
cy.visit('/', { userState: {} })
cy.get(getTestSelector('landing-page'))
cy.screenshot()
})

View File

@@ -3,7 +3,7 @@ import { getTestSelector } from '../../utils'
describe('Mini Portfolio account drawer', () => {
beforeEach(() => {
cy.intercept(/api.uniswap.org\/v1\/graphql/, cy.spy().as('gqlSpy'))
cy.visit('/swap')
cy.visit('/swap', { ethereum: 'hardhat' })
})
it('fetches balances when account button is first hovered', () => {
@@ -41,7 +41,6 @@ describe('Mini Portfolio account drawer', () => {
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('I Got Plenty')
cy.intercept(/graphql/, { fixture: 'mini-portfolio/pools.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Pools').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('No pools yet')
@@ -77,36 +76,4 @@ describe('Mini Portfolio account drawer', () => {
})
})
})
it('fetches ENS name', () => {
cy.hardhat().then(() => {
const haydenAccount = '0x50EC05ADe8280758E2077fcBC08D878D4aef79C3'
const haydenENS = 'hayden.eth'
// Opens the account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// Simulate wallet changing to Hayden's account
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
// Hayden's ENS name should be shown
cy.contains(haydenENS).should('exist')
// Close account drawer
cy.get(getTestSelector('close-account-drawer')).click()
// Switch chain to Polygon
cy.get(getTestSelector('chain-selector')).eq(1).click()
cy.contains('Polygon').click()
//Reopen account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// Simulate wallet changing to Hayden's account
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
// Hayden's ENS name should be shown
cy.contains(haydenENS).should('exist')
})
})
})

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber'
import { MaxUint160, MaxUint256 } from '@uniswap/permit2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { DAI, USDC_MAINNET, USDT } from '../../src/constants/tokens'
import { DAI, USDC_MAINNET } from '../../src/constants/tokens'
import { getTestSelector } from '../utils'
/** Initiates a swap. */
@@ -15,30 +13,32 @@ function initiateSwap() {
}
describe('Permit2', () => {
function setupInputs(inputToken: Token, outputToken: Token) {
// Sets up a swap between inputToken and outputToken.
cy.visit(`/swap/?inputCurrency=${inputToken.address}&outputCurrency=${outputToken.address}`)
// The same tokens are used for all permit2 tests.
const INPUT_TOKEN = DAI
const OUTPUT_TOKEN = USDC_MAINNET
beforeEach(() => {
// Sets up a swap between INPUT_TOKEN and OUTPUT_TOKEN.
cy.visit(`/swap/?inputCurrency=${INPUT_TOKEN.address}&outputCurrency=${OUTPUT_TOKEN.address}`, {
ethereum: 'hardhat',
})
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(inputToken: Token) {
function expectTokenAllowanceForPermit2ToBeMax() {
// check token approval
cy.hardhat()
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: inputToken }))
.then((allowance) => {
Cypress.log({ name: `Token allowance: ${allowance.toString()}` })
cy.wrap(allowance).should('deep.equal', MaxUint256)
})
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }))
.should('deep.equal', MaxUint256)
}
/** Asserts the universal router has a max permit2 approval for spend of the input token on-chain. */
function expectPermit2AllowanceForUniversalRouterToBeMax(inputToken: Token) {
function expectPermit2AllowanceForUniversalRouterToBeMax() {
cy.hardhat()
.then(({ approval, wallet }) => approval.getPermit2Allowance({ owner: wallet, token: inputToken }))
.then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN }))
.then((allowance) => {
Cypress.log({ name: `Permit2 allowance: ${allowance.amount.toString()}` })
cy.wrap(allowance.amount).should('deep.equal', MaxUint160)
cy.wrap(MaxUint160.eq(allowance.amount)).should('eq', true)
// Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds.
const THIRTY_DAYS_SECONDS = 2_592_000
const expected = Math.floor(Date.now() / 1000 + THIRTY_DAYS_SECONDS)
@@ -46,19 +46,11 @@ describe('Permit2', () => {
})
}
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
@@ -69,23 +61,21 @@ describe('Permit2', () => {
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax(DAI)
expectTokenAllowanceForPermit2ToBeMax()
// Verify permit2 approval
cy.contains('Allow DAI to be used for swapping')
cy.wait('@eth_signTypedData_v4')
cy.wait('@eth_sendRawTransaction')
cy.contains('Swap submitted')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Swap success!')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
expectPermit2AllowanceForUniversalRouterToBeMax()
})
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.approval.setPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN })
await hardhat.mine()
})
initiateSwap()
@@ -95,55 +85,12 @@ describe('Permit2', () => {
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax(DAI)
expectTokenAllowanceForPermit2ToBeMax()
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Swap 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('Swap success!')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
})
})
@@ -151,20 +98,18 @@ describe('Permit2', () => {
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 }),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
])
)
setupInputs(DAI, USDC_MAINNET)
initiateSwap()
// Verify transaction
cy.contains('Swap success!')
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) => {
// Reject token approval
@@ -187,7 +132,7 @@ describe('Permit2', () => {
// Verify token approval
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax(DAI)
expectTokenAllowanceForPermit2ToBeMax()
// Verify permit2 approval rejection
cy.wrap(permitApprovalStub).should('be.calledWith', 'eth_signTypedData_v4')
@@ -198,60 +143,57 @@ describe('Permit2', () => {
cy.contains('Confirm swap').click()
// Verify permit2 approval
cy.contains('Swap success!')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
expectPermit2AllowanceForUniversalRouterToBeMax()
})
})
it('prompts token approval when existing approval amount is too low', () => {
setupInputs(DAI, USDC_MAINNET)
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setPermit2Allowance({ owner: wallet, token: DAI }),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }, 1),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }, 1),
])
)
initiateSwap()
// Verify token approval
cy.get(getTestSelector('popups')).contains('Approved')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
expectPermit2AllowanceForUniversalRouterToBeMax()
})
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),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, expiredAllowance),
])
)
initiateSwap()
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
cy.contains('Swap success!')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
expectPermit2AllowanceForUniversalRouterToBeMax()
})
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),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, smallAllowance),
])
)
initiateSwap()
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
cy.contains('Swap success!')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
expectPermit2AllowanceForUniversalRouterToBeMax()
})
})

View File

@@ -1,7 +1,25 @@
describe('Remove Liquidity', () => {
it('loads the token pair', () => {
it('eth remove', () => {
cy.visit('/remove/v2/ETH/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'UNI')
})
it('eth remove swap order', () => {
cy.visit('/remove/v2/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'UNI')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
})
it('loads the two correct tokens', () => {
cy.visit('/remove/v2/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'UNI')
})
it('does not crash if ETH is duplicated', () => {
cy.visit('/remove/v2/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
})
})

View File

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

View File

@@ -1,15 +1,13 @@
import { FeatureFlag } from '../../../src/featureFlags'
import { getTestSelector } from '../../utils'
describe('Swap settings', () => {
it('Opens and closes the settings menu', () => {
cy.visit('/swap', { featureFlags: [FeatureFlag.uniswapXEnabled] })
cy.visit('/swap')
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('UniswapX').should('exist')
cy.contains('Local routing').should('exist')
cy.contains('Auto Router API').should('exist')
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Settings').should('not.exist')
})

View File

@@ -1,9 +1,9 @@
import { ChainId } from '@uniswap/sdk-core'
import { SupportedChainId } from '@uniswap/sdk-core'
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
import { getBalance, getTestSelector } from '../../utils'
const UNI_MAINNET = UNI[ChainId.MAINNET]
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
describe('Swap', () => {
describe('Swap on main page', () => {
@@ -37,28 +37,14 @@ 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')
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')).type(USDC_MAINNET.address)
cy.get(getTestSelector('common-base-USDC')).click()
cy.contains('USDC').click()
// Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
@@ -69,9 +55,9 @@ describe('Swap', () => {
cy.contains('Review swap')
cy.contains('Confirm swap').click()
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
cy.contains('Swap submitted')
cy.contains('Transaction submitted')
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.contains('Swap submitted').should('not.exist')
cy.contains('Transaction submitted').should('not.exist')
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
// Mine transaction

View File

@@ -1,45 +0,0 @@
import { SwapEventName } from '@uniswap/analytics-events'
import { USDC_MAINNET } from '../../../src/constants/tokens'
import { getTestSelector } from '../../utils'
describe('time-to-swap logging', () => {
it('completes two swaps and verifies the TTS logging for the first', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.hardhat()
// First swap in the session:
// Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.get(getTestSelector('popups')).contains('Swapped')
// Verify logging
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
cy.wrap(event.event_properties).should('have.property', 'time_to_swap')
cy.wrap(event.event_properties.time_to_swap).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_swap).should('be.gte', 0)
})
// Second swap in the session:
// Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').clear().type('1').should('have.value', '1')
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.get(getTestSelector('popups')).contains('Swapped')
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
cy.wrap(event.event_properties).should('not.have.property', 'time_to_swap')
})
})
})

View File

@@ -1,12 +1,14 @@
import { ChainId, CurrencyAmount, WETH9 } from '@uniswap/sdk-core'
import { CurrencyAmount, SupportedChainId, WETH9 } from '@uniswap/sdk-core'
import { getBalance, getTestSelector } from '../../utils'
const WETH = WETH9[ChainId.MAINNET]
const WETH = WETH9[SupportedChainId.MAINNET]
describe('Swap wrap', () => {
beforeEach(() => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${WETH.address}`).hardhat({ automine: false })
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${WETH.address}`, { ethereum: 'hardhat' }).hardhat({
automine: false,
})
})
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {

View File

@@ -1,9 +1,9 @@
import { ChainId, WETH9 } from '@uniswap/sdk-core'
import { SupportedChainId, WETH9 } from '@uniswap/sdk-core'
import { ARB, UNI } from '../../src/constants/tokens'
import { UNI } from '../../src/constants/tokens'
import { getTestSelector } from '../utils'
const UNI_MAINNET = UNI[ChainId.MAINNET]
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
@@ -93,7 +93,9 @@ describe('Token details', () => {
beforeEach(() => {
// On mobile widths, we just link back to /swap instead of rendering the swap component.
cy.viewport(1200, 800)
cy.visit(`/tokens/ethereum/${UNI_MAINNET.address}`).then(() => {
cy.visit(`/tokens/ethereum/${UNI_MAINNET.address}`, {
ethereum: 'hardhat',
}).then(() => {
cy.wait('@eth_blockNumber')
cy.scrollTo('top')
})
@@ -143,11 +145,11 @@ describe('Token details', () => {
})
it('should show a L2 token even if the user is connected to a different network', () => {
cy.visit('/tokens')
cy.visit('/tokens', { ethereum: 'hardhat' })
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.address.toLowerCase()}`)).click()
cy.get(getTestSelector('token-table-row-ARB')).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')

View File

@@ -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-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'))
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'))
.find(getTestSelector('percent-change-cell'))
.should('include.text', '%')
cy.get(getTestSelector('header-row')).find(getTestSelector('price-cell')).click()
@@ -24,24 +24,24 @@ 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-NATIVE'))
cy.get(getTestSelector('token-table-row-ETH'))
.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-NATIVE'))
cy.get(getTestSelector('token-table-row-ETH'))
.find(getTestSelector('volume-cell'))
.then(function ($elem) {
cy.wrap($elem.text()).as('yearlyEthVol')
})
cy.get('@dailyEthVol').should('not.equal', cy.get('@yearlyEthVol'))
expect(cy.get('@dailyEthVol')).to.not.equal(cy.get('@yearlyEthVol'))
})
it('should navigate to token detail page when row clicked', () => {
cy.visit('/tokens/ethereum')
cy.get(getTestSelector('token-table-row-NATIVE')).click()
cy.get(getTestSelector('token-table-row-ETH')).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,15 +53,13 @@ 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-NATIVE')).should('exist')
cy.get(getTestSelector('token-table-row-ETH')).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-NATIVE'))
.find(getTestSelector('name-cell'))
.should('include.text', 'Polygon Matic')
cy.get(getTestSelector('token-table-row-MATIC')).should('exist')
})
it('should update when token explore table network changed', () => {

View File

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

View File

@@ -1,9 +1,8 @@
import { getTestSelector } from '../../utils'
import { DISCONNECTED_WALLET_USER_STATE } from '../../utils/user-state'
describe('disconnect wallet', () => {
it('should clear state', () => {
cy.visit('/swap')
cy.visit('/swap', { ethereum: 'hardhat' })
cy.get('#swap-currency-input .token-amount-input').clear().type('1')
// Verify wallet is connected
@@ -28,7 +27,7 @@ describe('disconnect wallet', () => {
describe('connect wallet', () => {
it('should load state', () => {
cy.visit('/swap', { userState: DISCONNECTED_WALLET_USER_STATE })
cy.visit('/swap', { ethereum: 'hardhat', userState: {} })
// Connect the wallet
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect').click()

View File

@@ -1,144 +0,0 @@
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')
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')
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')
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')
})
})

View File

@@ -1,8 +1,8 @@
import { getTestSelector } from '../utils'
describe('Wallet Dropdown', () => {
function itChangesTheme() {
it('should change theme', () => {
function itShouldChangeTheTheme() {
it('should change the theme', () => {
cy.get(getTestSelector('theme-lightmode')).click()
cy.get(getTestSelector('theme-lightmode')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
@@ -21,17 +21,13 @@ describe('Wallet Dropdown', () => {
})
}
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')
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')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.location('hash').should('match', /\?lng=en-US$/)
cy.contains('Uniswap available in: English').should('not.exist')
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
cy.get(getTestSelector('wallet-back')).click()
})
}
@@ -41,25 +37,8 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
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')
})
itShouldChangeTheTheme()
itShouldChangeTheLanguage()
})
describe('disconnected', () => {
@@ -72,8 +51,8 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('wallet-disconnect')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
itChangesTheme()
itChangesLocale()
itShouldChangeTheTheme()
itShouldChangeTheLanguage()
})
describe('with color theme', () => {

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,19 +0,0 @@
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')
})
})

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,3 @@
import { ConnectionType } from '../../src/connection/types'
import { UserState } from '../../src/state/user/reducer'
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: ConnectionType.INJECTED }
export const DISCONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: undefined }
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: 'INJECTED' }

View File

@@ -0,0 +1,36 @@
/* 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(...))`',
})
}
},
}
},
}

View File

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

View File

@@ -1 +0,0 @@
export const presets = ['@babel/preset-env']

View File

@@ -1,20 +0,0 @@
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',
},
},
})

View File

@@ -1,38 +0,0 @@
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 implements HTMLRewriterElementContentHandlers {
constructor(private input: MetaTagInjectorInput) {}
append(element: Element, property: string, content: string) {
element.append(`<meta property="${property}" content="${content}"/>`, { html: true })
}
element(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)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,397 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should inject metadata for valid assets 1`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Azuki #2550"/><meta property="og:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki #2550"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki #2550"/><meta property="twitter:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="twitter:image:alt" content="Azuki #2550"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid assets 2`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club #3735"/><meta property="og:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club #3735"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club #3735"/><meta property="twitter:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club #3735"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid assets 3`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="CryptoPunk #3947"/><meta property="og:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CryptoPunk #3947"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CryptoPunk #3947"/><meta property="twitter:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="twitter:image:alt" content="CryptoPunk #3947"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;

View File

@@ -1,64 +0,0 @@
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 asset calls', async (url) => {
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).not.toContain('og:title')
expect(body).not.toContain('og:image')
expect(body).not.toContain('og:image:width')
expect(body).not.toContain('og:image:height')
expect(body).not.toContain('og:type')
expect(body).not.toContain('og:url')
expect(body).not.toContain('og:image:alt')
expect(body).not.toContain('twitter:card')
expect(body).not.toContain('twitter:title')
expect(body).not.toContain('twitter:image')
expect(body).not.toContain('twitter:image:alt')
})

View File

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

View File

@@ -1,397 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should inject metadata for valid collections 1`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Azuki on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="twitter:image:alt" content="Azuki on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid collections 2`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid collections 3`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="twitter:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;

View File

@@ -1,63 +0,0 @@
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 collection 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
)

View File

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

View File

@@ -1,529 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should inject metadata for valid tokens 1`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get USDC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get USDC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get USDC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="twitter:image:alt" content="Get USDC on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid tokens 2`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get ETH on Uniswap"/><meta property="og:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get ETH on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get ETH on Uniswap"/><meta property="twitter:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="twitter:image:alt" content="Get ETH on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid tokens 3`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get MATIC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get MATIC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/polygon/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get MATIC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="twitter:image:alt" content="Get MATIC on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;
exports[`should inject metadata for valid tokens 4`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' '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="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get PEPE on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get PEPE on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get PEPE on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="twitter:image:alt" content="Get PEPE on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;

View File

@@ -1,70 +0,0 @@
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: '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')
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
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 | undefined, 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
}

View File

@@ -15,7 +15,6 @@ 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,
},
},
},

View File

@@ -1,34 +1,24 @@
import { ChainId } from '@uniswap/sdk-core'
import { UNIVERSAL_ROUTER_CREATION_BLOCK } from '@uniswap/universal-router-sdk'
/* eslint-env node */
require('dotenv').config()
const forkingConfig = {
// 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.
// TODO(WEB-2187): Make more dynamic to avoid manually updating
const BLOCK_NUMBER = 17388567
const mainnetFork = {
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
blockNumber: BLOCK_NUMBER,
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: UNIVERSAL_ROUTER_CREATION_BLOCK(ChainId.MAINNET),
...forkingConfig,
},
[ChainId.POLYGON]: {
url: `https://polygon-mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
blockNumber: UNIVERSAL_ROUTER_CREATION_BLOCK(ChainId.POLYGON),
...forkingConfig,
},
}
module.exports = {
forks,
networks: {
hardhat: {
chainId: ChainId.MAINNET,
forking: forks[ChainId.MAINNET],
chainId: 1,
forking: mainnetFork,
accounts: {
count: 2,
},

View File

@@ -115,11 +115,13 @@ const linguiConfig = {
'vi-VN',
'zh-CN',
'zh-TW',
'pseudo',
],
orderBy: 'messageId',
rootDir: '.',
runtimeConfigModule: ['@lingui/core', 'i18n'],
sourceLocale: 'en-US',
pseudoLocale: 'pseudo',
extractors: [cachingExtractor],
}

View File

@@ -15,21 +15,18 @@
"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",
"serve": "serve build -l 3000",
"lint": "yarn eslint --ignore-path .gitignore --cache --cache-location node_modules/.cache/eslint/ .",
"typecheck": "tsc",
"typecheck:cloud": "tsc -p functions/tsconfig.json",
"typecheck:cypress": "tsc -p cypress/tsconfig.json",
"test": "craco test",
"test:cloud": "NODE_OPTIONS=--experimental-vm-modules yarn jest functions --config=functions/jest.config.json",
"cypress:open": "cypress open --browser chrome --e2e",
"cypress:run": "cypress run --browser chrome --e2e",
"deduplicate": "yarn-deduplicate --strategy=highest"
@@ -70,9 +67,6 @@
]
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.22.7",
"@cloudflare/workers-types": "^4.20230710.1",
"@craco/craco": "^7.1.0",
"@ethersproject/experimental": "^5.4.0",
"@lingui/cli": "^3.9.0",
@@ -104,44 +98,35 @@
"@types/ua-parser-js": "^0.7.36",
"@types/uuid": "^8.3.4",
"@types/wcag-contrast": "^3.0.0",
"@uniswap/default-token-list": "^11.2.0",
"@uniswap/default-token-list": "^9.6.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",
"browser-cache-mock": "^0.1.7",
"buffer": "^6.0.3",
"concurrently": "^8.0.1",
"cypress": "12.12.0",
"cypress-hardhat": "^2.5.0",
"cypress-hardhat": "^2.3.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-extended": "^4.0.1",
"jest-fail-on-console": "^3.1.1",
"jest-fetch-mock": "^3.0.3",
"jest-styled-components": "^7.0.8",
"ms.macro": "^2.0.0",
"path-browserify": "^1.0.1",
"prettier": "^2.8.8",
"prettier": "^2.7.1",
"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": "^3.4.0",
"yarn-deduplicate": "^6.0.0"
},
"dependencies": {
@@ -171,26 +156,25 @@
"@sentry/tracing": "^7.45.0",
"@sentry/types": "^7.45.0",
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "^1.4.0",
"@uniswap/analytics-events": "^2.14.0",
"@uniswap/conedison": "^1.8.0",
"@uniswap/analytics": "^1.3.1",
"@uniswap/analytics-events": "^2.10.0",
"@uniswap/conedison": "^1.7.1",
"@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.6.0",
"@uniswap/sdk-core": "^4.0.3",
"@uniswap/smart-order-router": "^3.15.0",
"@uniswap/token-lists": "^1.0.0-beta.33",
"@uniswap/uniswapx-sdk": "^1.1.0",
"@uniswap/universal-router-sdk": "^1.5.6",
"@uniswap/v2-core": "^1.0.1",
"@uniswap/router-sdk": "^1.3.0",
"@uniswap/sdk-core": "^3.2.3",
"@uniswap/smart-order-router": "^3.12.1",
"@uniswap/token-lists": "^1.0.0-beta.31",
"@uniswap/universal-router-sdk": "^1.5.1",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^3.2.0",
"@uniswap/v3-core": "^1.0.1",
"@uniswap/v2-sdk": "^3.0.1",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.10.0",
"@uniswap/v3-sdk": "^3.9.0",
"@vanilla-extract/css": "^1.7.2",
"@vanilla-extract/css-utils": "^0.1.2",
"@vanilla-extract/dynamic": "^2.0.2",
@@ -206,12 +190,13 @@
"@web3-react/core": "^8.2.0",
"@web3-react/eip1193": "^8.2.0",
"@web3-react/empty": "^8.2.0",
"@web3-react/gnosis-safe": "^8.2.1",
"@web3-react/gnosis-safe": "^8.2.0",
"@web3-react/metamask": "^8.2.0",
"@web3-react/network": "^8.2.0",
"@web3-react/types": "^8.2.0",
"@web3-react/url": "^8.2.0",
"@web3-react/walletconnect-v2": "^8.3.7",
"@web3-react/walletconnect": "^8.2.0",
"@web3-react/walletconnect-v2": "8.3.2",
"ajv": "^8.11.0",
"ajv-formats": "^2.1.1",
"array.prototype.flat": "^1.2.4",

View File

@@ -1,33 +0,0 @@
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.uniswap",
"sha256_cert_fingerprints":
["97:A5:81:51:DA:AF:8F:6E:65:3A:90:1E:82:12:6C:FB:61:2D:36:C7:CF:20:61:6B:A3:4C:52:CA:BC:58:43:8E"]
}
},
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.uniswap.beta",
"sha256_cert_fingerprints":
["E5:39:87:DC:4D:FD:4C:1B:A6:74:36:7D:3A:3B:6B:ED:9E:B3:66:89:92:8A:1B:B8:FC:1B:22:56:56:B4:46:A3"]
}
}
],
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.uniswap.dev",
"sha256_cert_fingerprints":
["A8:A7:D4:DE:46:8E:BE:F6:DE:3B:62:2B:A7:26:60:F2:9A:4C:CD:AF:A6:96:C9:E5:7C:91:68:A1:29:2A:48:D3"]
}
}
]
]

View File

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

View File

@@ -1,30 +0,0 @@
{
"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"
}
]
}
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

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

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -19,9 +19,9 @@
<meta
http-equiv="Content-Security-Policy"
<% if (process.env.REACT_APP_CSP_ALLOW_UNSAFE_EVAL) { %>
content="script-src 'self' 'unsafe-inline' 'unsafe-eval'"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval'"
<% } else { %>
content="script-src 'self' 'unsafe-inline'"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
<% } %>
/>
@@ -37,6 +37,8 @@
-->
<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>

View File

@@ -1,47 +0,0 @@
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)
}
}

View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,11 +0,0 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 14C0 6.26801 6.26801 0 14 0V0C21.732 0 28 6.26801 28 14V14C28 21.732 21.732 28 14 28V28C6.26801 28 0 21.732 0 14V14Z" fill="#0052FF"/>
<g clip-path="url(#clip0_13924_33076)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3332 14.0003C23.3332 19.155 19.1472 23.3337 13.9836 23.3337C9.08459 23.3337 5.06565 19.5724 4.6665 14.7849H17.0245V13.2158H4.6665C5.06565 8.42825 9.08459 4.66699 13.9836 4.66699C19.1472 4.66699 23.3332 8.84566 23.3332 14.0003Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_13924_33076">
<rect width="18.6667" height="18.6667" fill="white" transform="translate(4.66675 4.66699)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 745 B

View File

@@ -1,11 +0,0 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="28" height="28" rx="8" fill="#0052FF"/>
<g clip-path="url(#clip0_13921_13252)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3332 14.0003C23.3332 19.155 19.1472 23.3337 13.9836 23.3337C9.08459 23.3337 5.06565 19.5724 4.6665 14.7849H17.0245V13.2158H4.6665C5.06565 8.42825 9.08459 4.66699 13.9836 4.66699C19.1472 4.66699 23.3332 8.84566 23.3332 14.0003Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_13921_13252">
<rect width="18.6667" height="18.6667" fill="white" transform="translate(4.66675 4.66699)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 651 B

View File

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

Before

Width:  |  Height:  |  Size: 917 B

View File

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

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

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

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20.83 14.6C19.9 14.06 19.33 13.07 19.33 12C19.33 10.93 19.9 9.93999 20.83 9.39999C20.99 9.29999 21.05 9.1 20.95 8.94L19.28 6.06C19.22 5.95 19.11 5.89001 19 5.89001C18.94 5.89001 18.88 5.91 18.83 5.94C18.37 6.2 17.85 6.34 17.33 6.34C16.8 6.34 16.28 6.19999 15.81 5.92999C14.88 5.38999 14.31 4.41 14.31 3.34C14.31 3.15 14.16 3 13.98 3H10.02C9.83999 3 9.69 3.15 9.69 3.34C9.69 4.41 9.12 5.38999 8.19 5.92999C7.72 6.19999 7.20001 6.34 6.67001 6.34C6.15001 6.34 5.63001 6.2 5.17001 5.94C5.01001 5.84 4.81 5.9 4.72 6.06L3.04001 8.94C3.01001 8.99 3 9.05001 3 9.10001C3 9.22001 3.06001 9.32999 3.17001 9.39999C4.10001 9.93999 4.67001 10.92 4.67001 11.99C4.67001 13.07 4.09999 14.06 3.17999 14.6H3.17001C3.01001 14.7 2.94999 14.9 3.04999 15.06L4.72 17.94C4.78 18.05 4.89 18.11 5 18.11C5.06 18.11 5.12001 18.09 5.17001 18.06C6.11001 17.53 7.26 17.53 8.19 18.07C9.11 18.61 9.67999 19.59 9.67999 20.66C9.67999 20.85 9.82999 21 10.02 21H13.98C14.16 21 14.31 20.85 14.31 20.66C14.31 19.59 14.88 18.61 15.81 18.07C16.28 17.8 16.8 17.66 17.33 17.66C17.85 17.66 18.37 17.8 18.83 18.06C18.99 18.16 19.19 18.1 19.28 17.94L20.96 15.06C20.99 15.01 21 14.95 21 14.9C21 14.78 20.94 14.67 20.83 14.6ZM12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15Z"
fill="currentColor" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

Before

Width:  |  Height:  |  Size: 10 KiB

View File

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

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

Before

Width:  |  Height:  |  Size: 649 B

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