ci(release): publish latest release

This commit is contained in:
Uniswap Labs Service Account 2023-12-05 21:38:58 +00:00
parent fc7ecc7e3b
commit 3e5fb0516d
3360 changed files with 206132 additions and 24886 deletions

17
.editorconfig Normal file

@ -0,0 +1,17 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# Windows files
[*.bat]
end_of_line = crlf

8
.eslintignore Normal file

@ -0,0 +1,8 @@
**/dist/**
**/build/**
**/node_modules/**
**/types/**
**/*.test.{js,jsx,ts,tsx}
**/*.spec.{js,jsx,ts,tsx}
**/config-overrides.js
**/.eslintrc.js

5
.firebaserc Normal file

@ -0,0 +1,5 @@
{
"projects": {
"default": "uniswap-mobile"
}
}

3
.gitattributes vendored Normal file

@ -0,0 +1,3 @@
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf

1
.github/CODEOWNERS vendored

@ -1 +0,0 @@
* @uniswap/web-reviewers

@ -1,22 +0,0 @@
---
name: Bug Report
about: Describe an issue in the Uniswap Interface
title: ''
labels: bug
assignees: ''
---
**Bug Description**
A clear and concise description of the bug.
**Steps to Reproduce**
1. Go to ...
2. Click on ...
...
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Additional Context**
Add any other context about the problem here (screenshots, whether the bug only occurs only in certain mobile/desktop/browser environments, etc.)

@ -1,8 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Support
url: https://discord.gg/FCfyBSbCU5
about: Please ask and answer questions here
- name: List a token
url: https://github.com/Uniswap/default-token-list#adding-a-token
about: Any requests to add a token to Uniswap should go here

@ -1,19 +0,0 @@
---
name: Feature Request
about: Suggest an idea for improving the UX of the Uniswap Interface
title: ''
labels: 'improvement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

@ -1,48 +0,0 @@
name: Report
description: Report test failures via Slack
inputs:
name:
description: The name of the failing test
required: true
SLACK_WEBHOOK_URL:
description: The webhook URL to send the report to
required: true
runs:
using: composite
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{
"text": "${{ inputs.name }} failing on `${{ github.ref_name }}`",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ inputs.name }} failing on `${{ github.ref_name }}`:* <https://github.com/${{ github.repository}}/actions/runs/${{ github.run_id }}|view failing action>"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "_This is blocking pull requests and branch promotions._\n_Please prioritize fixing the build._"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
# The !oncall bot requires its own message:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{
"text": "!oncall web"
}
env:
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

@ -1,49 +0,0 @@
name: Setup
description: checkout repo, setup node, and install node_modules
runs:
using: composite
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
registry-url: https://registry.npmjs.org
# cache is intentionally omitted, as it is faster with yarn v1 to cache node_modules.
- uses: actions/cache@v3
id: install-cache
with:
# node_modules/.cache is intentionally omitted, as this is used for build tool caches.
path: |
node_modules
!node_modules/.cache
key: ${{ runner.os }}-install-${{ hashFiles('yarn.lock') }}
- if: steps.install-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --ignore-scripts
shell: bash
# Run patch-package to apply patches to dependencies.
- run: yarn patch-package
shell: bash
# Contracts are compiled from source. If source hasn't changed, the contracts do not need to be re-compiled.
- uses: actions/cache@v3
id: contracts-cache
with:
path: |
src/abis/types
src/types/v3
key: ${{ runner.os }}-contracts-${{ hashFiles('src/abis/**/*.json', 'node_modules/@uniswap/**/artifacts/contracts/**/*.json') }}
- if: steps.contracts-cache.outputs.cache-hit != 'true'
run: yarn contracts
shell: bash
# These operations cannot be cached, so they are run concurrently
# - ajv: Validators compile quickly, so caching can be omitted.
# - graphql: GraphQL is generated from schema and client-side graphql queries. The schema is always fetched and
# changes to client-side queries are hard to detect, so it is always re-generated.
# - i18n: Messages are extracted from source and compiled. No caching extractor is available (out-of-the-box).
- run: yarn concurrently --max-processes=100% npm:ajv npm:graphql npm:i18n
shell: bash

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
# Files stored in repository root
directory: '/'
schedule:
interval: 'daily'
allow:
- dependency-name: '@uniswap/default-token-list'
- dependency-name: '@uniswap/token-lists'
reviewers:
- 'Uniswap/dependabot-reviewers'

@ -1,52 +0,0 @@
<!-- Your PR title must follow conventional commits: https://github.com/Uniswap/interface#pr-title -->
## Description
<!-- Summary of change, including motivation and context. -->
<!-- Use verb-driven language: "Fixes XYZ" instead of "This change fixes XYZ" -->
<!-- Delete inapplicable lines: -->
_Linear ticket:_
_Slack thread:_
_Relevant docs:_
<!-- Delete this section if your change does not affect UI. -->
## Screen capture
### Before
| Mobile | Desktop |
| ------------ | ------------ |
| paste_before | paste_before |
### After
| Mobile | Desktop |
| ------------ | ----------- |
| paste_after | paste_after |
## Test plan
<!-- Delete this section if your change is not a bug fix. -->
### Reproducing the error
<!-- Include steps to reproduce the bug. -->
1.
### QA (ie manual testing)
<!-- Include steps to test the change, ensuring no regression. -->
- [ ] N/A
#### Devices
<!-- If applicable, include different devices and screen sizes that may be affected, and how you've tested them. -->
### Automated testing
<!-- If N/A, check and note so it is obvious to your reviewers and does not show up as an incomplete task. -->
<!-- eg - [x] Unit test N/A -->
- [ ] Unit test
- [ ] Integration/E2E test

@ -1,73 +0,0 @@
name: 1 | Push main -> staging
# This CI job is responsible for pushing the current contents of the `main` branch to the
# `releases/staging` branch, which will in turn kick off a deploy to the staging environment.
on:
workflow_dispatch:
# https://stackoverflow.com/questions/57921401/push-to-origin-from-github-action
jobs:
push-staging:
name: 'Push to staging branch'
runs-on: ubuntu-latest
environment:
name: push/staging
steps:
- name: Check test status
uses: actions/github-script@v6.4.1
with:
script: |
const statuses = await github.rest.repos.listCommitStatusesForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
})
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
core.info('Status: ' + status)
if (status !== 'success') {
core.setFailed('"Test / promotion" must be successful before pushing')
}
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
ref: main
# The source file must exist for the corresponding translation messages to be downloaded.
- run: touch src/locales/en-US.po
- name: Download translations
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
with:
upload_sources: false
download_translations: true
project_id: 458284
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
source: 'src/locales/en-US.po'
translation: 'src/locales/%locale%.po'
localization_branch_name: main
create_pull_request: false
push_translations: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Git config
run: |
git config user.name 'UL Service Account'
git config user.email 'hello-happy-puppy@users.noreply.github.com'
- name: Add translations
run: |
rm src/locales/en-US.po
git add -f src/locales/*.po
git commit -m 'ci(t9n): download translations from crowdin'
- name: Add CODEOWNERS
run: |
echo '* @uniswap/web-admins' > CODEOWNERS
git add CODEOWNERS
git commit -m 'ci: add global CODEOWNERS'
- name: Git push
run: |
git push origin main:releases/staging --force

@ -1,64 +0,0 @@
name: 2 | Deploy staging
on:
push:
branches:
- 'releases/staging'
jobs:
deploy-to-staging:
runs-on: ubuntu-latest
environment:
name: deploy/staging
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Deploy _started_ for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn build
env:
REACT_APP_STAGING: 1
- name: Update Cloudflare Pages deployment
id: pages-deployment
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: interface-staging
directory: build
githubToken: ${{ secrets.GITHUB_TOKEN }}
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
branch: main
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Upload source maps to Sentry
uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd
continue-on-error: true
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
with:
environment: staging
sourcemaps: './build/static/js'
url_prefix: '~/static/js'

@ -1,42 +0,0 @@
name: 3 | Push staging -> prod
# This CI job is responsible for force pushing the content of releases/staging to releases/prod. It
# is restricted to web-reviewers through virtue of the GitHub environment protection rules for the
# prod environment.
on:
workflow_dispatch:
jobs:
push-prod:
name: 'Push to prod branch'
runs-on: ubuntu-latest
environment:
name: push/prod
steps:
- name: Check test status
uses: actions/github-script@v6.4.1
with:
script: |
const statuses = await github.rest.repos.listCommitStatusesForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
})
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
core.info('Status: ' + status)
if (status !== 'success') {
core.setFailed('"Test / promotion" must be successful before pushing')
}
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
ref: releases/staging
- name: Git config
run: |
git config user.name "UL Service Account"
git config user.email "hello-happy-puppy@users.noreply.github.com"
- name: Git push
run: |
git push origin releases/staging:releases/prod --force

@ -1,111 +0,0 @@
name: 4 | Deploy prod
on:
push:
branches:
- 'releases/prod'
jobs:
deploy-to-prod:
runs-on: ubuntu-latest
environment:
name: deploy/prod
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Deploy _started_ for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn build
- name: Bump and tag
id: github-tag-action
uses: mathieudutour/github-tag-action@d745f2e74aaf1ee82e747b181f7a0967978abee0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: releases/prod
default_bump: patch
- name: Pin to IPFS
id: pinata
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
with:
pin-name: Uniswap ${{ steps.github-tag-action.outputs.new_tag }}
path: './build'
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
- name: Convert CIDv0 to CIDv1
id: convert-cidv0
uses: uniswap/convert-cidv0-cidv1@v1.0.0
with:
cidv0: ${{ steps.pinata.outputs.hash }}
- name: Publish release
uses: actions/create-release@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.github-tag-action.outputs.new_tag }}
release_name: Release ${{ steps.github-tag-action.outputs.new_tag }}
body: |
IPFS hash of the deployment:
- CIDv0: `${{ steps.pinata.outputs.hash }}`
- CIDv1: `${{ steps.convert-cidv0.outputs.cidv1 }}`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
- [ipfs://${{ steps.pinata.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
${{ steps.github-tag-action.outputs.changelog }}
- name: Update Cloudflare Pages deployment
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
id: pages-deployment
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
directory: build
githubToken: ${{ secrets.GITHUB_TOKEN }}
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
branch: main
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Upload source maps to Sentry
uses: getsentry/action-release@4744f6a65149f441c5f396d5b0877307c0db52c7
continue-on-error: true
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
with:
environment: production
sourcemaps: './build/static/js'
url_prefix: '~/static/js'

@ -1,17 +0,0 @@
name: Check PR Title
on:
pull_request_target:
types:
- opened
- edited
- synchronize
jobs:
# Ensures that the PR title adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
conventional-commit:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v3.4.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -1,26 +0,0 @@
name: Crowdin Upload
on:
push:
branches:
- main
jobs:
upload-sources:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn i18n:extract
- name: Upload Crowdin sources
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
with:
upload_sources: true
download_translations: false
project_id: 458284
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
source: 'src/locales/en-US.po'
translation: 'src/locales/%locale%.po'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -1,91 +0,0 @@
name: Slack notification on pushes to releases/*
# This CI job will push notifications to Slack whenever code is merged into any releases/* branch
#
# The steps of the command line kung-fu shown below are as follows:
# First we take the JSON-formatted Github context
# echo $GITHUB_CONTEXT \
# Then we parse out the specific fields we want for our messages using jq and format it into tab-separated values
# | jq '.event.commits[] | [.url, .id[0:7], .author.username, .timestamp, .message] | @tsv' \
# We need to do some cleaning on this output - specifically removing quotes and replacing newlines with something easier to split
# | sed 's/"//g' | sed 's/\\t/;/g' | sed 's/\\n/;/g' | sed 's/\\//g' \
# We then use awk to format the TSV into a Slack message
# | awk -F';' '{print "• <"$1"|"$2"> (<https://github.com/"$3"|"$3">, "$4") - "$5}' \
# We need to deal with some escaping issues with newlines so that we don't break the Slack message format
# | sed 's/$/\\n/g' | tr -d '\n' \
# Finally we have to truncate the message to 3,000 characters max, otherwise Slack will reject it
# | awk '{print substr($0,0,3000);}' \
# Then shove the bytes into a file to store them in their exact format
# > /tmp/parsed_github_context
on:
push:
branches:
- 'releases/*'
jobs:
notify-slack:
runs-on: ubuntu-latest
environment:
name: notify/releases
steps:
- name: Parse event to slug
id: parse-slug
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
# Formats the contents of the GitHub event into slugs: one line per commit, formatted for Slack.
# Explanation for each line is in the comments above.
run: |
echo $GITHUB_CONTEXT \
| jq '.event.commits[] | [.url, .id[0:7], .author.username, .timestamp, .message] | @tsv' \
| sed 's/"//g' | sed 's/\\t/;/g' | sed 's/\\n/;/g' | sed 's/\\//g' \
| awk -F';' '{print "• <"$1"|"$2"> (<https://github.com/"$3"|"$3">, "$4") - "$5}' \
| sed 's/$/\\n/g' | tr -d '\n' \
| awk '{print substr($0,0,3000);}' \
> /tmp/parsed_github_context
echo "SLACK_COMMITS=$(cat /tmp/parsed_github_context)" >> "$GITHUB_OUTPUT"
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
"blocks": [
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Code merged to <https://github.com/Uniswap/interface/tree/${{ github.ref }}|${{ github.ref_name }}> branch:*\n"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Actor*: <https://github.com/${{ github.triggering_actor }}/|${{ github.triggering_actor }}>\n*Force pushed*: ${{ github.event.forced || false }}\n"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ steps.parse-slug.outputs.SLACK_COMMITS || 'New branch created' }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<${{ github.event.compare}}|View Diff>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

@ -1,24 +0,0 @@
name: Semgrep
on:
workflow_dispatch: {}
pull_request: {}
push:
branches:
- main
paths:
- .github/workflows/semgrep.yml
schedule:
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
- cron: '2 11 * * *'
jobs:
semgrep:
name: semgrep/ci
runs-on: ubuntu-20.04
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
container:
image: returntocorp/semgrep
if: (github.actor != 'dependabot[bot]')
steps:
- uses: actions/checkout@v3
- run: semgrep ci

40
.github/workflows/tag_and_release.yml vendored Normal file

@ -0,0 +1,40 @@
name: "Tag and release"
on:
push:
branches:
- 'main'
jobs:
deploy-to-prod:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 🧷️ Get version
uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b
id: version
with:
path: VERSION
- name: 🧾️ Get release notes
uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b
id: release-notes
with:
path: RELEASE
- name: 🏷️ Tag
id: github-tag-action
uses: uniswap/github-tag-action@7bddacd4864a0f5671e836721db60174d8a9c399
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
custom_tag: ${{ steps.version.outputs.content }}
tag_prefix: ""
- name: 🪽 Release
uses: actions/create-release@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.github-tag-action.outputs.new_tag }}
release_name: Release ${{ steps.github-tag-action.outputs.new_tag }}
body: ${{ steps.release-notes.outputs.content }}

@ -1,278 +0,0 @@
name: Test
# Many build steps have their own caches, so each job has its own cache to improve subsequent build times.
# Build tools are configured to cache to node_modules/.cache, so they are cached independently of node_modules.
# Caches are saved every run (by keying on github.run_id), and the most recent available cache is loaded.
# See https://jongleberry.medium.com/speed-up-your-ci-and-dx-with-node-modules-cache-ac8df82b7bb0.
on:
push:
branches:
- main
- releases/staging
pull_request:
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-eslint-${{ github.run_id }}
restore-keys: ${{ runner.os }}-eslint-
- run: yarn lint
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Lint
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-tsc-
- run: yarn typecheck
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
deps-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn yarn-deduplicate --strategy=highest --list --fail
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Dependency checks
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-jest-
- run: yarn test --coverage --maxWorkers=100%
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: unit-tests
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Unit tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.swc
key: ${{ runner.os }}-swc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-swc-
- run: yarn build
- uses: actions/upload-artifact@v3
with:
name: build
path: build
if-no-files-found: error
cypress-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cypress-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cypress-tsc-
- run: yarn typecheck:cypress
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# Allows for parallel re-runs of cypress tests without re-building.
cypress-rerun:
runs-on: ubuntu-latest
steps:
- run: exit 0
cypress-test-matrix:
needs: [build, cypress-rerun]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
containers: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: /root/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
- run: |
yarn cypress install
yarn cypress info
- uses: actions/download-artifact@v3
with:
name: build
path: build
- uses: actions/cache/restore@v3
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-
- uses: cypress-io/github-action@v4
with:
install: false
record: true
parallel: true
start: yarn serve
wait-on: 'http://localhost:3000'
browser: electron
group: e2e
spec: ${{ github.ref_name == 'releases/staging' && 'cypress/{e2e,staging}/**/*.test.ts' || 'cypress/e2e/**/*.test.ts' }}
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_INFO_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message }}
COMMIT_INFO_AUTHOR: ${{ github.event.sender.login || github.event.head_commit.author.login }}
# Cypress requires an email for filtering by author, but GitHub does not expose one.
# GitHub's public profile email can be deterministically produced from user id/login.
COMMIT_INFO_EMAIL: ${{ github.event.sender.id || github.event.head_commit.author.id }}+${{ github.event.sender.login || github.event.head_commit.author.login }}@users.noreply.github.com
COMMIT_INFO_SHA: ${{ github.event.pull_request.head.sha || github.event.head_commit.sha }}
COMMIT_INFO_TIMESTAMP: ${{ github.event.pull_request.updated_at || github.event.head_commit.timestamp }}
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number }}
CYPRESS_PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }}
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
- uses: actions/upload-artifact@v3
with:
name: hardhat-cache
path: cache
hardhat-cache:
needs: [cypress-test-matrix]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: hardhat-cache
path: cache
- uses: actions/cache/save@v3
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
cloud-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-tsc-
- run: yarn typecheck:cloud
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cloud typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
cloud-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-jest-
# Ignore start:cloud output so it doesn't flood the test output.
# Only use 1 worker for testing, as the other is used to run start:cloud (the proxy server under test).
- run: yarn start-server-and-test 'yarn start:cloud >/dev/null' 3000 'yarn test:cloud --coverage --maxWorkers=1'
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: cloud-tests
pre:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6.4.1
with:
script: |
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: 'pending',
context: 'Test / promotion',
description: 'Running tests...',
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
})
post:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
needs: [pre, lint, typecheck, deps-tests, unit-tests, cypress-test-matrix, cloud-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6.4.1
with:
script: |
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: ${{ env.STATUS }} ? 'success' : 'failure',
context: 'Test / promotion',
description: ${{ env.STATUS }} ? 'All tests passed' : 'One or more tests failed and are blocking promotion',
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
})
env:
STATUS: |
${{ needs.lint.result == 'success' }} &&
${{ needs.typecheck.result == 'success' }} &&
${{ needs.deps-tests.result == 'success' }} &&
${{ needs.unit-tests.result == 'success' }} &&
${{ needs.cypress-test-matrix.result == 'success' }} &&
${{ needs.cloud-tests.result == 'success' }}

77
.gitignore vendored

@ -1,57 +1,48 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# generated contract types
/src/types/v3
/src/abis/types
/src/locales/**/*.js
/src/locales/**/*.po
# generated files
/src/**/__generated__
# schema
schema.graphql
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
node_modules
.pnp
.pnp.js
# testing
/coverage
/cache
/functions/coverage
/.swc
coverage
# builds
/build
/dts
# next.js
.next/
out/
build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
instrumented
.nyc_output
.nyc_output/**/*
/.netlify
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
notes.txt
# local env files
.env
.env.defaults.local
# turbo
.turbo
# .yarn files
.yarn/cache
.yarn/versions
.yarn/install-state.gz
dist/out-tsc/*
# Locale backup files
packages/wallet/src/i18n/locales/*_old.json
# ci
.ci-cache/
# JetBrains
.idea/
package-lock.json
cypress/downloads
cypress/videos
cypress/screenshots
.vercel
.wrangler

2
.husky/pre-commit Normal file → Executable file

@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
yarn g:run-fast-checks

3
.lintstagedrc Normal file

@ -0,0 +1,3 @@
{
"**/*+(.ts?|.tsx)": ["turbo staged:lint:fix staged:format --filter=// --"]
}

3
.npmrc

@ -1 +1,2 @@
engine-strict = true
# used in tandem with package.json engines section to only use yarn
engine-strict=true

2
.nvmrc

@ -1 +1 @@
v18.16.0
v18

@ -1,6 +0,0 @@
{
"recommendations": [
"dbaeumer.vscode-eslint"
],
"unwantedRecommendations": []
}

25
.vscode/settings.json vendored

@ -1,25 +0,0 @@
{
"npm.packageManager": "yarn",
"typescript.updateImportsOnFileMove.enabled": "always",
"javascript.updateImportsOnFileMove.enabled": "always",
"editor.formatOnSaveMode": "file",
"editor.tabCompletion": "on",
"editor.tabSize": 2,
"editor.formatOnSave": false,
"editor.inlineSuggest.enabled": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"files.eol": "\n",
"eslint.enable": true,
"eslint.debug": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
}

@ -0,0 +1,12 @@
diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx
index 3b1264fb69d8a32e06cba4e2a1d592cee921b2aa..29e4e6464dc38567a1c97ddffb1e1ae016afbc3f 100644
--- a/src/components/bottomSheet/BottomSheet.tsx
+++ b/src/components/bottomSheet/BottomSheet.tsx
@@ -636,6 +636,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
const animateToPositionCompleted = useWorkletCallback(
function animateToPositionCompleted(isFinished?: boolean) {
isForcedClosing.value = false;
+ animatedCurrentIndex.value = animatedNextPositionIndex.value;
if (!isFinished) {
return;

@ -0,0 +1,13 @@
diff --git a/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt b/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt
index 6b78bd93e244649ee5d80bf4436bd742994d91fb..457179850746db36f568737e36d31f18968a1e80 100644
--- a/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt
+++ b/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt
@@ -26,7 +26,7 @@ class AutoLayoutView(context: Context) : ReactViewGroup(context) {
/** Overriding draw instead of onLayout. RecyclerListView uses absolute positions for each and every item which means that changes in child layouts may not trigger onLayout on this container. The same layout
* can still cause views to overlap. Therefore, it makes sense to override draw to do correction. */
- override fun dispatchDraw(canvas: Canvas?) {
+ override fun dispatchDraw(canvas: Canvas) {
fixLayout()
fixFooter()
super.dispatchDraw(canvas)

@ -0,0 +1,91 @@
diff --git a/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt b/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt
index 9980dfabbd5179753ba99f50c6ecfadd732cfdb9..c7b0bc2acd8ab516e9d14ef108a078a64afe8b97 100644
--- a/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt
+++ b/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt
@@ -7,6 +7,7 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
+import android.provider.Settings
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
@@ -89,7 +90,7 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
@ExpoMethod
fun supportedAuthenticationTypesAsync(promise: Promise) {
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
val results: MutableList<Int> = ArrayList()
if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
promise.resolve(results)
@@ -122,13 +123,13 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
@ExpoMethod
fun hasHardwareAsync(promise: Promise) {
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
promise.resolve(result != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE)
}
@ExpoMethod
fun isEnrolledAsync(promise: Promise) {
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
promise.resolve(result == BiometricManager.BIOMETRIC_SUCCESS)
}
@@ -138,13 +139,31 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
if (isDeviceSecure) {
level = SECURITY_LEVEL_SECRET
}
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
if (result == BiometricManager.BIOMETRIC_SUCCESS) {
level = SECURITY_LEVEL_BIOMETRIC
}
promise.resolve(level)
}
+ @ExpoMethod
+ fun enrollForAuthentication(promise: Promise) {
+ if (Build.VERSION.SDK_INT >= 30) {
+ val intent = Intent(Settings.ACTION_BIOMETRIC_ENROLL)
+ intent.putExtra(
+ Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ )
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ currentActivity!!.startActivity(intent)
+ promise.resolve(true)
+ } else {
+ val intent = Intent(Settings.ACTION_FINGERPRINT_ENROLL)
+ currentActivity!!.startActivity(intent)
+ promise.resolve(true)
+ }
+ }
+
@ExpoMethod
fun authenticateAsync(options: Map<String?, Any?>, promise: Promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@@ -220,10 +239,16 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
promptInfoBuilder.setNegativeButtonText(it)
}
} else {
- promptInfoBuilder.setAllowedAuthenticators(
- BiometricManager.Authenticators.BIOMETRIC_WEAK
- or BiometricManager.Authenticators.DEVICE_CREDENTIAL
- )
+ if (Build.VERSION.SDK_INT >= 30) {
+ promptInfoBuilder.setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ or BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ } else {
+ promptInfoBuilder.setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ )
+ }
}
promptInfoBuilder.setConfirmationRequired(requireConfirmation)
val promptInfo = promptInfoBuilder.build()

@ -0,0 +1,14 @@
diff --git a/basics b/basics
deleted file mode 100644
index 1a79016a006c6efd5e4cee071f134128d0ffd100..0000000000000000000000000000000000000000
--- a/basics
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require('./cjs/src/basics.js')
diff --git a/basics.js b/basics.js
new file mode 100644
index 0000000000000000000000000000000000000000..1a79016a006c6efd5e4cee071f134128d0ffd100
--- /dev/null
+++ b/basics.js
@@ -0,0 +1 @@
+module.exports = require('./cjs/src/basics.js')

@ -0,0 +1,19 @@
diff --git a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
index 4b5b90b7b478668fdff3fd12d5e028d423ada057..af30dc6f700b3b3cfde5c149bf1f865786df3e27 100644
--- a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
+++ b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
@@ -67,6 +67,14 @@ public class ContextMenuView extends ReactViewGroup implements PopupMenu.OnMenuI
contextMenu.show();
}
}
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (dropdownMenuMode) {
+ contextMenu.show();
+ }
+ return super.onSingleTapConfirmed(e);
+ }
});
}

@ -0,0 +1,12 @@
diff --git a/RNFastImage.podspec b/RNFastImage.podspec
index db0fada63fc06191f8620d336d244edde6c3dba3..286fa816e47996fdff9f25261644d612c682ae0b 100644
--- a/RNFastImage.podspec
+++ b/RNFastImage.podspec
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
s.source_files = "ios/**/*.{h,m}"
s.dependency 'React-Core'
- s.dependency 'SDWebImage', '~> 5.11.1'
+ s.dependency 'SDWebImage', '~> 5.15.5'
s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
end

@ -0,0 +1,19 @@
diff --git a/jest/setup.js b/jest/setup.js
index 3738bd2c61e516fa431f61fda47f2474f72dba42..2b3266007b3c9412d99e7ceee205ee52e3008077 100644
--- a/jest/setup.js
+++ b/jest/setup.js
@@ -17,12 +17,12 @@ jest.requireActual('@react-native/polyfills/error-guard');
global.__DEV__ = true;
-global.performance = {
+if (!global.performance) global.performance = {
now: jest.fn(Date.now),
};
global.regeneratorRuntime = jest.requireActual('regenerator-runtime/runtime');
-global.window = global;
+if (!global.window) global.window = global;
global.requestAnimationFrame = function (callback) {
return setTimeout(callback, 0);

@ -0,0 +1,38 @@
diff --git a/src/charts/line/ChartPath.tsx b/src/charts/line/ChartPath.tsx
index 3807c185c9456d2976c305df94574ff7d948b32a..5cf985422cf49120f943c98084b08c16faf452d9 100644
--- a/src/charts/line/ChartPath.tsx
+++ b/src/charts/line/ChartPath.tsx
@@ -18,7 +18,6 @@ const BACKGROUND_COMPONENTS = [
'LineChartHighlight',
'LineChartHorizontalLine',
'LineChartGradient',
- 'LineChartDot',
'LineChartTooltip',
];
const FOREGROUND_COMPONENTS = ['LineChartHighlight', 'LineChartDot'];
@@ -166,10 +165,25 @@ export function LineChartPathWrapper({
<View style={StyleSheet.absoluteFill}>
<AnimatedSVG animatedProps={svgProps} height={height}>
<LineChartPath color={color} width={strokeWidth} {...pathProps} />
+ </AnimatedSVG>
+ </View>
+
+ </LineChartPathContext.Provider>
+ <LineChartPathContext.Provider
+ value={{
+ color,
+ isInactive: false,
+ isTransitionEnabled: pathProps.isTransitionEnabled ?? true,
+ }}
+ >
+ <View style={StyleSheet.absoluteFill}>
+ <AnimatedSVG animatedProps={svgProps} height={height}>
{foregroundChildren}
</AnimatedSVG>
</View>
+
</LineChartPathContext.Provider>
+
</>
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

783
.yarn/releases/yarn-3.2.3.cjs vendored Executable file

File diff suppressed because one or more lines are too long

39
.yarnrc.yml Normal file

@ -0,0 +1,39 @@
enableTelemetry: false
changesetBaseRefs:
- main
- origin/main
- upstream/main
changesetIgnorePatterns:
- '**/*.test.{js,jsx,ts,tsx}'
defaultSemverRangePrefix: ''
enableGlobalCache: false
nmMode: hardlinks-local
logFilters:
- code: YN0002
level: discard
- code: YN0060
level: discard
- code: YN0006
level: discard
- code: YN0076
level: discard
- code: YN0013
level: discard
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: '@yarnpkg/plugin-workspace-tools'
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: '@yarnpkg/plugin-version'
- path: .yarn/plugins/@yarnpkg/plugin-constraints.cjs
spec: '@yarnpkg/plugin-constraints'
yarnPath: .yarn/releases/yarn-3.2.3.cjs

1
CODEOWNERS Normal file

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

@ -1,80 +1,54 @@
# Uniswap Labs Interface
# Uniswap Labs: Front End Interfaces
[![codecov](https://codecov.io/gh/Uniswap/interface/branch/main/graph/badge.svg?token=YVT2Y86O82)](https://codecov.io/gh/Uniswap/interface)
An open source repository for all Uniswap front end interfaces maintained by Uniswap Labs. Uniswap is a protocol for decentralized exchange of Ethereum tokens.
[![Unit Tests](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml)
[![Integration Tests](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml)
[![Lint](https://github.com/Uniswap/interface/actions/workflows/lint.yml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/lint.yml)
[![Release](https://github.com/Uniswap/interface/actions/workflows/release.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/release.yaml)
[![Crowdin](https://badges.crowdin.net/uniswap-interface/localized.svg)](https://crowdin.com/project/uniswap-interface)
## Interfaces
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
- Web: [app.uniswap.org](https://app.uniswap.org)
- Wallet: [wallet.uniswap.org](https://wallet.uniswap.org)
## Socials / Contact
- Website: [uniswap.org](https://uniswap.org/)
- Interface: [app.uniswap.org](https://app.uniswap.org)
- Docs: [uniswap.org/docs/](https://docs.uniswap.org/)
- Twitter: [@Uniswap](https://twitter.com/Uniswap)
- Reddit: [/r/Uniswap](https://www.reddit.com/r/Uniswap/)
- Email: [contact@uniswap.org](mailto:contact@uniswap.org)
- Discord: [Uniswap](https://discord.gg/FCfyBSbCU5)
- Whitepapers:
- [V1](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
- [V2](https://uniswap.org/whitepaper.pdf)
- [V3](https://uniswap.org/whitepaper-v3.pdf)
## Accessing the Uniswap Interface
## Uniswap Links
To access the Uniswap Interface, use an IPFS gateway link from the
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
or visit [app.uniswap.org](https://app.uniswap.org).
- Website: [uniswap.org](https://uniswap.org/)
- Docs: [uniswap.org/docs/](https://docs.uniswap.org/)
## Unsupported tokens
## Whitepapers
Check out `useUnsupportedTokenList()` in [src/state/lists/hooks.ts](./src/state/lists/hooks.ts) for blocking tokens in your instance of the interface.
- [V3](https://uniswap.org/whitepaper-v3.pdf)
- [V2](https://uniswap.org/whitepaper.pdf)
- [V1](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts)
## Apps
## Contributions
For instructions per application or package, see the README published for each application:
For steps on local deployment, development, and code contribution, please see [CONTRIBUTING](./CONTRIBUTING.md).
- [Web](apps/web/README.md)
- [Mobile](apps/mobile/README.md)
#### PR Title
Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), and should start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type):
## Releases
- build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript)
- ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress)
- docs: Documentation only changes
- feat: A new feature
- fix: A bug fix
- perf: A code change that improves performance
- refactor: A code change that neither fixes a bug nor adds a feature
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- test: Adding missing tests or correcting existing tests
All interface releases are tagged and published to this repository. To browse them easily, see the [Github releases tab](https://github.com/Uniswap/interface/releases).
Example commit messages:
## Translations
- feat: adds support for gnosis safe wallet
- fix: removes a polling memory leak
- chore: bumps redux version
Translations for our applications are done through [crowdin](https://crowdin.com).
Other things to note:
| App | Coverage |
| ------- | -------- |
| web | [![Crowdin](https://badges.crowdin.net/uniswap-interface/localized.svg)](https://crowdin.com/project/uniswap-interface) |
| mobile | [![Crowdin](https://badges.crowdin.net/uniswap-wallet/localized.svg)](https://crowdin.com/project/uniswap-wallet) |
- Please describe the change using verb statements (ex: Removes X from Y)
- PRs with multiple changes should use a list of verb statements
- Add any relevant unit / integration tests
- Changes will be previewable via vercel. Non-obvious changes should include instructions for how to reproduce them
## 🗂 Directory Structure
## Accessing Uniswap V2
The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2.
- Swap on Uniswap V2: <https://app.uniswap.org/swap?use=v2>
- View V2 liquidity: <https://app.uniswap.org/pools/v2>
- Add V2 liquidity: <https://app.uniswap.org/add/v2>
- Migrate V2 liquidity to V3: <https://app.uniswap.org/migrate/v2>
## Accessing Uniswap V1
The Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).
| Folder | Contents |
| ----------- | ------------------------------------------------------------------------------ |
| `apps/` | The home for each standalone application. |
| `config/` | Shared infrastructure packages and configurations. |
| `packages/` | Shared code packages covering UI, shared functionality, and shared utilities. |

43
RELEASE Normal file

@ -0,0 +1,43 @@
IPFS hash of the deployment:
- CIDv0: `Qmbx2qmXcLfaqtEm71XnBrTZRJSepmMiCYqQa833MUehQR`
- CIDv1: `bafybeigkhbeiart6cgq77am7th53hpxehbvlevnzxuqn3z7qvk2nagv2my`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeigkhbeiart6cgq77am7th53hpxehbvlevnzxuqn3z7qvk2nagv2my.ipfs.dweb.link/
- https://bafybeigkhbeiart6cgq77am7th53hpxehbvlevnzxuqn3z7qvk2nagv2my.ipfs.cf-ipfs.com/
- [ipfs://Qmbx2qmXcLfaqtEm71XnBrTZRJSepmMiCYqQa833MUehQR/](ipfs://Qmbx2qmXcLfaqtEm71XnBrTZRJSepmMiCYqQa833MUehQR/)
## 5.1.0 (2023-12-05)
### Features
* **web:** [info] add TDP chart toggle controls (#5223) c9ffc36
* **web:** add H1 to token detail page (#5272) b75724c
* **web:** page titles for all pages (#5327) b45bcab
* **web:** refresh swap flow (#5315) 83f70d4
* **web:** use Context for swap state (#5266) 37a649e
### Bug Fixes
* **web:** network downtime warning is not positioned correctly (#5281) 8b3ebb3
* **web:** page titles not updating properly (#5346) 6a29762
* **web:** put tdp text content in p tags (#5308) e5ca253
* **web:** remove trailing slash from canonicals (#5305) 00844b1
* **web:** tab title bug (#5258) e4b92be
* **web:** use nondenominational festive uni logo (#5361) 6f47a78
### Tests
* **web:** add tests for token page title (#5307) 714c0fa

1
VERSION Normal file

@ -0,0 +1 @@
web/5.1.0

49
apps/mobile/.detoxrc.json Normal file

@ -0,0 +1,49 @@
{
"testRunner": "jest",
"runnerConfig": "e2e/config.json",
"skipLegacyWorkersInjection": true,
"apps": {
"ios.debug": {
"type": "ios.app",
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/Uniswap.app",
"build": "RN_SRC_EXT=e2e.js,e2e.ts xcodebuild -workspace ios/Uniswap.xcworkspace -scheme Uniswap -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build -UseModernBuildSystem=YES -arch x86_64"
},
"ios.release": {
"type": "ios.app",
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/Uniswap.app",
"build": "RN_SRC_EXT=e2e.js,e2e.ts xcodebuild -workspace ios/Uniswap.xcworkspace -scheme Uniswap -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -UseModernBuildSystem=YES -arch x86_64"
},
"android": {
"type": "android.apk",
"binaryPath": "SPECIFY_PATH_TO_YOUR_APP_BINARY"
}
},
"devices": {
"simulator": {
"type": "ios.simulator",
"device": {
"type": "iPhone 12 Pro Max"
}
},
"emulator": {
"type": "android.emulator",
"device": {
"avdName": "Pixel_3a_API_30_x86"
}
}
},
"configurations": {
"ios.sim.debug": {
"device": "simulator",
"app": "ios.debug"
},
"ios.sim.release": {
"device": "simulator",
"app": "ios.release"
},
"android": {
"device": "emulator",
"app": "android"
}
}
}

12
apps/mobile/.eslintignore Normal file

@ -0,0 +1,12 @@
.eslintrc.js
babel.config.js
jest.config.js
metro.config.js
node_modules
generated*.ts
__generated__/
storybook-static
coverage

28
apps/mobile/.eslintrc.js Normal file

@ -0,0 +1,28 @@
module.exports = {
root: true,
extends: ['custom'],
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {},
},
{
files: ['*.ts', '*.tsx'],
rules: {},
},
{
files: ['*.js', '*.jsx'],
rules: {},
},
],
rules: {},
}

116
apps/mobile/.gitignore vendored Normal file

@ -0,0 +1,116 @@
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
ios/.xcode.env.local
ios/GoogleService-Info.plist
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
*.jks
keystore.properties
*.aab
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# Yarn
.yarn/cache
.yarn/versions
.yarn/install-state.gz
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/.env
fastlane/.env.*
fastlane/builds
# firebase
firebase-debug.log
firestore-debug.log
ui-debug.log
# Bundle artifact
*.jsbundle
*.jsbundle.map
# Detox artifacts
artifacts/
# CocoaPods
/ios/Pods/
# ccache
.ccache
# hardhat network fork
/cache/
# Storybook
build-storybook.log
storybook-static/*
# Private keys
.env.local
# Snyk
.dccache
./lib/
# Jest
coverage/
# Swift GraphQL codegen
# Ignores everything inside the schema folder except the README.md
!ios/WidgetsCore/MobileSchema/
ios/WidgetsCore/MobileSchema/*
!ios/WidgetsCore/MobileSchema/README.md
# Swift env
ios/WidgetsCore/Env.swift
# Sentry
ios/sentry.properties
android/sentry.properties

@ -0,0 +1,6 @@
ios
android
src/abis/types
generated*.ts
__generated__/
.eslintrc.js

@ -0,0 +1 @@
{}

@ -0,0 +1,12 @@
diff --git a/RNFastImage.podspec b/RNFastImage.podspec
index db0fada63fc06191f8620d336d244edde6c3dba3..286fa816e47996fdff9f25261644d612c682ae0b 100644
--- a/RNFastImage.podspec
+++ b/RNFastImage.podspec
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
s.source_files = "ios/**/*.{h,m}"
s.dependency 'React-Core'
- s.dependency 'SDWebImage', '~> 5.11.1'
+ s.dependency 'SDWebImage', '~> 5.15.5'
s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
end

1
apps/mobile/CODEOWNERS Normal file

@ -0,0 +1 @@
* @Uniswap/mobile-release-admins

10
apps/mobile/Gemfile Normal file

@ -0,0 +1,10 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
source "https://rubygems.org"
gem 'fastlane'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

222
apps/mobile/Gemfile.lock Normal file

@ -0,0 +1,222 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
rexml
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.805.0)
aws-sdk-core (3.180.3)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.132.1)
aws-sdk-core (~> 3, >= 3.179.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.100.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastlane (2.214.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-get_version_name (0.2.2)
fastlane-plugin-versioning_android (0.1.1)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.48.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.1)
google-cloud-storage (1.44.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.7.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.1)
memoist (0.16.2)
mini_magick (4.12.0)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.3.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
os (1.1.4)
plist (3.7.0)
public_suffix (5.0.3)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.6)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
fastlane-plugin-get_version_name
fastlane-plugin-versioning_android
BUNDLED WITH
1.17.2

204
apps/mobile/README.md Normal file

@ -0,0 +1,204 @@
# Uniswap Wallet
[Uniswap Wallet](https://wallet.uniswap.org/) is the simplest, safest, and most powerful self-custodial crypto wallet. It is developed by the Uniswap Labs team, inventors of the Uniswap Protocol.
If you have suggestions on how we can improve the app, or would like to report a bug or a problem, check out the [Uniswap Help Center](https://support.uniswap.org/).
## Setup
### Requirements
This guide assumes that:
- You are using a Mac (you will need a Mac computer in order to run the Xcode iOS Simulator)
- You are using an Apple Silicon Mac (if youre not sure, go to  → About this Mac and check if the chip name starts with "Apple")
Note: if you are indeed using an Apple Silicon Mac, we recommend setting up your environment _without_ using Rosetta. Some instructions on how to do that can be found [here](https://medium.com/@davidjasonharding/developing-a-react-native-app-on-an-m1-mac-without-rosetta-29fcc7314d70).
### Packages and Software
#### Xcode
You should start with downloading Xcode if you don't already have it installed, since the file is so large. You can find it here: [developer.apple.com/xcode](https://developer.apple.com/xcode/)
You must use **XCode 15** to compile the app.
#### Homebrew
Well be using Homebrew to install many of the other required tools through the command line.
1. Open a terminal
2. Copy and paste the command from [brew.sh](https://brew.sh/) into your terminal and run it
#### nvm
`nvm` is the Node Version Manager. While not required, it makes it easy to install Node and switch between different versions. A minimum Node version of 18 is required to use this repository.
Copy the curl command listed under _Install & Update Script_ on [this page](https://github.com/nvm-sh/nvm#install--update-script) and run it in your terminal.
To make sure nvm installed correctly, try running `nvm -v` (you may need to quit and re-open the terminal window). It should return a version number. If it returns something like `zsh: command not found: nvm`, it hasnt been installed correctly.
#### node
Now we want to use nvm to install a specific version of node.
Run the following command in your terminal:
`nvm install 18`
and then when its finished, run:
`nvm use 18`
Quit and re-open the terminal, and then run:
`node -v`
to make sure you get a version number that starts with `v18.`.
#### yarn
We use yarn as our package manager and to run scripts.
Run the following command to install it:
`npm install --global yarn`
(npm comes with node, so it should work if the above step has been completed correctly)
Then run:
`yarn -v`
to see if it installed correctly.
#### Ruby
Use `rbenv` to install a specific version of `ruby`:
`brew install rbenv ruby-build`
Run `rbenv init` and follow the instructions to complete the installation.
After following the instructions, make sure you `source` your `.zshrc` or `.bash_profile`, or start a new terminal session.
Install a version of `ruby`:
`rbenv install 3.2.2`
Set this as your default version:
`rbenv global 3.2.2`
#### CocoaPods
Install cocoapods:
`gem install cocoapods -v 1.13.0`
### Add Xcode Command Line Tools
Open Xcode and go to:
`Preferences → Locations → Command Line Tools`
And select the version that pops up.
## Development
Once all the setup steps above are completed, you're ready to try running the app locally!
### Environment variables
Note: The app will likely have limited functionality when running it locally with the default environment variables.
Use the environment variables defined in the `.env.defaults.local` file to run the app locally.
### Compile contract ABI types
This is done in bootstrap but good to know about. Before the code will compile you need to generate types for the smart contracts the wallet interacts with. Run `yarn g:prepare` at the top level. Re-run this if the ABIs are ever changed.
### Run the app
In the root directory, run `yarn` to install all the necessary npm packages.
Then run `yarn mobile pod` to install all the necessary pods. (You may need to updated source repos with `pod repo update` if this fails.)
Finally, run `yarn mobile ios` to boot up the iOS Simulator and run the app inside it. The JS bundler (metro) should automatically open in a new terminal window. If it does not, start it manually with `yarn start`.
Or you can use one command to run them all one after the other: `yarn && yarn pod && yarn ios`
You can also run the app from Xcode, which is necessary for any Swift related changes. Xcode will automatically start the metro bundler.
Hopefully you now (after a few minutes) see the Uniswap Wallet running in the iOS Simulator!
### Enabling Flipper
We do not check Flipper into source. To prevent `pod install` from adding Flipper, set an environment variable in your `.bash_profile` or `.zshrc` or `.zprofile`:
```bash
# To enable flipper inclusion (optional)
export USE_FLIPPER=1
```
Note: To disable Flipper, the whole line should be commented out, as setting this value to 0 will not disable Flipper.
## Important Libraries and Tools
These are some tools you might want to familiarize yourself with to understand the codebase better and how different aspects of it work.
- [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/): state management
- [redux-saga](https://redux-saga.js.org/) & [typed-redux-saga](https://github.com/agiledigital/typed-redux-saga): Redux side effect manager -- used for complex/stateful network calls
- [ethers](https://docs.ethers.io/v5/)
- [Tamagui](https://tamagui.dev): UI framework
- [React navigation](https://reactnavigation.org/): routing and navigation with animations and gestures
- [react-i18next](https://react.i18next.com/): i18n
## Migrations
We use `redux-persist` to persist Redux state between user sessions. When the Redux state schema is altered, a migration may be needed to transfer the existing persisted state to the new Redux schema. Failing to define a migration results in the app defaulting to the persisted schema, which will very likely cause `undefined` errors because the code has references to Redux state properties that were dropped in favor the the persisted schema.
### When to define a migration
Anytime a required property is added or any property is renamed or deleted to/from Redux state. Migrations are not necessary when optional properties are added to an existing slice. Make sure to always add new required properties to the `schema.ts` file as well.
### How to migrate
1. Increment the `version` of `persistConfig` defined within `store.ts`
2. Create a migration function within `migrations.ts`. The migration key should be the same as the `version` defined in the previous step
3. Write a test for your migration within `migrations.test.ts`
4. Create a new schema within `schema.ts` and ensure it is being exported by the `getSchema` function at the bottom of the file
## Troubleshooting
### Common issues
- `zsh: command not found: [package name]`
This means whichever package you're trying to run (`[package name]`) wasnt correctly installed, or your Terminal cant figure out how to run it. If you just installed it, try quitting terminal and re-opening it. Otherwise try reinstalling the package.
- `unable to open file (in target "OneSignalNotificationServiceExtension" in project "Uniswap")`.
Resolve this issue by navigating to the `ios/` directory and running `pod update`.
### Common fixes
If something isnt working the way it should or youre getting a weird error when trying to run the app, try the following:
1. Quit the terminal
2. Quit Metro terminal
3. Open Finder and navigate to the `mobile` directory
4. Delete the `node_modules` folder
5. Navigate into the `ios` folder
6. Delete the `Pods` folder
7. Open XCode
8. Go to Product → Clean Build Folder
9. Open your terminal again
10. Navigate to the `mobile` directory in the terminal
11. Run `yarn && yarn pod` again
12. Run `yarn ios`
### Shell profile setup
Your shell profile file is most likely one of: `.bash_profile`, `.zshrc`, or `.zprofile`, and will be located in `/Users/[username]/`. You can reveal hidden files in Finder by pressing `⌘` + `Shift` + `.`.
If issues with your terminal or shell seem to be the cause of some of your problems, here is an example of what that file may look like in order for your terminal to be able to run the app locally:
```zsh
eval "$(/opt/homebrew/bin/brew shellenv)"
export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion
# To enable flipper inclusion (optional)
export USE_FLIPPER=1
export PATH="/opt/homebrew/opt/ruby/bin:$PATH"
```

@ -0,0 +1,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
export default {
app: () => ({
auth: () => ({
signInAnonymously: () => undefined,
}),
}),
}

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

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

@ -0,0 +1,13 @@
import React, { PropsWithChildren, ReactNode } from 'react'
import { View, ViewProps } from 'react-native'
// react-native-masked-view for Storybook web
// https://github.com/react-native-masked-view/masked-view/issues/70#issuecomment-1171801526
function MaskedViewWeb({
maskElement,
...props
}: PropsWithChildren<{ maskElement: ReactNode }>): React.CElement<ViewProps, View> {
return React.createElement(View, props, maskElement)
}
export default MaskedViewWeb

@ -0,0 +1,22 @@
import React, { PropsWithChildren } from 'react'
import { View, ViewProps } from 'react-native'
// Source: https://github.com/Shopify/react-native-skia/issues/548#issuecomment-1157609472
const PlainView = ({
children,
...props
}: PropsWithChildren<unknown>): React.CElement<ViewProps, View> => {
return React.createElement(View, props, children)
}
const noop = (): null => null
export const BlurMask = PlainView
export const Canvas = PlainView
export const Circle = PlainView
export const Group = PlainView
export const LinearGradient = PlainView
export const Mask = PlainView
export const Path = PlainView
export const Rect = PlainView
export const vec = noop

@ -0,0 +1,11 @@
import React, { PropsWithChildren } from 'react'
import { View, ViewProps } from 'react-native'
const PlainView = ({
children,
...props
}: PropsWithChildren<unknown>): React.CElement<ViewProps, View> => {
return React.createElement(View, props, children)
}
export default PlainView

@ -0,0 +1,13 @@
import React, { PropsWithChildren } from 'react'
import { Image, ImageProps } from 'react-native'
const PlainImage = ({
children,
...props
}: PropsWithChildren<ImageProps>): React.CElement<ImageProps, Image> => {
return React.createElement(Image, props, children)
}
PlainImage.resizeMode = {}
export default PlainImage

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

@ -0,0 +1,4 @@
KEYSTORE_FILE=op://Android/keystore-properties-$APP_ENV/storeFile
STORE_PASSWORD=op://Android/keystore-properties-$APP_ENV/storePassword
KEYSTORE_ALIAS=op://Android/keystore-properties-$APP_ENV/keyAlias
KEY_PASSWORD=op://Android/keystore-properties-$APP_ENV/keyPassword

@ -0,0 +1,4 @@
# Known Issues
Android 14 (API level 34) features cannot be used until we upgrade to React Native 0.71.13. That means that the sdk versions cannot be upgraded to 34 until React Native is also upgraded.
https://github.com/facebook/react-native/issues/37769

@ -0,0 +1,251 @@
import com.android.build.OutputFile
plugins {
id 'com.android.application'
id 'com.facebook.react'
id 'com.google.gms.google-services'
id 'maven-publish'
id 'kotlin-android'
}
def nodeModulesPath = "../../../../node_modules"
def rnRoot = "../.."
def keystorePropertiesFile = rootProject.file("keystore.properties");
def keystoreProperties = new Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
react {
root = file("$rnRoot/")
reactNativeDir = file("$nodeModulesPath/react-native")
codegenDir = file("$nodeModulesPath/react-native-codegen")
cliFile = file("$nodeModulesPath/react-native/cli.js")
debuggableVariants = ["devDebug", "betaDebug", "prodDebug"]
hermesCommand = "../../node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc" // This is relative to the project root.
}
/**
* Set this to true to create four separate APKs instead of one,
* one for each native architecture. This is useful if you don't
* use App Bundles (https://developer.android.com/guide/app-bundle/)
* and want to have separate APKs to upload to the Play Store.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc-intl:+'
/**
* Private function to get the list of Native Architectures you want to build.
* This reads the value from reactNativeArchitectures in your gradle.properties
* file and works together with the --active-arch-only flag of react-native run-android.
*/
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
boolean isCI = System.getenv('CI') != null
boolean sentryPropertiesAvailable = System.getenv('SENTRY_AUTH_TOKEN') != null && System.getenv('SENTRY_PROJECT') != null && System.getenv('SENTRY_ORG') != null
if (isCI && sentryPropertiesAvailable) {
project.ext.sentryCli = [
logLevel: "info",
]
apply from: "../../../../node_modules/@sentry/react-native/sentry.gradle"
apply plugin: "io.sentry.android.gradle"
sentry {
uploadNativeSymbols = true
includeNativeSources = true
autoInstallation {
enabled = false
}
}
}
android {
ndkVersion rootProject.ext.ndkVersion
namespace "com.uniswap"
defaultConfig {
applicationId "com.uniswap.mobile"
compileSdk rootProject.ext.compileSdkVersion
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
multiDexEnabled true
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include (*reactNativeArchitectures())
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
storeFile file(System.getenv("ANDROID_KEYSTORE_FILE") ?: 'keystore.jks')
storePassword System.getenv("ANDROID_STORE_PASSWORD") ?: keystoreProperties.getProperty("STORE_PASSWORD")
keyAlias System.getenv("ANDROID_KEYSTORE_ALIAS") ?: keystoreProperties.getProperty("KEYSTORE_ALIAS")
keyPassword System.getenv("ANDROID_KEY_PASSWORD") ?: keystoreProperties.getProperty("KEY_PASSWORD")
}
}
flavorDimensions += "variant"
productFlavors {
dev {
isDefault(true)
applicationIdSuffix ".dev"
versionName "1.18"
dimension "variant"
}
beta {
applicationIdSuffix ".beta"
versionName "1.18"
dimension "variant"
}
prod {
dimension "variant"
versionName "1.18"
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
}
packagingOptions {
resources.excludes.add("META-INF/*")
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.6"
}
sourceSets {
main {
jniLibs {
srcDir '../../../../node_modules/@uniswap/ethers-rs-mobile/android/jniLibs'
}
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation "com.facebook.react:react-android"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
// Used to deal with Flipper/OkHttp issues with DevSupportManager
implementation "androidx.multidex:multidex:$multidexVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerialization"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle"
implementation 'com.google.android.play:integrity:1.2.0'
// Guava
implementation "com.google.guava:guava:24.1-jre"
// Guava fix
implementation "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava"
//TODO: Revisit dependencies during security audit
//Drive
implementation('com.google.api-client:google-api-client-android:2.1.0') {
exclude group: 'org.apache.httpcomponents'
exclude module: 'guava-jdk5'
}
implementation('com.google.apis:google-api-services-drive:v3-rev20221023-2.0.0') {
exclude group: 'org.apache.httpcomponents'
exclude module: 'guava-jdk5'
}
implementation 'com.google.android.gms:play-services-auth:20.4.0'
implementation 'com.google.api-client:google-api-client-jackson2:1.31.1'
implementation 'com.google.auth:google-auth-library-oauth2-http:1.11.0'
implementation "androidx.compose.foundation:foundation:$compose"
implementation "androidx.compose.material:material:$compose"
implementation "androidx.compose.ui:ui:$compose"
implementation "androidx.security:security-crypto:1.0.0"
implementation 'com.lambdapioneer.argon2kt:argon2kt:1.3.0'
implementation "com.google.accompanist:accompanist-flowlayout:$flowlayout"
implementation project(':@sentry_react-native')
// Used for device-reported performance class.
implementation("androidx.core:core-performance:$corePerf")
implementation("androidx.core:core-performance-play-services:$corePerf")
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
apply from: file("${nodeModulesPath}/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

Binary file not shown.

@ -0,0 +1,10 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning"
tools:targetApi="28">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter android:autoVerify="true" android:priority="1">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="uniswap.org" />
<data android:host="app.uniswap.org" />
<data android:host="uniswap.com" />
<data android:host="app.uniswap.com" />
</intent-filter>
</activity>
</application>
</manifest>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<string name="app_name">Uniswap Beta</string>
</resources>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

@ -0,0 +1,71 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.uniswap;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning"
tools:targetApi="28">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter android:autoVerify="true" android:priority="2">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="uniswap.org" />
<data android:host="app.uniswap.org" />
<data android:host="uniswap.com" />
<data android:host="app.uniswap.com" />
</intent-filter>
</activity>
</application>
</manifest>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

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