diff --git a/.bun-version b/.bun-version
index 9728bd69ac8..3a3cd8cc8b0 100644
--- a/.bun-version
+++ b/.bun-version
@@ -1 +1 @@
-1.2.21
+1.3.1
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 00000000000..d5d401c5189
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,31 @@
+# Use the latest 2.1 version of CircleCI pipeline process engine.
+# See: https://circleci.com/docs/configuration-reference
+version: 2.1
+
+# Define a job to be invoked later in a workflow.
+# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs
+jobs:
+ say-hello:
+ # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub.
+ # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job
+ docker:
+ # Specify the version you desire here
+ # See: https://circleci.com/developer/images/image/cimg/base
+ - image: cimg/base:current
+
+ # Add steps to the job
+ # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps
+ steps:
+ # Checkout the code as the first step.
+ - checkout
+ - run:
+ name: "Say hello"
+ command: "echo Hello, World!"
+
+# Orchestrate jobs using workflows
+# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows
+workflows:
+ say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow.
+ # Inside the workflow, you define the jobs you want to run.
+ jobs:
+ - say-hello
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 00000000000..7d26733d0a9
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,20 @@
+{
+ "permissions": {
+ "deny": [
+ "Read(**/.env)",
+ "Edit(**/.env)",
+ "Read(~/.aws/**)",
+ "Edit(~/.aws/**)",
+ "Read(~/.ssh/**)",
+ "Edit(~/.ssh/**)",
+ "Read(~/.gnupg/**)",
+ "Edit(~/.gnupg/**)",
+ "Read(~/.git-credentials)",
+ "Edit(~/.git-credentials)",
+ "Read($HOME/Library/Keychains/**)",
+ "Edit($HOME/Library/Keychains/**)",
+ "Read(/private/etc/**)",
+ "Edit(/private/etc/**)"
+ ]
+ }
+ }
diff --git a/.cursor/cli.json b/.cursor/cli.json
new file mode 100644
index 00000000000..b50608cd785
--- /dev/null
+++ b/.cursor/cli.json
@@ -0,0 +1,21 @@
+{
+ "version": 1,
+ "permissions": {
+ "deny": [
+ "Read(**/.env)",
+ "Write(**/.env)",
+ "Read(~/.aws/**)",
+ "Write(~/.aws/**)",
+ "Read(~/.ssh/**)",
+ "Write(~/.ssh/**)",
+ "Read(~/.gnupg/**)",
+ "Write(~/.gnupg/**)",
+ "Read(~/.git-credentials)",
+ "Write(~/.git-credentials)",
+ "Read($HOME/Library/Keychains/**)",
+ "Write($HOME/Library/Keychains/**)",
+ "Read(/private/etc/**)",
+ "Write(/private/etc/**)"
+ ]
+ }
+ }
diff --git a/.cursorignore b/.cursorignore
new file mode 100644
index 00000000000..70179d19f2e
--- /dev/null
+++ b/.cursorignore
@@ -0,0 +1,7 @@
+**/.env
+**/.aws/**
+**/.ssh/**
+**/.gnupg/**
+**/.git-credentials
+**/Library/Keychains/**
+**/private/etc/**
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index b01ad10c152..feac4b614be 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -3,6 +3,8 @@ name: Bug Report
about: Report a bug or unexpected behavior in the Uniswap interfaces.
title: "[Bug] "
labels: bug
+assignees: ''
+
---
## 📱 Interface Affected
diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md
new file mode 100644
index 00000000000..48d5f81fa42
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/custom.md
@@ -0,0 +1,10 @@
+---
+name: Custom issue template
+about: Describe this issue template's purpose here.
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000000..36014cde565
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: 'enhancement'
+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.
diff --git a/.github/workflows/tag_and_release.yml b/.github/workflows/tag_and_release.yml
index 3bfcc2933d0..3791e6f9a72 100644
--- a/.github/workflows/tag_and_release.yml
+++ b/.github/workflows/tag_and_release.yml
@@ -3,7 +3,10 @@ on:
push:
branches:
- 'main'
-
+permissions:
+ contents: write
+ issues: write
+ pull-requests: write
jobs:
deploy-to-prod:
runs-on: ubuntu-latest
@@ -47,7 +50,7 @@ jobs:
- name: 🪽 Release
uses: actions/create-release@c9ba6969f07ed90fae07e2e66100dd03f9b1a50e
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Or use your PAT
with:
tag_name: ${{ steps.github-tag-action.outputs.new_tag }}
release_name: Release ${{ steps.github-tag-action.outputs.new_tag }}
diff --git a/.gitignore b/.gitignore
index 3b3a172871a..b3c573b208b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,9 +76,10 @@ CLAUDE.local.md
# lefthook
.lefthook/
-
-
+# Nx
.nx/cache
.nx/workspace-data
-.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md
+
+# Spec Workflow MCP
+.spec-workflow/
diff --git a/CLAUDE.md b/CLAUDE.md
index 00a8280afa0..7224bbdb032 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -44,6 +44,7 @@ bun extension build:production # Extension production
```bash
bun g:test # Run all tests
+bun notifications test # Run tests for a specific package (e.g. notifications)
bun g:test:coverage # With coverage
bun web playwright:test # Web E2E tests
bun mobile e2e # Mobile E2E tests
@@ -158,4 +159,4 @@ Be cognizant of the app or package within which a given change is being made. Be
- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors
-
\ No newline at end of file
+
diff --git a/RELEASE b/RELEASE
index 80f8e3ca3e2..9b31fb52f72 100644
--- a/RELEASE
+++ b/RELEASE
@@ -1,6 +1,6 @@
IPFS hash of the deployment:
-- CIDv0: `QmZmRjJXcRL1HVbuFBLX23qqmQydzmqGsLmb94qrqTU9A7`
-- CIDv1: `bafybeifjzfti2rq27be42zfwqnturszxqyecs4zxklxxr4pejgcgi72eja`
+- CIDv0: `QmNMoMSDQVQwUdfxLqhLpM4bKgGMyEHU2LU2RfDFaRqACc`
+- CIDv1: `bafybeiaajnnkurqabznrl32buiydbsym77nt2np3d5xv5mgskkd6euk3au`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
@@ -10,14 +10,65 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
-- https://bafybeifjzfti2rq27be42zfwqnturszxqyecs4zxklxxr4pejgcgi72eja.ipfs.dweb.link/
-- [ipfs://QmZmRjJXcRL1HVbuFBLX23qqmQydzmqGsLmb94qrqTU9A7/](ipfs://QmZmRjJXcRL1HVbuFBLX23qqmQydzmqGsLmb94qrqTU9A7/)
+- https://bafybeiaajnnkurqabznrl32buiydbsym77nt2np3d5xv5mgskkd6euk3au.ipfs.dweb.link/
+- [ipfs://QmNMoMSDQVQwUdfxLqhLpM4bKgGMyEHU2LU2RfDFaRqACc/](ipfs://QmNMoMSDQVQwUdfxLqhLpM4bKgGMyEHU2LU2RfDFaRqACc/)
-## 5.115.0 (2025-10-24)
+## 5.117.0 (2025-11-05)
### Features
-* **web:** special case metamask dual vm connection flow (#24756) (#24789) b21eafd
+* **web:** add activity items to new mini portfolio (#24822) f0994c6
+* **web:** add infinite scroll for portfolio activity table (#24691) d71921f
+* **web:** add infinite scroll to minip on web activity (#23352) 78d6dcb
+* **web:** add NFT context menu (#24791) 10ed3e4
+* **web:** add report position option to context menus (#24840) b587dfc
+* **web:** add report position option to PDP (#24841) 97d0b94
+* **web:** add toast on report (#24717) 65c8cd6
+* **web:** add web entry points for token reporting (#24714) b83cb90
+* **web:** block timestamp countdown (#24632) 6a1da1d
+* **web:** dialog content container + spacing tweaks (#24239) 035026d
+* **web:** portfolio Overview tab (#24737) ed3f451
+* **web:** stub out new mini portfolio behind flag (#24821) 0602da3
+* **web:** toucan auction status (#24866) 0d9d56b
+* **web:** use shared token details hooks with unified 24hr change (#24726) 501d754
+
+
+### Bug Fixes
+
+* **web:** calculate fiat delta from API percentage for multi-chain tokens (#24728) 36f4c68
+* **web:** data reporting abilities fixes (#25130) 9246200
+* **web:** default to mainnet for limits flow (#24884) 666bdb0
+* **web:** fix issue with listpools endpoint returning an error (#25141) 62f132b
+* **web:** fix nested scrollbar overflowY issue (#24688) 60d39f3
+* **web:** Fix non-reactive prices on explore tab (#24766) 4539d99
+* **web:** Load tokens until scrollbar appears- PORT-359 (#23085) 5a480cf
+* **web:** log interface swap finalization results for flashblocks (#24869) b800fb3
+* **web:** portfolio polishes (#24944) 6d94d95
+* **web:** position card context menu fixes and cleanup (#24839) 5568c4d
+* **web:** prevent blank loading state in swap modal 5790e16
+* **web:** remove bad import in server side worker code (#24803) 048e2a0
+* **web:** Remove unnecessary button outline on Switch (#24697) 6216bb8
+* **web:** revert table scroll update (#25113) 9d12691
+
+
+### Continuous Integration
+
+* **web:** update sitemaps c5134d4
+
+
+### Code Refactoring
+
+* **web:** Consolidate swap and wrap callbacks into useSwapHandlers (#24219) b151db3
+* **web:** Consolidate swap and wrap handlers into unified interface (#24184) 672c7be
+
+
+### Styles
+
+* **web:** apply textured background to pool details page table variants (#25040) 8381ec0
+* **web:** fix portfolio page account header scroll animation (#24953) 13aef52
+* **web:** update activity tab filter to reflect designs (#24687) 68f73cb
+* **web:** update loading skeletons for NFT Cards (#24951) d556e00
+* **web:** update price chart styles (#24773) 5f231db
diff --git a/VERSION b/VERSION
index 885f8abd813..8eadb5ad7bf 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-web/5.115.0
\ No newline at end of file
+web/5.117.0
\ No newline at end of file
diff --git a/apps/api-self-serve/.eslintrc.js b/apps/api-self-serve/.eslintrc.js
new file mode 100644
index 00000000000..bc045be8575
--- /dev/null
+++ b/apps/api-self-serve/.eslintrc.js
@@ -0,0 +1,44 @@
+const restrictedGlobals = require('confusing-browser-globals')
+const rulesDirPlugin = require('eslint-plugin-rulesdir')
+rulesDirPlugin.RULES_DIR = '../../packages/uniswap/eslint_rules'
+
+module.exports = {
+ root: true,
+ extends: ['@uniswap/eslint-config/extension'],
+ plugins: ['rulesdir'],
+ ignorePatterns: [
+ 'node_modules',
+ '.react-router',
+ 'dist',
+ 'build',
+ '.eslintrc.js',
+ 'manifest.json',
+ '.nx',
+ 'vite.config.ts',
+ ],
+ parserOptions: {
+ project: 'tsconfig.eslint.json',
+ tsconfigRootDir: __dirname,
+ ecmaFeatures: {
+ jsx: true,
+ },
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ },
+ rules: {
+ 'rulesdir/i18n': 'error',
+ },
+ overrides: [
+ {
+ files: ['*.ts', '*.tsx'],
+ rules: {
+ 'no-relative-import-paths/no-relative-import-paths': [
+ 'error',
+ {
+ allowSameFolder: false,
+ },
+ ],
+ },
+ },
+ ],
+}
diff --git a/apps/api-self-serve/.gitignore b/apps/api-self-serve/.gitignore
new file mode 100644
index 00000000000..039ee62d21a
--- /dev/null
+++ b/apps/api-self-serve/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+.env
+/node_modules/
+
+# React Router
+/.react-router/
+/build/
diff --git a/apps/api-self-serve/README.md b/apps/api-self-serve/README.md
new file mode 100644
index 00000000000..25d1b31c061
--- /dev/null
+++ b/apps/api-self-serve/README.md
@@ -0,0 +1 @@
+# API Self Serve Portal
diff --git a/apps/api-self-serve/app/app.css b/apps/api-self-serve/app/app.css
new file mode 100644
index 00000000000..abd90f244db
--- /dev/null
+++ b/apps/api-self-serve/app/app.css
@@ -0,0 +1,170 @@
+@import "tailwindcss/preflight";
+@import "tailwindcss";
+@plugin "tailwindcss-animate";
+@tailwind utilities;
+@config "../tailwind.config.ts";
+
+@custom-variant dark (&:is(.dark *));
+
+@font-face {
+ font-family: "Basel Grotesk";
+ src: url("https://app.uniswap.org/fonts/Basel-Grotesk-Book.woff2")
+ format("woff2");
+ font-weight: 485;
+ font-style: normal;
+ font-display: swap;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+@font-face {
+ font-family: "Basel Grotesk";
+ src: url("https://app.uniswap.org/fonts/Basel-Grotesk-Medium.woff2")
+ format("woff2");
+ font-weight: 535;
+ font-style: normal;
+ font-display: swap;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+@layer base {
+ html, body {
+ @apply bg-background text-foreground font-basel;
+ }
+ * {
+ @apply border-border outline-ring/50;
+ }
+}
+
+/* Light mode is the default */
+:root {
+ color-scheme: light;
+ --font-basel: "Basel Grotesk";
+ /* Light mode shadows */
+ --shadow-short:
+ 0px 1px 6px 2px rgba(0, 0, 0, 0.03), 0px 1px 2px 0px rgba(0, 0, 0, 0.02);
+ --shadow-medium:
+ 0px 6px 12px -3px rgba(19, 19, 19, 0.04),
+ 0px 2px 5px -2px rgba(19, 19, 19, 0.03);
+ --shadow-large:
+ 0px 10px 20px -5px rgba(19, 19, 19, 0.05),
+ 0px 4px 12px -3px rgba(19, 19, 19, 0.04);
+ /*shadcn*/
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+/* Dark mode applies when .dark class is present */
+.dark {
+ color-scheme: dark;
+ /* Dark mode shadows */
+ --shadow-short:
+ 0px 1px 3px 0px rgba(0, 0, 0, 0.12), 0px 1px 2px 0px rgba(0, 0, 0, 0.24);
+ --shadow-medium:
+ 0px 10px 15px -3px rgba(19, 19, 19, 0.54),
+ 0px 4px 6px -2px rgba(19, 19, 19, 0.4);
+ --shadow-large:
+ 0px 16px 24px -6px rgba(0, 0, 0, 0.6), 0px 8px 12px -4px rgba(0, 0, 0, 0.48);
+ /*shadcn*/
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+/*shadcn*/
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
diff --git a/apps/api-self-serve/app/lib/utils.ts b/apps/api-self-serve/app/lib/utils.ts
new file mode 100644
index 00000000000..d32b0fe652e
--- /dev/null
+++ b/apps/api-self-serve/app/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from 'clsx'
+import { twMerge } from 'tailwind-merge'
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/apps/api-self-serve/app/root.tsx b/apps/api-self-serve/app/root.tsx
new file mode 100644
index 00000000000..9b3208dc36b
--- /dev/null
+++ b/apps/api-self-serve/app/root.tsx
@@ -0,0 +1,53 @@
+import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router'
+import type { Route } from './+types/root'
+import './app.css'
+
+export const links: Route.LinksFunction = () => []
+
+export function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+export default function App() {
+ return
+}
+
+export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
+ let message = 'Oops!'
+ let details = 'An unexpected error occurred.'
+ let stack: string | undefined
+
+ if (isRouteErrorResponse(error)) {
+ message = error.status === 404 ? '404' : 'Error'
+ details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details
+ } else if (import.meta.env.DEV && error && error instanceof Error) {
+ details = error.message
+ stack = error.stack
+ }
+
+ return (
+
+ {message}
+ {details}
+ {stack && (
+
+ {stack}
+
+ )}
+
+ )
+}
diff --git a/apps/api-self-serve/app/routes.ts b/apps/api-self-serve/app/routes.ts
new file mode 100644
index 00000000000..10d7044bf99
--- /dev/null
+++ b/apps/api-self-serve/app/routes.ts
@@ -0,0 +1,3 @@
+import { index, type RouteConfig } from '@react-router/dev/routes'
+
+export default [index('routes/home.tsx')] satisfies RouteConfig
diff --git a/apps/api-self-serve/app/routes/home.tsx b/apps/api-self-serve/app/routes/home.tsx
new file mode 100644
index 00000000000..c6316aae628
--- /dev/null
+++ b/apps/api-self-serve/app/routes/home.tsx
@@ -0,0 +1,11 @@
+import { Welcome } from '~/welcome/welcome'
+import type { Route } from './+types/home'
+
+// biome-ignore lint/correctness/noEmptyPattern: this will likely be updated. this is ootb from the create react router app tool.
+export function meta({}: Route.MetaArgs) {
+ return [{ title: 'New React Router App' }, { name: 'description', content: 'Welcome to React Router!' }]
+}
+
+export default function Home() {
+ return
+}
diff --git a/apps/api-self-serve/app/welcome/logo-dark.svg b/apps/api-self-serve/app/welcome/logo-dark.svg
new file mode 100644
index 00000000000..dd820289447
--- /dev/null
+++ b/apps/api-self-serve/app/welcome/logo-dark.svg
@@ -0,0 +1,23 @@
+
diff --git a/apps/api-self-serve/app/welcome/logo-light.svg b/apps/api-self-serve/app/welcome/logo-light.svg
new file mode 100644
index 00000000000..73284929d36
--- /dev/null
+++ b/apps/api-self-serve/app/welcome/logo-light.svg
@@ -0,0 +1,23 @@
+
diff --git a/apps/api-self-serve/app/welcome/welcome.tsx b/apps/api-self-serve/app/welcome/welcome.tsx
new file mode 100644
index 00000000000..92555f1574a
--- /dev/null
+++ b/apps/api-self-serve/app/welcome/welcome.tsx
@@ -0,0 +1,80 @@
+/** biome-ignore-all lint/correctness/noRestrictedElements: this will be removed, it's default template */
+import logoDark from './logo-dark.svg'
+import logoLight from './logo-light.svg'
+
+export function Welcome() {
+ return (
+
+
+
+
+

+

+
+
+
+
+
+ )
+}
+
+const resources = [
+ {
+ href: 'https://reactrouter.com/docs',
+ text: 'React Router Docs',
+ icon: (
+
+ ),
+ },
+ {
+ href: 'https://rmx.as/discord',
+ text: 'Join Discord',
+ icon: (
+
+ ),
+ },
+]
diff --git a/apps/api-self-serve/components.json b/apps/api-self-serve/components.json
new file mode 100644
index 00000000000..d0a566ccc19
--- /dev/null
+++ b/apps/api-self-serve/components.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/app.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "aliases": {
+ "components": "~/components",
+ "utils": "~/lib/utils",
+ "ui": "~/components/ui",
+ "lib": "~/lib",
+ "hooks": "~/hooks"
+ },
+ "registries": {}
+}
diff --git a/apps/api-self-serve/package.json b/apps/api-self-serve/package.json
new file mode 100644
index 00000000000..2c38ca368e3
--- /dev/null
+++ b/apps/api-self-serve/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "api-self-serve",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "react-router build",
+ "dev": "react-router dev",
+ "start": "react-router-serve ./build/server/index.js",
+ "typecheck": "react-router typegen && tsc"
+ },
+ "dependencies": {
+ "@react-router/node": "7.6.3",
+ "@react-router/serve": "7.6.3",
+ "class-variance-authority": "0.7.1",
+ "clsx": "2.1.1",
+ "isbot": "5.1.31",
+ "lucide-react": "0.548.0",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "react-router": "7.6.3",
+ "tailwind-merge": "3.3.1",
+ "tailwindcss-animate": "1.0.7"
+ },
+ "devDependencies": {
+ "@react-router/dev": "7.6.3",
+ "@tailwindcss/vite": "4.1.13",
+ "@types/node": "22.13.1",
+ "@types/react": "18.3.18",
+ "@uniswap/eslint-config": "workspace:^",
+ "eslint": "8.44.0",
+ "tailwindcss": "4.1.16",
+ "typescript": "5.3.3",
+ "vite": "npm:rolldown-vite@7.0.10",
+ "vite-tsconfig-paths": "5.1.4"
+ }
+}
diff --git a/apps/api-self-serve/react-router.config.ts b/apps/api-self-serve/react-router.config.ts
new file mode 100644
index 00000000000..6ff16f91779
--- /dev/null
+++ b/apps/api-self-serve/react-router.config.ts
@@ -0,0 +1,7 @@
+import type { Config } from "@react-router/dev/config";
+
+export default {
+ // Config options...
+ // Server-side render by default, to enable SPA mode set this to `false`
+ ssr: true,
+} satisfies Config;
diff --git a/apps/api-self-serve/tailwind.config.ts b/apps/api-self-serve/tailwind.config.ts
new file mode 100644
index 00000000000..8135de497d5
--- /dev/null
+++ b/apps/api-self-serve/tailwind.config.ts
@@ -0,0 +1,430 @@
+import type { Config } from 'tailwindcss'
+import tailwindAnimate from 'tailwindcss-animate'
+
+export default {
+ darkMode: 'class',
+ content: [
+ './pages/**/*.{js,ts,jsx,tsx,mdx}',
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
+ './registry/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ basel: ['var(--font-basel)', 'sans-serif'],
+ baselBook: [
+ 'Basel Grotesk Book',
+ '-apple-system',
+ 'system-ui',
+ 'BlinkMacSystemFont',
+ 'Segoe UI',
+ 'Roboto',
+ 'Helvetica',
+ 'Arial',
+ 'sans-serif',
+ ],
+ baselMedium: [
+ 'Basel Grotesk Medium',
+ '-apple-system',
+ 'system-ui',
+ 'BlinkMacSystemFont',
+ 'Segoe UI',
+ 'Roboto',
+ 'Helvetica',
+ 'Arial',
+ 'sans-serif',
+ ],
+ mono: ['InputMono-Regular', 'monospace'],
+ },
+ fontWeight: {
+ book: '485',
+ medium: '535',
+ },
+ screens: {
+ xxs: '360px',
+ xs: '380px',
+ sm: '450px',
+ md: '640px',
+ lg: '768px',
+ xl: '1024px',
+ xxl: '1280px',
+ xxxl: '1536px',
+ 'h-short': { raw: '(max-height: 736px)' },
+ 'h-mid': { raw: '(max-height: 800px)' },
+ },
+ fontSize: {
+ // Headings
+ 'heading-1': [
+ '52px',
+ {
+ lineHeight: '60px',
+ letterSpacing: '-0.02em',
+ fontWeight: '485',
+ },
+ ],
+ 'heading-2': [
+ '36px',
+ {
+ lineHeight: '44px',
+ letterSpacing: '-0.01em',
+ fontWeight: '485',
+ },
+ ],
+ 'heading-3': [
+ '24px',
+ {
+ lineHeight: '32px',
+ letterSpacing: '-0.005em',
+ fontWeight: '485',
+ },
+ ],
+ // Subheadings
+ 'subheading-1': [
+ '18px',
+ {
+ lineHeight: '24px',
+ fontWeight: '485',
+ },
+ ],
+ 'subheading-2': [
+ '16px',
+ {
+ lineHeight: '24px',
+ fontWeight: '485',
+ },
+ ],
+ // Body
+ 'body-1': [
+ '18px',
+ {
+ lineHeight: '24px',
+ fontWeight: '485',
+ },
+ ],
+ 'body-2': [
+ '16px',
+ {
+ lineHeight: '24px',
+ fontWeight: '485',
+ },
+ ],
+ 'body-3': [
+ '14px',
+ {
+ lineHeight: '20px',
+ fontWeight: '485',
+ },
+ ],
+ 'body-4': [
+ '12px',
+ {
+ lineHeight: '16px',
+ fontWeight: '485',
+ },
+ ],
+ // Button Labels
+ 'button-1': [
+ '18px',
+ {
+ lineHeight: '24px',
+ fontWeight: '535',
+ },
+ ],
+ 'button-2': [
+ '16px',
+ {
+ lineHeight: '24px',
+ fontWeight: '535',
+ },
+ ],
+ 'button-3': [
+ '14px',
+ {
+ lineHeight: '20px',
+ fontWeight: '535',
+ },
+ ],
+ 'button-4': [
+ '12px',
+ {
+ lineHeight: '16px',
+ fontWeight: '535',
+ },
+ ],
+ },
+ colors: {
+ // Base colors
+ white: '#FFFFFF',
+ black: '#000000',
+
+ // Semantic colors for light theme
+ background: {
+ DEFAULT: '#FFFFFF', // colors.white
+ dark: '#000000', // colors.black
+ },
+
+ // Neutral colors with semantic naming
+ neutral1: {
+ DEFAULT: '#222222', // neutral1_light
+ dark: '#FFFFFF', // neutral1_dark
+ },
+ neutral2: {
+ DEFAULT: '#7D7D7D', // neutral2_light
+ dark: '#9B9B9B', // neutral2_dark
+ },
+ neutral3: {
+ DEFAULT: '#CECECE', // neutral3_light
+ dark: '#5E5E5E', // neutral3_dark
+ },
+
+ // Surface colors with semantic naming
+ surface1: {
+ DEFAULT: '#FFFFFF', // surface1_light
+ dark: '#131313', // surface1_dark
+ hovered: {
+ DEFAULT: '#F5F5F5', // surface1_hovered_light
+ dark: '#181818', // surface1_hovered_dark
+ },
+ },
+ surface2: {
+ DEFAULT: '#F9F9F9', // surface2_light
+ dark: '#1B1B1B', // surface2_dark
+ hovered: {
+ DEFAULT: '#F2F2F2', // surface2_hovered_light
+ dark: '#242424', // surface2_hovered_dark
+ },
+ },
+ surface3: {
+ DEFAULT: '#22222212', // surface3_light
+ dark: '#FFFFFF12', // surface3_dark
+ hovered: {
+ DEFAULT: 'rgba(34, 34, 34, 0.12)', // surface3_hovered_light
+ dark: 'rgba(255, 255, 255, 0.16)', // surface3_hovered_dark
+ },
+ },
+ surface4: {
+ DEFAULT: '#FFFFFF64', // surface4_light
+ dark: '#FFFFFF20', // surface4_dark
+ },
+ surface5: {
+ DEFAULT: '#00000004', // surface5_light
+ dark: '#00000004', // surface5_dark
+ },
+
+ // Accent colors with semantic naming
+ accent1: {
+ DEFAULT: '#FC72FF', // accent1_light
+ dark: '#FC72FF', // accent1_dark
+ },
+ accent2: {
+ DEFAULT: '#FFEFFF', // accent2_light
+ dark: '#311C31', // accent2_dark
+ },
+ accent3: {
+ DEFAULT: '#4C82FB', // accent3_light
+ dark: '#4C82FB', // accent3_dark
+ },
+
+ // Token colors
+ token0: {
+ DEFAULT: '#FC72FF', // token0 in light theme
+ dark: '#FC72FF', // token0 in dark theme
+ },
+ token1: {
+ DEFAULT: '#4C82FB', // token1 in light theme
+ dark: '#4C82FB', // token1 in dark theme
+ },
+
+ // Status colors
+ success: {
+ DEFAULT: '#40B66B', // success
+ },
+ critical: {
+ DEFAULT: '#FF5F52', // critical
+ secondary: {
+ DEFAULT: '#FFF2F1', // critical2_light
+ dark: '#2E0805', // critical2_dark
+ },
+ },
+ warning: {
+ DEFAULT: '#EEB317', // gold200
+ },
+
+ // Network colors
+ network: {
+ ethereum: '#627EEA',
+ optimism: '#FF0420',
+ polygon: '#A457FF',
+ arbitrum: '#28A0F0',
+ bsc: '#F0B90B',
+ base: '#0052FF',
+ blast: '#FCFC03',
+ },
+
+ // Gray palette
+ gray: {
+ 50: '#F5F6FC',
+ 100: '#E8ECFB',
+ 150: '#D2D9EE',
+ 200: '#B8C0DC',
+ 250: '#A6AFCA',
+ 300: '#98A1C0',
+ 350: '#888FAB',
+ 400: '#7780A0',
+ 450: '#6B7594',
+ 500: '#5D6785',
+ 550: '#505A78',
+ 600: '#404A67',
+ 650: '#333D59',
+ 700: '#293249',
+ 750: '#1B2236',
+ 800: '#131A2A',
+ 850: '#0E1524',
+ 900: '#0D111C',
+ 950: '#080B11',
+ },
+
+ // Pink palette
+ pink: {
+ 50: '#F9ECF1',
+ 100: '#FFD9E4',
+ 200: '#FBA4C0',
+ 300: '#FF6FA3',
+ 400: '#FB118E',
+ 500: '#C41969',
+ 600: '#8C0F49',
+ 700: '#55072A',
+ 800: '#350318',
+ 900: '#2B000B',
+ vibrant: '#F50DB4',
+ base: '#FC74FE',
+ },
+
+ // Red palette
+ red: {
+ 50: '#FAECEA',
+ 100: '#FED5CF',
+ 200: '#FEA79B',
+ 300: '#FD766B',
+ 400: '#FA2B39',
+ 500: '#C4292F',
+ 600: '#891E20',
+ 700: '#530F0F',
+ 800: '#380A03',
+ 900: '#240800',
+ vibrant: '#F14544',
+ },
+
+ // Additional color palettes
+ yellow: {
+ 50: '#F6F2D5',
+ 100: '#DBBC19',
+ 200: '#DBBC19',
+ 300: '#BB9F13',
+ 400: '#A08116',
+ 500: '#866311',
+ 600: '#5D4204',
+ 700: '#3E2B04',
+ 800: '#231902',
+ 900: '#180F02',
+ vibrant: '#FAF40A',
+ },
+
+ green: {
+ 50: '#E3F3E6',
+ 100: '#BFEECA',
+ 200: '#76D191',
+ 300: '#40B66B',
+ 400: '#209853',
+ 500: '#0B783E',
+ 600: '#0C522A',
+ 700: '#053117',
+ 800: '#091F10',
+ 900: '#09130B',
+ vibrant: '#5CFE9D',
+ },
+
+ blue: {
+ 50: '#EDEFF8',
+ 100: '#DEE1FF',
+ 200: '#ADBCFF',
+ 300: '#869EFF',
+ 400: '#4C82FB',
+ 500: '#1267D6',
+ 600: '#1D4294',
+ 700: '#09265E',
+ 800: '#0B193F',
+ 900: '#040E34',
+ vibrant: '#587BFF',
+ },
+
+ gold: {
+ 200: '#EEB317',
+ 400: '#B17900',
+ vibrant: '#FEB239',
+ },
+
+ magenta: {
+ 300: '#FD82FF',
+ vibrant: '#FC72FF',
+ },
+
+ purple: {
+ 300: '#8440F2',
+ 900: '#1C0337',
+ vibrant: '#6100FF',
+ },
+
+ // Legacy colors mapping (for compatibility)
+ border: '#F9F9F9',
+ input: '#F9F9F9',
+ ring: '#222222',
+ foreground: '#222222',
+ card: {
+ DEFAULT: '#FFFFFF',
+ foreground: '#222222',
+ },
+ popover: {
+ DEFAULT: '#FFFFFF',
+ foreground: '#222222',
+ },
+ primary: {
+ DEFAULT: '#222222',
+ foreground: '#F9F9F9',
+ },
+ secondary: {
+ DEFAULT: '#F9F9F9',
+ foreground: '#222222',
+ },
+ muted: {
+ DEFAULT: '#F9F9F9',
+ foreground: '#7D7D7D',
+ },
+ destructive: {
+ DEFAULT: '#FF5F52',
+ foreground: '#F9F9F9',
+ },
+ scrim: 'rgba(0, 0, 0, 0.60)',
+ },
+ borderRadius: {
+ none: '0px',
+ rounded4: '4px',
+ rounded6: '6px',
+ rounded8: '8px',
+ rounded12: '12px',
+ rounded16: '16px',
+ rounded20: '20px',
+ rounded24: '24px',
+ rounded32: '32px',
+ roundedFull: '999999px',
+ },
+ boxShadow: {
+ short: 'var(--shadow-short)',
+ medium: 'var(--shadow-medium)',
+ large: 'var(--shadow-large)',
+ },
+ },
+ },
+ plugins: [tailwindAnimate],
+} satisfies Config
diff --git a/apps/api-self-serve/tsconfig.eslint.json b/apps/api-self-serve/tsconfig.eslint.json
new file mode 100644
index 00000000000..0af7bb26f3f
--- /dev/null
+++ b/apps/api-self-serve/tsconfig.eslint.json
@@ -0,0 +1,5 @@
+// same as tsconfig.json but without references which caused performance issues with typescript-eslint
+{
+ "extends": "./tsconfig.json",
+ "references": []
+}
diff --git a/apps/api-self-serve/tsconfig.json b/apps/api-self-serve/tsconfig.json
new file mode 100644
index 00000000000..a0d80e99890
--- /dev/null
+++ b/apps/api-self-serve/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
+ "exclude": ["tools/**/*"],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "types": ["node", "vite/client"],
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "rootDirs": [".", "./.react-router/types"],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+ "esModuleInterop": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "strict": true
+ }
+}
diff --git a/apps/api-self-serve/vite.config.ts b/apps/api-self-serve/vite.config.ts
new file mode 100644
index 00000000000..e0925ec7ad1
--- /dev/null
+++ b/apps/api-self-serve/vite.config.ts
@@ -0,0 +1,15 @@
+import { reactRouter } from '@react-router/dev/vite'
+import tailwindcss from '@tailwindcss/vite'
+import { defineConfig } from 'vite'
+import tsconfigPaths from 'vite-tsconfig-paths'
+
+export default defineConfig({
+ plugins: [
+ tailwindcss(),
+ reactRouter(),
+ tsconfigPaths({
+ // ignores tsconfig files in Nx generator template directories
+ skip: (dir) => dir.includes('files'),
+ }),
+ ],
+})
diff --git a/apps/extension/package.json b/apps/extension/package.json
index 9ac9b2af0df..a161e2523e1 100644
--- a/apps/extension/package.json
+++ b/apps/extension/package.json
@@ -17,11 +17,14 @@
"@types/uuid": "9.0.1",
"@uniswap/analytics-events": "2.43.0",
"@uniswap/client-embeddedwallet": "0.0.16",
+ "@uniswap/sdk-core": "7.7.2",
"@uniswap/uniswapx-sdk": "3.0.0-beta.7",
"@uniswap/universal-router-sdk": "4.19.5",
"@uniswap/v3-sdk": "3.25.2",
"@uniswap/v4-sdk": "1.21.2",
"@universe/api": "workspace:^",
+ "@universe/gating": "workspace:^",
+ "@universe/sessions": "workspace:^",
"@wxt-dev/module-react": "1.1.3",
"confusing-browser-globals": "1.0.11",
"dotenv-webpack": "8.0.1",
@@ -96,9 +99,9 @@
"swc-loader": "0.2.6",
"tamagui-loader": "1.125.17",
"typescript": "5.3.3",
- "webpack": "5.90.0",
+ "webpack": "5.94.0",
"webpack-cli": "5.1.4",
- "webpack-dev-server": "4.15.1"
+ "webpack-dev-server": "5.2.1"
},
"private": true,
"scripts": {
diff --git a/apps/extension/src/app/apollo.tsx b/apps/extension/src/app/apollo.tsx
index 41416c70604..e006c3ed06a 100644
--- a/apps/extension/src/app/apollo.tsx
+++ b/apps/extension/src/app/apollo.tsx
@@ -1,8 +1,7 @@
import { ApolloProvider } from '@apollo/client/react/context'
-import { PropsWithChildren, useEffect } from 'react'
+import { PropsWithChildren } from 'react'
import { localStorage } from 'redux-persist-webextension-storage'
import { getReduxStore } from 'src/store/store'
-import { initializePortfolioQueryOverrides } from 'uniswap/src/data/rest/portfolioBalanceOverrides'
// biome-ignore lint/style/noRestrictedImports: Direct wallet import needed for Apollo client setup in extension context
import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient'
@@ -16,14 +15,9 @@ export function GraphqlProvider({ children }: PropsWithChildren): JSX.E
reduxStore: getReduxStore(),
})
- useEffect(() => {
- if (apolloClient) {
- initializePortfolioQueryOverrides({ store: getReduxStore(), apolloClient })
- }
- }, [apolloClient])
-
if (!apolloClient) {
return <>>
}
+
return {children}
}
diff --git a/apps/extension/src/app/components/AutoLockProvider.test.tsx b/apps/extension/src/app/components/AutoLockProvider.test.tsx
index 0b082e9cc91..5e3f69795e7 100644
--- a/apps/extension/src/app/components/AutoLockProvider.test.tsx
+++ b/apps/extension/src/app/components/AutoLockProvider.test.tsx
@@ -1,252 +1,337 @@
-import { waitFor } from '@testing-library/react'
import React from 'react'
import { AutoLockProvider } from 'src/app/components/AutoLockProvider'
import { render } from 'src/test/test-utils'
import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants'
import { Language } from 'uniswap/src/features/language/constants'
import { DeviceAccessTimeout } from 'uniswap/src/features/settings/constants'
-import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
-import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logger } from 'utilities/src/logger/logger'
-import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
// Mock dependencies
jest.mock('uniswap/src/extension/useIsChromeWindowFocused')
-jest.mock('uniswap/src/features/telemetry/send')
jest.mock('utilities/src/logger/logger')
-jest.mock('wallet/src/features/wallet/Keyring/Keyring')
-
-// Import mocked modules with proper typing
-import { useIsChromeWindowFocusedWithTimeout } from 'uniswap/src/extension/useIsChromeWindowFocused'
-
-const mockUseIsChromeWindowFocusedWithTimeout = useIsChromeWindowFocusedWithTimeout as jest.MockedFunction<
- typeof useIsChromeWindowFocusedWithTimeout
->
-const mockSendAnalyticsEvent = sendAnalyticsEvent as jest.MockedFunction
-const mockLogger = logger as jest.Mocked
-const mockKeyring = Keyring as jest.Mocked
-
-// Helper functions for common test patterns
-const renderAutoLockProvider = (
- deviceAccessTimeout: DeviceAccessTimeout,
- children: React.ReactNode = Test
,
-) => {
- return render({children}, {
- preloadedState: {
- userSettings: {
- currentLanguage: Language.English,
- currentCurrency: FiatCurrency.UnitedStatesDollar,
- hideSmallBalances: true,
- hideSpamTokens: true,
- hapticsEnabled: true,
- deviceAccessTimeout,
- },
- },
- })
+jest.mock('src/app/hooks/useIsWalletUnlocked', () => ({
+ useIsWalletUnlocked: jest.fn(),
+ isWalletUnlocked: null,
+}))
+
+// Import mocked modules
+import { useIsWalletUnlocked } from 'src/app/hooks/useIsWalletUnlocked'
+import { useIsChromeWindowFocused } from 'uniswap/src/extension/useIsChromeWindowFocused'
+
+const mockUseIsChromeWindowFocused = jest.mocked(useIsChromeWindowFocused)
+const mockUseIsWalletUnlocked = jest.mocked(useIsWalletUnlocked)
+const mockLogger = jest.mocked(logger)
+
+// Mock chrome.alarms API
+const mockChromeAlarms = {
+ create: jest.fn(),
+ clear: jest.fn(),
}
-const simulateFocusChange = (component: ReturnType) => (fromFocused: boolean, toFocused: boolean) => {
- mockUseIsChromeWindowFocusedWithTimeout.mockReturnValue(fromFocused)
- const { rerender } = component
+global.chrome = {
+ ...global.chrome,
+ alarms: mockChromeAlarms as unknown as typeof chrome.alarms,
+}
- mockUseIsChromeWindowFocusedWithTimeout.mockReturnValue(toFocused)
- rerender(
+// Helper function
+const renderAutoLockProvider = (deviceAccessTimeout: DeviceAccessTimeout) => {
+ return render(
Test
,
+ {
+ preloadedState: {
+ userSettings: {
+ currentLanguage: Language.English,
+ currentCurrency: FiatCurrency.UnitedStatesDollar,
+ hideSmallBalances: true,
+ hideSpamTokens: true,
+ hapticsEnabled: true,
+ deviceAccessTimeout,
+ },
+ },
+ },
)
}
-const expectWalletLockCalled = async (times: number = 1) => {
- await waitFor(() => {
- expect(mockKeyring.lock).toHaveBeenCalledTimes(times)
- })
-}
+const simulateFocusChange = (component: ReturnType) => (fromFocused: boolean, toFocused: boolean) => {
+ mockUseIsChromeWindowFocused.mockReturnValue(fromFocused)
+ const { rerender } = component
-const expectAnalyticsEventCalled = async () => {
- await waitFor(() => {
- expect(mockSendAnalyticsEvent).toHaveBeenCalledWith(ExtensionEventName.ChangeLockedState, {
- locked: true,
- location: 'background',
- })
- })
+ mockUseIsChromeWindowFocused.mockReturnValue(toFocused)
+ rerender()
}
describe('AutoLockProvider', () => {
beforeEach(() => {
jest.clearAllMocks()
- mockUseIsChromeWindowFocusedWithTimeout.mockReturnValue(true)
- mockKeyring.lock.mockResolvedValue(true)
- mockSendAnalyticsEvent.mockImplementation(() => {})
+ mockUseIsChromeWindowFocused.mockReturnValue(true)
+ mockUseIsWalletUnlocked.mockReturnValue(true)
mockLogger.debug.mockImplementation(() => {})
mockLogger.error.mockImplementation(() => {})
+ mockChromeAlarms.create.mockImplementation(() => {})
+ mockChromeAlarms.clear.mockImplementation(() => {})
})
- describe('window focus monitoring', () => {
- const testTimeoutValues = [
- { timeout: DeviceAccessTimeout.FiveMinutes, expectedMs: 5 * 60 * 1000, description: '5 minutes' },
- { timeout: DeviceAccessTimeout.ThirtyMinutes, expectedMs: 30 * 60 * 1000, description: '30 minutes' },
- { timeout: DeviceAccessTimeout.OneHour, expectedMs: 60 * 60 * 1000, description: '1 hour' },
- { timeout: DeviceAccessTimeout.TwentyFourHours, expectedMs: 24 * 60 * 60 * 1000, description: '24 hours' },
- {
- timeout: DeviceAccessTimeout.Never,
- expectedMs: Number.MAX_SAFE_INTEGER,
- description: 'Never (MAX_SAFE_INTEGER)',
- },
- ]
+ describe('mount behavior', () => {
+ it('should clear alarm on mount', () => {
+ renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- testTimeoutValues.forEach(({ timeout, expectedMs, description }) => {
- it(`should call useIsChromeWindowFocusedWithTimeout with correct timeout for ${description}`, () => {
- renderAutoLockProvider(timeout)
- expect(mockUseIsChromeWindowFocusedWithTimeout).toHaveBeenCalledWith(expectedMs)
- })
+ expect(mockChromeAlarms.clear).toHaveBeenCalledWith('AutoLockAlarm')
+ })
+
+ it('should always render children', () => {
+ const { container } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ expect(container.textContent).toBe('Test')
})
})
- describe('wallet locking behavior', () => {
- it('should lock wallet when window loses focus and timeout is configured', async () => {
+ describe('unmount behavior', () => {
+ it('should not schedule alarm on unmount (handled by background port disconnect)', () => {
+ const { unmount } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+
+ unmount()
+
+ // Unmount no longer schedules alarm - this is handled by background script
+ expect(mockChromeAlarms.create).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('focus change behavior (while sidebar is open)', () => {
+ it('should schedule alarm when window loses focus and wallet is unlocked', () => {
const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- expect(mockKeyring.lock).not.toHaveBeenCalled()
+ mockChromeAlarms.create.mockClear() // Clear the mount call
simulateFocusChange(component)(true, false)
- await expectWalletLockCalled()
+
+ expect(mockChromeAlarms.create).toHaveBeenCalledWith('AutoLockAlarm', {
+ delayInMinutes: 5,
+ })
})
- it('should not lock wallet when window is focused', () => {
- mockUseIsChromeWindowFocusedWithTimeout.mockReturnValue(true)
- renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- expect(mockKeyring.lock).not.toHaveBeenCalled()
+ it('should clear alarm when window regains focus', () => {
+ const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ mockChromeAlarms.clear.mockClear() // Clear the mount call
+
+ // First lose focus (creates alarm)
+ simulateFocusChange(component)(true, false)
+ expect(mockChromeAlarms.create).toHaveBeenCalled()
+
+ // Then regain focus
+ simulateFocusChange(component)(false, true)
+
+ expect(mockChromeAlarms.clear).toHaveBeenCalledWith('AutoLockAlarm')
})
- it('should not lock wallet when timeout is set to Never', () => {
- mockUseIsChromeWindowFocusedWithTimeout.mockReturnValue(false)
- renderAutoLockProvider(DeviceAccessTimeout.Never)
- expect(mockKeyring.lock).not.toHaveBeenCalled()
+ it('should not schedule alarm when window loses focus and wallet is locked', () => {
+ mockUseIsWalletUnlocked.mockReturnValue(false)
+ const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ mockChromeAlarms.create.mockClear() // Clear the mount call
+
+ simulateFocusChange(component)(true, false)
+
+ expect(mockChromeAlarms.create).not.toHaveBeenCalled()
})
- it('should send analytics event when locking wallet', async () => {
- const component = renderAutoLockProvider(DeviceAccessTimeout.ThirtyMinutes)
+ it('should not schedule alarm when window loses focus and timeout is Never', () => {
+ const component = renderAutoLockProvider(DeviceAccessTimeout.Never)
+ mockChromeAlarms.create.mockClear() // Clear the mount call
+
simulateFocusChange(component)(true, false)
- await expectAnalyticsEventCalled()
- expect(mockSendAnalyticsEvent).toHaveBeenCalledTimes(1)
+
+ expect(mockChromeAlarms.create).not.toHaveBeenCalled()
})
})
- describe('error handling', () => {
- it('should handle Keyring.lock() errors gracefully', async () => {
- const lockError = new Error('Failed to lock keyring')
- mockKeyring.lock.mockRejectedValue(lockError)
+ describe('wallet state changes', () => {
+ it('should clear alarm when wallet becomes locked', () => {
+ mockUseIsWalletUnlocked.mockReturnValue(true)
+ const { rerender } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- simulateFocusChange(component)(true, false)
+ // Clear the initial mount call
+ mockChromeAlarms.clear.mockClear()
- await waitFor(() => {
- expect(mockLogger.error).toHaveBeenCalledWith(lockError, {
- tags: {
- file: 'AutoLockProvider.tsx',
- function: 'lockWallet',
- },
- })
- })
+ // Wallet becomes locked
+ mockUseIsWalletUnlocked.mockReturnValue(false)
+ rerender()
+
+ expect(mockChromeAlarms.clear).toHaveBeenCalledWith('AutoLockAlarm')
})
- it('should not send analytics event when lock fails', async () => {
- const lockError = new Error('Failed to lock keyring')
- mockKeyring.lock.mockRejectedValue(lockError)
+ it('should clear alarm when wallet becomes unlocked', () => {
+ mockUseIsWalletUnlocked.mockReturnValue(false)
+ const { rerender } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- simulateFocusChange(component)(true, false)
+ // Clear the initial mount call
+ mockChromeAlarms.clear.mockClear()
- await waitFor(() => {
- expect(mockLogger.error).toHaveBeenCalledWith(lockError, {
- tags: {
- file: 'AutoLockProvider.tsx',
- function: 'lockWallet',
- },
- })
- })
+ // Wallet becomes unlocked
+ mockUseIsWalletUnlocked.mockReturnValue(true)
+ rerender()
- expect(mockSendAnalyticsEvent).not.toHaveBeenCalled()
- })
-
- it('should handle undefined deviceAccessTimeout gracefully', () => {
- render(
-
- Test
- ,
- {
- preloadedState: {
- userSettings: {
- currentLanguage: Language.English,
- currentCurrency: FiatCurrency.UnitedStatesDollar,
- hideSmallBalances: true,
- hideSpamTokens: true,
- hapticsEnabled: true,
- deviceAccessTimeout: undefined as any,
- },
- },
- },
- )
+ expect(mockChromeAlarms.clear).toHaveBeenCalledWith('AutoLockAlarm')
+ })
+
+ it('should not clear alarm on initial render (only mount clear)', () => {
+ mockUseIsWalletUnlocked.mockReturnValue(true)
+ renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+
+ // Only the mount clear should have been called
+ expect(mockChromeAlarms.clear).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('combined scenarios', () => {
+ it('should handle mount -> unmount -> remount correctly', () => {
+ // First mount
+ const { unmount } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ expect(mockChromeAlarms.clear).toHaveBeenCalledTimes(1)
+
+ // Unmount (alarm scheduling now handled in background)
+ unmount()
+
+ // Second mount (clears alarm)
+ renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ expect(mockChromeAlarms.clear).toHaveBeenCalledTimes(2)
+ })
+
+ it('should handle wallet unlock during mounted state', () => {
+ mockUseIsWalletUnlocked.mockReturnValue(false)
+ const { rerender } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+
+ mockChromeAlarms.clear.mockClear()
+
+ // Wallet unlocks while mounted
+ mockUseIsWalletUnlocked.mockReturnValue(true)
+ rerender()
+
+ // Should clear alarm due to wallet state change
+ expect(mockChromeAlarms.clear).toHaveBeenCalled()
+ })
- expect(mockUseIsChromeWindowFocusedWithTimeout).toHaveBeenCalledWith(30 * 60 * 1000)
+ it('should handle wallet lock during mounted state', () => {
+ mockUseIsWalletUnlocked.mockReturnValue(true)
+ const { rerender } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+
+ mockChromeAlarms.clear.mockClear()
+
+ // Wallet locks while mounted
+ mockUseIsWalletUnlocked.mockReturnValue(false)
+ rerender()
+
+ // Should clear alarm due to wallet state change
+ expect(mockChromeAlarms.clear).toHaveBeenCalled()
})
})
- describe('focus state changes', () => {
- it('should react to focus state changes from focused to unfocused', async () => {
+ describe('error handling', () => {
+ it('should handle chrome.alarms.create errors gracefully', () => {
+ const error = new Error('Permission denied')
+ mockChromeAlarms.create.mockImplementationOnce(() => {
+ throw error
+ })
+
const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- expect(mockKeyring.lock).not.toHaveBeenCalled()
+ mockChromeAlarms.create.mockClear()
- simulateFocusChange(component)(true, false)
- await expectWalletLockCalled()
+ // This should not throw, error should be logged
+ expect(() => {
+ simulateFocusChange(component)(true, false)
+ }).not.toThrow()
+
+ expect(mockLogger.error).toHaveBeenCalledWith(error, {
+ tags: { file: 'AutoLockProvider', function: 'createAutoLockAlarm' },
+ extra: { delayInMinutes: 5 },
+ })
+ })
+
+ it('should handle chrome.alarms.clear errors gracefully', () => {
+ const error = new Error('Permission denied')
+ mockChromeAlarms.clear.mockImplementationOnce(() => {
+ throw error
+ })
+
+ // This should not throw, error should be logged
+ expect(() => {
+ renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ }).not.toThrow()
+
+ expect(mockLogger.error).toHaveBeenCalledWith(error, {
+ tags: { file: 'AutoLockProvider', function: 'clearAutoLockAlarm' },
+ extra: { reason: 'Cleared auto-lock alarm (sidebar opened)' },
+ })
})
- it('should not trigger multiple locks when already unfocused', async () => {
+ it('should continue to function after chrome.alarms errors', () => {
+ // First call fails
+ mockChromeAlarms.clear.mockImplementationOnce(() => {
+ throw new Error('Permission denied')
+ })
+
const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ // Clear the error and mock calls
+ mockLogger.error.mockClear()
+ mockChromeAlarms.clear.mockClear()
+ mockChromeAlarms.create.mockClear()
+
+ // Subsequent calls should still work
simulateFocusChange(component)(true, false)
- await expectWalletLockCalled()
+ expect(mockChromeAlarms.create).toHaveBeenCalledWith('AutoLockAlarm', {
+ delayInMinutes: 5,
+ })
+ expect(mockLogger.error).not.toHaveBeenCalled()
+ })
+ })
- // Rerender with same unfocused state - should not trigger additional calls
- const { rerender } = component
- rerender(
-
- Test
- ,
- )
+ describe('edge cases', () => {
+ it('should handle rapid mount/unmount cycles', () => {
+ const { unmount: unmount1 } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ unmount1()
- expect(mockKeyring.lock).toHaveBeenCalledTimes(1)
+ const { unmount: unmount2 } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ unmount2()
+
+ // Should have cleared alarm twice (once per mount)
+ expect(mockChromeAlarms.clear).toHaveBeenCalledTimes(2)
+ // No create calls because unmount scheduling is handled in background
+ expect(mockChromeAlarms.create).not.toHaveBeenCalled()
})
- it('should not lock again when returning to focused state', async () => {
+ it('should handle rapid focus changes', () => {
const component = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
+ mockChromeAlarms.create.mockClear()
+ mockChromeAlarms.clear.mockClear()
+ // Lose focus
simulateFocusChange(component)(true, false)
- await expectWalletLockCalled()
+ expect(mockChromeAlarms.create).toHaveBeenCalledTimes(1)
- // Change back to focused
+ // Regain focus
simulateFocusChange(component)(false, true)
- expect(mockKeyring.lock).toHaveBeenCalledTimes(1)
- })
- })
+ expect(mockChromeAlarms.clear).toHaveBeenCalledTimes(1)
- describe('timeout setting changes', () => {
- it('should respond to timeout setting changes', () => {
- renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- expect(mockUseIsChromeWindowFocusedWithTimeout).toHaveBeenLastCalledWith(5 * 60 * 1000)
-
- renderAutoLockProvider(DeviceAccessTimeout.OneHour)
- expect(mockUseIsChromeWindowFocusedWithTimeout).toHaveBeenLastCalledWith(60 * 60 * 1000)
+ // Lose focus again
+ simulateFocusChange(component)(true, false)
+ expect(mockChromeAlarms.create).toHaveBeenCalledTimes(2)
})
- it('should handle timeout change from configured to Never', () => {
- mockUseIsChromeWindowFocusedWithTimeout.mockReturnValue(false)
+ it('should prioritize wallet state changes over focus changes (race condition)', () => {
+ mockUseIsWalletUnlocked.mockReturnValue(true)
+ mockUseIsChromeWindowFocused.mockReturnValue(true)
+ const { rerender } = renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- renderAutoLockProvider(DeviceAccessTimeout.FiveMinutes)
- expect(mockUseIsChromeWindowFocusedWithTimeout).toHaveBeenCalledWith(5 * 60 * 1000)
+ mockChromeAlarms.clear.mockClear()
+ mockChromeAlarms.create.mockClear()
+
+ // Simulate simultaneous wallet lock + focus loss
+ mockUseIsWalletUnlocked.mockReturnValue(false)
+ mockUseIsChromeWindowFocused.mockReturnValue(false)
+ rerender()
- renderAutoLockProvider(DeviceAccessTimeout.Never)
- expect(mockUseIsChromeWindowFocusedWithTimeout).toHaveBeenLastCalledWith(Number.MAX_SAFE_INTEGER)
+ // Should only clear alarm (wallet state change priority), not schedule
+ expect(mockChromeAlarms.clear).toHaveBeenCalledTimes(1)
+ expect(mockChromeAlarms.create).not.toHaveBeenCalled()
})
})
})
diff --git a/apps/extension/src/app/components/AutoLockProvider.tsx b/apps/extension/src/app/components/AutoLockProvider.tsx
index 4d7989e0fc7..68c18d819e6 100644
--- a/apps/extension/src/app/components/AutoLockProvider.tsx
+++ b/apps/extension/src/app/components/AutoLockProvider.tsx
@@ -1,74 +1,98 @@
-import { PropsWithChildren, useEffect } from 'react'
+import { PropsWithChildren, useEffect, useRef } from 'react'
import { useSelector } from 'react-redux'
-import { ExtensionState } from 'src/store/extensionReducer'
-import { useIsChromeWindowFocusedWithTimeout } from 'uniswap/src/extension/useIsChromeWindowFocused'
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
-import { deviceAccessTimeoutToMs } from 'uniswap/src/features/settings/constants'
-import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
-import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
+import { useIsWalletUnlocked } from 'src/app/hooks/useIsWalletUnlocked'
+import { useIsChromeWindowFocused } from 'uniswap/src/extension/useIsChromeWindowFocused'
+import { selectDeviceAccessTimeoutMinutes } from 'uniswap/src/features/settings/selectors'
import { logger } from 'utilities/src/logger/logger'
-import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
-const AUTO_LOCK_ALARM_NAME = 'AutoLockAlarm'
+export const AUTO_LOCK_ALARM_NAME = 'AutoLockAlarm'
/**
- * AutoLockProvider monitors window focus and automatically locks the wallet
- * after the configured inactivity timeout period.
+ * Helper to safely clear the auto-lock alarm with error handling
+ */
+function clearAutoLockAlarm(reason: string): void {
+ try {
+ chrome.alarms.clear(AUTO_LOCK_ALARM_NAME)
+ logger.debug('AutoLockProvider', 'clearAutoLockAlarm', reason)
+ } catch (error) {
+ logger.error(error, {
+ tags: { file: 'AutoLockProvider', function: 'clearAutoLockAlarm' },
+ extra: { reason },
+ })
+ }
+}
+
+/**
+ * Helper to safely create the auto-lock alarm with error handling
+ */
+function createAutoLockAlarm(delayInMinutes: number): void {
+ try {
+ chrome.alarms.create(AUTO_LOCK_ALARM_NAME, { delayInMinutes })
+ logger.debug('AutoLockProvider', 'createAutoLockAlarm', `Scheduled auto-lock alarm for ${delayInMinutes} minutes`)
+ } catch (error) {
+ logger.error(error, {
+ tags: { file: 'AutoLockProvider', function: 'createAutoLockAlarm' },
+ extra: { delayInMinutes },
+ })
+ }
+}
+
+/**
+ * AutoLockProvider schedules chrome alarms to automatically lock the wallet
+ * after the configured timeout period when the sidebar is not focused.
*
- * This component should be placed high in the component tree to ensure
- * it's always active when the extension is running.
+ * Uses chrome.alarms API which persists even when the extension is closed,
+ * ensuring reliable auto-lock behavior.
*/
export function AutoLockProvider({ children }: PropsWithChildren): JSX.Element {
- const deviceAccessTimeout = useSelector((state: ExtensionState) => state.userSettings.deviceAccessTimeout)
- const useAlarmsApi = useFeatureFlag(FeatureFlags.UseAlarmsApi)
- const timeoutMs = deviceAccessTimeoutToMs(deviceAccessTimeout)
+ const delayInMinutes = useSelector(selectDeviceAccessTimeoutMinutes)
+ const isWalletUnlocked = useIsWalletUnlocked()
+ const isChromeWindowFocused = useIsChromeWindowFocused()
- // Use the window focus hook with the configured timeout
- // If timeoutMs is undefined (Never setting), use a very large number to effectively disable
- const isChromeWindowFocused = useIsChromeWindowFocusedWithTimeout(timeoutMs ?? Number.MAX_SAFE_INTEGER)
+ // Ref to track previous focus state
+ const prevFocusedRef = useRef(true)
+ // Ref to track previous unlock state
+ const prevUnlockedRef = useRef(null)
- // Maintain chrome.alarms usage behind feature flag
+ // On mount: Clear any existing alarm (sidebar just opened)
useEffect(() => {
- if (useAlarmsApi) {
- chrome.alarms.create(AUTO_LOCK_ALARM_NAME, {
- delayInMinutes: 1000,
- })
+ clearAutoLockAlarm('Cleared auto-lock alarm (sidebar opened)')
+ }, [])
+
+ useEffect(() => {
+ // Skip if timeout not configured (Never)
+ if (delayInMinutes === undefined) {
+ clearAutoLockAlarm('Cleared auto-lock alarm (timeout not configured)')
+ return
}
- return () => {
- chrome.alarms.clear(AUTO_LOCK_ALARM_NAME)
+ const prevFocused = prevFocusedRef.current
+ const prevUnlocked = prevUnlockedRef.current
+ prevFocusedRef.current = isChromeWindowFocused
+ prevUnlockedRef.current = isWalletUnlocked
+
+ // Skip first render for unlock state
+ if (prevUnlocked === null) {
+ return
}
- }, [useAlarmsApi])
- useEffect(() => {
- // Only lock if timeout is configured (not "Never")
- if (timeoutMs === undefined) {
+ // Clear alarm when wallet state changes (locked or unlocked)
+ if (prevUnlocked !== isWalletUnlocked) {
+ clearAutoLockAlarm(`Cleared auto-lock alarm (wallet ${isWalletUnlocked ? 'unlocked' : 'locked'})`)
return
}
- if (!isChromeWindowFocused) {
- const lockWallet = async (): Promise => {
- try {
- logger.debug('AutoLockProvider', 'lockWallet', 'Locking wallet due to inactivity')
- await Keyring.lock()
- sendAnalyticsEvent(ExtensionEventName.ChangeLockedState, {
- locked: true,
- location: 'background',
- })
- } catch (error) {
- logger.error(error, {
- tags: {
- file: 'AutoLockProvider.tsx',
- function: 'lockWallet',
- },
- })
- }
- }
+ // When window loses focus AND wallet is unlocked: schedule alarm
+ if (prevFocused && !isChromeWindowFocused && isWalletUnlocked) {
+ createAutoLockAlarm(delayInMinutes)
+ return
+ }
- lockWallet()
+ // When window regains focus: clear alarm
+ if (!prevFocused && isChromeWindowFocused) {
+ clearAutoLockAlarm('Cleared auto-lock alarm (window focused)')
}
- }, [isChromeWindowFocused, timeoutMs])
+ }, [isChromeWindowFocused, isWalletUnlocked, delayInMinutes])
return <>{children}>
}
diff --git a/apps/extension/src/app/components/tabs/ActivityTab.tsx b/apps/extension/src/app/components/tabs/ActivityTab.tsx
index a0a8074467f..95ca43b900f 100644
--- a/apps/extension/src/app/components/tabs/ActivityTab.tsx
+++ b/apps/extension/src/app/components/tabs/ActivityTab.tsx
@@ -1,5 +1,6 @@
import { memo } from 'react'
-import { ScrollView } from 'ui/src'
+import { Flex, Loader, ScrollView } from 'ui/src'
+import { useInfiniteScroll } from 'utilities/src/react/useInfiniteScroll'
import { useActivityDataWallet } from 'wallet/src/features/activity/useActivityDataWallet'
export const ActivityTab = memo(function _ActivityTab({
@@ -9,9 +10,16 @@ export const ActivityTab = memo(function _ActivityTab({
address: Address
skip?: boolean
}): JSX.Element {
- const { maybeEmptyComponent, renderActivityItem, sectionData } = useActivityDataWallet({
- evmOwner: address,
- skip,
+ const { maybeEmptyComponent, renderActivityItem, sectionData, fetchNextPage, hasNextPage, isFetchingNextPage } =
+ useActivityDataWallet({
+ evmOwner: address,
+ skip,
+ })
+
+ const { sentinelRef } = useInfiniteScroll({
+ onLoadMore: fetchNextPage,
+ hasNextPage,
+ isFetching: isFetchingNextPage,
})
if (maybeEmptyComponent) {
@@ -22,6 +30,14 @@ export const ActivityTab = memo(function _ActivityTab({
{/* `sectionData` will be either an array of transactions or an array of loading skeletons */}
{sectionData.map((item, index) => renderActivityItem({ item, index }))}
+ {/* Show skeleton loading indicator while fetching next page */}
+ {isFetchingNextPage && (
+
+
+
+ )}
+ {/* Intersection observer sentinel for infinite scroll */}
+
)
})
diff --git a/apps/extension/src/app/core/BaseAppContainer.tsx b/apps/extension/src/app/core/BaseAppContainer.tsx
index 09f9327325c..c38fbbc9e56 100644
--- a/apps/extension/src/app/core/BaseAppContainer.tsx
+++ b/apps/extension/src/app/core/BaseAppContainer.tsx
@@ -1,3 +1,5 @@
+import { ApiInit, getSessionService } from '@universe/api'
+import { createChallengeSolverService, createSessionInitializationService } from '@universe/sessions'
import { PropsWithChildren } from 'react'
import { I18nextProvider } from 'react-i18next'
import { GraphqlProvider } from 'src/app/apollo'
@@ -14,6 +16,14 @@ import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary
import { AccountsStoreContextProvider } from 'wallet/src/features/accounts/store/provider'
import { SharedWalletProvider } from 'wallet/src/providers/SharedWalletProvider'
+const sessionInitializationService = createSessionInitializationService({
+ sessionService: getSessionService({
+ // TODO: Use real base url
+ getBaseUrl: () => 'https://entry-gateway.backend-dev.api.uniswap.org',
+ }),
+ challengeSolverService: createChallengeSolverService(),
+})
+
export function BaseAppContainer({
children,
appName,
@@ -30,6 +40,7 @@ export function BaseAppContainer({
+
{children}
diff --git a/apps/extension/src/app/core/SidebarApp.tsx b/apps/extension/src/app/core/SidebarApp.tsx
index db280c45dfb..eeadc95bfa9 100644
--- a/apps/extension/src/app/core/SidebarApp.tsx
+++ b/apps/extension/src/app/core/SidebarApp.tsx
@@ -6,6 +6,7 @@ import { useEffect, useRef, useState } from 'react'
import { useDispatch } from 'react-redux'
import { createHashRouter, RouterProvider } from 'react-router'
import { PersistGate } from 'redux-persist/integration/react'
+import { AUTO_LOCK_ALARM_NAME } from 'src/app/components/AutoLockProvider'
import { ErrorElement } from 'src/app/components/ErrorElement'
import { useTraceSidebarDappUrl } from 'src/app/components/Trace/useTraceSidebarDappUrl'
import { BaseAppContainer } from 'src/app/core/BaseAppContainer'
@@ -188,8 +189,21 @@ function useDappRequestPortListener(): void {
}, PORT_PING_INTERVAL)
}
+/**
+ * Creates a connection so that the background script can detect when the sidebar is closed and schedule an auto-lock alarm.
+ */
+function useAutoLockAlarmConnection(): void {
+ useEffect(() => {
+ const port = chrome.runtime.connect({ name: AUTO_LOCK_ALARM_NAME })
+ return () => {
+ port.disconnect()
+ }
+ }, [])
+}
+
function SidebarWrapper(): JSX.Element {
useDappRequestPortListener()
+ useAutoLockAlarmConnection()
useTestnetModeForLoggingAndAnalytics()
const resetUnitagsQueries = useResetUnitagsQueries()
diff --git a/apps/extension/src/app/core/StatsigProvider.tsx b/apps/extension/src/app/core/StatsigProvider.tsx
index 63f55e72d4f..7c6aeb01b76 100644
--- a/apps/extension/src/app/core/StatsigProvider.tsx
+++ b/apps/extension/src/app/core/StatsigProvider.tsx
@@ -1,10 +1,9 @@
import { useQuery } from '@tanstack/react-query'
import { SharedQueryClient } from '@universe/api'
+import { StatsigCustomAppValue, StatsigUser } from '@universe/gating'
import { useEffect, useState } from 'react'
import { makeStatsigUser } from 'src/app/core/initStatSigForBrowserScripts'
-import { StatsigCustomAppValue } from 'uniswap/src/features/gating/constants'
import { StatsigProviderWrapper } from 'uniswap/src/features/gating/StatsigProviderWrapper'
-import { StatsigUser } from 'uniswap/src/features/gating/sdk/statsig'
import { initializeDatadog } from 'uniswap/src/utils/datadog'
import { uniqueIdQuery } from 'utilities/src/device/uniqueIdQuery'
diff --git a/apps/extension/src/app/core/initStatSigForBrowserScripts.tsx b/apps/extension/src/app/core/initStatSigForBrowserScripts.tsx
index 3e3ffaaa9ba..7a495477c72 100644
--- a/apps/extension/src/app/core/initStatSigForBrowserScripts.tsx
+++ b/apps/extension/src/app/core/initStatSigForBrowserScripts.tsx
@@ -1,6 +1,5 @@
+import { StatsigClient, StatsigCustomAppValue, StatsigUser } from '@universe/gating'
import { config } from 'uniswap/src/config'
-import { StatsigCustomAppValue } from 'uniswap/src/features/gating/constants'
-import { StatsigClient, StatsigUser } from 'uniswap/src/features/gating/sdk/statsig'
import { statsigBaseConfig } from 'uniswap/src/features/gating/statsigBaseConfig'
import { getUniqueId } from 'utilities/src/device/uniqueId'
import { logger } from 'utilities/src/logger/logger'
diff --git a/apps/extension/src/app/features/accounts/AccountItem.tsx b/apps/extension/src/app/features/accounts/AccountItem.tsx
index dc2db86aa01..99eb7319601 100644
--- a/apps/extension/src/app/features/accounts/AccountItem.tsx
+++ b/apps/extension/src/app/features/accounts/AccountItem.tsx
@@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { EditLabelModal } from 'src/app/features/accounts/EditLabelModal'
import { removeAllDappConnectionsForAccount } from 'src/app/features/dapp/actions'
-import { AppRoutes, SettingsRoutes } from 'src/app/navigation/constants'
-import { useExtensionNavigation } from 'src/app/navigation/utils'
+import { AppRoutes, SettingsRoutes, UnitagClaimRoutes } from 'src/app/navigation/constants'
+import { focusOrCreateUnitagTab, useExtensionNavigation } from 'src/app/navigation/utils'
import { Flex, Text, TouchableArea } from 'ui/src'
import { CopySheets, Edit, Ellipsis, Globe, TrashFilled } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
@@ -97,13 +97,17 @@ export function AccountItem({ address, onAccountSelect, balanceUSD }: AccountIte
label: !accountHasUnitag
? t('account.wallet.menu.edit.title')
: t('settings.setting.wallet.action.editProfile'),
- onPress: (e: BaseSyntheticEvent): void => {
+ onPress: async (e: BaseSyntheticEvent): Promise => {
// We have to manually prevent click-through because the way the context menu is inside of a TouchableArea in this component it
// means that without it the TouchableArea handler will get called
e.preventDefault()
e.stopPropagation()
- setShowEditLabelModal(true)
+ if (accountHasUnitag) {
+ await focusOrCreateUnitagTab(address, UnitagClaimRoutes.EditProfile)
+ } else {
+ setShowEditLabelModal(true)
+ }
},
Icon: Edit,
},
@@ -134,7 +138,7 @@ export function AccountItem({ address, onAccountSelect, balanceUSD }: AccountIte
iconProps: { color: '$statusCritical' },
},
]
- }, [accountHasUnitag, onPressCopyAddress, navigateTo, t])
+ }, [accountHasUnitag, onPressCopyAddress, navigateTo, t, address])
return (
<>
diff --git a/apps/extension/src/app/features/biometricUnlock/BiometricUnlockStorage.ts b/apps/extension/src/app/features/biometricUnlock/BiometricUnlockStorage.ts
index bd06afb9236..599a36b1c07 100644
--- a/apps/extension/src/app/features/biometricUnlock/BiometricUnlockStorage.ts
+++ b/apps/extension/src/app/features/biometricUnlock/BiometricUnlockStorage.ts
@@ -4,6 +4,7 @@ import { PersistedStorage } from 'wallet/src/utils/persistedStorage'
export type BiometricUnlockStorageData = {
credentialId: string
+ transports: AuthenticatorTransport[]
secretPayload: Omit & { ciphertext: string }
}
diff --git a/apps/extension/src/app/features/biometricUnlock/biometricAuthUtils.ts b/apps/extension/src/app/features/biometricUnlock/biometricAuthUtils.ts
index d82aeb49f58..21aa3d19515 100644
--- a/apps/extension/src/app/features/biometricUnlock/biometricAuthUtils.ts
+++ b/apps/extension/src/app/features/biometricUnlock/biometricAuthUtils.ts
@@ -16,9 +16,11 @@ import {
*/
export async function authenticateWithBiometricCredential({
credentialId,
+ transports,
abortSignal,
}: {
credentialId: string
+ transports: AuthenticatorTransport[]
abortSignal: AbortSignal
}): Promise<{ publicKeyCredential: PublicKeyCredential; encryptionKey: CryptoKey }> {
// Convert stored credential ID back to binary format
@@ -31,6 +33,7 @@ export async function authenticateWithBiometricCredential({
{
type: 'public-key',
id: credentialIdBuffer,
+ transports,
},
],
userVerification: 'required',
@@ -70,10 +73,12 @@ export async function encryptPasswordWithBiometricData({
password,
encryptionKey,
credentialId,
+ transports,
}: {
password: string
encryptionKey: CryptoKey
credentialId: string
+ transports: AuthenticatorTransport[]
}): Promise {
// Create a new secret payload for the password
const secretPayload = await createEmptySecretPayload()
@@ -86,7 +91,7 @@ export async function encryptPasswordWithBiometricData({
additionalData: credentialId, // Use credential ID as additional authenticated data
})
- return { credentialId, secretPayload: secretPayloadWithCiphertext }
+ return { credentialId, transports, secretPayload: secretPayloadWithCiphertext }
}
/**
diff --git a/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.test.ts b/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.test.ts
index 1b15deca6b5..1eb0c37cb0b 100644
--- a/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.test.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.test.ts
@@ -42,7 +42,12 @@ const mockGetEncryptionKeyFromBuffer = jest.requireMock(
// Mock PublicKeyCredential (doesn't exist in Jest environment)
class MockPublicKeyCredential {
- constructor(public rawId: ArrayBuffer) {}
+ constructor(
+ public rawId: ArrayBuffer,
+ public response = {
+ getTransports: () => ['internal' as AuthenticatorTransport],
+ },
+ ) {}
}
Object.defineProperty(global, 'PublicKeyCredential', {
writable: true,
@@ -124,10 +129,12 @@ describe('useBiometricUnlockSetupMutation', () => {
// Verify the stored secret payload has all required properties
const storedData = mockBiometricUnlockStorage.set.mock.calls[0]![0] as {
credentialId: string
+ transports: AuthenticatorTransport[]
secretPayload: typeof mockSecretPayload
}
expect(storedData.credentialId).toBe(expectedCredentialId)
+ expect(storedData.transports).toEqual(['internal'])
expect(storedData.secretPayload).toEqual(
expect.objectContaining({
diff --git a/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.ts b/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.ts
index 345545a74c8..bd0e4616f51 100644
--- a/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useBiometricUnlockSetupMutation.ts
@@ -69,7 +69,7 @@ async function createCredentialAndEncryptPassword({
const rawKey = await window.crypto.subtle.exportKey('raw', encryptionKey)
- const { credentialId } = await createCredential({
+ const { credentialId, transports } = await createCredential({
encryptionKey: rawKey,
abortSignal,
})
@@ -78,6 +78,7 @@ async function createCredentialAndEncryptPassword({
password,
encryptionKey,
credentialId,
+ transports,
})
}
@@ -116,7 +117,7 @@ async function createCredential({
}: {
encryptionKey: ArrayBuffer
abortSignal: AbortSignal
-}): Promise<{ credentialId: string }> {
+}): Promise<{ credentialId: string; transports: AuthenticatorTransport[] }> {
// Create WebAuthn credential with platform authenticator (Touch ID, Windows Hello, etc.) forced
const credential = await navigator.credentials.create({
publicKey: {
@@ -149,5 +150,8 @@ async function createCredential({
// Convert raw ID to a storable string format
const credentialId = btoa(String.fromCharCode(...new Uint8Array(publicKeyCredential.rawId)))
- return { credentialId }
+ const response = publicKeyCredential.response as AuthenticatorAttestationResponse
+ const transports = response.getTransports() as AuthenticatorTransport[]
+
+ return { credentialId, transports }
}
diff --git a/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.test.ts b/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.test.ts
index fd312c48e6a..c8f23d90136 100644
--- a/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.test.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.test.ts
@@ -108,6 +108,7 @@ describe('useChangePasswordWithBiometricMutation', () => {
// Setup default mocks
mockBiometricUnlockStorage.get.mockResolvedValue({
credentialId: mockCredentialId,
+ transports: ['internal'],
secretPayload: mockOldEncryptedPayload,
})
@@ -146,6 +147,7 @@ describe('useChangePasswordWithBiometricMutation', () => {
{
type: 'public-key',
id: credentialIdBuffer,
+ transports: ['internal'],
},
],
userVerification: 'required',
@@ -160,6 +162,7 @@ describe('useChangePasswordWithBiometricMutation', () => {
// 4. Should update the stored biometric data with re-encrypted password
expect(mockBiometricUnlockStorage.set).toHaveBeenCalledWith({
credentialId: mockCredentialId,
+ transports: ['internal'],
secretPayload: expect.objectContaining({
ciphertext: expect.any(String),
iv: expect.any(String),
@@ -189,6 +192,7 @@ describe('useChangePasswordWithBiometricMutation', () => {
const newBiometricData = setCall?.[0]
expect(newBiometricData?.credentialId).toBe(mockCredentialId)
+ expect(newBiometricData?.transports).toEqual(['internal'])
expect(newBiometricData?.secretPayload).toMatchObject({
ciphertext: expect.any(String),
iv: expect.any(String),
diff --git a/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.ts b/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.ts
index 8da478fe45c..11361bdf03f 100644
--- a/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useChangePasswordWithBiometricMutation.ts
@@ -25,6 +25,7 @@ export function useChangePasswordWithBiometricMutation(options?: {
// Authenticate with WebAuthn to get the encryption key
const { encryptionKey } = await authenticateWithBiometricCredential({
credentialId: biometricUnlockCredential.credentialId,
+ transports: biometricUnlockCredential.transports,
abortSignal,
})
@@ -36,6 +37,7 @@ export function useChangePasswordWithBiometricMutation(options?: {
password: newPassword,
encryptionKey,
credentialId: biometricUnlockCredential.credentialId,
+ transports: biometricUnlockCredential.transports,
})
// Update the stored biometric data
diff --git a/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlock.ts b/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlock.ts
index 3491a94d6bb..54582813431 100644
--- a/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlock.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlock.ts
@@ -1,7 +1,6 @@
import { useQuery } from '@tanstack/react-query'
+import { DynamicConfigs, ExtensionBiometricUnlockConfigKey, useDynamicConfigValue } from '@universe/gating'
import { biometricUnlockCredentialQuery } from 'src/app/features/biometricUnlock/biometricUnlockCredentialQuery'
-import { DynamicConfigs, ExtensionBiometricUnlockConfigKey } from 'uniswap/src/features/gating/configs'
-import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
export function useShouldShowBiometricUnlock(): boolean {
const isEnabled = useDynamicConfigValue({
diff --git a/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlockEnrollment.ts b/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlockEnrollment.ts
index 7c496296c1d..f642af2d9ad 100644
--- a/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlockEnrollment.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useShouldShowBiometricUnlockEnrollment.ts
@@ -1,8 +1,7 @@
import { useQuery } from '@tanstack/react-query'
+import { DynamicConfigs, ExtensionBiometricUnlockConfigKey, useDynamicConfigValue } from '@universe/gating'
import { useTranslation } from 'react-i18next'
import { builtInBiometricCapabilitiesQuery } from 'src/app/utils/device/builtInBiometricCapabilitiesQuery'
-import { DynamicConfigs, ExtensionBiometricUnlockConfigKey } from 'uniswap/src/features/gating/configs'
-import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
export function useShouldShowBiometricUnlockEnrollment({ flow }: { flow: 'onboarding' | 'settings' }): boolean {
const { t } = useTranslation()
diff --git a/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.test.ts b/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.test.ts
index 832e727c382..5685ba37f1a 100644
--- a/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.test.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.test.ts
@@ -3,15 +3,13 @@ import { waitFor } from '@testing-library/react'
import { BiometricUnlockStorage } from 'src/app/features/biometricUnlock/BiometricUnlockStorage'
import { useUnlockWithBiometricCredentialMutation } from 'src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation'
import { renderHookWithProviders } from 'src/test/render'
-import { authActions } from 'wallet/src/features/auth/saga'
-import { AuthActionType } from 'wallet/src/features/auth/types'
import { encodeForStorage, encrypt, generateNew256BitRandomBuffer } from 'wallet/src/features/wallet/Keyring/crypto'
jest.mock('src/app/features/biometricUnlock/BiometricUnlockStorage')
-jest.mock('wallet/src/features/auth/saga', () => ({
- authActions: {
- trigger: jest.fn(),
- },
+
+const mockUnlockWithPassword = jest.fn()
+jest.mock('src/app/features/lockScreen/useUnlockWithPassword', () => ({
+ useUnlockWithPassword: jest.fn(() => mockUnlockWithPassword),
}))
// Mock the Web Crypto API with Node.js built-in
@@ -27,7 +25,6 @@ Object.defineProperty(navigator, 'credentials', {
})
const mockBiometricUnlockStorage = BiometricUnlockStorage as jest.Mocked
-const mockAuthActions = authActions as jest.Mocked
// Mock AuthenticatorAssertionResponse
class MockAuthenticatorAssertionResponse {
@@ -100,6 +97,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
// Setup default mocks
mockBiometricUnlockStorage.get.mockResolvedValue({
credentialId: mockCredentialId,
+ transports: ['internal'],
secretPayload: mockEncryptedPayload,
})
@@ -107,15 +105,9 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
const mockPublicKeyCredential = new MockPublicKeyCredential(mockAuthResponse)
mockCredentialsGet.mockResolvedValue(mockPublicKeyCredential)
- mockAuthActions.trigger.mockReturnValue({
- type: 'AUTH_TRIGGER',
- payload: {
- type: AuthActionType.Unlock,
- password: mockPassword,
- },
- })
-
- jest.clearAllMocks()
+ // Reset and configure mockUnlockWithPassword
+ mockUnlockWithPassword.mockReset()
+ mockUnlockWithPassword.mockResolvedValue(undefined)
})
describe('successful unlock', () => {
@@ -140,6 +132,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
{
type: 'public-key',
id: credentialIdBuffer,
+ transports: ['internal'],
},
],
userVerification: 'required',
@@ -148,11 +141,8 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
signal: expect.any(AbortSignal),
})
- // 3. Should dispatch unlock action with the decrypted password
- expect(mockAuthActions.trigger).toHaveBeenCalledWith({
- type: AuthActionType.Unlock,
- password: mockPassword,
- })
+ // 3. Should call unlockWithPassword with the decrypted password
+ expect(mockUnlockWithPassword).toHaveBeenCalledWith({ password: mockPassword })
})
})
@@ -170,7 +160,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
expect(result.current.error?.message).toBe('No biometric unlock credential found')
expect(mockCredentialsGet).not.toHaveBeenCalled()
- expect(mockAuthActions.trigger).not.toHaveBeenCalled()
+ expect(mockUnlockWithPassword).not.toHaveBeenCalled()
})
it('should throw error when biometric authentication fails', async () => {
@@ -185,7 +175,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
})
expect(result.current.error?.message).toBe('Failed to create credential')
- expect(mockAuthActions.trigger).not.toHaveBeenCalled()
+ expect(mockUnlockWithPassword).not.toHaveBeenCalled()
})
it('should throw error when no user handle returned from authentication', async () => {
@@ -202,7 +192,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
})
expect(result.current.error?.message).toBe('No user handle returned from biometric authentication')
- expect(mockAuthActions.trigger).not.toHaveBeenCalled()
+ expect(mockUnlockWithPassword).not.toHaveBeenCalled()
})
it('should throw error when password decryption fails', async () => {
@@ -226,7 +216,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
})
expect(result.current.error?.message).toBe('Failed to decrypt password')
- expect(mockAuthActions.trigger).not.toHaveBeenCalled()
+ expect(mockUnlockWithPassword).not.toHaveBeenCalled()
})
it('should handle WebAuthn API errors', async () => {
@@ -242,7 +232,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
})
expect(result.current.error).toBe(webAuthnError)
- expect(mockAuthActions.trigger).not.toHaveBeenCalled()
+ expect(mockUnlockWithPassword).not.toHaveBeenCalled()
})
it('should handle storage retrieval errors', async () => {
@@ -259,7 +249,7 @@ describe('useUnlockWithBiometricCredentialMutation', () => {
expect(result.current.error).toBe(storageError)
expect(mockCredentialsGet).not.toHaveBeenCalled()
- expect(mockAuthActions.trigger).not.toHaveBeenCalled()
+ expect(mockUnlockWithPassword).not.toHaveBeenCalled()
})
})
diff --git a/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.ts b/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.ts
index 4eac011d28f..b37d3357cb1 100644
--- a/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.ts
+++ b/apps/extension/src/app/features/biometricUnlock/useUnlockWithBiometricCredentialMutation.ts
@@ -58,10 +58,10 @@ async function getPasswordFromBiometricCredential(abortSignal: AbortSignal): Pro
throw new Error('No biometric unlock credential found')
}
- const { credentialId } = biometricUnlockCredential
+ const { credentialId, transports } = biometricUnlockCredential
// Authenticate with WebAuthn using the stored credential and decrypt password
- const { encryptionKey } = await authenticateWithBiometricCredential({ credentialId, abortSignal })
+ const { encryptionKey } = await authenticateWithBiometricCredential({ credentialId, transports, abortSignal })
const password = await decryptPasswordFromBiometricData({ encryptionKey, biometricUnlockCredential })
return password
}
diff --git a/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx b/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx
index 19606509983..b015627b61b 100644
--- a/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx
+++ b/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx
@@ -1,3 +1,4 @@
+import { FeatureFlags, useFeatureFlag } from '@universe/gating'
import { useTranslation } from 'react-i18next'
import { DappRequestContent } from 'src/app/features/dappRequests/DappRequestContent'
import { ActionCanNotBeCompletedContent } from 'src/app/features/dappRequests/requestContent/ActionCanNotBeCompleted/ActionCanNotBeCompletedContent'
@@ -11,8 +12,6 @@ import { EIP712Message, isEIP712TypedData } from 'src/app/features/dappRequests/
import { isPermit2, isUniswapXSwapRequest } from 'src/app/features/dappRequests/types/Permit2Types'
import { Flex, Text } from 'ui/src'
import { toSupportedChainId } from 'uniswap/src/features/chains/utils'
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { useHasAccountMismatchCallback } from 'uniswap/src/features/smartWallet/mismatch/hooks'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { isEVMAddressWithChecksum } from 'utilities/src/addresses/evm/evm'
diff --git a/apps/extension/src/app/features/dappRequests/saga.ts b/apps/extension/src/app/features/dappRequests/saga.ts
index 8ce3f50de43..7a511bca103 100644
--- a/apps/extension/src/app/features/dappRequests/saga.ts
+++ b/apps/extension/src/app/features/dappRequests/saga.ts
@@ -1,6 +1,7 @@
/* eslint-disable max-lines */
import { Provider } from '@ethersproject/providers'
import { providerErrors, rpcErrors, serializeError } from '@metamask/rpc-errors'
+import { FeatureFlags, getFeatureFlag } from '@universe/gating'
import { createSearchParams } from 'react-router'
import { changeChain } from 'src/app/features/dapp/changeChain'
import { DappInfo, dappStore } from 'src/app/features/dapp/store'
@@ -45,8 +46,6 @@ import getCalldataInfoFromTransaction from 'src/background/utils/getCalldataInfo
import { call, put, select, take } from 'typed-redux-saga'
import { hexadecimalStringToInt, toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { DappRequestType, DappResponseType } from 'uniswap/src/features/dappRequests/types'
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { getFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { pushNotification } from 'uniswap/src/features/notifications/slice/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/slice/types'
import { Platform } from 'uniswap/src/features/platforms/types/Platform'
diff --git a/apps/extension/src/app/features/home/HomeScreen.tsx b/apps/extension/src/app/features/home/HomeScreen.tsx
index f7a5e10a591..00145f31da7 100644
--- a/apps/extension/src/app/features/home/HomeScreen.tsx
+++ b/apps/extension/src/app/features/home/HomeScreen.tsx
@@ -1,5 +1,6 @@
import { useApolloClient } from '@apollo/client'
import { SharedEventName } from '@uniswap/analytics-events'
+import { FeatureFlags, useFeatureFlag } from '@universe/gating'
import { memo, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
@@ -21,8 +22,6 @@ import { navigate } from 'src/app/navigation/state'
import { Flex, Loader, styled, Text, TouchableArea } from 'ui/src'
import { SMART_WALLET_UPGRADE_VIDEO } from 'ui/src/assets'
import { NFTS_TAB_DATA_DEPENDENCIES } from 'uniswap/src/components/nfts/constants'
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { useSelectAddressHasNotifications } from 'uniswap/src/features/notifications/slice/hooks'
import { setNotificationStatus } from 'uniswap/src/features/notifications/slice/slice'
import { PortfolioBalance } from 'uniswap/src/features/portfolio/PortfolioBalance/PortfolioBalance'
diff --git a/apps/extension/src/app/features/home/PortfolioActionButtons.tsx b/apps/extension/src/app/features/home/PortfolioActionButtons.tsx
index a0ac5b56037..9a7a08625ce 100644
--- a/apps/extension/src/app/features/home/PortfolioActionButtons.tsx
+++ b/apps/extension/src/app/features/home/PortfolioActionButtons.tsx
@@ -1,4 +1,5 @@
import { SharedEventName } from '@uniswap/analytics-events'
+import { FeatureFlags, useFeatureFlag } from '@universe/gating'
import { cloneElement, memo, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useInterfaceBuyNavigator } from 'src/app/features/for/utils'
@@ -7,8 +8,6 @@ import { navigate } from 'src/app/navigation/state'
import { Flex, getTokenValue, Text, TouchableArea, useMedia } from 'ui/src'
import { ArrowDownCircle, Bank, CoinConvert, SendAction } from 'ui/src/components/icons'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal'
diff --git a/apps/extension/src/app/features/home/PortfolioHeader.tsx b/apps/extension/src/app/features/home/PortfolioHeader.tsx
index 45df41829bc..b951fd5c50a 100644
--- a/apps/extension/src/app/features/home/PortfolioHeader.tsx
+++ b/apps/extension/src/app/features/home/PortfolioHeader.tsx
@@ -55,7 +55,7 @@ const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }):
}),
)
}
- }, [isScreenFocused, pressProgress])
+ }, [isScreenFocused])
const onBegin = (): void => {
pressProgress.value = withTiming(1)
diff --git a/apps/extension/src/app/features/home/TokenBalanceList.tsx b/apps/extension/src/app/features/home/TokenBalanceList.tsx
index b29a53a4a2c..ebbed006847 100644
--- a/apps/extension/src/app/features/home/TokenBalanceList.tsx
+++ b/apps/extension/src/app/features/home/TokenBalanceList.tsx
@@ -1,9 +1,13 @@
-import { memo } from 'react'
+import { Currency } from '@uniswap/sdk-core'
+import { memo, useState } from 'react'
import { useInterfaceBuyNavigator } from 'src/app/features/for/utils'
import { AppRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state'
import { TokenBalanceListWeb } from 'uniswap/src/components/portfolio/TokenBalanceListWeb'
+import { ReportTokenIssueModal } from 'uniswap/src/components/reporting/ReportTokenIssueModal'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
+import { useEvent } from 'utilities/src/react/hooks'
+import { useBooleanState } from 'utilities/src/react/useBooleanState'
import { usePortfolioEmptyStateBackground } from 'wallet/src/components/portfolio/empty'
export const ExtensionTokenBalanceList = memo(function _ExtensionTokenBalanceList({
@@ -15,13 +19,35 @@ export const ExtensionTokenBalanceList = memo(function _ExtensionTokenBalanceLis
navigate(`/${AppRoutes.Receive}`)
}
const onPressBuy = useInterfaceBuyNavigator(ElementName.EmptyStateBuy)
+
+ const { value: isReportTokenModalOpen, setTrue: openModal, setFalse: closeReportTokenModal } = useBooleanState(false)
+ const [reportTokenCurrency, setReportTokenCurrency] = useState(undefined)
+ const [isMarkedSpam, setIsMarkedSpam] = useState>(undefined)
+
+ const openReportTokenModal = useEvent((currency: Currency, isMarkedSpam: Maybe) => {
+ setReportTokenCurrency(currency)
+ setIsMarkedSpam(isMarkedSpam)
+ openModal()
+ })
+
const backgroundImageWrapperCallback = usePortfolioEmptyStateBackground()
return (
-
+ <>
+
+ {reportTokenCurrency && (
+
+ )}
+ >
)
})
diff --git a/apps/extension/src/app/features/onboarding/import/SelectWallets.tsx b/apps/extension/src/app/features/onboarding/import/SelectWallets.tsx
index dcf3940bb5e..3c8597824a0 100644
--- a/apps/extension/src/app/features/onboarding/import/SelectWallets.tsx
+++ b/apps/extension/src/app/features/onboarding/import/SelectWallets.tsx
@@ -1,4 +1,5 @@
import { useQuery } from '@tanstack/react-query'
+import { FeatureFlags, useFeatureFlag } from '@universe/gating'
import { ComponentProps, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { SelectWalletsSkeleton } from 'src/app/components/loading/SelectWalletSkeleton'
@@ -9,8 +10,6 @@ import { Flex, ScrollView, SpinningLoader, Square, Text, Tooltip, TouchableArea
import { WalletFilled } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { uniswapUrls } from 'uniswap/src/constants/urls'
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionOnboardingFlow, ExtensionOnboardingScreens } from 'uniswap/src/types/screens/extension'
import { openUri } from 'uniswap/src/utils/linking'
diff --git a/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx b/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx
index 30080a5b13f..3e01afb4a99 100644
--- a/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx
+++ b/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx
@@ -174,7 +174,7 @@ export function ScanToOnboard(): JSX.Element {
)
return () => cancelAnimation(qrScale)
- }, [isLoadingUUID, qrScale])
+ }, [isLoadingUUID])
// Using useAnimatedStyle and AnimatedFlex because tamagui scale animation not working
const qrAnimatedStyle = useAnimatedStyle(() => {
return {
diff --git a/apps/extension/src/app/features/settings/DeviceAccessScreen.tsx b/apps/extension/src/app/features/settings/DeviceAccessScreen.tsx
index c9b6d70c21b..c739b0cbe06 100644
--- a/apps/extension/src/app/features/settings/DeviceAccessScreen.tsx
+++ b/apps/extension/src/app/features/settings/DeviceAccessScreen.tsx
@@ -48,6 +48,7 @@ export function DeviceAccessScreen(): JSX.Element {
const {
flowState,
+ oldPassword,
startPasswordReset,
closeModal,
onPasswordModalNext,
@@ -98,6 +99,7 @@ export function DeviceAccessScreen(): JSX.Element {
return (
closeModal(PasswordResetFlowState.EnterNewPassword)}
/>
diff --git a/apps/extension/src/app/features/settings/SettingsScreen.tsx b/apps/extension/src/app/features/settings/SettingsScreen.tsx
index 4dd4947e716..48d2ca462ba 100644
--- a/apps/extension/src/app/features/settings/SettingsScreen.tsx
+++ b/apps/extension/src/app/features/settings/SettingsScreen.tsx
@@ -1,3 +1,4 @@
+import { FeatureFlags, useFeatureFlag } from '@universe/gating'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
@@ -31,8 +32,6 @@ import { resetUniswapBehaviorHistory } from 'uniswap/src/features/behaviorHistor
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { FiatCurrency, ORDERED_CURRENCIES } from 'uniswap/src/features/fiatCurrency/constants'
import { getFiatCurrencyName, useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks'
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks'
import { PasskeyManagementModal } from 'uniswap/src/features/passkey/PasskeyManagementModal'
import { setCurrentFiatCurrency, setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice'
@@ -228,7 +227,7 @@ export function SettingsScreen(): JSX.Element {
/>
setIsPortfolioBalanceModalOpen(true)}
/>
{isSmartWalletEnabled ? (
diff --git a/apps/extension/src/app/features/settings/password/ChangePasswordForm.test.tsx b/apps/extension/src/app/features/settings/password/ChangePasswordForm.test.tsx
new file mode 100644
index 00000000000..f4721fb4899
--- /dev/null
+++ b/apps/extension/src/app/features/settings/password/ChangePasswordForm.test.tsx
@@ -0,0 +1,166 @@
+import { act, fireEvent, waitFor } from '@testing-library/react'
+import { ChangePasswordForm } from 'src/app/features/settings/password/ChangePasswordForm'
+import { cleanup, render, screen } from 'src/test/test-utils'
+import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
+
+// Mock the Keyring
+jest.mock('wallet/src/features/wallet/Keyring/Keyring', () => ({
+ Keyring: {
+ changePassword: jest.fn().mockResolvedValue(undefined),
+ },
+}))
+
+// Mock analytics
+jest.mock('uniswap/src/features/telemetry/send', () => ({
+ sendAnalyticsEvent: jest.fn(),
+}))
+
+const mockChangePassword = Keyring.changePassword as jest.MockedFunction
+
+describe('ChangePasswordForm', () => {
+ const mockOnNext = jest.fn()
+ const oldPassword = 'MyOldPassword123!'
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ mockChangePassword.mockClear()
+ })
+
+ afterEach(() => {
+ cleanup()
+ })
+
+ it('renders without error', () => {
+ const tree = render()
+ expect(tree).toMatchSnapshot()
+ })
+
+ it('renders password input fields', () => {
+ render()
+
+ // Check for translated placeholders
+ expect(screen.getByPlaceholderText('New password')).toBeDefined()
+ expect(screen.getByPlaceholderText('Confirm password')).toBeDefined()
+ })
+
+ it('renders continue button', () => {
+ render()
+
+ expect(screen.getByText('Continue')).toBeDefined()
+ })
+
+ it('shows error when new password matches old password', async () => {
+ render()
+
+ const newPasswordInput = screen.getByPlaceholderText('New password')
+ const confirmPasswordInput = screen.getByPlaceholderText('Confirm password')
+
+ // Type the same password as the old password
+ act(() => {
+ fireEvent.change(newPasswordInput, { target: { value: oldPassword } })
+ fireEvent.change(confirmPasswordInput, { target: { value: oldPassword } })
+ })
+
+ // Wait for error to appear
+ await waitFor(() => {
+ const errorText = screen.getByText('New password must be different from current password')
+ expect(errorText).toBeDefined()
+ })
+ })
+
+ it('clears error when password changes to be different', async () => {
+ render()
+
+ const newPasswordInput = screen.getByPlaceholderText('New password')
+ const confirmPasswordInput = screen.getByPlaceholderText('Confirm password')
+
+ // First, type the same password as old password
+ act(() => {
+ fireEvent.change(newPasswordInput, { target: { value: oldPassword } })
+ fireEvent.change(confirmPasswordInput, { target: { value: oldPassword } })
+ })
+
+ // Wait for error to appear
+ await waitFor(() => {
+ expect(screen.getByText('New password must be different from current password')).toBeDefined()
+ })
+
+ // Clear and type a different password
+ act(() => {
+ fireEvent.change(newPasswordInput, { target: { value: 'DifferentPassword789!' } })
+ fireEvent.change(confirmPasswordInput, { target: { value: 'DifferentPassword789!' } })
+ })
+
+ // Error should be cleared
+ await waitFor(() => {
+ expect(screen.queryByText('New password must be different from current password')).toBeNull()
+ })
+ })
+
+ it('does not call onNext when passwords match old password', async () => {
+ render()
+
+ const newPasswordInput = screen.getByPlaceholderText('New password')
+ const confirmPasswordInput = screen.getByPlaceholderText('Confirm password')
+ const submitButton = screen.getByText('Continue')
+
+ // Type the same password as the old password
+ act(() => {
+ fireEvent.change(newPasswordInput, { target: { value: oldPassword } })
+ fireEvent.change(confirmPasswordInput, { target: { value: oldPassword } })
+ })
+
+ // Try to submit
+ await act(async () => {
+ fireEvent.click(submitButton)
+ })
+
+ expect(mockOnNext).not.toHaveBeenCalled()
+ expect(mockChangePassword).not.toHaveBeenCalled()
+ })
+
+ it('calls onNext and changePassword when passwords are different and valid', async () => {
+ render()
+
+ const newPasswordInput = screen.getByPlaceholderText('New password')
+ const confirmPasswordInput = screen.getByPlaceholderText('Confirm password')
+ const submitButton = screen.getByText('Continue')
+
+ const newPassword = 'MyNewStrongPassword456!'
+
+ // Type a different password
+ act(() => {
+ fireEvent.change(newPasswordInput, { target: { value: newPassword } })
+ fireEvent.change(confirmPasswordInput, { target: { value: newPassword } })
+ })
+
+ // Submit the form
+ await act(async () => {
+ fireEvent.click(submitButton)
+ })
+
+ await waitFor(() => {
+ expect(mockChangePassword).toHaveBeenCalledWith(newPassword)
+ expect(mockOnNext).toHaveBeenCalledWith(newPassword)
+ })
+ })
+
+ it('handles undefined oldPassword gracefully', async () => {
+ render()
+
+ const newPasswordInput = screen.getByPlaceholderText('New password')
+ const confirmPasswordInput = screen.getByPlaceholderText('Confirm password')
+
+ const newPassword = 'AnyPassword123!'
+
+ // Type any password - should not show "same password" error since oldPassword is undefined
+ act(() => {
+ fireEvent.change(newPasswordInput, { target: { value: newPassword } })
+ fireEvent.change(confirmPasswordInput, { target: { value: newPassword } })
+ })
+
+ await waitFor(() => {
+ expect(screen.queryByText('New password must be different from current password')).toBeNull()
+ })
+ })
+})
diff --git a/apps/extension/src/app/features/settings/password/ChangePasswordForm.tsx b/apps/extension/src/app/features/settings/password/ChangePasswordForm.tsx
index c03b41a1cac..d82b56ba129 100644
--- a/apps/extension/src/app/features/settings/password/ChangePasswordForm.tsx
+++ b/apps/extension/src/app/features/settings/password/ChangePasswordForm.tsx
@@ -1,13 +1,19 @@
-import { useCallback } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { PADDING_STRENGTH_INDICATOR, PasswordInput } from 'src/app/components/PasswordInput'
import { Button, Flex, Text } from 'ui/src'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
-import { usePasswordForm } from 'wallet/src/utils/password'
+import { PasswordErrors, usePasswordForm } from 'wallet/src/utils/password'
-export function ChangePasswordForm({ onNext }: { onNext: (password: string) => void }): JSX.Element {
+export function ChangePasswordForm({
+ oldPassword,
+ onNext,
+}: {
+ oldPassword: string | undefined
+ onNext: (password: string) => void
+}): JSX.Element {
const { t } = useTranslation()
const {
@@ -20,18 +26,45 @@ export function ChangePasswordForm({ onNext }: { onNext: (password: string) => v
confirmPassword,
onChangeConfirmPassword,
setHideInput,
- errorText,
+ errorText: baseErrorText,
checkSubmit,
} = usePasswordForm()
+ const [customError, setCustomError] = useState(undefined)
+
+ // Check if new password is same as old password
+ const isSamePassword = useMemo(
+ () => Boolean(password && oldPassword && password === oldPassword),
+ [password, oldPassword],
+ )
+
+ // Update custom error when password matches old password
+ useEffect(() => {
+ setCustomError(isSamePassword ? PasswordErrors.SamePassword : undefined)
+ }, [isSamePassword])
+
+ // Override error text if custom error exists
+ const errorText = useMemo(() => {
+ if (customError === PasswordErrors.SamePassword) {
+ return t('common.input.password.error.same')
+ }
+ return baseErrorText
+ }, [customError, baseErrorText, t])
+
const onSubmit = useCallback(async () => {
+ // Check for same password error
+ if (isSamePassword) {
+ setCustomError(PasswordErrors.SamePassword)
+ return
+ }
+
if (checkSubmit()) {
// Just change the password and pass it to the parent
await Keyring.changePassword(password)
sendAnalyticsEvent(ExtensionEventName.PasswordChanged)
onNext(password)
}
- }, [checkSubmit, password, onNext])
+ }, [checkSubmit, password, onNext, isSamePassword])
return (
@@ -70,7 +103,7 @@ export function ChangePasswordForm({ onNext }: { onNext: (password: string) => v
-
diff --git a/apps/extension/src/app/features/settings/password/CreateNewPasswordModal.tsx b/apps/extension/src/app/features/settings/password/CreateNewPasswordModal.tsx
index 65860afaa14..9411c1fa006 100644
--- a/apps/extension/src/app/features/settings/password/CreateNewPasswordModal.tsx
+++ b/apps/extension/src/app/features/settings/password/CreateNewPasswordModal.tsx
@@ -7,10 +7,12 @@ import { ModalName } from 'uniswap/src/features/telemetry/constants'
export function CreateNewPasswordModal({
isOpen,
+ oldPassword,
onNext,
onClose,
}: {
isOpen: boolean
+ oldPassword: string | undefined
onNext: (password: string) => void
onClose: () => void
}): JSX.Element {
@@ -36,7 +38,7 @@ export function CreateNewPasswordModal({
{t('settings.setting.password.change.title')}
-
+
)
diff --git a/apps/extension/src/app/features/settings/password/__snapshots__/ChangePasswordForm.test.tsx.snap b/apps/extension/src/app/features/settings/password/__snapshots__/ChangePasswordForm.test.tsx.snap
new file mode 100644
index 00000000000..71e0ea60127
--- /dev/null
+++ b/apps/extension/src/app/features/settings/password/__snapshots__/ChangePasswordForm.test.tsx.snap
@@ -0,0 +1,279 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ChangePasswordForm renders without error 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+
+
+
+
+ Continue
+
+
+
+
+
+
+
+ ,
+ "container":
+
+
+
+
+
+
+ Continue
+
+
+
+
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "store": {
+ "dispatch": [Function],
+ "getState": [Function],
+ "replaceReducer": [Function],
+ "subscribe": [Function],
+ Symbol(observable): [Function],
+ },
+ "unmount": [Function],
+}
+`;
diff --git a/apps/extension/src/app/features/settings/password/usePasswordResetFlow.test.ts b/apps/extension/src/app/features/settings/password/usePasswordResetFlow.test.ts
index 9ccb4c71a73..3685be9bde1 100644
--- a/apps/extension/src/app/features/settings/password/usePasswordResetFlow.test.ts
+++ b/apps/extension/src/app/features/settings/password/usePasswordResetFlow.test.ts
@@ -67,6 +67,12 @@ describe('usePasswordResetFlow', () => {
expect(result.current.flowState).toBe(PasswordResetFlowState.None)
})
+ it('should initialize with undefined oldPassword', () => {
+ const { result } = renderHook(() => usePasswordResetFlow())
+
+ expect(result.current.oldPassword).toBeUndefined()
+ })
+
it('should transition to EnterCurrentPassword when starting password reset', () => {
const { result } = renderHook(() => usePasswordResetFlow())
@@ -91,6 +97,22 @@ describe('usePasswordResetFlow', () => {
expect(result.current.flowState).toBe(PasswordResetFlowState.EnterNewPassword)
})
+ it('should store oldPassword when valid password is provided', () => {
+ const { result } = renderHook(() => usePasswordResetFlow())
+ const testPassword = 'myOldPassword123!'
+
+ act(() => {
+ result.current.startPasswordReset()
+ })
+
+ act(() => {
+ result.current.onPasswordModalNext(testPassword)
+ })
+
+ expect(result.current.oldPassword).toBe(testPassword)
+ expect(result.current.flowState).toBe(PasswordResetFlowState.EnterNewPassword)
+ })
+
it('should return to None state when no password is provided', () => {
const { result } = renderHook(() => usePasswordResetFlow())
@@ -105,6 +127,32 @@ describe('usePasswordResetFlow', () => {
expect(result.current.flowState).toBe(PasswordResetFlowState.None)
})
+ it('should clear oldPassword when no password is provided', () => {
+ const { result } = renderHook(() => usePasswordResetFlow())
+
+ act(() => {
+ result.current.startPasswordReset()
+ })
+
+ act(() => {
+ result.current.onPasswordModalNext('validPassword')
+ })
+
+ expect(result.current.oldPassword).toBe('validPassword')
+
+ // Go back and provide no password
+ act(() => {
+ result.current.startPasswordReset()
+ })
+
+ act(() => {
+ result.current.onPasswordModalNext()
+ })
+
+ expect(result.current.oldPassword).toBeUndefined()
+ expect(result.current.flowState).toBe(PasswordResetFlowState.None)
+ })
+
it('should transition to BiometricAuth when biometric is enabled', () => {
mockUseHasBiometricUnlockCredential.mockReturnValue(true)
const { result } = renderHook(() => usePasswordResetFlow())
@@ -160,6 +208,28 @@ describe('usePasswordResetFlow', () => {
expect(result.current.flowState).toBe(PasswordResetFlowState.None)
})
+ it('should clear oldPassword when closeModal is called with matching state', () => {
+ const { result } = renderHook(() => usePasswordResetFlow())
+
+ act(() => {
+ result.current.startPasswordReset()
+ })
+
+ act(() => {
+ result.current.onPasswordModalNext('testPassword123')
+ })
+
+ expect(result.current.oldPassword).toBe('testPassword123')
+ expect(result.current.flowState).toBe(PasswordResetFlowState.EnterNewPassword)
+
+ act(() => {
+ result.current.closeModal(PasswordResetFlowState.EnterNewPassword)
+ })
+
+ expect(result.current.flowState).toBe(PasswordResetFlowState.None)
+ expect(result.current.oldPassword).toBeUndefined()
+ })
+
it('should not close modal when closeModal is called with non-matching state', () => {
const { result } = renderHook(() => usePasswordResetFlow())
@@ -176,6 +246,29 @@ describe('usePasswordResetFlow', () => {
expect(result.current.flowState).toBe(PasswordResetFlowState.EnterCurrentPassword)
})
+ it('should not clear oldPassword when closeModal is called with non-matching state', () => {
+ const { result } = renderHook(() => usePasswordResetFlow())
+
+ act(() => {
+ result.current.startPasswordReset()
+ })
+
+ act(() => {
+ result.current.onPasswordModalNext('testPassword456')
+ })
+
+ expect(result.current.oldPassword).toBe('testPassword456')
+ expect(result.current.flowState).toBe(PasswordResetFlowState.EnterNewPassword)
+
+ act(() => {
+ result.current.closeModal(PasswordResetFlowState.BiometricAuth)
+ })
+
+ // Should not clear oldPassword or change state
+ expect(result.current.flowState).toBe(PasswordResetFlowState.EnterNewPassword)
+ expect(result.current.oldPassword).toBe('testPassword456')
+ })
+
it('should transition to BiometricAuth state when biometric is enabled and trigger internal mutation', () => {
mockUseHasBiometricUnlockCredential.mockReturnValue(true)
const { result } = renderHook(() => usePasswordResetFlow())
diff --git a/apps/extension/src/app/features/settings/password/usePasswordResetFlow.ts b/apps/extension/src/app/features/settings/password/usePasswordResetFlow.ts
index 48e02692ef5..9c2b61b4eda 100644
--- a/apps/extension/src/app/features/settings/password/usePasswordResetFlow.ts
+++ b/apps/extension/src/app/features/settings/password/usePasswordResetFlow.ts
@@ -54,6 +54,7 @@ export enum PasswordResetFlowState {
interface PasswordResetFlowResult {
// State
flowState: PasswordResetFlowState
+ oldPassword: string | undefined
// Actions
startPasswordReset: () => void
@@ -68,6 +69,7 @@ interface PasswordResetFlowResult {
export function usePasswordResetFlow(): PasswordResetFlowResult {
const dispatch = useDispatch()
const [flowState, setFlowState] = useState(PasswordResetFlowState.None)
+ const [oldPassword, setOldPassword] = useState(undefined)
const hasBiometricUnlockCredential = useHasBiometricUnlockCredential()
@@ -95,15 +97,18 @@ export function usePasswordResetFlow(): PasswordResetFlowResult {
// This check ensures the close action is from user interaction, not from modal state changes.
if (flowState === expectedState) {
setFlowState(PasswordResetFlowState.None)
+ setOldPassword(undefined)
}
})
const onPasswordModalNext = useEvent((password?: string): void => {
if (!password) {
setFlowState(PasswordResetFlowState.None)
+ setOldPassword(undefined)
return
}
+ setOldPassword(password)
setFlowState(PasswordResetFlowState.EnterNewPassword)
})
@@ -127,6 +132,7 @@ export function usePasswordResetFlow(): PasswordResetFlowResult {
return {
// State
flowState,
+ oldPassword,
// Actions
startPasswordReset,
diff --git a/apps/extension/src/app/hooks/useIsExtensionPasskeyImportEnabled.ts b/apps/extension/src/app/hooks/useIsExtensionPasskeyImportEnabled.ts
index 8abf941051b..823ac6ba868 100644
--- a/apps/extension/src/app/hooks/useIsExtensionPasskeyImportEnabled.ts
+++ b/apps/extension/src/app/hooks/useIsExtensionPasskeyImportEnabled.ts
@@ -1,5 +1,4 @@
-import { FeatureFlags } from 'uniswap/src/features/gating/flags'
-import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
+import { FeatureFlags, useFeatureFlag } from '@universe/gating'
export function useIsExtensionPasskeyImportEnabled(): boolean {
return useFeatureFlag(FeatureFlags.EmbeddedWallet)
diff --git a/apps/extension/src/app/saga.ts b/apps/extension/src/app/saga.ts
index a5e6ddbf951..e4c17a340bd 100644
--- a/apps/extension/src/app/saga.ts
+++ b/apps/extension/src/app/saga.ts
@@ -28,13 +28,6 @@ import {
prepareAndSignSwapSaga,
prepareAndSignSwapSagaName,
} from 'wallet/src/features/transactions/swap/configuredSagas'
-import { swapActions, swapReducer, swapSaga, swapSagaName } from 'wallet/src/features/transactions/swap/swapSaga'
-import {
- tokenWrapActions,
- tokenWrapReducer,
- tokenWrapSaga,
- tokenWrapSagaName,
-} from 'wallet/src/features/transactions/swap/wrapSaga'
import { watchTransactionEvents } from 'wallet/src/features/transactions/watcher/transactionFinalizationSaga'
import { transactionWatcher } from 'wallet/src/features/transactions/watcher/transactionWatcherSaga'
import {
@@ -83,18 +76,6 @@ export const monitoredSagas: Record = {
reducer: executeSwapReducer,
actions: executeSwapActions,
},
- [swapSagaName]: {
- name: swapSagaName,
- wrappedSaga: swapSaga,
- reducer: swapReducer,
- actions: swapActions,
- },
- [tokenWrapSagaName]: {
- name: tokenWrapSagaName,
- wrappedSaga: tokenWrapSaga,
- reducer: tokenWrapReducer,
- actions: tokenWrapActions,
- },
[removeDelegationSagaName]: {
name: removeDelegationSagaName,
wrappedSaga: removeDelegationSaga,
diff --git a/apps/extension/src/background/backgroundDappRequests.ts b/apps/extension/src/background/backgroundDappRequests.ts
index 968024943b1..a72af9d0126 100644
--- a/apps/extension/src/background/backgroundDappRequests.ts
+++ b/apps/extension/src/background/backgroundDappRequests.ts
@@ -1,3 +1,4 @@
+/* eslint-disable max-lines */
import { rpcErrors, serializeError } from '@metamask/rpc-errors'
import { removeDappConnection } from 'src/app/features/dapp/actions'
import { changeChain } from 'src/app/features/dapp/changeChain'
diff --git a/apps/extension/src/background/utils/persistedStateUtils.ts b/apps/extension/src/background/utils/persistedStateUtils.ts
index 50f2b49cdc3..2b646c64565 100644
--- a/apps/extension/src/background/utils/persistedStateUtils.ts
+++ b/apps/extension/src/background/utils/persistedStateUtils.ts
@@ -2,6 +2,7 @@ import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector'
import { STATE_STORAGE_KEY } from 'src/store/constants'
import { ExtensionState } from 'src/store/extensionReducer'
import { EXTENSION_STATE_VERSION } from 'src/store/migrations'
+import { deviceAccessTimeoutToMinutes } from 'uniswap/src/features/settings/constants'
import { logger } from 'utilities/src/logger/logger'
export async function readReduxStateFromStorage(storageChanges?: {
@@ -30,6 +31,11 @@ export async function readIsOnboardedFromStorage(): Promise {
return state ? isOnboardedSelector(state) : false
}
+export async function readDeviceAccessTimeoutMinutesFromStorage(): Promise {
+ const state = await readReduxStateFromStorage()
+ return state ? deviceAccessTimeoutToMinutes(state.userSettings.deviceAccessTimeout) : undefined
+}
+
/**
* Checks if Redux migrations are pending by comparing persisted version with current version
* @returns true if migrations are pending and sidebar should handle the request
diff --git a/apps/extension/src/entrypoints/background.ts b/apps/extension/src/entrypoints/background.ts
index f75bfd172b9..773b1b9114a 100644
--- a/apps/extension/src/entrypoints/background.ts
+++ b/apps/extension/src/entrypoints/background.ts
@@ -1,5 +1,6 @@
import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, order matters
+import { AUTO_LOCK_ALARM_NAME } from 'src/app/components/AutoLockProvider'
import { initStatSigForBrowserScripts } from 'src/app/core/initStatSigForBrowserScripts'
import { focusOrCreateOnboardingTab } from 'src/app/navigation/focusOrCreateOnboardingTab'
import { initExtensionAnalytics } from 'src/app/utils/analytics'
@@ -14,9 +15,15 @@ import {
ContentScriptUtilityMessageType,
} from 'src/background/messagePassing/types/requests'
import { setSidePanelBehavior, setSidePanelOptions } from 'src/background/utils/chromeSidePanelUtils'
-import { readIsOnboardedFromStorage } from 'src/background/utils/persistedStateUtils'
+import {
+ readDeviceAccessTimeoutMinutesFromStorage,
+ readIsOnboardedFromStorage,
+} from 'src/background/utils/persistedStateUtils'
import { uniswapUrls } from 'uniswap/src/constants/urls'
+import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
+import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logger } from 'utilities/src/logger/logger'
+import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
import { defineBackground } from 'wxt/utils/define-background'
async function enableSidebar(): Promise {
@@ -69,6 +76,53 @@ function makeBackground(): void {
await checkAndHandleOnboarding()
})
+ // Auto-lock alarm listener
+ chrome.alarms.onAlarm.addListener((alarm) => {
+ if (alarm.name === AUTO_LOCK_ALARM_NAME) {
+ Keyring.lock()
+ .then(() => {
+ sendAnalyticsEvent(ExtensionEventName.ChangeLockedState, {
+ locked: true,
+ location: 'background',
+ })
+ })
+ .catch((error) => {
+ logger.error(error, {
+ tags: {
+ file: 'background.ts',
+ function: 'alarms.onAlarm',
+ },
+ })
+ })
+ }
+ })
+
+ // Listen for sidebar port disconnects to schedule auto-lock alarm
+ chrome.runtime.onConnect.addListener((port) => {
+ if (port.name === AUTO_LOCK_ALARM_NAME) {
+ port.onDisconnect.addListener(async () => {
+ try {
+ // Get timeout setting from Redux state
+ const delayInMinutes = await readDeviceAccessTimeoutMinutesFromStorage()
+ if (delayInMinutes === undefined) {
+ return
+ }
+
+ // Schedule alarm
+ chrome.alarms.create(AUTO_LOCK_ALARM_NAME, { delayInMinutes })
+ logger.debug('background', 'port.onDisconnect', `Scheduled auto-lock alarm for ${delayInMinutes} minutes`)
+ } catch (error) {
+ logger.error(error, {
+ tags: {
+ file: 'background.ts',
+ function: 'port.onDisconnect',
+ },
+ })
+ }
+ })
+ }
+ })
+
// on arc browser, show unsupported browser page (lives on onboarding flow)
// this is because arc doesn't support the sidebar
contentScriptUtilityMessageChannel.addMessageListener(
diff --git a/apps/extension/src/entrypoints/sidepanel/main.tsx b/apps/extension/src/entrypoints/sidepanel/main.tsx
index 4cbeaaa35d0..5465a71e672 100644
--- a/apps/extension/src/entrypoints/sidepanel/main.tsx
+++ b/apps/extension/src/entrypoints/sidepanel/main.tsx
@@ -11,8 +11,10 @@ import { createRoot } from 'react-dom/client'
import SidebarApp from 'src/app/core/SidebarApp'
import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels'
import { OnboardingMessageType } from 'src/background/messagePassing/types/ExtensionMessages'
+import { getReduxStore } from 'src/store/store'
import { ExtensionAppLocation, StoreSynchronization } from 'src/store/storeSynchronization'
import { initializeScrollWatcher } from 'uniswap/src/components/modals/ScrollLock'
+import { initializePortfolioQueryOverrides } from 'uniswap/src/data/rest/portfolioBalanceOverrides'
import { logger } from 'utilities/src/logger/logger'
// biome-ignore lint/suspicious/noExplicitAny: Global polyfill cleanup requires any type for runtime modification
;(globalThis as any).regeneratorRuntime = undefined
@@ -44,6 +46,7 @@ export function makeSidebar(): void {
}
StoreSynchronization.init(ExtensionAppLocation.SidePanel)
+ initializePortfolioQueryOverrides({ store: getReduxStore() })
initSidebar()
initializeScrollWatcher()
}
diff --git a/apps/extension/src/manifest.json b/apps/extension/src/manifest.json
index 4b599a3f4bd..c2d1319446d 100644
--- a/apps/extension/src/manifest.json
+++ b/apps/extension/src/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "Uniswap Extension",
"description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.",
- "version": "1.61.0",
+ "version": "1.62.0",
"minimum_chrome_version": "116",
"icons": {
"16": "assets/icon16.png",
diff --git a/apps/extension/src/store/migrations.test.ts b/apps/extension/src/store/migrations.test.ts
index f78dd2d2f16..e257005881e 100644
--- a/apps/extension/src/store/migrations.test.ts
+++ b/apps/extension/src/store/migrations.test.ts
@@ -36,6 +36,7 @@ import {
v24Schema,
v25Schema,
v26Schema,
+ v27Schema,
} from 'src/store/schema'
import { initialUniswapBehaviorHistoryState } from 'uniswap/src/features/behaviorHistory/slice'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
@@ -48,7 +49,11 @@ import { initialTokensState } from 'uniswap/src/features/tokens/slice/slice'
import { initialTransactionsState } from 'uniswap/src/features/transactions/slice'
import { TransactionStatus, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails'
import { initialVisibilityState } from 'uniswap/src/features/visibility/slice'
-import { testMigrateSearchHistory, testRemoveTHBFromCurrency } from 'uniswap/src/state/uniswapMigrationTests'
+import {
+ testAddActivityVisibility,
+ testMigrateSearchHistory,
+ testRemoveTHBFromCurrency,
+} from 'uniswap/src/state/uniswapMigrationTests'
import { getAllKeysOfNestedObject } from 'utilities/src/primitives/objects'
import { initialAppearanceSettingsState } from 'wallet/src/features/appearance/slice'
import { initialBatchedTransactionsState } from 'wallet/src/features/batchedTransactions/slice'
@@ -347,4 +352,8 @@ describe('Redux state migrations', () => {
it('migrates from v26 to v27', () => {
testMigrateSearchHistory(migrations[27], v26Schema)
})
+
+ it('migrates from v27 to v29', () => {
+ testAddActivityVisibility(migrations[29], v27Schema)
+ })
})
diff --git a/apps/extension/src/store/migrations.ts b/apps/extension/src/store/migrations.ts
index e9534188698..189147fee8a 100644
--- a/apps/extension/src/store/migrations.ts
+++ b/apps/extension/src/store/migrations.ts
@@ -7,6 +7,7 @@ import {
removeDappInfoToChromeLocalStorage,
} from 'src/store/extensionMigrations'
import {
+ addActivityVisibility,
addDismissedBridgedAndCompatibleWarnings,
migrateSearchHistory,
removeThaiBahtFromFiatCurrency,
@@ -67,6 +68,7 @@ export const migrations = {
26: migrateLiquidityTransactionInfo,
27: migrateSearchHistory,
28: addDismissedBridgedAndCompatibleWarnings,
+ 29: addActivityVisibility,
}
-export const EXTENSION_STATE_VERSION = 28
+export const EXTENSION_STATE_VERSION = 29
diff --git a/apps/extension/src/store/schema.ts b/apps/extension/src/store/schema.ts
index 0fd7dc87697..3238da4aa9f 100644
--- a/apps/extension/src/store/schema.ts
+++ b/apps/extension/src/store/schema.ts
@@ -276,6 +276,8 @@ export const v25Schema = { ...v24Schema }
export const v26Schema = { ...v25Schema }
-const v27Schema = { ...v26Schema }
+export const v27Schema = { ...v26Schema }
-export const getSchema = (): typeof v27Schema => v27Schema
+const v29Schema = { ...v27Schema, visibility: { ...v27Schema.visibility, activity: {} } }
+
+export const getSchema = (): typeof v29Schema => v29Schema
diff --git a/apps/extension/tsconfig.json b/apps/extension/tsconfig.json
index 007f0494490..72cc9db7534 100644
--- a/apps/extension/tsconfig.json
+++ b/apps/extension/tsconfig.json
@@ -17,6 +17,12 @@
{
"path": "../../packages/ui"
},
+ {
+ "path": "../../packages/sessions"
+ },
+ {
+ "path": "../../packages/gating"
+ },
{
"path": "../../packages/api"
}
diff --git a/apps/extension/wxt.config.ts b/apps/extension/wxt.config.ts
index 37e4fc458e7..ec88b1603c4 100644
--- a/apps/extension/wxt.config.ts
+++ b/apps/extension/wxt.config.ts
@@ -16,7 +16,7 @@ const icons = {
const BASE_NAME = 'Uniswap Extension'
const BASE_DESCRIPTION = "The Uniswap Extension is a self-custody crypto wallet that's built for swapping."
-const BASE_VERSION = '1.61.0'
+const BASE_VERSION = '1.62.0'
const BUILD_NUM = parseInt(process.env.BUILD_NUM || '0')
const EXTENSION_VERSION = `${BASE_VERSION}.${BUILD_NUM}`
diff --git a/apps/mobile/.eslintrc.js b/apps/mobile/.eslintrc.js
index 8a2ee3a2cee..26a62d9f007 100644
--- a/apps/mobile/.eslintrc.js
+++ b/apps/mobile/.eslintrc.js
@@ -58,13 +58,6 @@ module.exports = {
'import/no-unused-modules': 'off',
},
},
- {
- // Allow test files to exceed max-lines limit
- files: ['**/*.test.ts', '**/*.test.tsx', '**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'],
- rules: {
- 'max-lines': 'off',
- },
- },
],
rules: {
'rulesdir/i18n': 'error',
diff --git a/apps/mobile/.maestro/flows/deeplinks/deeplink-comprehensive.yaml b/apps/mobile/.maestro/flows/deeplinks/deeplink-comprehensive.yaml
index 7986a843f72..71df2ffe3af 100644
--- a/apps/mobile/.maestro/flows/deeplinks/deeplink-comprehensive.yaml
+++ b/apps/mobile/.maestro/flows/deeplinks/deeplink-comprehensive.yaml
@@ -12,7 +12,7 @@ env:
- runScript:
file: ../../scripts/performance/dist/actions/start-flow.js
env:
- FLOW_NAME: 'deeplink-comprehensive'
+ FLOW_NAME: "deeplink-comprehensive"
# Run prerequisite flows (tracked as sub-flows)
- runFlow: ../../shared-flows/start.yaml
@@ -22,15 +22,15 @@ env:
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'onramp-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "onramp-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://app/fiatonramp?userAddress=0xEEf806b3Cae8fcecAe1793EE1e0B2c738F61C6bB&source=push'
+ link: "uniswap://app/fiatonramp?userAddress=0xEEf806b3Cae8fcecAe1793EE1e0B2c738F61C6bB&source=push"
autoVerify: true
# Handle iOS deeplink permission dialog (optional - only appears on first run)
- tapOn:
- text: 'Open'
+ text: "Open"
optional: true
- waitForAnimationToEnd:
timeout: 5000
@@ -41,43 +41,43 @@ env:
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'onramp-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "onramp-deeplink"
+ PHASE: "end"
- killApp
# Open widget deeplink
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'widget-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "widget-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://widget/#/tokens/ethereum/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
+ link: "uniswap://widget/#/tokens/ethereum/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"
autoVerify: true
- waitForAnimationToEnd:
timeout: 3000
- assertVisible:
id: ${output.testIds.TokenDetailsHeaderText}
- text: 'Uniswap'
+ text: "Uniswap"
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'widget-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "widget-deeplink"
+ PHASE: "end"
- killApp
# Open swap deeplink
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'swap-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "swap-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://redirect?screen=swap&userAddress=0xEEf806b3Cae8fcecAe1793EE1e0B2c738F61C6bB&inputCurrencyId=1-0x6B175474E89094C44Da98b954EedeAC495271d0F&outputCurrencyId=1-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984¤cyField=input&amount=100'
+ link: "uniswap://redirect?screen=swap&userAddress=0xEEf806b3Cae8fcecAe1793EE1e0B2c738F61C6bB&inputCurrencyId=1-0x6B175474E89094C44Da98b954EedeAC495271d0F&outputCurrencyId=1-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984¤cyField=input&amount=100"
autoVerify: true
- waitForAnimationToEnd:
timeout: 3000
@@ -92,20 +92,20 @@ env:
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'swap-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "swap-deeplink"
+ PHASE: "end"
- killApp
# Open token details deeplink
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'token-details-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "token-details-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://app/tokendetails?currencyId=10-0x6fd9d7ad17242c41f7131d257212c54a0e816691&source=push'
+ link: "uniswap://app/tokendetails?currencyId=10-0x6fd9d7ad17242c41f7131d257212c54a0e816691&source=push"
autoVerify: true
- waitForAnimationToEnd:
timeout: 3000
@@ -116,20 +116,20 @@ env:
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'token-details-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "token-details-deeplink"
+ PHASE: "end"
- killApp
# Open transaction history deeplink
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'transaction-history-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "transaction-history-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://redirect?screen=transaction&fiatOnRamp=true&userAddress=0xEEf806b3Cae8fcecAe1793EE1e0B2c738F61C6bB'
+ link: "uniswap://redirect?screen=transaction&fiatOnRamp=true&userAddress=0xEEf806b3Cae8fcecAe1793EE1e0B2c738F61C6bB"
autoVerify: true
- waitForAnimationToEnd:
timeout: 3000
@@ -138,20 +138,20 @@ env:
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'transaction-history-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "transaction-history-deeplink"
+ PHASE: "end"
- killApp
# Invalid deeplink (should fail gracefully and remain functional)
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'invalid-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "invalid-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://invalid-path'
+ link: "uniswap://invalid-path"
autoVerify: true
- waitForAnimationToEnd:
timeout: 3000
@@ -160,46 +160,46 @@ env:
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'invalid-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "invalid-deeplink"
+ PHASE: "end"
- killApp
# Open moonpayOnly onramp deeplink
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'moonpay-onramp-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "moonpay-onramp-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://app/fiatonramp?source=push&moonpayOnly=true&moonpayCurrencyCode=usdc&amount=200'
+ link: "uniswap://app/fiatonramp?source=push&moonpayOnly=true&moonpayCurrencyCode=usdc&amount=200"
autoVerify: true
- waitForAnimationToEnd:
timeout: 5000
- assertVisible:
id: ${output.testIds.ForFormTokenSelected}
- text: 'USD Coin'
+ text: "USD Coin"
- assertVisible:
id: ${output.testIds.BuyFormAmountInput}
- text: '200'
+ text: "200"
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'moonpay-onramp-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "moonpay-onramp-deeplink"
+ PHASE: "end"
- killApp
# Open scantastic deeplink (when user scans QR code on the extension)
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'scantastic-deeplink'
- PHASE: 'start'
+ ACTION: "openLink"
+ TARGET: "scantastic-deeplink"
+ PHASE: "start"
- openLink:
- link: 'uniswap://scantastic?pubKey=%7B%22alg%22%3A%22RSA-OAEP-256%22%2C%22kty%22%3A%22RSA%22%2C%22n%22%3A%224X4nRAEZ8FWoVmoQ5KrxcssIR7XpdcVo_y7yD1SgmYuXekvHMIYuLxxkxVTjsyxj2s9jctIHOhZ-g96w4oM8-HXjCJG_v55w6FZyDskllcmaGeUlZFwWkiqZ-PKkHCWxCe_dZGvL33sazS_L8P3eAxXEPEJMG9p9lxsIlPp7ki0GSyVjq4rrHgW0lIz6qy6WqHbnyJWQAMSPnZTGM697ZCdkW_GTD3MyqitBwK5xNQN8Pxgbu6S7xbQglanYNBbeMYpJ3X1PDl37sp16YwPm6ryGaX1ESDPHa3M7-_we_yQEUQvtU5t2dd8chISJX8L1D7s8iNxM1LxG_nZTwKnccRPtrzKj-osBMbfCoU4fiNS2LC7q6zsyHxgDpeFlrV--iboQ9TsaQ7RGaFOSKs0l74_dt8GvX2JtNJ0ah8K__eNg9q0xBD8DTdeY2duMTEKJZIKgEyX0KUiRpsbsNmm_76iqhhZyYvcb6mwvNnVcXPg_TabX7lQEEippd7JTWVnF2LKzldlUonchQSsbLEUlN_ALa0Nuq6GG1MVJ0JjSsNMcpin6rH9fPzmDKkqzM2qvhdyuV66vkS82Wj9tQpqXL_jkRk7bQsDlB-HiVbzM2oNPk6or5u6p5tJni0th6BZm4z-sYgmMj3D5xHeusyap-8dmS9J4mXDxGLL_NloaHY8%22%2C%22e%22%3A%22AQAB%22%7D&uuid=28c01911-8e69-46e9-b2f0-f5e719bb714b&vendor=Apple&model=Macintosh&browser=Chrome'
+ link: "uniswap://scantastic?pubKey=%7B%22alg%22%3A%22RSA-OAEP-256%22%2C%22kty%22%3A%22RSA%22%2C%22n%22%3A%224X4nRAEZ8FWoVmoQ5KrxcssIR7XpdcVo_y7yD1SgmYuXekvHMIYuLxxkxVTjsyxj2s9jctIHOhZ-g96w4oM8-HXjCJG_v55w6FZyDskllcmaGeUlZFwWkiqZ-PKkHCWxCe_dZGvL33sazS_L8P3eAxXEPEJMG9p9lxsIlPp7ki0GSyVjq4rrHgW0lIz6qy6WqHbnyJWQAMSPnZTGM697ZCdkW_GTD3MyqitBwK5xNQN8Pxgbu6S7xbQglanYNBbeMYpJ3X1PDl37sp16YwPm6ryGaX1ESDPHa3M7-_we_yQEUQvtU5t2dd8chISJX8L1D7s8iNxM1LxG_nZTwKnccRPtrzKj-osBMbfCoU4fiNS2LC7q6zsyHxgDpeFlrV--iboQ9TsaQ7RGaFOSKs0l74_dt8GvX2JtNJ0ah8K__eNg9q0xBD8DTdeY2duMTEKJZIKgEyX0KUiRpsbsNmm_76iqhhZyYvcb6mwvNnVcXPg_TabX7lQEEippd7JTWVnF2LKzldlUonchQSsbLEUlN_ALa0Nuq6GG1MVJ0JjSsNMcpin6rH9fPzmDKkqzM2qvhdyuV66vkS82Wj9tQpqXL_jkRk7bQsDlB-HiVbzM2oNPk6or5u6p5tJni0th6BZm4z-sYgmMj3D5xHeusyap-8dmS9J4mXDxGLL_NloaHY8%22%2C%22e%22%3A%22AQAB%22%7D&uuid=28c01911-8e69-46e9-b2f0-f5e719bb714b&vendor=Apple&model=Macintosh&browser=Chrome"
autoVerify: true
- waitForAnimationToEnd:
timeout: 3000
@@ -207,16 +207,16 @@ env:
id: ${output.testIds.ScantasticConfirmationTitle}
- assertVisible:
id: ${output.testIds.ScantasticDevice}
- text: 'Apple Macintosh'
+ text: "Apple Macintosh"
- assertVisible:
id: ${output.testIds.ScantasticBrowser}
- text: 'Chrome'
+ text: "Chrome"
- runScript:
file: ../../scripts/performance/dist/actions/track-action.js
env:
- ACTION: 'openLink'
- TARGET: 'scantastic-deeplink'
- PHASE: 'end'
+ ACTION: "openLink"
+ TARGET: "scantastic-deeplink"
+ PHASE: "end"
- killApp
# End flow tracking
@@ -228,4 +228,4 @@ env:
file: ../../scripts/performance/upload-metrics.js
env:
DATADOG_API_KEY: ${DATADOG_API_KEY}
- ENVIRONMENT: 'maestro_cloud'
+ ENVIRONMENT: "maestro_cloud"
diff --git a/apps/mobile/.maestro/flows/restore/restore-new-device.yaml b/apps/mobile/.maestro/flows/restore/restore-new-device.yaml
index 0f73d3201f3..08ab78e29fa 100644
--- a/apps/mobile/.maestro/flows/restore/restore-new-device.yaml
+++ b/apps/mobile/.maestro/flows/restore/restore-new-device.yaml
@@ -184,10 +184,29 @@ env:
PHASE: 'end'
- waitForAnimationToEnd
+# Wait for cloud backup to fail - handle both possible error states
+# First try waiting for "No backups found"
- extendedWaitUntil:
visible:
text: 'No backups found'
- timeout: 5000 # wait for cloud backup to fail
+ timeout: 5000
+ optional: true
+
+# If that didn't appear, wait for "Error while importing backups"
+- extendedWaitUntil:
+ visible:
+ text: 'Error while importing backups'
+ timeout: 5000
+ optional: true
+
+# If error while importing backups appeared, tap to enter recovery phrase manually
+- runFlow:
+ when:
+ visible:
+ text: 'Enter recovery phrase'
+ commands:
+ - tapOn:
+ text: 'Enter recovery phrase'
# Track seed phrase input
- runScript:
diff --git a/apps/mobile/.maestro/shared-flows/navigate-to-explore.yaml b/apps/mobile/.maestro/shared-flows/navigate-to-explore.yaml
index 3431fe0cb24..200f366aeaf 100644
--- a/apps/mobile/.maestro/shared-flows/navigate-to-explore.yaml
+++ b/apps/mobile/.maestro/shared-flows/navigate-to-explore.yaml
@@ -1,20 +1,37 @@
appId: com.uniswap.mobile.dev
---
# This flow handles the common action of navigating to the Explore tab
-# from the main wallet screen.
+# from the main wallet screen. It supports both bottom tabs navigation (new)
+# and the legacy floating navigation bar.
# Wait for the home screen to be visible
- extendedWaitUntil:
- visible: 'noop'
+ visible: "noop"
timeout: 2000
optional: true
-# Tap on the Explore/Search button in the navigation bar
+# OPTION 1: Try bottom tabs navigation first (new UI pattern)
+# This will be available when BottomTabs feature flag is enabled
+- tapOn:
+ id: ${output.testIds.ExploreTab}
+ optional: true
+
+# TODO: INFRA-1074 - Remove this fallback when we no longer support the legacy navigation bar
+# OPTION 2: Fallback to legacy floating navigation bar
+# This will be used when BottomTabs feature flag is disabled
- tapOn:
id: ${output.testIds.SearchTokensAndWallets}
+ optional: true
+# Wait for tab animations to complete (200ms animation duration)
- waitForAnimationToEnd
# Verify we're in the Explore screen by checking for the search input
+# This verification works for both navigation patterns
+- extendedWaitUntil:
+ visible:
+ id: ${output.testIds.ExploreSearchInput}
+ timeout: 3000
+
- assertVisible:
id: ${output.testIds.ExploreSearchInput}
diff --git a/apps/mobile/Gemfile b/apps/mobile/Gemfile
index a07e6ca69f3..a9e494ea9a1 100644
--- a/apps/mobile/Gemfile
+++ b/apps/mobile/Gemfile
@@ -1,8 +1,8 @@
source "https://rubygems.org"
-gem 'fastlane', '2.214.0'
+gem 'fastlane', '2.215.0'
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
-gem 'cocoapods', '1.14.3'
+gem 'cocoapods', '1.15.0'
gem 'activesupport', '7.1.2'
gem 'xcodeproj', '1.27.0'
gem 'concurrent-ruby', '1.3.4'
diff --git a/apps/mobile/Gemfile.lock b/apps/mobile/Gemfile.lock
index b5891798a1a..c99f3366066 100644
--- a/apps/mobile/Gemfile.lock
+++ b/apps/mobile/Gemfile.lock
@@ -1,7 +1,9 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.6)
+ CFPropertyList (3.0.7)
+ base64
+ nkf
rexml
activesupport (7.1.2)
base64
@@ -13,37 +15,40 @@ GEM
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
- addressable (2.8.6)
- public_suffix (>= 2.0.2, < 6.0)
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
- artifactory (3.0.15)
+ artifactory (3.0.17)
atomos (0.1.3)
- aws-eventstream (1.3.0)
- aws-partitions (1.877.0)
- aws-sdk-core (3.190.1)
+ aws-eventstream (1.4.0)
+ aws-partitions (1.1166.0)
+ aws-sdk-core (3.233.0)
aws-eventstream (~> 1, >= 1.3.0)
- aws-partitions (~> 1, >= 1.651.0)
- aws-sigv4 (~> 1.8)
+ aws-partitions (~> 1, >= 1.992.0)
+ aws-sigv4 (~> 1.9)
+ base64
+ bigdecimal
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.75.0)
- aws-sdk-core (~> 3, >= 3.188.0)
- aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.142.0)
- aws-sdk-core (~> 3, >= 3.189.0)
+ logger
+ aws-sdk-kms (1.113.0)
+ aws-sdk-core (~> 3, >= 3.231.0)
+ aws-sigv4 (~> 1.5)
+ aws-sdk-s3 (1.199.1)
+ aws-sdk-core (~> 3, >= 3.231.0)
aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.8)
- aws-sigv4 (1.8.0)
+ aws-sigv4 (~> 1.5)
+ aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
- base64 (0.2.0)
- bigdecimal (3.1.9)
+ base64 (0.3.0)
+ bigdecimal (3.2.3)
claide (1.1.0)
- cocoapods (1.14.3)
+ cocoapods (1.15.0)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.14.3)
+ cocoapods-core (= 1.15.0)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
@@ -58,7 +63,7 @@ GEM
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.23.0, < 2.0)
- cocoapods-core (1.14.3)
+ cocoapods-core (1.15.0)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
@@ -82,19 +87,19 @@ GEM
commander (4.6.0)
highline (~> 2.0.0)
concurrent-ruby (1.3.4)
- connection_pool (2.5.0)
+ connection_pool (2.5.4)
declarative (0.0.20)
- digest-crc (0.6.5)
+ digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
- domain_name (0.6.20231109)
+ domain_name (0.6.20240107)
dotenv (2.8.1)
- drb (2.2.1)
+ drb (2.2.3)
emoji_regex (3.2.3)
escape (0.0.4)
- ethon (0.16.0)
+ ethon (0.15.0)
ffi (>= 1.15.0)
- excon (0.109.0)
- faraday (1.10.3)
+ excon (0.112.0)
+ faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -110,20 +115,20 @@ GEM
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
- faraday-em_synchrony (1.0.0)
+ faraday-em_synchrony (1.0.1)
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-multipart (1.1.1)
+ multipart-post (~> 2.0)
+ faraday-net_http (1.0.2)
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_middleware (1.2.1)
faraday (~> 1.0)
- fastimage (2.3.0)
- fastlane (2.214.0)
+ fastimage (2.4.0)
+ fastlane (2.215.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -144,6 +149,7 @@ GEM
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
+ http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
@@ -155,116 +161,120 @@ GEM
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
- terminal-table (>= 1.4.5, < 2.0.0)
+ terminal-table (~> 3)
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)
- ffi (1.17.1)
+ ffi (1.17.2)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.54.0)
- google-apis-core (>= 0.11.0, < 2.a)
- google-apis-core (0.11.2)
+ google-apis-androidpublisher_v3 (0.87.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1)
- googleauth (>= 0.16.2, < 2.a)
- httpclient (>= 2.8.1, < 3.a)
+ googleauth (~> 1.9)
+ httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
+ mutex_m
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.29.0)
- google-apis-core (>= 0.11.0, < 2.a)
- google-cloud-core (1.6.1)
+ google-apis-iamcredentials_v1 (0.24.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.17.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-storage_v1 (0.56.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
- google-cloud-env (2.1.0)
+ google-cloud-env (2.3.1)
+ base64 (~> 0.2)
faraday (>= 1.0, < 3.a)
- google-cloud-errors (1.3.1)
- google-cloud-storage (1.45.0)
+ google-cloud-errors (1.5.0)
+ google-cloud-storage (1.57.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
- google-apis-iamcredentials_v1 (~> 0.1)
- google-apis-storage_v1 (~> 0.29.0)
+ google-apis-core (>= 0.18, < 2)
+ google-apis-iamcredentials_v1 (~> 0.18)
+ google-apis-storage_v1 (>= 0.42)
google-cloud-core (~> 1.6)
- googleauth (>= 0.16.2, < 2.a)
+ googleauth (~> 1.9)
mini_mime (~> 1.0)
- googleauth (1.9.1)
+ google-logging-utils (0.2.0)
+ googleauth (1.15.0)
faraday (>= 1.0, < 3.a)
- google-cloud-env (~> 2.1)
- jwt (>= 1.4, < 3.0)
+ google-cloud-env (~> 2.2)
+ google-logging-utils (~> 0.1)
+ jwt (>= 1.4, < 4.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
- http-cookie (1.0.5)
+ http-cookie (1.0.8)
domain_name (~> 0.5)
- httpclient (2.8.3)
+ httpclient (2.9.0)
+ mutex_m
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jmespath (1.6.2)
- json (2.7.1)
- jwt (2.7.1)
- mini_magick (4.12.0)
+ json (2.15.0)
+ jwt (2.10.2)
+ base64
+ logger (1.7.0)
+ mini_magick (4.13.2)
mini_mime (1.1.5)
- minitest (5.25.4)
+ minitest (5.25.5)
molinillo (0.8.0)
- multi_json (1.15.0)
- multipart-post (2.3.0)
+ multi_json (1.17.0)
+ multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
nap (1.1.0)
- naturally (2.2.1)
+ naturally (2.3.0)
netrc (0.11.0)
+ nkf (0.2.0)
optparse (0.1.1)
os (1.1.4)
- plist (3.7.1)
+ plist (3.7.2)
public_suffix (4.0.7)
- rake (13.1.0)
+ rake (13.3.0)
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.4.1)
+ rexml (3.4.4)
rouge (2.0.7)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
- rubyzip (2.3.2)
+ rubyzip (2.4.1)
security (0.1.3)
- signet (0.18.0)
+ signet (0.21.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
- jwt (>= 1.5, < 3.0)
+ jwt (>= 1.5, < 4.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)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
- typhoeus (1.4.1)
- ethon (>= 0.9.0)
+ typhoeus (1.5.0)
+ ethon (>= 0.9.0, < 0.16.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uber (0.1.0)
- unicode-display_width (1.8.0)
- webrick (1.8.1)
+ unicode-display_width (2.6.0)
word_wrap (1.0.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
@@ -283,12 +293,10 @@ PLATFORMS
DEPENDENCIES
activesupport (= 7.1.2)
- cocoapods (= 1.14.3)
+ cocoapods (= 1.15.0)
concurrent-ruby (= 1.3.4)
- fastlane (= 2.214.0)
- fastlane-plugin-get_version_name
- fastlane-plugin-versioning_android
+ fastlane (= 2.215.0)
xcodeproj (= 1.27.0)
BUNDLED WITH
- 2.4.10
+ 2.3.26
diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle
index cf470808dde..7d7adf356ea 100644
--- a/apps/mobile/android/app/build.gradle
+++ b/apps/mobile/android/app/build.gradle
@@ -72,9 +72,9 @@ if (isCI && datadogPropertiesAvailable) {
apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle"
}
-def devVersionName = "1.61"
-def betaVersionName = "1.61"
-def prodVersionName = "1.61"
+def devVersionName = "1.62"
+def betaVersionName = "1.62"
+def prodVersionName = "1.62"
android {
ndkVersion rootProject.ext.ndkVersion
diff --git a/apps/mobile/android/app/src/main/AndroidManifest.xml b/apps/mobile/android/app/src/main/AndroidManifest.xml
index 66bdd248726..d790d48341a 100644
--- a/apps/mobile/android/app/src/main/AndroidManifest.xml
+++ b/apps/mobile/android/app/src/main/AndroidManifest.xml
@@ -31,6 +31,10 @@
android:name="com.onesignal.NotificationAccentColor.DEFAULT"
android:value="@string/notification_accent_color" />
+
+
0
+
+ companion object {
+ const val MODULE_NAME = "SilentPushEventEmitter"
+ private const val EVENT_NAME = "SilentPushReceived"
+ private const val TAG = "SilentPushEmitter"
+ private val pendingPayloads = mutableListOf()
+
+ @Volatile
+ private var instance: SilentPushEventEmitterModule? = null
+
+ @Volatile
+ private var listenerCount: Int = 0
+
+ fun emitEvent(payload: JSONObject?) {
+ val eventPayload = payload?.let { JSONObject(it.toString()) } ?: JSONObject()
+ val currentInstance = instance
+
+ if (currentInstance != null && currentInstance.hasListeners()) {
+ Log.d(TAG, "Sending silent push event to JS immediately: $eventPayload")
+ currentInstance.sendEvent(eventPayload)
+ return
+ }
+
+ synchronized(pendingPayloads) {
+ Log.d(TAG, "Queueing silent push payload until listeners attach: $eventPayload")
+ pendingPayloads.add(eventPayload)
+ }
+ }
+ }
+}
diff --git a/apps/mobile/android/app/src/main/java/com/uniswap/notifications/SilentPushNotificationServiceExtension.kt b/apps/mobile/android/app/src/main/java/com/uniswap/notifications/SilentPushNotificationServiceExtension.kt
new file mode 100644
index 00000000000..90321dc944d
--- /dev/null
+++ b/apps/mobile/android/app/src/main/java/com/uniswap/notifications/SilentPushNotificationServiceExtension.kt
@@ -0,0 +1,123 @@
+package com.uniswap.notifications
+
+import android.util.Log
+import androidx.annotation.Keep
+import com.onesignal.notifications.INotification
+import com.onesignal.notifications.INotificationReceivedEvent
+import com.onesignal.notifications.INotificationServiceExtension
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.json.JSONException
+import org.json.JSONObject
+
+@Keep
+class SilentPushNotificationServiceExtension : INotificationServiceExtension {
+ override fun onNotificationReceived(event: INotificationReceivedEvent) {
+ val notification = event.notification
+ val payload = buildPayload(notification)
+
+ val hasContentAvailableFlag = hasContentAvailable(payload)
+ val isMissingVisibleContent = notification.isMissingVisibleContent()
+
+ Log.d(
+ TAG,
+ "Notification received. hasContentAvailable=$hasContentAvailableFlag, " +
+ "missingVisibleContent=$isMissingVisibleContent, payload=$payload",
+ )
+
+ if (!hasContentAvailableFlag && !isMissingVisibleContent) {
+ return
+ }
+
+ Log.d(TAG, "Emitting silent push event: $payload")
+ val payloadForEmission = try {
+ JSONObject(payload.toString())
+ } catch (error: JSONException) {
+ Log.w(TAG, "Failed to clone payload for emission: ${error.message}")
+ payload
+ }
+
+ CoroutineScope(Dispatchers.Default).launch {
+ withContext(Dispatchers.Main) {
+ SilentPushEventEmitterModule.emitEvent(payloadForEmission)
+ }
+ }
+
+ if (isMissingVisibleContent) {
+ event.preventDefault()
+ }
+ }
+
+ private fun INotification.isMissingVisibleContent(): Boolean {
+ val title: String? = this.title
+ val body: String? = this.body
+ return title.isNullOrBlank() && body.isNullOrBlank()
+ }
+
+ private fun buildPayload(notification: INotification): JSONObject {
+ val rawPayload = notification.rawPayload
+ val payload = try {
+ if (rawPayload.isNullOrBlank()) JSONObject() else JSONObject(rawPayload)
+ } catch (error: JSONException) {
+ Log.w(TAG, "Failed parsing raw payload: ${error.message}")
+ JSONObject()
+ }
+
+ notification.additionalData?.let { additionalData ->
+ try {
+ payload.put("additionalData", additionalData)
+ } catch (error: JSONException) {
+ Log.w(TAG, "Failed to append additional data: ${error.message}")
+ }
+ }
+
+ return payload
+ }
+
+ private fun hasContentAvailable(payload: JSONObject?): Boolean {
+ if (payload == null) {
+ return false
+ }
+
+ if (payload.hasContentAvailableFlag()) {
+ return true
+ }
+
+ val aps = payload.optJSONObject("aps")
+ if (aps != null && aps.hasContentAvailableFlag()) {
+ return true
+ }
+
+ val additionalData = payload.optJSONObject("additionalData")
+ if (additionalData != null && additionalData.hasContentAvailableFlag()) {
+ return true
+ }
+
+ return false
+ }
+
+ private fun JSONObject.hasContentAvailableFlag(): Boolean {
+ return opt(CONTENT_AVAILABLE_UNDERSCORE).isTruthy() || opt(CONTENT_AVAILABLE_HYPHEN).isTruthy()
+ }
+
+ private fun Any?.isTruthy(): Boolean {
+ return when (this) {
+ null, JSONObject.NULL -> false
+ is Boolean -> this
+ is Int -> this == 1
+ is Long -> this == 1L
+ is Double -> this == 1.0
+ is Float -> this == 1f
+ is String -> equals("1", ignoreCase = true) || equals("true", ignoreCase = true)
+ else -> false
+ }
+ }
+
+ companion object {
+ private const val TAG = "SilentPushExt"
+ private const val CONTENT_AVAILABLE_UNDERSCORE = "content_available"
+ private const val CONTENT_AVAILABLE_HYPHEN = "content-available"
+ }
+}
diff --git a/apps/mobile/android/app/src/main/java/com/uniswap/utils/JsonWritableExtensions.kt b/apps/mobile/android/app/src/main/java/com/uniswap/utils/JsonWritableExtensions.kt
new file mode 100644
index 00000000000..81415055e6d
--- /dev/null
+++ b/apps/mobile/android/app/src/main/java/com/uniswap/utils/JsonWritableExtensions.kt
@@ -0,0 +1,61 @@
+package com.uniswap.utils
+
+import com.facebook.react.bridge.Arguments
+import com.facebook.react.bridge.WritableArray
+import com.facebook.react.bridge.WritableMap
+import org.json.JSONArray
+import org.json.JSONObject
+
+fun JSONObject.toWritableMap(): WritableMap {
+ val map = Arguments.createMap()
+ val iterator = keys()
+ while (iterator.hasNext()) {
+ val key = iterator.next()
+ when (val value = opt(key)) {
+ null, JSONObject.NULL -> map.putNull(key)
+ is JSONObject -> map.putMap(key, value.toWritableMap())
+ is JSONArray -> map.putArray(key, value.toWritableArray())
+ is Boolean -> map.putBoolean(key, value)
+ is Int -> map.putInt(key, value)
+ is Long -> {
+ if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
+ map.putInt(key, value.toInt())
+ } else {
+ map.putDouble(key, value.toDouble())
+ }
+ }
+ is Double -> map.putDouble(key, value)
+ is Float -> map.putDouble(key, value.toDouble())
+ is Number -> map.putDouble(key, value.toDouble())
+ is String -> map.putString(key, value)
+ else -> map.putString(key, value.toString())
+ }
+ }
+ return map
+}
+
+fun JSONArray.toWritableArray(): WritableArray {
+ val array = Arguments.createArray()
+ for (index in 0 until length()) {
+ when (val value = opt(index)) {
+ null, JSONObject.NULL -> array.pushNull()
+ is JSONObject -> array.pushMap(value.toWritableMap())
+ is JSONArray -> array.pushArray(value.toWritableArray())
+ is Boolean -> array.pushBoolean(value)
+ is Int -> array.pushInt(value)
+ is Long -> {
+ if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
+ array.pushInt(value.toInt())
+ } else {
+ array.pushDouble(value.toDouble())
+ }
+ }
+ is Double -> array.pushDouble(value)
+ is Float -> array.pushDouble(value.toDouble())
+ is Number -> array.pushDouble(value.toDouble())
+ is String -> array.pushString(value)
+ else -> array.pushString(value.toString())
+ }
+ }
+ return array
+}
diff --git a/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj b/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj
index 6cdc18319f2..8e43796dc97 100644
--- a/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj
+++ b/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj
@@ -10,13 +10,14 @@
0013F5F72C93399400D6EF09 /* ProtectionInfo.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0013F5F62C93399400D6EF09 /* ProtectionInfo.graphql.swift */; };
00265F792C933CE300A5DA57 /* ProtectionResult.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00265F772C933CE300A5DA57 /* ProtectionResult.graphql.swift */; };
00265F7A2C933CE300A5DA57 /* ProtectionAttackType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00265F782C933CE300A5DA57 /* ProtectionAttackType.graphql.swift */; };
- 0094F4FBBC1C0A2FFABF7157 /* TokenProject.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981A14906A35E8A0B7F9457 /* TokenProject.graphql.swift */; };
00E356F31AD99517003FC87E /* UniswapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* UniswapTests.m */; };
- 03291E0DA448AF438F97EA5D /* DescriptionTranslations.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E1F288B7EDDE906C4C00C46 /* DescriptionTranslations.graphql.swift */; };
+ 02B7B534DFEC9DA0F7F06B8D /* TokenBalanceQuantityParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0A1F5F6FC3E1141E228B7D /* TokenBalanceQuantityParts.graphql.swift */; };
037C5AAA2C04970B00B1D808 /* CopyIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037C5AA92C04970B00B1D808 /* CopyIcon.swift */; };
+ 03AE019583139330A3E408AB /* SwapOrderDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F4E5E9341BE66984935185 /* SwapOrderDetails.graphql.swift */; };
03C788232C10E7390011E5DC /* ActionButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C788222C10E7390011E5DC /* ActionButtons.swift */; };
03D2F3182C218D390030D987 /* RelativeOffsetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D2F3172C218D380030D987 /* RelativeOffsetView.swift */; };
- 03E3515E38E247F459218CAA /* SwapOrderDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = C955CE2592785712A11892DE /* SwapOrderDetails.graphql.swift */; };
+ 03EEEDA1A3D5EF23C0D14B08 /* OnRampTransactionDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CF9B03AE29A19C5B0F4E35 /* OnRampTransactionDetails.graphql.swift */; };
+ 06C9F16E22B8B64855980A69 /* TokenMarketParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55081ADF188C967FC1ECD289 /* TokenMarketParts.graphql.swift */; };
0703EE032A5734A600AED1DA /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0703EE022A5734A600AED1DA /* UserDefaults.swift */; };
0703EE052A57351800AED1DA /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 072F6C372A44BECC00DA720A /* Logging.swift */; };
072E238E2A44D5BD006AD6C9 /* WidgetsCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 072E23862A44D5BC006AD6C9 /* WidgetsCore.framework */; };
@@ -109,6 +110,7 @@
0743223D2A83E3CA00F8518D /* IAmount.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074321E92A83E3C900F8518D /* IAmount.graphql.swift */; };
0743223E2A83E3CA00F8518D /* IContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074321EA2A83E3C900F8518D /* IContract.graphql.swift */; };
074322402A841BBD00F8518D /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0743223F2A841BBD00F8518D /* Constants.swift */; };
+ 0764D0BF05C44615BDD9AD65 /* NftBalanceAssetInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 696F6C5220D0072E380E198F /* NftBalanceAssetInput.graphql.swift */; };
0767E0382A65C8330042ADA2 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0767E0372A65C8330042ADA2 /* Colors.swift */; };
0767E03B2A65D2550042ADA2 /* Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0767E03A2A65D2550042ADA2 /* Styling.swift */; };
077E60392A85587800ABC4B9 /* TokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077E60382A85587800ABC4B9 /* TokensQuery.graphql.swift */; };
@@ -123,10 +125,10 @@
07F136422A5763480067004F /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F136412A5763480067004F /* Network.swift */; };
07F5CF712A6AD97D00C648A5 /* Chart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F5CF702A6AD97D00C648A5 /* Chart.swift */; };
07F5CF752A7020FD00C648A5 /* Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F5CF742A7020FD00C648A5 /* Format.swift */; };
- 09E8C497051C37FE604FD40E /* OnRampTransactionsAuth.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315B8A905FA2C60C053F138B /* OnRampTransactionsAuth.graphql.swift */; };
- 09F9DEB33392F3051BEA8D52 /* TokenFeeDataParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018603D0FA4D05DBF16F1441 /* TokenFeeDataParts.graphql.swift */; };
- 0C6FC49E0FC9BCB2DF5B627E /* TokenSortableField.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32012AC135EBD991C8F02F92 /* TokenSortableField.graphql.swift */; };
- 0CEBEB8BE3AB95C3F584F6CE /* Image.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED613D63FE9C56D9270517 /* Image.graphql.swift */; };
+ 092E5F0FE7DE6113285AF5E3 /* NftsTabQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3BCD49511DE84892B34ED0 /* NftsTabQuery.graphql.swift */; };
+ 0AC6A2E1C5837AAD902742DA /* TokenBalanceMainParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD452B34344D670BA23EA331 /* TokenBalanceMainParts.graphql.swift */; };
+ 0AD9B4E176D2E9FE86E6ED21 /* AssetActivity.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05C66C49AF2B78703FCE4C7C /* AssetActivity.graphql.swift */; };
+ 0D87DBFD19D5A6DF176C0AB7 /* MobileSchema.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87442A651C068E552BEA89C6 /* MobileSchema.graphql.swift */; };
0DB282262CDADB260014CF77 /* EmbeddedWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DB282242CDADB260014CF77 /* EmbeddedWallet.m */; };
0DB282272CDADB260014CF77 /* EmbeddedWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB282252CDADB260014CF77 /* EmbeddedWallet.swift */; };
0DC6ADF02B1E2C100092909C /* PortfolioValueModifier.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC6ADEF2B1E2C0F0092909C /* PortfolioValueModifier.graphql.swift */; };
@@ -134,61 +136,57 @@
0DE251472C13B69D005F47F9 /* OnRampTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE251442C13B69D005F47F9 /* OnRampTransfer.graphql.swift */; };
0DE251482C13B69D005F47F9 /* OnRampServiceProvider.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE251452C13B69D005F47F9 /* OnRampServiceProvider.graphql.swift */; };
0DE251492C13B69D005F47F9 /* OnRampTransactionDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE251462C13B69D005F47F9 /* OnRampTransactionDetails.graphql.swift */; };
- 0F282C26344F1AE3622232B0 /* OffRampTransactionDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396B6AB3BB41898B56EB3828 /* OffRampTransactionDetails.graphql.swift */; };
- 126CC4BC99F3F15CC1E2F1C3 /* NftAssetTrait.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70851E3D1A4B296A1CE22A20 /* NftAssetTrait.graphql.swift */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
- 143C638C2CCAC689371BFC93 /* NftActivityType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D95FE36D2667E070345CDD0 /* NftActivityType.graphql.swift */; };
1440B371A1C9A42F3E91DAAE /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DF5F26A06553EFDD4D99214 /* ExpoModulesProvider.swift */; };
- 171DD1C966C7FBF9DD68CFAC /* TopTokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E5C2900254E7BA7F10CE51 /* TopTokensQuery.graphql.swift */; };
- 1B59968CF49C45B127E9C768 /* NftOrder.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3A78386B3B779B688021FF /* NftOrder.graphql.swift */; };
- 1BC3A49161EB906542F8E23B /* ProtectionAttackType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E034582FBD2C374CEF808E /* ProtectionAttackType.graphql.swift */; };
- 1C3559D557BC09C709104802 /* NftsTabQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 730CA356D6E1D498CC0C1472 /* NftsTabQuery.graphql.swift */; };
- 1CE49305114BF4792290DDC6 /* AmountChange.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = E63EA0C75D8011BCC182492C /* AmountChange.graphql.swift */; };
- 1DC34AE3E11264475E8B9F62 /* NftApproval.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E32BBB65A6DD9CFF608C8E8 /* NftApproval.graphql.swift */; };
- 1E2AF2C38C8FBEB2A95B644E /* OffRampTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D5E6A07F404974045BD525 /* OffRampTransfer.graphql.swift */; };
- 252BD40CB9B46FE47B2440B1 /* NftCollection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63ACFF2A1D5AE3498731AC69 /* NftCollection.graphql.swift */; };
- 257C7A9F2C4AC3C99C6B7348 /* TransactionHistoryUpdaterQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF3D7A923E5E1FB504E1F64D /* TransactionHistoryUpdaterQuery.graphql.swift */; };
- 2B12DFE797E2CBF314565D81 /* TokenProtectionInfoParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB4A40E30E1D985519DB3CF /* TokenProtectionInfoParts.graphql.swift */; };
- 2B422D705C68BB51FBDA9895 /* NFTItemScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207F4DF7664454C31DE069F8 /* NFTItemScreenQuery.graphql.swift */; };
- 2E05037B51AF526A47701B09 /* TransactionDirection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2FBE9F353D3753FB94B8BF /* TransactionDirection.graphql.swift */; };
- 302E24504C4FCE674CF95984 /* TokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F596250F55A3D6E9E1F499 /* TokenParts.graphql.swift */; };
- 30872BFB39EEC66944E3EFD7 /* MobileSchema.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4E0C448C5D45BF1071FA458 /* MobileSchema.graphql.swift */; };
- 31661D70B58410EA030E2C53 /* TimestampedAmount.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0DB67D6C438F623976727E9 /* TimestampedAmount.graphql.swift */; };
- 326CFFDBB89D95FA4CB95EBB /* HistoryDuration.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF42F88002431BFDC9FCA9F /* HistoryDuration.graphql.swift */; };
- 3361FE5837059AEF4AA877BE /* TransactionListQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224CA82F1871016F4173F69E /* TransactionListQuery.graphql.swift */; };
- 33928A7366AFE7F892CDC89F /* NftBalanceConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FC7B80EC0E0D2134B67575B /* NftBalanceConnection.graphql.swift */; };
- 35B8176433A98BA798BBEE79 /* PageInfo.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = E475EBABF7EA310435E2F19C /* PageInfo.graphql.swift */; };
- 36E601F269D40A67FC353947 /* AssetChange.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39F60B2B54796DC978765C99 /* AssetChange.graphql.swift */; };
- 3E338E14E33C721DAB708664 /* NftActivityFilterInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2ECCEFBC3BE8D13BE6CAB /* NftActivityFilterInput.graphql.swift */; };
- 3FE25F715DFFD1D03DCDA57D /* NftContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B9EB43501127BE1B14F1A5 /* NftContract.graphql.swift */; };
+ 147F4DFD43CE86324A066003 /* FeedTransactionListQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EFADDBB38BB9261933C1349 /* FeedTransactionListQuery.graphql.swift */; };
+ 15193A0A3CE80FF72AAB54B4 /* SafetyLevel.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F8093091A9E5FC0D6284BA /* SafetyLevel.graphql.swift */; };
+ 15A72E27235628C56431EC98 /* FeeData.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE1CB436B299B0349BA9957 /* FeeData.graphql.swift */; };
+ 15F6E43DA2BD9A8A681EC70C /* TokenTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BEBBC668E69EE3A1FC6AB05 /* TokenTransfer.graphql.swift */; };
+ 16506815CDEE17670D3DD363 /* TimestampedAmount.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CBA4397C44A04E218AEF28 /* TimestampedAmount.graphql.swift */; };
+ 19BC2371F6BE6CAD5218AA53 /* NftActivityConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE0699A65BEEEE6E9A6B03F /* NftActivityConnection.graphql.swift */; };
+ 1C84E14C7F52CF6C9A6D2930 /* TokenProjectDescriptionQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1276D33B06E840B8E6839F39 /* TokenProjectDescriptionQuery.graphql.swift */; };
+ 1CA252C75CD5291E2A0B308B /* NftCollection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25620D6E0E297C56198F190 /* NftCollection.graphql.swift */; };
+ 206E0025B8FCE0EA96AC744A /* NftCollectionScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3BFDC64C5E0A058877A348 /* NftCollectionScreenQuery.graphql.swift */; };
+ 23B195B262495EABE4A6CDF4 /* HistoryDuration.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1053483279043EED7612BE90 /* HistoryDuration.graphql.swift */; };
+ 24B4011ADC672F470EC515AC /* BridgedWithdrawalInfo.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC62CDEEBE5712219A457C7 /* BridgedWithdrawalInfo.graphql.swift */; };
+ 252A28057D1D481D14D2F5E5 /* TransactionDirection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D494D944670E557175A694 /* TransactionDirection.graphql.swift */; };
+ 2925C6E0262B95C5EA0C2AE7 /* TransactionDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55240C84512D3851C8B9F027 /* TransactionDetails.graphql.swift */; };
+ 2B2738BAB9E906B561E0D4D6 /* TokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB76D1207400E308430A837F /* TokenParts.graphql.swift */; };
+ 2C9935142A9582467B5B5FC5 /* ProtectionInfo.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD12FD368B3A96F323444D8E /* ProtectionInfo.graphql.swift */; };
+ 2D73D2D80BC8C455DB35CE57 /* TokenApproval.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD56CBC543897BE8E37CC2C1 /* TokenApproval.graphql.swift */; };
+ 2FC53D1C58218F0BF1D4E5F3 /* IContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9189C818AFF8E988F861D0 /* IContract.graphql.swift */; };
+ 31A4EC91F1924E25AECA2F4E /* NftCollectionMarket.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F286C514E849B29F5235310A /* NftCollectionMarket.graphql.swift */; };
+ 3389D45727F15B8E4F59B99F /* NftApproval.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD111A711D83D1D82E4025A9 /* NftApproval.graphql.swift */; };
+ 34610491FF9A452F4F806157 /* TokenPriceHistoryQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9602E2A290179F8741EDD35A /* TokenPriceHistoryQuery.graphql.swift */; };
+ 3720F641F397A2819500B1B1 /* SwapOrderStatus.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0359DDEA2E2E17759FCC5CF /* SwapOrderStatus.graphql.swift */; };
+ 391FD815120230BDAF53F6F0 /* BlockaidFees.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF9AEC00868BFD7CEBF202C /* BlockaidFees.graphql.swift */; };
+ 39DEA445993253BCC1201199 /* NftApproveForAll.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1B893C0275DAAA156B0108 /* NftApproveForAll.graphql.swift */; };
+ 4260B9F719F8E25DFBF99D73 /* OnRampTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5D1A04B42B25218C53C107 /* OnRampTransfer.graphql.swift */; };
+ 438115E2759B1194751A8021 /* NftContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55A243177E9FA92A0524A222 /* NftContract.graphql.swift */; };
+ 45FFF7DF2E8C2A8100362570 /* SilentPushEventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 45FFF7DE2E8C2A6400362570 /* SilentPushEventEmitter.m */; };
+ 45FFF7E12E8C2E6900362570 /* SilentPushEventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FFF7E02E8C2E6100362570 /* SilentPushEventEmitter.swift */; };
463BA791004B1B7AC1773914 /* Pods_Uniswap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2226DF79BEAFECEE11A51347 /* Pods_Uniswap.framework */; };
- 4917B04DB81579CF4243A1DA /* Chain.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8990A17F489F284E0DB1B3 /* Chain.graphql.swift */; };
- 4BB9E218D89B8AF5E4E27CCB /* NftOrderEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49BD6C776029FAF4FC711DE7 /* NftOrderEdge.graphql.swift */; };
- 4C187202229BDC4D8898E067 /* Dimensions.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B1C71AED738D17898103A6 /* Dimensions.graphql.swift */; };
- 4EF8D293BB1EBCFBFC65A330 /* TokenTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DA4E7C052A6C4AC3A9DCDA6 /* TokenTransfer.graphql.swift */; };
- 4FED6AAF896BA371C56D88B1 /* Portfolio.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1849039FD4A210DA990363C8 /* Portfolio.graphql.swift */; };
- 50C89DFAF2DBC80D6343B164 /* NftAssetTraitInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 556339705BD103365D4ED07B /* NftAssetTraitInput.graphql.swift */; };
- 553E6B467BEE49C9984691C5 /* OnRampTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2139ABF56EA067ED792A48C9 /* OnRampTransfer.graphql.swift */; };
- 5804A1B1BB144D9EFBBE177D /* NftAssetEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF7C8888BE6AFA2F22889FE /* NftAssetEdge.graphql.swift */; };
+ 47797825D18637A2FE57AC1F /* TokenProtectionInfoParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835D9DF76533D22EDF0E9D67 /* TokenProtectionInfoParts.graphql.swift */; };
+ 4DB6B64CFF3B00FCF0258336 /* NftMarketplace.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83FB27D93A9468E6DB47231C /* NftMarketplace.graphql.swift */; };
+ 4DB6D0FB611F6C68EADFB948 /* TokenProjectMarket.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B0505DB30407C221B6A8491 /* TokenProjectMarket.graphql.swift */; };
+ 4DB88A2CBFF5FF356CA757F0 /* ApplicationContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59DD4D834D7B42A6D02F4F12 /* ApplicationContract.graphql.swift */; };
+ 4FD78AA88D2EB8E22144BCDF /* NftAssetTraitInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1731869775D16A9EAB475C /* NftAssetTraitInput.graphql.swift */; };
+ 51362EC6F0E6D8928C067F5E /* NftBalancesFilterInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1810C60B56CC74150C868801 /* NftBalancesFilterInput.graphql.swift */; };
+ 5551DDD6A8B9B28E03459816 /* TokenFeeDataParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1166529407CA13E4D4F24F12 /* TokenFeeDataParts.graphql.swift */; };
+ 5676CCD265609B71D3B1DB7C /* NftCollectionEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70593F94C8B315F5304B9BE /* NftCollectionEdge.graphql.swift */; };
+ 57ECD348EA564F0CBF715E9E /* PortfolioBalancesQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A53C3A09AD259B9A8EDBDD /* PortfolioBalancesQuery.graphql.swift */; };
5B4398EC2DD3B22C00F6BE08 /* PrivateKeyDisplayManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B4398E82DD3B22C00F6BE08 /* PrivateKeyDisplayManager.m */; };
5B4398ED2DD3B22C00F6BE08 /* PrivateKeyDisplayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4398E92DD3B22C00F6BE08 /* PrivateKeyDisplayManager.swift */; };
5B4398EE2DD3B22C00F6BE08 /* PrivateKeyDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4398EA2DD3B22C00F6BE08 /* PrivateKeyDisplayView.swift */; };
5B4CEC5F2DD65DD4009F082B /* CopyIconOutline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4CEC5E2DD65DD4009F082B /* CopyIconOutline.swift */; };
- 5C3958DA046B5A84FE90C1BD /* FeeData.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17819A49DE69723EC60D9D72 /* FeeData.graphql.swift */; };
- 5D06BAB366D14A5AFE2355E8 /* TransactionType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F2FFD11FB5E462CC454A15 /* TransactionType.graphql.swift */; };
5E5E0A632D380F5800E166AA /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5E0A622D380F5700E166AA /* Env.swift */; };
5EFB78362B1E585000E77EAC /* ConvertQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFB78352B1E585000E77EAC /* ConvertQuery.graphql.swift */; };
- 614FCC3D8E9AA8175E950726 /* Currency.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B4C7C7FEF32EAF10D030F9 /* Currency.graphql.swift */; };
- 61988A922656857278F7CBF9 /* TokenBalanceMainParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 137696908D703632CEE3AB28 /* TokenBalanceMainParts.graphql.swift */; };
- 62B32E9623DA0E939F062033 /* TokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0403FAEFDD632AC6CCE /* TokensQuery.graphql.swift */; };
- 63BDDE4499AC6B2375C9F795 /* FavoriteTokenCardQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F0EEAC1DADE827AE38EB8A6 /* FavoriteTokenCardQuery.graphql.swift */; };
- 648B919F00D069DE1F0040F6 /* NftMarketplace.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 600250B00B6C02AAB063818B /* NftMarketplace.graphql.swift */; };
+ 6250D4ACD5696D845FD83DFD /* HomeScreenTokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7FF51A507BF788E64E3EFE /* HomeScreenTokensQuery.graphql.swift */; };
+ 63FBF9C23ED568816590F093 /* ActivityDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075E4C1DFAA00EE5B3CE792D /* ActivityDetails.graphql.swift */; };
649A7A782D9AE70B00B53589 /* KeychainUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A7A772D9AE70B00B53589 /* KeychainUtils.swift */; };
649A7A792D9AE70B00B53589 /* KeychainConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A7A762D9AE70B00B53589 /* KeychainConstants.swift */; };
- 66D2765A00D8CE3136D28F1F /* SafetyLevel.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ACFA5B4204741038C986C5D /* SafetyLevel.graphql.swift */; };
- 677FC15A05D9BD23930FAC95 /* TokenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74885865ED8CFEA875E40916 /* TokenQuery.graphql.swift */; };
- 6A60BDC9D46A710D871DEC6E /* NftProfile.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35E518AD7FBFEBEA9CAB95 /* NftProfile.graphql.swift */; };
+ 6882C4768011FDD4D746BDDB /* TokenBasicInfoParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13E592852F52B2855F05A5E /* TokenBasicInfoParts.graphql.swift */; };
6BC7D07E2B5FF02400617C95 /* ScantasticEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BC7D07B2B5FF02400617C95 /* ScantasticEncryption.m */; };
6BC7D07F2B5FF02400617C95 /* ScantasticEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC7D07C2B5FF02400617C95 /* ScantasticEncryption.swift */; };
6BC7D0802B5FF02400617C95 /* EncryptionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC7D07D2B5FF02400617C95 /* EncryptionUtils.swift */; };
@@ -198,21 +196,25 @@
6CA91BE12A95226200C4063E /* RNCloudStorageBackupsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6CA91BDE2A95226200C4063E /* RNCloudStorageBackupsManager.m */; };
6CA91BE22A95226200C4063E /* EncryptionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA91BDF2A95226200C4063E /* EncryptionHelper.swift */; };
6CA91BE32A95226200C4063E /* RNCloudStorageBackupsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA91BE02A95226200C4063E /* RNCloudStorageBackupsManager.swift */; };
+ 70CD2D0665E5AC6A0DF6F697 /* NFTItemScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AB6B21A1BFB3A18982B47E /* NFTItemScreenQuery.graphql.swift */; };
70EB8338CA39744B7DBD553E /* Pods_WidgetIntentExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1064E23E366D0C2C2B20C30E /* Pods_WidgetIntentExtension.framework */; };
- 71CF37F19F1C138B57CC135C /* TopTokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5640432978873FB030467750 /* TopTokenParts.graphql.swift */; };
+ 71954B20E4341CEC36203F87 /* NftActivityEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C7D80D6722AFCF1715D07 /* NftActivityEdge.graphql.swift */; };
+ 7700BED7CD7F52C83A3FF430 /* NftAssetEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFD4C1E5A4B30DE3CE02DB4 /* NftAssetEdge.graphql.swift */; };
77CF6065C8A24FE48204A2C1 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF9176E944C84910B1C0B057 /* SplashScreen.storyboard */; };
- 7BE97D20155AE69FF36C4CB4 /* NftStandard.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B308EC3DB7C3C1DD5DDE03D /* NftStandard.graphql.swift */; };
- 818179F5724AA35E1B1D5A9C /* ProtectionInfo.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0702203B17BDD858CED28F8B /* ProtectionInfo.graphql.swift */; };
+ 7B557C3224F990851430DBD2 /* Portfolio.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5397E77A577E9952D2477020 /* Portfolio.graphql.swift */; };
+ 7C886F9D4C02EF4E5F67B011 /* NftsQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE735ED4766E65C0D6808F9A /* NftsQuery.graphql.swift */; };
+ 7E3412EB776E1F43D6904BA0 /* PageInfo.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97FBBE677B56FFE07052EF1D /* PageInfo.graphql.swift */; };
+ 8117BFB02DCF0F315FD83E67 /* TokenBalance.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181209B4CDE80BDC80891CFD /* TokenBalance.graphql.swift */; };
+ 8141B38CCDB09F91180C0EBD /* NftCollectionConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D09FFE2B5E56CDCAB2F9455 /* NftCollectionConnection.graphql.swift */; };
+ 81E55E8D3056E2378A332B31 /* TokenDetailsScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957A35D2DE7A140BA198A0F1 /* TokenDetailsScreenQuery.graphql.swift */; };
8273FC23FB1AE47B80C5E09F /* Pods_OneSignalNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15092E550A1C78508ABA3280 /* Pods_OneSignalNotificationServiceExtension.framework */; };
8385A47D3C765B841F450090 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26D739993D5C939C6FBB58A /* ExpoModulesProvider.swift */; };
- 8410B98A8D7974A941AAF299 /* WidgetTokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA33683D2BA6BBE3251D67C /* WidgetTokensQuery.graphql.swift */; };
- 85ADD03923353DB3D6CD7301 /* SwapOrderStatus.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = D574AB5194929038A54B4D4B /* SwapOrderStatus.graphql.swift */; };
- 85D0E81B798D0F2D841386E9 /* NftCollectionMarket.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4F47D22B749A438B5BF674 /* NftCollectionMarket.graphql.swift */; };
- 869F3639FBC6D8156FFE3BD3 /* TokenProjectMarket.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B9A505509D9A85BE1DB7D05 /* TokenProjectMarket.graphql.swift */; };
- 8950F8354810AA673E1E35DA /* SchemaMetadata.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65195A1CC9894C6341385C3 /* SchemaMetadata.graphql.swift */; };
- 8ADDD2E2AB1FD9D1D9AF5627 /* BlockaidFees.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD35300F81746B2D4A23065A /* BlockaidFees.graphql.swift */; };
- 8CC06A8205186D0640F0BC55 /* NftAssetConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218B8AB05C80D919671D3970 /* NftAssetConnection.graphql.swift */; };
- 8CEA6459A5B739C3A0382000 /* TokenProjectsQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A49D011C60137D4034CAC4 /* TokenProjectsQuery.graphql.swift */; };
+ 845327D60EFBB850189D6AB3 /* Amount.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C3461E088654B80ABE3DE7 /* Amount.graphql.swift */; };
+ 86038259E487A108DDC2948B /* Image.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9047B981FA322A727947F7 /* Image.graphql.swift */; };
+ 8971E73E041A6283E2684481 /* TokenProject.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8856DFFC7FDC9BF105B0B4E3 /* TokenProject.graphql.swift */; };
+ 8BCFF1F648887F78894F1071 /* ContractInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED050A208757F137DBDA57D /* ContractInput.graphql.swift */; };
+ 8D85F349FEF81A52FB93EAAB /* AssetChange.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C849A7805FCCEE8C8C3E6 /* AssetChange.graphql.swift */; };
+ 8E6DA65AC5CAE9F9A67F9E38 /* TopTokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 243576FAC8D2801F9D99ECC9 /* TopTokensQuery.graphql.swift */; };
8E89C3AE2AB8AAA400C84DE5 /* MnemonicConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E89C3A62AB8AAA400C84DE5 /* MnemonicConfirmationView.swift */; };
8E89C3AF2AB8AAA400C84DE5 /* MnemonicDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E89C3A72AB8AAA400C84DE5 /* MnemonicDisplayView.swift */; };
8E89C3B12AB8AAA400C84DE5 /* MnemonicConfirmationWordBankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E89C3A92AB8AAA400C84DE5 /* MnemonicConfirmationWordBankView.swift */; };
@@ -227,7 +229,7 @@
8EBFB1552ABA6AA6006B32A8 /* PasteIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EBFB1542ABA6AA6006B32A8 /* PasteIcon.swift */; };
8ED0562C2AA78E2C009BD5A2 /* ScrollFadeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ED0562B2AA78E2C009BD5A2 /* ScrollFadeExtensions.swift */; };
8EE7C0582AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE7C0572AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift */; };
- 8FE0F30936E893297558F467 /* ProtectionResult.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0FC2D534CB82B151C7F9B7 /* ProtectionResult.graphql.swift */; };
+ 9061ACE5AF99CEDE7BA51C94 /* NftProfile.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2A9FBFB7F718246B668A60 /* NftProfile.graphql.swift */; };
9127D1362CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9127D1342CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift */; };
9127D1372CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9127D1352CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift */; };
9173CEBC2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9173CEBB2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift */; };
@@ -243,15 +245,14 @@
91D501792CDBEAE700B09B7F /* TopTokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D5016E2CDBEAE700B09B7F /* TopTokenParts.graphql.swift */; };
91D5017A2CDBEAE700B09B7F /* TokenFeeDataParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D5016F2CDBEAE700B09B7F /* TokenFeeDataParts.graphql.swift */; };
91D5017E2CDBEAF600B09B7F /* HomeScreenTokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D5017C2CDBEAF600B09B7F /* HomeScreenTokenParts.graphql.swift */; };
- 9301D18644F3DFA9ABB8F0BE /* TokenApproval.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E975BEDBED5FE51D0DC6E96 /* TokenApproval.graphql.swift */; };
- 93566FBDE94E1A2D8CC5AB62 /* ConvertQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3F0794908635CDFA7DAB6 /* ConvertQuery.graphql.swift */; };
- 9381B5EA5839DA17D07A45F0 /* TokenDetailsScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7A33933670CCAD9E62A1A80 /* TokenDetailsScreenQuery.graphql.swift */; };
- 93AEECDBDB160B5E0C9E3149 /* TokenProjectDescriptionQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF8B660E9C2C0D8DDF7588A /* TokenProjectDescriptionQuery.graphql.swift */; };
- 97CA219E1FFD832D8FA02C20 /* TransactionDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE51731EB853137302A7B0D /* TransactionDetails.graphql.swift */; };
- 9822D243ED2F5C350404A375 /* HomeScreenTokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F8B915042BD8D67D9627EA /* HomeScreenTokenParts.graphql.swift */; };
- 9A3E861F5D7B0F2CB0EFA7A6 /* AssetActivity.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4624A41EC1468712BD77653 /* AssetActivity.graphql.swift */; };
- 9AF0D1FDF2BFB17FB732C5FD /* SchemaConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E49E724432C70A2551FB2C7 /* SchemaConfiguration.swift */; };
- 9B6C88F7D8D542AAC353A3EA /* NftOrderConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39FC2A13550FA6754EC1A7FC /* NftOrderConnection.graphql.swift */; };
+ 91DBDA9B4F4006350AEB8E4B /* NftStandard.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373C61A7AF015058A502CE2 /* NftStandard.graphql.swift */; };
+ 9260D04A891FEDE2BF511707 /* Currency.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0CA37FB709C06F123EBF74 /* Currency.graphql.swift */; };
+ 997BFD5324E4BDE2422FEFED /* NftAssetsFilterInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310373063D8A11E9E02314FE /* NftAssetsFilterInput.graphql.swift */; };
+ 9A9B39BC22F3BDFCD0917B66 /* OffRampTransactionDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CA32B2B2997D73A0185635 /* OffRampTransactionDetails.graphql.swift */; };
+ 9D638326CD705ABE549C8CA7 /* TokenProjectMarketsParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C2E70E43A02405C5CEAD7C /* TokenProjectMarketsParts.graphql.swift */; };
+ 9E7EC26AC45301198E280DC7 /* NftOrderEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD158681F2E84A5E49656B11 /* NftOrderEdge.graphql.swift */; };
+ 9EBC41A90B1A80653960045E /* SchemaMetadata.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522135E54BDB258B45B5A694 /* SchemaMetadata.graphql.swift */; };
+ 9EC9E4AB5C3FD254EDB28D7A /* PortfolioValueModifier.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09D12605BAA868AB51573BE /* PortfolioValueModifier.graphql.swift */; };
9F00A43A2B33894C0088A0D0 /* ApplicationContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F00A4392B33894C0088A0D0 /* ApplicationContract.graphql.swift */; };
9F29D4ED2B47126D004D003A /* NftBalanceAssetInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F29D4EC2B47126D004D003A /* NftBalanceAssetInput.graphql.swift */; };
9F78980B2A819CC4004D5A98 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 072F6C222A44A32E00DA720A /* SwiftUI.framework */; };
@@ -268,72 +269,74 @@
9FCEBF012A95A8E00079EDDB /* RNWalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FCEBEFF2A95A8E00079EDDB /* RNWalletConnect.swift */; };
9FCEBF042A95A99C0079EDDB /* RCTThemeModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FCEBF032A95A99B0079EDDB /* RCTThemeModule.m */; };
9FEC9B8B2A858CF1003CD019 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FEC9B8A2A858CF1003CD019 /* AppDelegate.m */; };
- A104024A1861354EC1DD53C1 /* PortfolioBalancesQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 985DDC8324B872DD44E87781 /* PortfolioBalancesQuery.graphql.swift */; };
+ A250CF455F6CEFB64ABFC277 /* HomeScreenTokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB772B265A35BFBC3A7761AD /* HomeScreenTokenParts.graphql.swift */; };
A32F9FBD272343C9002CFCDB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A32F9FBC272343C8002CFCDB /* GoogleService-Info.plist */; };
- A3318C676D7FBF78A1583B16 /* NftCollectionEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37038CCEB11E70E2027EF4EA /* NftCollectionEdge.graphql.swift */; };
A3551F2CAC134AD49D40927F /* Basel-Grotesk-Book.otf in Resources */ = {isa = PBXBuildFile; fileRef = 6F33E8069B7B40AFB313B8B0 /* Basel-Grotesk-Book.otf */; };
A3F0A5B1272B1DFA00895B25 /* KeychainSwiftDistrib.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F0A5B0272B1DFA00895B25 /* KeychainSwiftDistrib.swift */; };
A70E4DD42C25DA0A002D6D86 /* NetworkFee.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70E4DD32C25DA0A002D6D86 /* NetworkFee.graphql.swift */; };
A70E4DD72C260416002D6D86 /* SwapOrderType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70E4DD52C260416002D6D86 /* SwapOrderType.graphql.swift */; };
A70E4DD82C260416002D6D86 /* SwapOrderStatus.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70E4DD62C260416002D6D86 /* SwapOrderStatus.graphql.swift */; };
A7B8EFCB2BF68F0D00CA4A1C /* FeeData.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8EFCA2BF68F0D00CA4A1C /* FeeData.graphql.swift */; };
- A974633048E27D5D23420F34 /* TokenBasicInfoParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3377F7BB38BD5A45AE31909D /* TokenBasicInfoParts.graphql.swift */; };
- AA3AE4E5C2AAC3F9460A3637 /* NftBalanceAssetInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5FA43C5E31035775917C4E /* NftBalanceAssetInput.graphql.swift */; };
+ A88A26642AC25B022F428953 /* OnRampServiceProvider.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BED80DB034487FCA1450EDB /* OnRampServiceProvider.graphql.swift */; };
+ A949039A6A9EB3584B5644F3 /* IAmount.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37376E4CF3A4B76E9DAE150C /* IAmount.graphql.swift */; };
+ A9AF7B483E9666E88CD6253E /* NftOrderConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F489175099FE697721272D /* NftOrderConnection.graphql.swift */; };
+ AAB837C01239A62D00068853 /* TransactionType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D48B25E03122149B0D1304 /* TransactionType.graphql.swift */; };
AC0EE0982BD826E700BCCF07 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = AC0EE0972BD826E700BCCF07 /* PrivacyInfo.xcprivacy */; };
AC2EF4032C914B1600EEEFDB /* fonts in Resources */ = {isa = PBXBuildFile; fileRef = AC2EF4022C914B1600EEEFDB /* fonts */; };
- AC70FF8207ED26561B634E30 /* NftsQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9030AC59702756C39396FFE /* NftsQuery.graphql.swift */; };
- ADE104A101B3DFFBDB308189 /* Query.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED157D3DB73302C8A1B9522E /* Query.graphql.swift */; };
- AF83E7713BB625D787DD1A1D /* TokenMarket.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD833F6580F9B82A68D4A98 /* TokenMarket.graphql.swift */; };
+ ACA7AE75760B76F7B30DCD43 /* AmountChange.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 066BBBD815CF2DE8803338BC /* AmountChange.graphql.swift */; };
+ AD6036D32C44048781B728E1 /* SchemaConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4115ACC8D8F0C7529AABA7 /* SchemaConfiguration.swift */; };
B193AD315CF844A3BDC3D11D /* Basel-Grotesk-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 3C606D2C81014A0A8898F38E /* Basel-Grotesk-Medium.otf */; };
- B377DB0418EA15695F208D9F /* NetworkFee.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEADEB4AE05B860CF2232D1 /* NetworkFee.graphql.swift */; };
- B61E182CF4937B5169885C95 /* OnRampTransactionDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D3AB6C2FC9513E9F224B7C /* OnRampTransactionDetails.graphql.swift */; };
- B746C09DA19B4C7F9C700989 /* NftActivityConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F912AB9AB67808B740246798 /* NftActivityConnection.graphql.swift */; };
- BA83003638263D702D03C3C1 /* ActivityDetails.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EB7BAD2C90E61FD6C30AF /* ActivityDetails.graphql.swift */; };
+ B64BD6DEB100AEB723DB640D /* NftBalanceConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DDC1F42E7172783EAF3776 /* NftBalanceConnection.graphql.swift */; };
BA869E372D56B0B600D7A718 /* WidgetTokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA869E362D56B0B600D7A718 /* WidgetTokensQuery.graphql.swift */; };
BA8FC9627A40644259D9E2F9 /* Pods_WidgetsCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB29AC0C0907A833F23D2C30 /* Pods_WidgetsCore.framework */; };
- BD1FF76A8E50EFEA0720CC04 /* NftAsset.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2932B486DD7070DF226A27B /* NftAsset.graphql.swift */; };
- BD59A9C8414D6A4BFE9C9A2E /* NftActivity.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A24D364C0D262A14BB7182 /* NftActivity.graphql.swift */; };
- C1FC1F8B4A2725A195F66D60 /* PortfolioValueModifier.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41003491EF939043CF9D0F4E /* PortfolioValueModifier.graphql.swift */; };
- C791C5505DBB3036B9D5066D /* SwapOrderType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148617FD73F2CD7117960DBA /* SwapOrderType.graphql.swift */; };
- C7ABAADA504107D152A52FD4 /* NftBalancesFilterInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41BA2129FFE961C8549C8A30 /* NftBalancesFilterInput.graphql.swift */; };
- C8338C5BE951EF9111C48217 /* TokenBasicProjectParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2F4BE3080D4129D4478528 /* TokenBasicProjectParts.graphql.swift */; };
- C8402101FF510BAA358DCA46 /* TokenBalanceParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C34E94906CB9F0A016D566 /* TokenBalanceParts.graphql.swift */; };
- CA0EDABCA1B6C2B0936BF5CF /* NftBalanceEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B132543F80E3C9191219E6D /* NftBalanceEdge.graphql.swift */; };
- CCBC45FD4310D8153D859361 /* TransactionStatus.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABA47847A73130D356EB6BF /* TransactionStatus.graphql.swift */; };
- CE1DF567D58943ACCBB8360C /* Amount.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20E9461A6C9C1CD6A24BEED /* Amount.graphql.swift */; };
- CF2BAECCF9A2EC43AC3EC583 /* OnRampServiceProvider.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A84A192F750A0F1BC8D7A69 /* OnRampServiceProvider.graphql.swift */; };
- CF36F741187285B430EFE557 /* NftBalance.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05BE2F49DD1FB9A692E21C13 /* NftBalance.graphql.swift */; };
+ BAB54489223FD0027F7A8DBE /* Dimensions.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764C4757EB194D13A8857DFA /* Dimensions.graphql.swift */; };
+ BB28AF35DC102F8DE2A4BE9D /* OnRampTransactionsAuth.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3CC3D9DE64A39897BA580B3 /* OnRampTransactionsAuth.graphql.swift */; };
+ BE0DBD01CF4DCA6D3CDDA369 /* NftActivity.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C67E53A2C8AEB6AE92E33F /* NftActivity.graphql.swift */; };
+ BED0DD22C0D9E09C85DF010D /* NftAsset.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9F3B31FC6AF8CE30CD7DA2 /* NftAsset.graphql.swift */; };
+ BFB114718A0D262E3AEAEDC0 /* TransactionStatus.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945EF0A3BD6F19DA14E40AE1 /* TransactionStatus.graphql.swift */; };
+ C064037906B259088B6B78B2 /* NetworkFee.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BE4B220A4F8465C3D01BA5 /* NetworkFee.graphql.swift */; };
+ C0BBEE1CAEA4B2F778426BDE /* SelectWalletScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB9CACD175D6C20B6D4707C8 /* SelectWalletScreenQuery.graphql.swift */; };
+ C8F5AF75BDB439143FE1C173 /* NftActivityFilterInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C3E075E850CEC1714BE52C /* NftActivityFilterInput.graphql.swift */; };
+ CA966CBD02A7B16BC55F1B8E /* ProtectionResult.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB6B8C64F7525D0D0E978C6 /* ProtectionResult.graphql.swift */; };
+ CBEB9122D993BCB0E9A604B7 /* Query.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5956ECBE73216D266D8D2E86 /* Query.graphql.swift */; };
+ CE8C365D91CADE7A5A8F4D52 /* TopTokenParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F0E504152E22110917353E /* TopTokenParts.graphql.swift */; };
+ CEE9E9912D5621F6E8F819B7 /* MultiplePortfolioBalancesQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 595699514939A2CE780AF0CF /* MultiplePortfolioBalancesQuery.graphql.swift */; };
+ D0B9B6DEC559290B7F64B24F /* TokenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0914ECF70C274E2B064BCD42 /* TokenQuery.graphql.swift */; };
+ D1FB4E293EC152C12A495ACD /* TokenStandard.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34AC1057AD14362BB91E88B /* TokenStandard.graphql.swift */; };
D3B63ACA9B0C42F68080B080 /* InputMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1834199AFFB04D91B05FFB64 /* InputMono-Regular.ttf */; };
- D660CA59775C3634324037ED /* MultiplePortfolioBalancesQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4429CA6DA254ECC24A65DA14 /* MultiplePortfolioBalancesQuery.graphql.swift */; };
- D680C4844F2C5DACF5D0892E /* NftAssetsFilterInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B477EBEA1EDA3914C77E8A6 /* NftAssetsFilterInput.graphql.swift */; };
+ D48D1B7A4469BAAA22608058 /* TokenSortableField.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F133663B435829DE47E3CE /* TokenSortableField.graphql.swift */; };
+ D5F36D3EDC206DF15EC368AC /* NftActivityType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDAC035887C3908C3BD1A77 /* NftActivityType.graphql.swift */; };
+ D6149AE9ED70F546DF5841BD /* ConvertQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4DEE7FC8D7BCD030CFA4D3 /* ConvertQuery.graphql.swift */; };
D7926D4A878B2237137B300F /* Pods_WidgetsCoreTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 021E59CE7ECBD4FE0F3BFCFD /* Pods_WidgetsCoreTests.framework */; };
- D7E3642851C618D369D26B82 /* TokenMarketParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC36D963A2A52D1E7CA77E4 /* TokenMarketParts.graphql.swift */; };
- D81BAFFCC105668B88B607FC /* NftCollectionConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92DE390311705FF1EE65C0E6 /* NftCollectionConnection.graphql.swift */; };
- DC0641C5870F91D26C914F1D /* TokenPriceHistoryQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DFBD29C0BDFDB4464B8189C /* TokenPriceHistoryQuery.graphql.swift */; };
+ D8A0C6D04FF53BA4F50543FE /* NftAssetTrait.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B43D489CC5ACF1EA34E9EB /* NftAssetTrait.graphql.swift */; };
+ DB752E8F664505726ABDBCD3 /* NftBalance.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B02717B1A87BD41920151 /* NftBalance.graphql.swift */; };
+ DC4AA6DE28B9F40A3D7600F1 /* TokenProjectsQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98CEFE7189D63880FD5A702 /* TokenProjectsQuery.graphql.swift */; };
DE2F24512E7204C2CA255C50 /* Pods_Widgets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2E8B7D36D2E14D9488F351EB /* Pods_Widgets.framework */; };
- E091F379106E92D3181103CB /* IAmount.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD8C2B1226EBE1524F2573 /* IAmount.graphql.swift */; };
- E0F63BA3A99DB10FA1CCAB5E /* TokenProjectUrlsParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236126979349098B98C70F27 /* TokenProjectUrlsParts.graphql.swift */; };
- E24ED8688E9B18BBD543F8F0 /* TokenBalance.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B149117182C6CC58DD570C /* TokenBalance.graphql.swift */; };
+ E1009979B48ACF5017C12F09 /* TransactionListQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95283AC560844B50E95934E4 /* TransactionListQuery.graphql.swift */; };
+ E2C528F617A9675690171D54 /* DescriptionTranslations.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = D91432EF6C35F5A6301340A2 /* DescriptionTranslations.graphql.swift */; };
+ E475BA358DD8BE30A6FDC051 /* NftBalanceEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD55DFFC717D37DD960B82E /* NftBalanceEdge.graphql.swift */; };
+ E4985BA9090E013F2C9FC190 /* TokenMarket.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E5B09464C768590127BFA6 /* TokenMarket.graphql.swift */; };
E4B3067A930D2E57558E5229 /* Pods_Uniswap_UniswapTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0929C0B4AE1570B8C0B45D4D /* Pods_Uniswap_UniswapTests.framework */; };
- E54093DC857B8C634804D42B /* NftTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3317EBD4E3B11E2F2A1CE58 /* NftTransfer.graphql.swift */; };
- EF05F69E61EA8A16C6A66D53 /* IContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0D68CC8D1F13D864F069BB /* IContract.graphql.swift */; };
- EF3D92CA76DEE66090F18D0C /* NftCollectionScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0973BB445AAD8FAA25757F /* NftCollectionScreenQuery.graphql.swift */; };
- F0D6A92BB4FCE19CDFA5BBE1 /* HomeScreenTokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7E03AA02B39A7A90C96AA76 /* HomeScreenTokensQuery.graphql.swift */; };
- F2EB62621E64B57B07DE8B13 /* Token.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647933A5DC35BDFEEF3620A1 /* Token.graphql.swift */; };
+ E654760CA0FF19139A85472A /* TokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C8174A3A9EB3244AA844F4 /* TokensQuery.graphql.swift */; };
+ E7B6F1CA0E30585C949C9D9A /* NftAssetConnection.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD86EFAA0D6B8FBECCC4828B /* NftAssetConnection.graphql.swift */; };
+ E7D4A29333634717D099F80E /* SwapOrderType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFDF569E43F173C2F5A07AF /* SwapOrderType.graphql.swift */; };
+ E7EDBB8CDF65D5D6602BF8FC /* NftOrder.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67C0D95F7EA9E7ACA9B18692 /* NftOrder.graphql.swift */; };
+ EB0A75424F8EEF6612D28D52 /* NftTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AD16E33DC518A42C386FE2 /* NftTransfer.graphql.swift */; };
+ ED606CD83873CC9DFCCA44F1 /* FavoriteTokenCardQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B03A0A6AB0957A512A2D225 /* FavoriteTokenCardQuery.graphql.swift */; };
+ EE4B861AE191A1BF70CF3784 /* WidgetTokensQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0A91F56B1CA2E71B479BAD /* WidgetTokensQuery.graphql.swift */; };
+ F09E3F15A36A9050B028B3E3 /* TokenBasicProjectParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8286984864AD69A0FAE3BC /* TokenBasicProjectParts.graphql.swift */; };
+ F1193089AE71CA3C4C101EA5 /* OffRampTransfer.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 166A9AE7203545C397E683E8 /* OffRampTransfer.graphql.swift */; };
F35AFD3E27EE49990011A725 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F35AFD3D27EE49990011A725 /* NotificationService.swift */; };
F35AFD4227EE49990011A725 /* OneSignalNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = F35AFD3B27EE49990011A725 /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- F53121FB1B070DB9A81347DF /* TokenProjectMarketsParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC055C3EDFDE2966FCA83C9E /* TokenProjectMarketsParts.graphql.swift */; };
- F5B577CA1201CB6FF04A9A58 /* ApplicationContract.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608B07304ED1387B5AEAA3BB /* ApplicationContract.graphql.swift */; };
- F772B09295DABC5E8895C1C5 /* SelectWalletScreenQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B033206DC974BCB6AF8CC613 /* SelectWalletScreenQuery.graphql.swift */; };
- FA8EB6A63AB3F56194C6CFB8 /* TokenBalanceQuantityParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2370BAD675DC8064C00AC037 /* TokenBalanceQuantityParts.graphql.swift */; };
- FBE634E2AEC7A62B89863366 /* ContractInput.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B3C6DF5DED22E892851B10 /* ContractInput.graphql.swift */; };
- FC7C117CEA5EA63460E6A2C6 /* NftApproveForAll.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BCBB5D46312050E4A5BD64 /* NftApproveForAll.graphql.swift */; };
- FD4E55146E046C7F5983A379 /* FeedTransactionListQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BA0ACE5B9B2C41187CD41F3 /* FeedTransactionListQuery.graphql.swift */; };
+ F49C1C4E9175DFE7EEF9FB51 /* Token.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FB1590D18A58D37C750D9AC /* Token.graphql.swift */; };
+ F77F1A182DCA1D8DFE46ACC8 /* TokenBalanceParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AD13BFAD4EBA18AA6205E3 /* TokenBalanceParts.graphql.swift */; };
+ F814C0144D90ADB4A8E0A34E /* Chain.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBCFBC330BF97BA2F630908 /* Chain.graphql.swift */; };
+ F86D86FC31BBDA7246C50392 /* ProtectionAttackType.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3F4D5E8B9ED6E0FE29EA779 /* ProtectionAttackType.graphql.swift */; };
+ FA318FB37223FAF04A5C887B /* TokenProjectUrlsParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190523DBBA423868C74FF80D /* TokenProjectUrlsParts.graphql.swift */; };
+ FC61EDE606C85346CE069C5D /* TransactionHistoryUpdaterQuery.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559CBC7F568C769C48E7C6F4 /* TransactionHistoryUpdaterQuery.graphql.swift */; };
FD54D51D296C79A4007A37E9 /* GoogleServiceInfo in Resources */ = {isa = PBXBuildFile; fileRef = FD54D51C296C79A4007A37E9 /* GoogleServiceInfo */; };
FD7304CE28A364FC0085BDEA /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FD7304CD28A364FC0085BDEA /* Colors.xcassets */; };
FD7304D028A3650A0085BDEA /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7304CF28A3650A0085BDEA /* Colors.swift */; };
- FD84AA379C4562598807843D /* TokenStandard.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC419570F5A025E80AB10A72 /* TokenStandard.graphql.swift */; };
- FE9CE5C3B5A456B249FC34C5 /* NftActivityEdge.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB4379B5E222207CA7328D0 /* NftActivityEdge.graphql.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -436,16 +439,16 @@
00E356EE1AD99517003FC87E /* UniswapTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UniswapTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
00E356F21AD99517003FC87E /* UniswapTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UniswapTests.m; sourceTree = ""; };
- 018603D0FA4D05DBF16F1441 /* TokenFeeDataParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenFeeDataParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenFeeDataParts.graphql.swift; sourceTree = ""; };
021E59CE7ECBD4FE0F3BFCFD /* Pods_WidgetsCoreTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WidgetsCoreTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0373C61A7AF015058A502CE2 /* NftStandard.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftStandard.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/NftStandard.graphql.swift; sourceTree = ""; };
037C5AA92C04970B00B1D808 /* CopyIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyIcon.swift; sourceTree = ""; };
- 03AD8C2B1226EBE1524F2573 /* IAmount.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IAmount.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Interfaces/IAmount.graphql.swift; sourceTree = ""; };
+ 03AB6B21A1BFB3A18982B47E /* NFTItemScreenQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NFTItemScreenQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/NFTItemScreenQuery.graphql.swift; sourceTree = ""; };
03C788222C10E7390011E5DC /* ActionButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtons.swift; sourceTree = ""; };
03D2F3172C218D380030D987 /* RelativeOffsetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeOffsetView.swift; sourceTree = ""; };
- 04E5C2900254E7BA7F10CE51 /* TopTokensQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TopTokensQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TopTokensQuery.graphql.swift; sourceTree = ""; };
- 05BE2F49DD1FB9A692E21C13 /* NftBalance.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalance.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftBalance.graphql.swift; sourceTree = ""; };
+ 043C7D80D6722AFCF1715D07 /* NftActivityEdge.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftActivityEdge.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftActivityEdge.graphql.swift; sourceTree = ""; };
+ 05C66C49AF2B78703FCE4C7C /* AssetActivity.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssetActivity.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/AssetActivity.graphql.swift; sourceTree = ""; };
065A981F892F7A06A900FCD5 /* Pods-WidgetsCoreTests.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsCoreTests.dev.xcconfig"; path = "Target Support Files/Pods-WidgetsCoreTests/Pods-WidgetsCoreTests.dev.xcconfig"; sourceTree = ""; };
- 0702203B17BDD858CED28F8B /* ProtectionInfo.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ProtectionInfo.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/ProtectionInfo.graphql.swift; sourceTree = ""; };
+ 066BBBD815CF2DE8803338BC /* AmountChange.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AmountChange.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/AmountChange.graphql.swift; sourceTree = ""; };
0703EE022A5734A600AED1DA /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; };
070480372A58A507009006CE /* WidgetIntentExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetIntentExtension.entitlements; sourceTree = ""; };
0712B3629C74D1F958DF35FB /* Pods-Uniswap-UniswapTests.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uniswap-UniswapTests.dev.xcconfig"; path = "Target Support Files/Pods-Uniswap-UniswapTests/Pods-Uniswap-UniswapTests.dev.xcconfig"; sourceTree = ""; };
@@ -544,6 +547,7 @@
074321E92A83E3C900F8518D /* IAmount.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IAmount.graphql.swift; sourceTree = ""; };
074321EA2A83E3C900F8518D /* IContract.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IContract.graphql.swift; sourceTree = ""; };
0743223F2A841BBD00F8518D /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
+ 075E4C1DFAA00EE5B3CE792D /* ActivityDetails.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ActivityDetails.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Unions/ActivityDetails.graphql.swift; sourceTree = ""; };
0767E0372A65C8330042ADA2 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; };
0767E03A2A65D2550042ADA2 /* Styling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Styling.swift; sourceTree = ""; };
077E60382A85587800ABC4B9 /* TokensQuery.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensQuery.graphql.swift; sourceTree = ""; };
@@ -556,17 +560,20 @@
07B0676A2A7D6EC8001DD9B9 /* RNWidgets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNWidgets.swift; sourceTree = ""; };
07B0676B2A7D6EC8001DD9B9 /* RNWidgets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNWidgets.m; sourceTree = ""; };
07F0C28E2A5F3E2E00D5353E /* Env.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Env.swift; sourceTree = ""; };
+ 07F0E504152E22110917353E /* TopTokenParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TopTokenParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TopTokenParts.graphql.swift; sourceTree = ""; };
07F1363F2A575EC00067004F /* DataQueries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataQueries.swift; sourceTree = ""; };
07F136412A5763480067004F /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; };
07F5CF702A6AD97D00C648A5 /* Chart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chart.swift; sourceTree = ""; };
07F5CF742A7020FD00C648A5 /* Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Format.swift; sourceTree = ""; };
08C60D53AB82A6D0D31D0F78 /* Pods-WidgetIntentExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetIntentExtension.release.xcconfig"; path = "Target Support Files/Pods-WidgetIntentExtension/Pods-WidgetIntentExtension.release.xcconfig"; sourceTree = ""; };
08EBF075A4482F701892270B /* Pods-Widgets.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Widgets.dev.xcconfig"; path = "Target Support Files/Pods-Widgets/Pods-Widgets.dev.xcconfig"; sourceTree = ""; };
+ 0914ECF70C274E2B064BCD42 /* TokenQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TokenQuery.graphql.swift; sourceTree = ""; };
0929C0B4AE1570B8C0B45D4D /* Pods_Uniswap_UniswapTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Uniswap_UniswapTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 0B132543F80E3C9191219E6D /* NftBalanceEdge.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalanceEdge.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftBalanceEdge.graphql.swift; sourceTree = ""; };
+ 09BE4B220A4F8465C3D01BA5 /* NetworkFee.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkFee.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NetworkFee.graphql.swift; sourceTree = ""; };
+ 0B0505DB30407C221B6A8491 /* TokenProjectMarket.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProjectMarket.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TokenProjectMarket.graphql.swift; sourceTree = ""; };
0B7E5D62E11408EB5F0F5A80 /* Pods-WidgetsCore.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsCore.beta.xcconfig"; path = "Target Support Files/Pods-WidgetsCore/Pods-WidgetsCore.beta.xcconfig"; sourceTree = ""; };
- 0BB4A40E30E1D985519DB3CF /* TokenProtectionInfoParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProtectionInfoParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenProtectionInfoParts.graphql.swift; sourceTree = ""; };
0C19DE44A750FB17647FF2B6 /* Pods-Widgets.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Widgets.beta.xcconfig"; path = "Target Support Files/Pods-Widgets/Pods-Widgets.beta.xcconfig"; sourceTree = ""; };
+ 0D09FFE2B5E56CDCAB2F9455 /* NftCollectionConnection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftCollectionConnection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftCollectionConnection.graphql.swift; sourceTree = ""; };
0DB282242CDADB260014CF77 /* EmbeddedWallet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EmbeddedWallet.m; sourceTree = ""; };
0DB282252CDADB260014CF77 /* EmbeddedWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedWallet.swift; sourceTree = ""; };
0DC6ADEF2B1E2C0F0092909C /* PortfolioValueModifier.graphql.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortfolioValueModifier.graphql.swift; sourceTree = ""; };
@@ -574,91 +581,82 @@
0DE251442C13B69D005F47F9 /* OnRampTransfer.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnRampTransfer.graphql.swift; sourceTree = ""; };
0DE251452C13B69D005F47F9 /* OnRampServiceProvider.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnRampServiceProvider.graphql.swift; sourceTree = ""; };
0DE251462C13B69D005F47F9 /* OnRampTransactionDetails.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnRampTransactionDetails.graphql.swift; sourceTree = ""; };
+ 0E7FF51A507BF788E64E3EFE /* HomeScreenTokensQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HomeScreenTokensQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/HomeScreenTokensQuery.graphql.swift; sourceTree = ""; };
+ 1053483279043EED7612BE90 /* HistoryDuration.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HistoryDuration.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/HistoryDuration.graphql.swift; sourceTree = ""; };
1064E23E366D0C2C2B20C30E /* Pods_WidgetIntentExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WidgetIntentExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1166529407CA13E4D4F24F12 /* TokenFeeDataParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenFeeDataParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenFeeDataParts.graphql.swift; sourceTree = ""; };
1193B3A845BC3BE8CAA00D01 /* Pods-OneSignalNotificationServiceExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.release.xcconfig"; path = "Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.release.xcconfig"; sourceTree = ""; };
- 12D3AB6C2FC9513E9F224B7C /* OnRampTransactionDetails.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OnRampTransactionDetails.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OnRampTransactionDetails.graphql.swift; sourceTree = ""; };
- 137696908D703632CEE3AB28 /* TokenBalanceMainParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBalanceMainParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenBalanceMainParts.graphql.swift; sourceTree = ""; };
+ 1276D33B06E840B8E6839F39 /* TokenProjectDescriptionQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProjectDescriptionQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TokenProjectDescriptionQuery.graphql.swift; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* Uniswap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Uniswap.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Uniswap/AppDelegate.h; sourceTree = ""; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Uniswap/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Uniswap/Info.plist; sourceTree = ""; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Uniswap/main.m; sourceTree = ""; };
- 148617FD73F2CD7117960DBA /* SwapOrderType.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwapOrderType.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/SwapOrderType.graphql.swift; sourceTree = ""; };
15092E550A1C78508ABA3280 /* Pods_OneSignalNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OneSignalNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 17819A49DE69723EC60D9D72 /* FeeData.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FeeData.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/FeeData.graphql.swift; sourceTree = ""; };
+ 166A9AE7203545C397E683E8 /* OffRampTransfer.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OffRampTransfer.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OffRampTransfer.graphql.swift; sourceTree = ""; };
178644A78AB62609EFDB66B3 /* Pods-Uniswap.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uniswap.release.xcconfig"; path = "Target Support Files/Pods-Uniswap/Pods-Uniswap.release.xcconfig"; sourceTree = ""; };
+ 1810C60B56CC74150C868801 /* NftBalancesFilterInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalancesFilterInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftBalancesFilterInput.graphql.swift; sourceTree = ""; };
+ 181209B4CDE80BDC80891CFD /* TokenBalance.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBalance.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TokenBalance.graphql.swift; sourceTree = ""; };
1834199AFFB04D91B05FFB64 /* InputMono-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "InputMono-Regular.ttf"; path = "../src/assets/fonts/InputMono-Regular.ttf"; sourceTree = ""; };
- 1849039FD4A210DA990363C8 /* Portfolio.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Portfolio.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Portfolio.graphql.swift; sourceTree = ""; };
- 18B9EB43501127BE1B14F1A5 /* NftContract.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftContract.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftContract.graphql.swift; sourceTree = ""; };
+ 18D48B25E03122149B0D1304 /* TransactionType.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionType.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/TransactionType.graphql.swift; sourceTree = ""; };
+ 190523DBBA423868C74FF80D /* TokenProjectUrlsParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProjectUrlsParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenProjectUrlsParts.graphql.swift; sourceTree = ""; };
1CC6ADAADCA38FDAEB181E86 /* Pods-WidgetIntentExtension.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetIntentExtension.dev.xcconfig"; path = "Target Support Files/Pods-WidgetIntentExtension/Pods-WidgetIntentExtension.dev.xcconfig"; sourceTree = ""; };
- 1FC7B80EC0E0D2134B67575B /* NftBalanceConnection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalanceConnection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftBalanceConnection.graphql.swift; sourceTree = ""; };
- 207F4DF7664454C31DE069F8 /* NFTItemScreenQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NFTItemScreenQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/NFTItemScreenQuery.graphql.swift; sourceTree = ""; };
- 2139ABF56EA067ED792A48C9 /* OnRampTransfer.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OnRampTransfer.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OnRampTransfer.graphql.swift; sourceTree = ""; };
- 218B8AB05C80D919671D3970 /* NftAssetConnection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetConnection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftAssetConnection.graphql.swift; sourceTree = ""; };
+ 1CDAC035887C3908C3BD1A77 /* NftActivityType.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftActivityType.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/NftActivityType.graphql.swift; sourceTree = ""; };
+ 1D9189C818AFF8E988F861D0 /* IContract.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IContract.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Interfaces/IContract.graphql.swift; sourceTree = ""; };
+ 1F1731869775D16A9EAB475C /* NftAssetTraitInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetTraitInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftAssetTraitInput.graphql.swift; sourceTree = ""; };
2226DF79BEAFECEE11A51347 /* Pods_Uniswap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Uniswap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 224CA82F1871016F4173F69E /* TransactionListQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionListQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TransactionListQuery.graphql.swift; sourceTree = ""; };
- 236126979349098B98C70F27 /* TokenProjectUrlsParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProjectUrlsParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenProjectUrlsParts.graphql.swift; sourceTree = ""; };
- 2370BAD675DC8064C00AC037 /* TokenBalanceQuantityParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBalanceQuantityParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenBalanceQuantityParts.graphql.swift; sourceTree = ""; };
- 25B4C7C7FEF32EAF10D030F9 /* Currency.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Currency.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/Currency.graphql.swift; sourceTree = ""; };
- 27F8B915042BD8D67D9627EA /* HomeScreenTokenParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HomeScreenTokenParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/HomeScreenTokenParts.graphql.swift; sourceTree = ""; };
- 2ABA47847A73130D356EB6BF /* TransactionStatus.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionStatus.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/TransactionStatus.graphql.swift; sourceTree = ""; };
- 2BA0ACE5B9B2C41187CD41F3 /* FeedTransactionListQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FeedTransactionListQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/FeedTransactionListQuery.graphql.swift; sourceTree = ""; };
- 2C2F4BE3080D4129D4478528 /* TokenBasicProjectParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBasicProjectParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenBasicProjectParts.graphql.swift; sourceTree = ""; };
- 2D95FE36D2667E070345CDD0 /* NftActivityType.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftActivityType.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/NftActivityType.graphql.swift; sourceTree = ""; };
- 2E49E724432C70A2551FB2C7 /* SchemaConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SchemaConfiguration.swift; path = WidgetsCore/MobileSchema/Schema/SchemaConfiguration.swift; sourceTree = ""; };
+ 22F8093091A9E5FC0D6284BA /* SafetyLevel.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SafetyLevel.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/SafetyLevel.graphql.swift; sourceTree = ""; };
+ 243576FAC8D2801F9D99ECC9 /* TopTokensQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TopTokensQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TopTokensQuery.graphql.swift; sourceTree = ""; };
+ 2E8286984864AD69A0FAE3BC /* TokenBasicProjectParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBasicProjectParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenBasicProjectParts.graphql.swift; sourceTree = ""; };
2E8B7D36D2E14D9488F351EB /* Pods_Widgets.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Widgets.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 315B8A905FA2C60C053F138B /* OnRampTransactionsAuth.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OnRampTransactionsAuth.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/OnRampTransactionsAuth.graphql.swift; sourceTree = ""; };
- 31ED613D63FE9C56D9270517 /* Image.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Image.graphql.swift; sourceTree = ""; };
- 32012AC135EBD991C8F02F92 /* TokenSortableField.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenSortableField.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/TokenSortableField.graphql.swift; sourceTree = ""; };
- 3377F7BB38BD5A45AE31909D /* TokenBasicInfoParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBasicInfoParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenBasicInfoParts.graphql.swift; sourceTree = ""; };
- 37038CCEB11E70E2027EF4EA /* NftCollectionEdge.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftCollectionEdge.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftCollectionEdge.graphql.swift; sourceTree = ""; };
- 396B6AB3BB41898B56EB3828 /* OffRampTransactionDetails.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OffRampTransactionDetails.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OffRampTransactionDetails.graphql.swift; sourceTree = ""; };
- 39F60B2B54796DC978765C99 /* AssetChange.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssetChange.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Unions/AssetChange.graphql.swift; sourceTree = ""; };
- 39FC2A13550FA6754EC1A7FC /* NftOrderConnection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftOrderConnection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftOrderConnection.graphql.swift; sourceTree = ""; };
+ 2FB1590D18A58D37C750D9AC /* Token.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Token.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Token.graphql.swift; sourceTree = ""; };
+ 310373063D8A11E9E02314FE /* NftAssetsFilterInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetsFilterInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftAssetsFilterInput.graphql.swift; sourceTree = ""; };
+ 31CBA4397C44A04E218AEF28 /* TimestampedAmount.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimestampedAmount.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TimestampedAmount.graphql.swift; sourceTree = ""; };
+ 37376E4CF3A4B76E9DAE150C /* IAmount.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IAmount.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Interfaces/IAmount.graphql.swift; sourceTree = ""; };
3A2186B1FF7FB85663D96EA9 /* Pods-OneSignalNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; };
- 3B9A505509D9A85BE1DB7D05 /* TokenProjectMarket.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProjectMarket.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TokenProjectMarket.graphql.swift; sourceTree = ""; };
- 3BF42F88002431BFDC9FCA9F /* HistoryDuration.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HistoryDuration.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/HistoryDuration.graphql.swift; sourceTree = ""; };
+ 3B03A0A6AB0957A512A2D225 /* FavoriteTokenCardQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FavoriteTokenCardQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/FavoriteTokenCardQuery.graphql.swift; sourceTree = ""; };
3C606D2C81014A0A8898F38E /* Basel-Grotesk-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Basel-Grotesk-Medium.otf"; path = "../src/assets/fonts/Basel-Grotesk-Medium.otf"; sourceTree = ""; };
3D8FCE4CD401350CA74DCC89 /* Pods-WidgetsCoreTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsCoreTests.debug.xcconfig"; path = "Target Support Files/Pods-WidgetsCoreTests/Pods-WidgetsCoreTests.debug.xcconfig"; sourceTree = ""; };
3E279F675B02CBC50D3B57D5 /* Pods-WidgetsCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsCore.release.xcconfig"; path = "Target Support Files/Pods-WidgetsCore/Pods-WidgetsCore.release.xcconfig"; sourceTree = ""; };
- 3F0EEAC1DADE827AE38EB8A6 /* FavoriteTokenCardQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FavoriteTokenCardQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/FavoriteTokenCardQuery.graphql.swift; sourceTree = ""; };
- 40E034582FBD2C374CEF808E /* ProtectionAttackType.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ProtectionAttackType.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/ProtectionAttackType.graphql.swift; sourceTree = ""; };
- 41003491EF939043CF9D0F4E /* PortfolioValueModifier.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PortfolioValueModifier.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/PortfolioValueModifier.graphql.swift; sourceTree = ""; };
- 41BA2129FFE961C8549C8A30 /* NftBalancesFilterInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalancesFilterInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftBalancesFilterInput.graphql.swift; sourceTree = ""; };
- 43E2ECCEFBC3BE8D13BE6CAB /* NftActivityFilterInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftActivityFilterInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftActivityFilterInput.graphql.swift; sourceTree = ""; };
- 4429CA6DA254ECC24A65DA14 /* MultiplePortfolioBalancesQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultiplePortfolioBalancesQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/MultiplePortfolioBalancesQuery.graphql.swift; sourceTree = ""; };
+ 3E5D1A04B42B25218C53C107 /* OnRampTransfer.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OnRampTransfer.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OnRampTransfer.graphql.swift; sourceTree = ""; };
+ 3EF9AEC00868BFD7CEBF202C /* BlockaidFees.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BlockaidFees.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/BlockaidFees.graphql.swift; sourceTree = ""; };
+ 45FFF7DE2E8C2A6400362570 /* SilentPushEventEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SilentPushEventEmitter.m; sourceTree = ""; };
+ 45FFF7E02E8C2E6100362570 /* SilentPushEventEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SilentPushEventEmitter.swift; sourceTree = ""; };
4781CD4CDD95B5792B793F75 /* Pods-Uniswap-UniswapTests.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uniswap-UniswapTests.beta.xcconfig"; path = "Target Support Files/Pods-Uniswap-UniswapTests/Pods-Uniswap-UniswapTests.beta.xcconfig"; sourceTree = ""; };
- 4981A14906A35E8A0B7F9457 /* TokenProject.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProject.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TokenProject.graphql.swift; sourceTree = ""; };
- 49BD6C776029FAF4FC711DE7 /* NftOrderEdge.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftOrderEdge.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftOrderEdge.graphql.swift; sourceTree = ""; };
- 4A8990A17F489F284E0DB1B3 /* Chain.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Chain.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/Chain.graphql.swift; sourceTree = ""; };
- 4ACFA5B4204741038C986C5D /* SafetyLevel.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SafetyLevel.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/SafetyLevel.graphql.swift; sourceTree = ""; };
- 4B308EC3DB7C3C1DD5DDE03D /* NftStandard.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftStandard.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/NftStandard.graphql.swift; sourceTree = ""; };
4C445DB9798210862C34D0E0 /* Pods-WidgetsCoreTests.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsCoreTests.beta.xcconfig"; path = "Target Support Files/Pods-WidgetsCoreTests/Pods-WidgetsCoreTests.beta.xcconfig"; sourceTree = ""; };
- 4DB4379B5E222207CA7328D0 /* NftActivityEdge.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftActivityEdge.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftActivityEdge.graphql.swift; sourceTree = ""; };
+ 4DD55DFFC717D37DD960B82E /* NftBalanceEdge.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalanceEdge.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftBalanceEdge.graphql.swift; sourceTree = ""; };
4DF5F26A06553EFDD4D99214 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Uniswap/ExpoModulesProvider.swift"; sourceTree = ""; };
- 4E32BBB65A6DD9CFF608C8E8 /* NftApproval.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftApproval.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftApproval.graphql.swift; sourceTree = ""; };
- 4E5FA43C5E31035775917C4E /* NftBalanceAssetInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalanceAssetInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftBalanceAssetInput.graphql.swift; sourceTree = ""; };
- 4F4F47D22B749A438B5BF674 /* NftCollectionMarket.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftCollectionMarket.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftCollectionMarket.graphql.swift; sourceTree = ""; };
- 54B1C71AED738D17898103A6 /* Dimensions.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Dimensions.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Dimensions.graphql.swift; sourceTree = ""; };
- 556339705BD103365D4ED07B /* NftAssetTraitInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetTraitInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftAssetTraitInput.graphql.swift; sourceTree = ""; };
- 5640432978873FB030467750 /* TopTokenParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TopTokenParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TopTokenParts.graphql.swift; sourceTree = ""; };
+ 50C3E075E850CEC1714BE52C /* NftActivityFilterInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftActivityFilterInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftActivityFilterInput.graphql.swift; sourceTree = ""; };
+ 51F133663B435829DE47E3CE /* TokenSortableField.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenSortableField.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/TokenSortableField.graphql.swift; sourceTree = ""; };
+ 522135E54BDB258B45B5A694 /* SchemaMetadata.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SchemaMetadata.graphql.swift; path = WidgetsCore/MobileSchema/Schema/SchemaMetadata.graphql.swift; sourceTree = ""; };
+ 5397E77A577E9952D2477020 /* Portfolio.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Portfolio.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Portfolio.graphql.swift; sourceTree = ""; };
+ 53F4E5E9341BE66984935185 /* SwapOrderDetails.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwapOrderDetails.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/SwapOrderDetails.graphql.swift; sourceTree = ""; };
+ 55081ADF188C967FC1ECD289 /* TokenMarketParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenMarketParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenMarketParts.graphql.swift; sourceTree = ""; };
+ 55240C84512D3851C8B9F027 /* TransactionDetails.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionDetails.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TransactionDetails.graphql.swift; sourceTree = ""; };
+ 559CBC7F568C769C48E7C6F4 /* TransactionHistoryUpdaterQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionHistoryUpdaterQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TransactionHistoryUpdaterQuery.graphql.swift; sourceTree = ""; };
+ 55A243177E9FA92A0524A222 /* NftContract.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftContract.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftContract.graphql.swift; sourceTree = ""; };
+ 55C67E53A2C8AEB6AE92E33F /* NftActivity.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftActivity.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftActivity.graphql.swift; sourceTree = ""; };
56FE9C9AF785221B7E3F4C04 /* Pods-Uniswap.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uniswap.dev.xcconfig"; path = "Target Support Files/Pods-Uniswap/Pods-Uniswap.dev.xcconfig"; sourceTree = ""; };
+ 595699514939A2CE780AF0CF /* MultiplePortfolioBalancesQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultiplePortfolioBalancesQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/MultiplePortfolioBalancesQuery.graphql.swift; sourceTree = ""; };
+ 5956ECBE73216D266D8D2E86 /* Query.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Query.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Query.graphql.swift; sourceTree = ""; };
+ 59DD4D834D7B42A6D02F4F12 /* ApplicationContract.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ApplicationContract.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/ApplicationContract.graphql.swift; sourceTree = ""; };
+ 5B0A1F5F6FC3E1141E228B7D /* TokenBalanceQuantityParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBalanceQuantityParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenBalanceQuantityParts.graphql.swift; sourceTree = ""; };
5B4398E82DD3B22C00F6BE08 /* PrivateKeyDisplayManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivateKeyDisplayManager.m; sourceTree = ""; };
5B4398E92DD3B22C00F6BE08 /* PrivateKeyDisplayManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateKeyDisplayManager.swift; sourceTree = ""; };
5B4398EA2DD3B22C00F6BE08 /* PrivateKeyDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateKeyDisplayView.swift; sourceTree = ""; };
5B4CEC5E2DD65DD4009F082B /* CopyIconOutline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyIconOutline.swift; sourceTree = ""; };
- 5E0D68CC8D1F13D864F069BB /* IContract.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IContract.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Interfaces/IContract.graphql.swift; sourceTree = ""; };
+ 5BED80DB034487FCA1450EDB /* OnRampServiceProvider.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OnRampServiceProvider.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OnRampServiceProvider.graphql.swift; sourceTree = ""; };
+ 5C9F3B31FC6AF8CE30CD7DA2 /* NftAsset.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAsset.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftAsset.graphql.swift; sourceTree = ""; };
5E5E0A622D380F5700E166AA /* Env.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Env.swift; sourceTree = ""; };
5EFB78352B1E585000E77EAC /* ConvertQuery.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvertQuery.graphql.swift; sourceTree = ""; };
- 600250B00B6C02AAB063818B /* NftMarketplace.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftMarketplace.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/NftMarketplace.graphql.swift; sourceTree = ""; };
- 608B07304ED1387B5AEAA3BB /* ApplicationContract.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ApplicationContract.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/ApplicationContract.graphql.swift; sourceTree = ""; };
62CEA9F2D5176D20A6402A3E /* Pods-Uniswap.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uniswap.beta.xcconfig"; path = "Target Support Files/Pods-Uniswap/Pods-Uniswap.beta.xcconfig"; sourceTree = ""; };
- 63ACFF2A1D5AE3498731AC69 /* NftCollection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftCollection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftCollection.graphql.swift; sourceTree = ""; };
- 647933A5DC35BDFEEF3620A1 /* Token.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Token.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Token.graphql.swift; sourceTree = ""; };
+ 62DDC1F42E7172783EAF3776 /* NftBalanceConnection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalanceConnection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftBalanceConnection.graphql.swift; sourceTree = ""; };
649A7A762D9AE70B00B53589 /* KeychainConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainConstants.swift; sourceTree = ""; };
649A7A772D9AE70B00B53589 /* KeychainUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainUtils.swift; sourceTree = ""; };
- 69F2FFD11FB5E462CC454A15 /* TransactionType.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionType.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/TransactionType.graphql.swift; sourceTree = ""; };
- 6A84A192F750A0F1BC8D7A69 /* OnRampServiceProvider.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OnRampServiceProvider.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OnRampServiceProvider.graphql.swift; sourceTree = ""; };
+ 64D494D944670E557175A694 /* TransactionDirection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionDirection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/TransactionDirection.graphql.swift; sourceTree = ""; };
+ 65AD13BFAD4EBA18AA6205E3 /* TokenBalanceParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenBalanceParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenBalanceParts.graphql.swift; sourceTree = ""; };
+ 67C0D95F7EA9E7ACA9B18692 /* NftOrder.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftOrder.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftOrder.graphql.swift; sourceTree = ""; };
+ 696F6C5220D0072E380E198F /* NftBalanceAssetInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftBalanceAssetInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftBalanceAssetInput.graphql.swift; sourceTree = ""; };
6BC7D07B2B5FF02400617C95 /* ScantasticEncryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScantasticEncryption.m; sourceTree = ""; };
6BC7D07C2B5FF02400617C95 /* ScantasticEncryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScantasticEncryption.swift; sourceTree = ""; };
6BC7D07D2B5FF02400617C95 /* EncryptionUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionUtils.swift; sourceTree = ""; };
@@ -669,26 +667,26 @@
6CA91BDE2A95226200C4063E /* RNCloudStorageBackupsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCloudStorageBackupsManager.m; sourceTree = ""; };
6CA91BDF2A95226200C4063E /* EncryptionHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionHelper.swift; sourceTree = ""; };
6CA91BE02A95226200C4063E /* RNCloudStorageBackupsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNCloudStorageBackupsManager.swift; sourceTree = ""; };
- 6DF7C8888BE6AFA2F22889FE /* NftAssetEdge.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetEdge.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftAssetEdge.graphql.swift; sourceTree = ""; };
- 6E3A78386B3B779B688021FF /* NftOrder.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftOrder.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftOrder.graphql.swift; sourceTree = ""; };
+ 6D4C849A7805FCCEE8C8C3E6 /* AssetChange.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssetChange.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Unions/AssetChange.graphql.swift; sourceTree = ""; };
+ 6E4DEE7FC8D7BCD030CFA4D3 /* ConvertQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConvertQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/ConvertQuery.graphql.swift; sourceTree = ""; };
+ 6F0A91F56B1CA2E71B479BAD /* WidgetTokensQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = WidgetTokensQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/WidgetTokensQuery.graphql.swift; sourceTree = ""; };
6F33E8069B7B40AFB313B8B0 /* Basel-Grotesk-Book.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Basel-Grotesk-Book.otf"; path = "../src/assets/fonts/Basel-Grotesk-Book.otf"; sourceTree = ""; };
6F3DC921A65D749C0852B10C /* Pods-Uniswap-UniswapTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uniswap-UniswapTests.debug.xcconfig"; path = "Target Support Files/Pods-Uniswap-UniswapTests/Pods-Uniswap-UniswapTests.debug.xcconfig"; sourceTree = ""; };
6F7814C6D40D9C348EA1F1C7 /* Pods-OneSignalNotificationServiceExtension.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.dev.xcconfig"; path = "Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.dev.xcconfig"; sourceTree = ""; };
- 6FF8B660E9C2C0D8DDF7588A /* TokenProjectDescriptionQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProjectDescriptionQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TokenProjectDescriptionQuery.graphql.swift; sourceTree = ""; };
- 70851E3D1A4B296A1CE22A20 /* NftAssetTrait.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetTrait.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftAssetTrait.graphql.swift; sourceTree = ""; };
- 730CA356D6E1D498CC0C1472 /* NftsTabQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftsTabQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/NftsTabQuery.graphql.swift; sourceTree = ""; };
- 74885865ED8CFEA875E40916 /* TokenQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TokenQuery.graphql.swift; sourceTree = ""; };
+ 74AD16E33DC518A42C386FE2 /* NftTransfer.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftTransfer.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftTransfer.graphql.swift; sourceTree = ""; };
+ 764C4757EB194D13A8857DFA /* Dimensions.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Dimensions.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Dimensions.graphql.swift; sourceTree = ""; };
7A7637BBC9B3A68E0338D96E /* Pods-WidgetIntentExtension.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetIntentExtension.beta.xcconfig"; path = "Target Support Files/Pods-WidgetIntentExtension/Pods-WidgetIntentExtension.beta.xcconfig"; sourceTree = ""; };
- 7BD3F0794908635CDFA7DAB6 /* ConvertQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConvertQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/ConvertQuery.graphql.swift; sourceTree = ""; };
- 7E1F288B7EDDE906C4C00C46 /* DescriptionTranslations.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DescriptionTranslations.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/DescriptionTranslations.graphql.swift; sourceTree = ""; };
+ 7AFDF569E43F173C2F5A07AF /* SwapOrderType.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwapOrderType.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/SwapOrderType.graphql.swift; sourceTree = ""; };
+ 7DBCFBC330BF97BA2F630908 /* Chain.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Chain.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/Chain.graphql.swift; sourceTree = ""; };
82C9871585F60F92D079FB95 /* Pods-Widgets.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Widgets.release.xcconfig"; path = "Target Support Files/Pods-Widgets/Pods-Widgets.release.xcconfig"; sourceTree = ""; };
- 84997376A0B436ECC0A996C5 /* ExploreSearchQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExploreSearchQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/ExploreSearchQuery.graphql.swift; sourceTree = ""; };
+ 835D9DF76533D22EDF0E9D67 /* TokenProtectionInfoParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProtectionInfoParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenProtectionInfoParts.graphql.swift; sourceTree = ""; };
+ 83FB27D93A9468E6DB47231C /* NftMarketplace.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftMarketplace.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Enums/NftMarketplace.graphql.swift; sourceTree = ""; };
+ 85B43D489CC5ACF1EA34E9EB /* NftAssetTrait.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetTrait.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftAssetTrait.graphql.swift; sourceTree = ""; };
8719E5872CC41AB64503E903 /* Pods-WidgetIntentExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetIntentExtension.debug.xcconfig"; path = "Target Support Files/Pods-WidgetIntentExtension/Pods-WidgetIntentExtension.debug.xcconfig"; sourceTree = ""; };
- 8A4EB7BAD2C90E61FD6C30AF /* ActivityDetails.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ActivityDetails.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Unions/ActivityDetails.graphql.swift; sourceTree = ""; };
- 8A5342DEF1410E0E81E97F40 /* SearchTokensQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SearchTokensQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/SearchTokensQuery.graphql.swift; sourceTree = ""; };
- 8B477EBEA1EDA3914C77E8A6 /* NftAssetsFilterInput.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftAssetsFilterInput.graphql.swift; path = WidgetsCore/MobileSchema/Schema/InputObjects/NftAssetsFilterInput.graphql.swift; sourceTree = ""; };
- 8BEADEB4AE05B860CF2232D1 /* NetworkFee.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkFee.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NetworkFee.graphql.swift; sourceTree = ""; };
- 8DA4E7C052A6C4AC3A9DCDA6 /* TokenTransfer.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenTransfer.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TokenTransfer.graphql.swift; sourceTree = ""; };
+ 87442A651C068E552BEA89C6 /* MobileSchema.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MobileSchema.graphql.swift; path = WidgetsCore/MobileSchema/MobileSchema.graphql.swift; sourceTree = ""; };
+ 87CF9B03AE29A19C5B0F4E35 /* OnRampTransactionDetails.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OnRampTransactionDetails.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/OnRampTransactionDetails.graphql.swift; sourceTree = ""; };
+ 8856DFFC7FDC9BF105B0B4E3 /* TokenProject.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenProject.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/TokenProject.graphql.swift; sourceTree = ""; };
+ 8C9047B981FA322A727947F7 /* Image.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/Image.graphql.swift; sourceTree = ""; };
8E89C3A62AB8AAA400C84DE5 /* MnemonicConfirmationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MnemonicConfirmationView.swift; sourceTree = ""; };
8E89C3A72AB8AAA400C84DE5 /* MnemonicDisplayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MnemonicDisplayView.swift; sourceTree = ""; };
8E89C3A92AB8AAA400C84DE5 /* MnemonicConfirmationWordBankView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MnemonicConfirmationWordBankView.swift; sourceTree = ""; };
@@ -719,11 +717,14 @@
91D5016E2CDBEAE700B09B7F /* TopTokenParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopTokenParts.graphql.swift; sourceTree = ""; };
91D5016F2CDBEAE700B09B7F /* TokenFeeDataParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenFeeDataParts.graphql.swift; sourceTree = ""; };
91D5017C2CDBEAF600B09B7F /* HomeScreenTokenParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenTokenParts.graphql.swift; sourceTree = ""; };
- 92DE390311705FF1EE65C0E6 /* NftCollectionConnection.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NftCollectionConnection.graphql.swift; path = WidgetsCore/MobileSchema/Schema/Objects/NftCollectionConnection.graphql.swift; sourceTree = ""; };
- 985DDC8324B872DD44E87781 /* PortfolioBalancesQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PortfolioBalancesQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/PortfolioBalancesQuery.graphql.swift; sourceTree = ""; };
- 98F596250F55A3D6E9E1F499 /* TokenParts.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenParts.graphql.swift; path = WidgetsCore/MobileSchema/Fragments/TokenParts.graphql.swift; sourceTree = ""; };
- 9DFBD29C0BDFDB4464B8189C /* TokenPriceHistoryQuery.graphql.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TokenPriceHistoryQuery.graphql.swift; path = WidgetsCore/MobileSchema/Operations/Queries/TokenPriceHistoryQuery.graphql.swift; sourceTree = "