diff --git a/.editorconfig b/.editorconfig old mode 100755 new mode 100644 index bfba45975..99242f1ed --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,7 @@ trim_trailing_whitespace = true [*.{sh,bash}] end_of_line = lf + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 000000000..d332a7683 --- /dev/null +++ b/.env @@ -0,0 +1,19 @@ +VITE_IS_EMBEDDED_APP=1 +VITE_TRANSAK_API_KEY="" +VITE_DEV_DEFAULT_EMBEDDED_CLIENT_ID="FREE_TRIAL" +VITE_DEV_DEFAULT_EMBEDDED_SERVER_BASE_URL="http://localhost:3001" +VITE_PROD_DEFAULT_EMBEDDED_CLIENT_ID="FREE_TRIAL" +VITE_PROD_EMBEDDED_SERVER_BASE_URL="https://connect-api.wander.app" +VITE_TEST_DEFAULT_EMBEDDED_CLIENT_ID="test" +VITE_TEST_DEFAULT_EMBEDDED_SERVER_BASE_URL="https://test.com" + +# Temp. Will be removed once activation is done on wallet usage and we implement AES challenges. +VITE_SKIP_RSA_PRIVATE_KEY_DERIVATION=1 + +# DEVELOPMENT +VITE_SUPABASE_URL="https://pboorlggoqpyiucxmneq.supabase.co" +VITE_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBib29ybGdnb3FweWl1Y3htbmVxIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Mzk5ODM5NTgsImV4cCI6MjA1NTU1OTk1OH0.c-9_9AbNHgdUUhEcslNRIXuqADgLoJT_ZVhZiII3_Oo" + +# PRODUCTION +# VITE_SUPABASE_URL="" +# VITE_SUPABASE_ANON_KEY="" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..2549c38b1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +permissions: + actions: read + contents: read + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + filter: tree:0 + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + version: 9.8.0 + run_install: false + + # This enables task distribution via Nx Cloud + # Run this command as early as possible, before dependencies are installed + # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun + # Uncomment this line to enable task distribution + # - run: pnpm dlx nx start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci" + + # Cache node_modules + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - run: pnpm install --frozen-lockfile + - run: pnpm exec playwright install --with-deps + + # Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud + # - run: pnpm exec nx-cloud record -- echo Hello World + # When you enable task distribution, run the e2e-ci task instead of e2e + - run: pnpm exec nx run-many -t lint test build typecheck e2e + # Nx Cloud recommends fixes for failures to help you get CI green faster. Learn more: https://nx.dev/ci/features/self-healing-ci + - run: pnpm exec nx fix-ci + if: always() diff --git a/.gitignore b/.gitignore index 6e53c1a29..45b73e63a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,47 +1,48 @@ +# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# compiled output +dist +tmp +out-tsc + # dependencies node_modules -/.pnp -.pnp.js - -# testing -/coverage -#cache -.turbo -.next -.vercel +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json # misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files .DS_Store -*.pem -.vscode - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - - -# Exclude all .env files -.env -.env* -# Except for .env.example -!.env.example - -out/ -build/ -dist/ -sdk-dist -wander-connect-sdk/wallet-api-dist/ -# plasmo - https://www.plasmo.com -.plasmo - -# bpp - http://bpp.browser.market/ -keys.json - -# typescript -.tsbuildinfo - -# temporary remove development icon -assets/icon.development.png +Thumbs.db + +.nx/cache +.nx/workspace-data +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md + +vite.config.*.timestamp* +vitest.config.*.timestamp* +test-output diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..19be10eb3 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +strict-peer-dependencies=false +auto-install-peers=true diff --git a/.nxignore b/.nxignore new file mode 100644 index 000000000..bd9867486 --- /dev/null +++ b/.nxignore @@ -0,0 +1,2 @@ +.old +.old-already-moved diff --git a/.old-already-moved/.editorconfig b/.old-already-moved/.editorconfig new file mode 100755 index 000000000..bfba45975 --- /dev/null +++ b/.old-already-moved/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{sh,bash}] +end_of_line = lf diff --git a/.old-already-moved/.env b/.old-already-moved/.env new file mode 100644 index 000000000..d332a7683 --- /dev/null +++ b/.old-already-moved/.env @@ -0,0 +1,19 @@ +VITE_IS_EMBEDDED_APP=1 +VITE_TRANSAK_API_KEY="" +VITE_DEV_DEFAULT_EMBEDDED_CLIENT_ID="FREE_TRIAL" +VITE_DEV_DEFAULT_EMBEDDED_SERVER_BASE_URL="http://localhost:3001" +VITE_PROD_DEFAULT_EMBEDDED_CLIENT_ID="FREE_TRIAL" +VITE_PROD_EMBEDDED_SERVER_BASE_URL="https://connect-api.wander.app" +VITE_TEST_DEFAULT_EMBEDDED_CLIENT_ID="test" +VITE_TEST_DEFAULT_EMBEDDED_SERVER_BASE_URL="https://test.com" + +# Temp. Will be removed once activation is done on wallet usage and we implement AES challenges. +VITE_SKIP_RSA_PRIVATE_KEY_DERIVATION=1 + +# DEVELOPMENT +VITE_SUPABASE_URL="https://pboorlggoqpyiucxmneq.supabase.co" +VITE_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBib29ybGdnb3FweWl1Y3htbmVxIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Mzk5ODM5NTgsImV4cCI6MjA1NTU1OTk1OH0.c-9_9AbNHgdUUhEcslNRIXuqADgLoJT_ZVhZiII3_Oo" + +# PRODUCTION +# VITE_SUPABASE_URL="" +# VITE_SUPABASE_ANON_KEY="" diff --git a/.old-already-moved/.env.example b/.old-already-moved/.env.example new file mode 100644 index 000000000..9a1798188 --- /dev/null +++ b/.old-already-moved/.env.example @@ -0,0 +1,16 @@ +VITE_IS_EMBEDDED_APP=1 +VITE_TRANSAK_API_KEY="" +VITE_DEV_DEFAULT_EMBEDDED_CLIENT_ID="FREE_TRIAL" +VITE_DEV_DEFAULT_EMBEDDED_SERVER_BASE_URL="http://localhost:3001" +VITE_PROD_DEFAULT_EMBEDDED_CLIENT_ID="FREE_TRIAL" +VITE_PROD_EMBEDDED_SERVER_BASE_URL="https://connect-api.wander.app" +VITE_TEST_DEFAULT_EMBEDDED_CLIENT_ID="test" +VITE_TEST_DEFAULT_EMBEDDED_SERVER_BASE_URL="https://test.com" + +# DEVELOPMENT +VITE_SUPABASE_URL="" +VITE_SUPABASE_ANON_KEY="" + +# PRODUCTION +# VITE_SUPABASE_URL="" +# VITE_SUPABASE_ANON_KEY="" diff --git a/.old-already-moved/.prettierignore b/.old-already-moved/.prettierignore new file mode 100644 index 000000000..98b51c44e --- /dev/null +++ b/.old-already-moved/.prettierignore @@ -0,0 +1,5 @@ +node_modules +build +.plasmo +assets +.husky \ No newline at end of file diff --git a/.old-already-moved/.prettierrc b/.old-already-moved/.prettierrc new file mode 100644 index 000000000..a45b45148 --- /dev/null +++ b/.old-already-moved/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 120, + "trailingComma": "all", + "bracketSameLine": true, + "proseWrap": "always" +} diff --git a/.old-already-moved/LICENSE b/.old-already-moved/LICENSE new file mode 100644 index 000000000..94badf8c0 --- /dev/null +++ b/.old-already-moved/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 th8ta + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/index.html b/.old-already-moved/index.html similarity index 100% rename from index.html rename to .old-already-moved/index.html diff --git a/src/api/background/background-modules.ts b/.old-already-moved/src/api/background/background-modules.ts similarity index 100% rename from src/api/background/background-modules.ts rename to .old-already-moved/src/api/background/background-modules.ts diff --git a/src/api/background/background-setup.ts b/.old-already-moved/src/api/background/background-setup.ts similarity index 100% rename from src/api/background/background-setup.ts rename to .old-already-moved/src/api/background/background-setup.ts diff --git a/src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts similarity index 99% rename from src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts index c03cb7d42..360fdfb69 100644 --- a/src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts +++ b/.old-already-moved/src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts @@ -11,7 +11,7 @@ import { AO_SENT_QUERY, AR_RECEIVER_QUERY, AR_SENT_QUERY, -} from "~notifications/utils"; +} from "~_notifications/utils"; export async function handleNotificationsAlarm(alarm?: Alarms.Alarm) { if (!alarm?.name.startsWith("notifications")) return; diff --git a/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts b/.old-already-moved/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts similarity index 99% rename from src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts rename to .old-already-moved/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts index 9028c2998..d1b7bb3d3 100644 --- a/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts +++ b/.old-already-moved/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts @@ -2,7 +2,7 @@ import BigNumber from "bignumber.js"; import { gql } from "~gateways/api"; import { suggestedGateways } from "~gateways/gateway"; import { checkTransferStatus } from "~lib/transactions"; -import { combineAndSortTransactions, processTransactions } from "~notifications/utils"; +import { combineAndSortTransactions, processTransactions } from "~_notifications/utils"; import { ExtensionStorage } from "~utils/storage"; export type RawTransaction = { diff --git a/src/api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts diff --git a/src/api/background/handlers/alarms/transak-purchase/transak-purchase-alarm.handler.ts b/.old-already-moved/src/api/background/handlers/alarms/transak-purchase/transak-purchase-alarm.handler.ts similarity index 100% rename from src/api/background/handlers/alarms/transak-purchase/transak-purchase-alarm.handler.ts rename to .old-already-moved/src/api/background/handlers/alarms/transak-purchase/transak-purchase-alarm.handler.ts diff --git a/src/api/background/handlers/browser/install/install.handler.ts b/.old-already-moved/src/api/background/handlers/browser/install/install.handler.ts similarity index 100% rename from src/api/background/handlers/browser/install/install.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/install/install.handler.ts diff --git a/src/api/background/handlers/browser/install/permissions.handler.ts b/.old-already-moved/src/api/background/handlers/browser/install/permissions.handler.ts similarity index 100% rename from src/api/background/handlers/browser/install/permissions.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/install/permissions.handler.ts diff --git a/src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts b/.old-already-moved/src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts similarity index 100% rename from src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts diff --git a/src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts b/.old-already-moved/src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts similarity index 100% rename from src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts diff --git a/src/api/background/handlers/browser/printer/print/print.handler.ts b/.old-already-moved/src/api/background/handlers/browser/printer/print/print.handler.ts similarity index 100% rename from src/api/background/handlers/browser/printer/print/print.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/printer/print/print.handler.ts diff --git a/src/api/background/handlers/browser/printer/printer.constants.ts b/.old-already-moved/src/api/background/handlers/browser/printer/printer.constants.ts similarity index 100% rename from src/api/background/handlers/browser/printer/printer.constants.ts rename to .old-already-moved/src/api/background/handlers/browser/printer/printer.constants.ts diff --git a/src/api/background/handlers/browser/protocol/protocol.handler.ts b/.old-already-moved/src/api/background/handlers/browser/protocol/protocol.handler.ts similarity index 100% rename from src/api/background/handlers/browser/protocol/protocol.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/protocol/protocol.handler.ts diff --git a/src/api/background/handlers/browser/tabs/tabs.handler.ts b/.old-already-moved/src/api/background/handlers/browser/tabs/tabs.handler.ts similarity index 100% rename from src/api/background/handlers/browser/tabs/tabs.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/tabs/tabs.handler.ts diff --git a/src/api/background/handlers/browser/window-close/window-close.handler.ts b/.old-already-moved/src/api/background/handlers/browser/window-close/window-close.handler.ts similarity index 100% rename from src/api/background/handlers/browser/window-close/window-close.handler.ts rename to .old-already-moved/src/api/background/handlers/browser/window-close/window-close.handler.ts diff --git a/src/api/background/handlers/message/api-call-message/api-call-message.handler.ts b/.old-already-moved/src/api/background/handlers/message/api-call-message/api-call-message.handler.ts similarity index 100% rename from src/api/background/handlers/message/api-call-message/api-call-message.handler.ts rename to .old-already-moved/src/api/background/handlers/message/api-call-message/api-call-message.handler.ts diff --git a/src/api/background/handlers/message/chunk-message/chunk-message.handler.ts b/.old-already-moved/src/api/background/handlers/message/chunk-message/chunk-message.handler.ts similarity index 100% rename from src/api/background/handlers/message/chunk-message/chunk-message.handler.ts rename to .old-already-moved/src/api/background/handlers/message/chunk-message/chunk-message.handler.ts diff --git a/src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts b/.old-already-moved/src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts similarity index 100% rename from src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts rename to .old-already-moved/src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts diff --git a/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts b/.old-already-moved/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts similarity index 100% rename from src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts rename to .old-already-moved/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts diff --git a/src/api/background/handlers/storage/apps-change/app-change.handler.ts b/.old-already-moved/src/api/background/handlers/storage/apps-change/app-change.handler.ts similarity index 100% rename from src/api/background/handlers/storage/apps-change/app-change.handler.ts rename to .old-already-moved/src/api/background/handlers/storage/apps-change/app-change.handler.ts diff --git a/src/api/background/handlers/storage/auth-state-change/auth-state-change.handler.ts b/.old-already-moved/src/api/background/handlers/storage/auth-state-change/auth-state-change.handler.ts similarity index 100% rename from src/api/background/handlers/storage/auth-state-change/auth-state-change.handler.ts rename to .old-already-moved/src/api/background/handlers/storage/auth-state-change/auth-state-change.handler.ts diff --git a/src/api/background/handlers/storage/wallet-change/wallet-change.handler.ts b/.old-already-moved/src/api/background/handlers/storage/wallet-change/wallet-change.handler.ts similarity index 100% rename from src/api/background/handlers/storage/wallet-change/wallet-change.handler.ts rename to .old-already-moved/src/api/background/handlers/storage/wallet-change/wallet-change.handler.ts diff --git a/src/api/foreground/foreground-modules.ts b/.old-already-moved/src/api/foreground/foreground-modules.ts similarity index 100% rename from src/api/foreground/foreground-modules.ts rename to .old-already-moved/src/api/foreground/foreground-modules.ts diff --git a/src/api/foreground/foreground-setup-embedded-wallet-sdk.ts b/.old-already-moved/src/api/foreground/foreground-setup-embedded-wallet-sdk.ts similarity index 96% rename from src/api/foreground/foreground-setup-embedded-wallet-sdk.ts rename to .old-already-moved/src/api/foreground/foreground-setup-embedded-wallet-sdk.ts index ca34c4992..75fd21a27 100644 --- a/src/api/foreground/foreground-setup-embedded-wallet-sdk.ts +++ b/.old-already-moved/src/api/foreground/foreground-setup-embedded-wallet-sdk.ts @@ -4,8 +4,8 @@ import { nanoid } from "nanoid"; import { foregroundModules, type ForegroundModule } from "~api/foreground/foreground-modules"; import mitt from "mitt"; import { log, LOG_GROUP } from "~utils/log/log.utils"; -import { version } from "../../../package.json"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { version } from "../../../../.old/package.json"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; import { isApiErrorResponse } from "~utils/messaging/common/messaging.utils"; import { isomorphicSendMessage } from "~isomorphic-messaging"; import { setEmbeddedTargetIframe } from "~utils/messaging/strategies/iframe/iframe-messaging.strategy"; diff --git a/src/api/foreground/foreground-setup-events.ts b/.old-already-moved/src/api/foreground/foreground-setup-events.ts similarity index 100% rename from src/api/foreground/foreground-setup-events.ts rename to .old-already-moved/src/api/foreground/foreground-setup-events.ts diff --git a/src/api/foreground/foreground-setup-wallet-sdk.ts b/.old-already-moved/src/api/foreground/foreground-setup-wallet-sdk.ts similarity index 98% rename from src/api/foreground/foreground-setup-wallet-sdk.ts rename to .old-already-moved/src/api/foreground/foreground-setup-wallet-sdk.ts index c94cecb18..b0799214c 100644 --- a/src/api/foreground/foreground-setup-wallet-sdk.ts +++ b/.old-already-moved/src/api/foreground/foreground-setup-wallet-sdk.ts @@ -4,8 +4,8 @@ import { nanoid } from "nanoid"; import { foregroundModules, type ForegroundModule } from "~api/foreground/foreground-modules"; import mitt from "mitt"; import { log, LOG_GROUP } from "~utils/log/log.utils"; -import { version } from "../../../package.json"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { version } from "../../../../.old/package.json"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; import { isApiErrorResponse } from "~utils/messaging/common/messaging.utils"; // import { version as sdkVersion } from "../../../wander-connect-sdk/package.json"; diff --git a/src/api/module.ts b/.old-already-moved/src/api/module.ts similarity index 100% rename from src/api/module.ts rename to .old-already-moved/src/api/module.ts diff --git a/src/api/modules/README.md b/.old-already-moved/src/api/modules/README.md similarity index 100% rename from src/api/modules/README.md rename to .old-already-moved/src/api/modules/README.md diff --git a/src/api/modules/active_address/active_address.background.ts b/.old-already-moved/src/api/modules/active_address/active_address.background.ts similarity index 100% rename from src/api/modules/active_address/active_address.background.ts rename to .old-already-moved/src/api/modules/active_address/active_address.background.ts diff --git a/src/api/modules/active_address/active_address.foreground.ts b/.old-already-moved/src/api/modules/active_address/active_address.foreground.ts similarity index 100% rename from src/api/modules/active_address/active_address.foreground.ts rename to .old-already-moved/src/api/modules/active_address/active_address.foreground.ts diff --git a/src/api/modules/active_address/index.ts b/.old-already-moved/src/api/modules/active_address/index.ts similarity index 100% rename from src/api/modules/active_address/index.ts rename to .old-already-moved/src/api/modules/active_address/index.ts diff --git a/src/api/modules/add_token/add_token.background.ts b/.old-already-moved/src/api/modules/add_token/add_token.background.ts similarity index 100% rename from src/api/modules/add_token/add_token.background.ts rename to .old-already-moved/src/api/modules/add_token/add_token.background.ts diff --git a/src/api/modules/add_token/add_token.foreground.ts b/.old-already-moved/src/api/modules/add_token/add_token.foreground.ts similarity index 100% rename from src/api/modules/add_token/add_token.foreground.ts rename to .old-already-moved/src/api/modules/add_token/add_token.foreground.ts diff --git a/src/api/modules/add_token/index.ts b/.old-already-moved/src/api/modules/add_token/index.ts similarity index 100% rename from src/api/modules/add_token/index.ts rename to .old-already-moved/src/api/modules/add_token/index.ts diff --git a/src/api/modules/all_addresses/all_addresses.background.ts b/.old-already-moved/src/api/modules/all_addresses/all_addresses.background.ts similarity index 100% rename from src/api/modules/all_addresses/all_addresses.background.ts rename to .old-already-moved/src/api/modules/all_addresses/all_addresses.background.ts diff --git a/src/api/modules/all_addresses/all_addresses.foreground.ts b/.old-already-moved/src/api/modules/all_addresses/all_addresses.foreground.ts similarity index 100% rename from src/api/modules/all_addresses/all_addresses.foreground.ts rename to .old-already-moved/src/api/modules/all_addresses/all_addresses.foreground.ts diff --git a/src/api/modules/all_addresses/index.ts b/.old-already-moved/src/api/modules/all_addresses/index.ts similarity index 100% rename from src/api/modules/all_addresses/index.ts rename to .old-already-moved/src/api/modules/all_addresses/index.ts diff --git a/src/api/modules/arweave_config/arweave_config.background.ts b/.old-already-moved/src/api/modules/arweave_config/arweave_config.background.ts similarity index 100% rename from src/api/modules/arweave_config/arweave_config.background.ts rename to .old-already-moved/src/api/modules/arweave_config/arweave_config.background.ts diff --git a/src/api/modules/arweave_config/arweave_config.foreground.ts b/.old-already-moved/src/api/modules/arweave_config/arweave_config.foreground.ts similarity index 100% rename from src/api/modules/arweave_config/arweave_config.foreground.ts rename to .old-already-moved/src/api/modules/arweave_config/arweave_config.foreground.ts diff --git a/src/api/modules/arweave_config/index.ts b/.old-already-moved/src/api/modules/arweave_config/index.ts similarity index 100% rename from src/api/modules/arweave_config/index.ts rename to .old-already-moved/src/api/modules/arweave_config/index.ts diff --git a/src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts b/.old-already-moved/src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts similarity index 100% rename from src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts rename to .old-already-moved/src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts diff --git a/src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts b/.old-already-moved/src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts similarity index 100% rename from src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts rename to .old-already-moved/src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts diff --git a/src/api/modules/batch_sign_data_item/index.ts b/.old-already-moved/src/api/modules/batch_sign_data_item/index.ts similarity index 100% rename from src/api/modules/batch_sign_data_item/index.ts rename to .old-already-moved/src/api/modules/batch_sign_data_item/index.ts diff --git a/src/api/modules/connect/connect.background.ts b/.old-already-moved/src/api/modules/connect/connect.background.ts similarity index 96% rename from src/api/modules/connect/connect.background.ts rename to .old-already-moved/src/api/modules/connect/connect.background.ts index b0b69bbee..3afec52a1 100644 --- a/src/api/modules/connect/connect.background.ts +++ b/.old-already-moved/src/api/modules/connect/connect.background.ts @@ -6,7 +6,7 @@ import { updateIcon } from "~utils/icon"; import Application from "~applications/application"; import { requestUserAuthorization } from "../../../utils/auth/auth.utils"; import { getActiveAddress, getWallets, openOrSelectWelcomePage } from "~wallets"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { ERR_MSG_NO_WALLETS_ADDED } from "~utils/auth/auth.constants"; const background: BackgroundModuleFunction = async ( diff --git a/src/api/modules/connect/connect.foreground.ts b/.old-already-moved/src/api/modules/connect/connect.foreground.ts similarity index 87% rename from src/api/modules/connect/connect.foreground.ts rename to .old-already-moved/src/api/modules/connect/connect.foreground.ts index b05adb1fb..a6abedfdc 100644 --- a/src/api/modules/connect/connect.foreground.ts +++ b/.old-already-moved/src/api/modules/connect/connect.foreground.ts @@ -3,8 +3,8 @@ import type { AppInfo } from "~applications/application"; import type { ModuleFunction } from "~api/module"; import { type Gateway } from "~gateways/gateway"; import { getAppURL } from "~utils/format"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; -import { getAppLogo } from "~utils/embedded/utils/logo/logo.utils"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; +import { getAppLogo } from "~utils/_embedded/utils/logo/logo.utils"; const foreground: ModuleFunction = async ( permissions: PermissionType[], diff --git a/src/api/modules/connect/index.ts b/.old-already-moved/src/api/modules/connect/index.ts similarity index 100% rename from src/api/modules/connect/index.ts rename to .old-already-moved/src/api/modules/connect/index.ts diff --git a/src/api/modules/decrypt/decrypt.background.ts b/.old-already-moved/src/api/modules/decrypt/decrypt.background.ts similarity index 100% rename from src/api/modules/decrypt/decrypt.background.ts rename to .old-already-moved/src/api/modules/decrypt/decrypt.background.ts diff --git a/src/api/modules/decrypt/decrypt.foreground.ts b/.old-already-moved/src/api/modules/decrypt/decrypt.foreground.ts similarity index 100% rename from src/api/modules/decrypt/decrypt.foreground.ts rename to .old-already-moved/src/api/modules/decrypt/decrypt.foreground.ts diff --git a/src/api/modules/decrypt/index.ts b/.old-already-moved/src/api/modules/decrypt/index.ts similarity index 100% rename from src/api/modules/decrypt/index.ts rename to .old-already-moved/src/api/modules/decrypt/index.ts diff --git a/src/api/modules/disconnect/disconnect.background.ts b/.old-already-moved/src/api/modules/disconnect/disconnect.background.ts similarity index 100% rename from src/api/modules/disconnect/disconnect.background.ts rename to .old-already-moved/src/api/modules/disconnect/disconnect.background.ts diff --git a/src/api/modules/disconnect/disconnect.foreground.ts b/.old-already-moved/src/api/modules/disconnect/disconnect.foreground.ts similarity index 100% rename from src/api/modules/disconnect/disconnect.foreground.ts rename to .old-already-moved/src/api/modules/disconnect/disconnect.foreground.ts diff --git a/src/api/modules/disconnect/index.ts b/.old-already-moved/src/api/modules/disconnect/index.ts similarity index 100% rename from src/api/modules/disconnect/index.ts rename to .old-already-moved/src/api/modules/disconnect/index.ts diff --git a/src/api/modules/dispatch/allowance.ts b/.old-already-moved/src/api/modules/dispatch/allowance.ts similarity index 100% rename from src/api/modules/dispatch/allowance.ts rename to .old-already-moved/src/api/modules/dispatch/allowance.ts diff --git a/src/api/modules/dispatch/dispatch.background.ts b/.old-already-moved/src/api/modules/dispatch/dispatch.background.ts similarity index 100% rename from src/api/modules/dispatch/dispatch.background.ts rename to .old-already-moved/src/api/modules/dispatch/dispatch.background.ts diff --git a/src/api/modules/dispatch/dispatch.foreground.ts b/.old-already-moved/src/api/modules/dispatch/dispatch.foreground.ts similarity index 100% rename from src/api/modules/dispatch/dispatch.foreground.ts rename to .old-already-moved/src/api/modules/dispatch/dispatch.foreground.ts diff --git a/src/api/modules/dispatch/index.ts b/.old-already-moved/src/api/modules/dispatch/index.ts similarity index 100% rename from src/api/modules/dispatch/index.ts rename to .old-already-moved/src/api/modules/dispatch/index.ts diff --git a/src/api/modules/dispatch/uploader.ts b/.old-already-moved/src/api/modules/dispatch/uploader.ts similarity index 100% rename from src/api/modules/dispatch/uploader.ts rename to .old-already-moved/src/api/modules/dispatch/uploader.ts diff --git a/src/api/modules/encrypt/encrypt.background.ts b/.old-already-moved/src/api/modules/encrypt/encrypt.background.ts similarity index 100% rename from src/api/modules/encrypt/encrypt.background.ts rename to .old-already-moved/src/api/modules/encrypt/encrypt.background.ts diff --git a/src/api/modules/encrypt/encrypt.foreground.ts b/.old-already-moved/src/api/modules/encrypt/encrypt.foreground.ts similarity index 100% rename from src/api/modules/encrypt/encrypt.foreground.ts rename to .old-already-moved/src/api/modules/encrypt/encrypt.foreground.ts diff --git a/src/api/modules/encrypt/index.ts b/.old-already-moved/src/api/modules/encrypt/index.ts similarity index 100% rename from src/api/modules/encrypt/index.ts rename to .old-already-moved/src/api/modules/encrypt/index.ts diff --git a/src/api/modules/encrypt/types.ts b/.old-already-moved/src/api/modules/encrypt/types.ts similarity index 100% rename from src/api/modules/encrypt/types.ts rename to .old-already-moved/src/api/modules/encrypt/types.ts diff --git a/src/api/modules/example/example.background.ts b/.old-already-moved/src/api/modules/example/example.background.ts similarity index 100% rename from src/api/modules/example/example.background.ts rename to .old-already-moved/src/api/modules/example/example.background.ts diff --git a/src/api/modules/example/example.foreground.ts b/.old-already-moved/src/api/modules/example/example.foreground.ts similarity index 100% rename from src/api/modules/example/example.foreground.ts rename to .old-already-moved/src/api/modules/example/example.foreground.ts diff --git a/src/api/modules/example/index.ts b/.old-already-moved/src/api/modules/example/index.ts similarity index 100% rename from src/api/modules/example/index.ts rename to .old-already-moved/src/api/modules/example/index.ts diff --git a/src/api/modules/is_token_added/index.ts b/.old-already-moved/src/api/modules/is_token_added/index.ts similarity index 100% rename from src/api/modules/is_token_added/index.ts rename to .old-already-moved/src/api/modules/is_token_added/index.ts diff --git a/src/api/modules/is_token_added/is_token_added.background.ts b/.old-already-moved/src/api/modules/is_token_added/is_token_added.background.ts similarity index 100% rename from src/api/modules/is_token_added/is_token_added.background.ts rename to .old-already-moved/src/api/modules/is_token_added/is_token_added.background.ts diff --git a/src/api/modules/is_token_added/is_token_added.foreground.ts b/.old-already-moved/src/api/modules/is_token_added/is_token_added.foreground.ts similarity index 100% rename from src/api/modules/is_token_added/is_token_added.foreground.ts rename to .old-already-moved/src/api/modules/is_token_added/is_token_added.foreground.ts diff --git a/src/api/modules/permissions/index.ts b/.old-already-moved/src/api/modules/permissions/index.ts similarity index 100% rename from src/api/modules/permissions/index.ts rename to .old-already-moved/src/api/modules/permissions/index.ts diff --git a/src/api/modules/permissions/permissions.background.ts b/.old-already-moved/src/api/modules/permissions/permissions.background.ts similarity index 100% rename from src/api/modules/permissions/permissions.background.ts rename to .old-already-moved/src/api/modules/permissions/permissions.background.ts diff --git a/src/api/modules/permissions/permissions.foreground.ts b/.old-already-moved/src/api/modules/permissions/permissions.foreground.ts similarity index 100% rename from src/api/modules/permissions/permissions.foreground.ts rename to .old-already-moved/src/api/modules/permissions/permissions.foreground.ts diff --git a/src/api/modules/private_hash/index.ts b/.old-already-moved/src/api/modules/private_hash/index.ts similarity index 100% rename from src/api/modules/private_hash/index.ts rename to .old-already-moved/src/api/modules/private_hash/index.ts diff --git a/src/api/modules/private_hash/private_hash.background.ts b/.old-already-moved/src/api/modules/private_hash/private_hash.background.ts similarity index 100% rename from src/api/modules/private_hash/private_hash.background.ts rename to .old-already-moved/src/api/modules/private_hash/private_hash.background.ts diff --git a/src/api/modules/private_hash/private_hash.foreground.ts b/.old-already-moved/src/api/modules/private_hash/private_hash.foreground.ts similarity index 100% rename from src/api/modules/private_hash/private_hash.foreground.ts rename to .old-already-moved/src/api/modules/private_hash/private_hash.foreground.ts diff --git a/src/api/modules/public_key/index.ts b/.old-already-moved/src/api/modules/public_key/index.ts similarity index 100% rename from src/api/modules/public_key/index.ts rename to .old-already-moved/src/api/modules/public_key/index.ts diff --git a/src/api/modules/public_key/public_key.background.ts b/.old-already-moved/src/api/modules/public_key/public_key.background.ts similarity index 100% rename from src/api/modules/public_key/public_key.background.ts rename to .old-already-moved/src/api/modules/public_key/public_key.background.ts diff --git a/src/api/modules/public_key/public_key.foreground.ts b/.old-already-moved/src/api/modules/public_key/public_key.foreground.ts similarity index 100% rename from src/api/modules/public_key/public_key.foreground.ts rename to .old-already-moved/src/api/modules/public_key/public_key.foreground.ts diff --git a/src/api/modules/sign/allowance.ts b/.old-already-moved/src/api/modules/sign/allowance.ts similarity index 100% rename from src/api/modules/sign/allowance.ts rename to .old-already-moved/src/api/modules/sign/allowance.ts diff --git a/src/api/modules/sign/animation.ts b/.old-already-moved/src/api/modules/sign/animation.ts similarity index 100% rename from src/api/modules/sign/animation.ts rename to .old-already-moved/src/api/modules/sign/animation.ts diff --git a/src/api/modules/sign/chunks.ts b/.old-already-moved/src/api/modules/sign/chunks.ts similarity index 100% rename from src/api/modules/sign/chunks.ts rename to .old-already-moved/src/api/modules/sign/chunks.ts diff --git a/src/api/modules/sign/fee.ts b/.old-already-moved/src/api/modules/sign/fee.ts similarity index 100% rename from src/api/modules/sign/fee.ts rename to .old-already-moved/src/api/modules/sign/fee.ts diff --git a/src/api/modules/sign/index.ts b/.old-already-moved/src/api/modules/sign/index.ts similarity index 100% rename from src/api/modules/sign/index.ts rename to .old-already-moved/src/api/modules/sign/index.ts diff --git a/src/api/modules/sign/sign.background.ts b/.old-already-moved/src/api/modules/sign/sign.background.ts similarity index 100% rename from src/api/modules/sign/sign.background.ts rename to .old-already-moved/src/api/modules/sign/sign.background.ts diff --git a/src/api/modules/sign/sign.foreground.ts b/.old-already-moved/src/api/modules/sign/sign.foreground.ts similarity index 100% rename from src/api/modules/sign/sign.foreground.ts rename to .old-already-moved/src/api/modules/sign/sign.foreground.ts diff --git a/src/api/modules/sign/sign_auth.ts b/.old-already-moved/src/api/modules/sign/sign_auth.ts similarity index 100% rename from src/api/modules/sign/sign_auth.ts rename to .old-already-moved/src/api/modules/sign/sign_auth.ts diff --git a/src/api/modules/sign/sign_policy.ts b/.old-already-moved/src/api/modules/sign/sign_policy.ts similarity index 100% rename from src/api/modules/sign/sign_policy.ts rename to .old-already-moved/src/api/modules/sign/sign_policy.ts diff --git a/src/api/modules/sign/tags.ts b/.old-already-moved/src/api/modules/sign/tags.ts similarity index 66% rename from src/api/modules/sign/tags.ts rename to .old-already-moved/src/api/modules/sign/tags.ts index fa9a40d33..6ace8c38e 100644 --- a/src/api/modules/sign/tags.ts +++ b/.old-already-moved/src/api/modules/sign/tags.ts @@ -1,5 +1,5 @@ -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; -import { version } from "../../../../package.json"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; +import { version } from "../../../../../.old/package.json"; export interface DecodedTag { name: string; diff --git a/src/api/modules/sign/transaction_builder.ts b/.old-already-moved/src/api/modules/sign/transaction_builder.ts similarity index 100% rename from src/api/modules/sign/transaction_builder.ts rename to .old-already-moved/src/api/modules/sign/transaction_builder.ts diff --git a/src/api/modules/sign/utils.ts b/.old-already-moved/src/api/modules/sign/utils.ts similarity index 100% rename from src/api/modules/sign/utils.ts rename to .old-already-moved/src/api/modules/sign/utils.ts diff --git a/src/api/modules/sign_data_item/index.ts b/.old-already-moved/src/api/modules/sign_data_item/index.ts similarity index 100% rename from src/api/modules/sign_data_item/index.ts rename to .old-already-moved/src/api/modules/sign_data_item/index.ts diff --git a/src/api/modules/sign_data_item/sign_data_item.background.ts b/.old-already-moved/src/api/modules/sign_data_item/sign_data_item.background.ts similarity index 100% rename from src/api/modules/sign_data_item/sign_data_item.background.ts rename to .old-already-moved/src/api/modules/sign_data_item/sign_data_item.background.ts diff --git a/src/api/modules/sign_data_item/sign_data_item.foreground.ts b/.old-already-moved/src/api/modules/sign_data_item/sign_data_item.foreground.ts similarity index 100% rename from src/api/modules/sign_data_item/sign_data_item.foreground.ts rename to .old-already-moved/src/api/modules/sign_data_item/sign_data_item.foreground.ts diff --git a/src/api/modules/sign_data_item/types.ts b/.old-already-moved/src/api/modules/sign_data_item/types.ts similarity index 100% rename from src/api/modules/sign_data_item/types.ts rename to .old-already-moved/src/api/modules/sign_data_item/types.ts diff --git a/src/api/modules/sign_message/index.ts b/.old-already-moved/src/api/modules/sign_message/index.ts similarity index 100% rename from src/api/modules/sign_message/index.ts rename to .old-already-moved/src/api/modules/sign_message/index.ts diff --git a/src/api/modules/sign_message/sign_message.background.ts b/.old-already-moved/src/api/modules/sign_message/sign_message.background.ts similarity index 100% rename from src/api/modules/sign_message/sign_message.background.ts rename to .old-already-moved/src/api/modules/sign_message/sign_message.background.ts diff --git a/src/api/modules/sign_message/sign_message.foreground.ts b/.old-already-moved/src/api/modules/sign_message/sign_message.foreground.ts similarity index 100% rename from src/api/modules/sign_message/sign_message.foreground.ts rename to .old-already-moved/src/api/modules/sign_message/sign_message.foreground.ts diff --git a/src/api/modules/sign_message/types.ts b/.old-already-moved/src/api/modules/sign_message/types.ts similarity index 100% rename from src/api/modules/sign_message/types.ts rename to .old-already-moved/src/api/modules/sign_message/types.ts diff --git a/src/api/modules/signature/index.ts b/.old-already-moved/src/api/modules/signature/index.ts similarity index 100% rename from src/api/modules/signature/index.ts rename to .old-already-moved/src/api/modules/signature/index.ts diff --git a/src/api/modules/signature/signature.background.ts b/.old-already-moved/src/api/modules/signature/signature.background.ts similarity index 100% rename from src/api/modules/signature/signature.background.ts rename to .old-already-moved/src/api/modules/signature/signature.background.ts diff --git a/src/api/modules/signature/signature.foreground.ts b/.old-already-moved/src/api/modules/signature/signature.foreground.ts similarity index 100% rename from src/api/modules/signature/signature.foreground.ts rename to .old-already-moved/src/api/modules/signature/signature.foreground.ts diff --git a/src/api/modules/signature/types.ts b/.old-already-moved/src/api/modules/signature/types.ts similarity index 100% rename from src/api/modules/signature/types.ts rename to .old-already-moved/src/api/modules/signature/types.ts diff --git a/src/api/modules/subscription/index.ts b/.old-already-moved/src/api/modules/subscription/index.ts similarity index 100% rename from src/api/modules/subscription/index.ts rename to .old-already-moved/src/api/modules/subscription/index.ts diff --git a/src/api/modules/subscription/subscription.background.ts b/.old-already-moved/src/api/modules/subscription/subscription.background.ts similarity index 100% rename from src/api/modules/subscription/subscription.background.ts rename to .old-already-moved/src/api/modules/subscription/subscription.background.ts diff --git a/src/api/modules/subscription/subscription.foreground.ts b/.old-already-moved/src/api/modules/subscription/subscription.foreground.ts similarity index 100% rename from src/api/modules/subscription/subscription.foreground.ts rename to .old-already-moved/src/api/modules/subscription/subscription.foreground.ts diff --git a/src/api/modules/token_balance/index.ts b/.old-already-moved/src/api/modules/token_balance/index.ts similarity index 100% rename from src/api/modules/token_balance/index.ts rename to .old-already-moved/src/api/modules/token_balance/index.ts diff --git a/src/api/modules/token_balance/token_balance.background.ts b/.old-already-moved/src/api/modules/token_balance/token_balance.background.ts similarity index 100% rename from src/api/modules/token_balance/token_balance.background.ts rename to .old-already-moved/src/api/modules/token_balance/token_balance.background.ts diff --git a/src/api/modules/token_balance/token_balance.foreground.ts b/.old-already-moved/src/api/modules/token_balance/token_balance.foreground.ts similarity index 100% rename from src/api/modules/token_balance/token_balance.foreground.ts rename to .old-already-moved/src/api/modules/token_balance/token_balance.foreground.ts diff --git a/src/api/modules/user_tokens/index.ts b/.old-already-moved/src/api/modules/user_tokens/index.ts similarity index 100% rename from src/api/modules/user_tokens/index.ts rename to .old-already-moved/src/api/modules/user_tokens/index.ts diff --git a/src/api/modules/user_tokens/user_tokens.background.ts b/.old-already-moved/src/api/modules/user_tokens/user_tokens.background.ts similarity index 100% rename from src/api/modules/user_tokens/user_tokens.background.ts rename to .old-already-moved/src/api/modules/user_tokens/user_tokens.background.ts diff --git a/src/api/modules/user_tokens/user_tokens.foreground.ts b/.old-already-moved/src/api/modules/user_tokens/user_tokens.foreground.ts similarity index 100% rename from src/api/modules/user_tokens/user_tokens.foreground.ts rename to .old-already-moved/src/api/modules/user_tokens/user_tokens.foreground.ts diff --git a/src/api/modules/verify_message/index.ts b/.old-already-moved/src/api/modules/verify_message/index.ts similarity index 100% rename from src/api/modules/verify_message/index.ts rename to .old-already-moved/src/api/modules/verify_message/index.ts diff --git a/src/api/modules/verify_message/verify_message.background.ts b/.old-already-moved/src/api/modules/verify_message/verify_message.background.ts similarity index 100% rename from src/api/modules/verify_message/verify_message.background.ts rename to .old-already-moved/src/api/modules/verify_message/verify_message.background.ts diff --git a/src/api/modules/verify_message/verify_message.foreground.ts b/.old-already-moved/src/api/modules/verify_message/verify_message.foreground.ts similarity index 100% rename from src/api/modules/verify_message/verify_message.foreground.ts rename to .old-already-moved/src/api/modules/verify_message/verify_message.foreground.ts diff --git a/src/api/modules/wallet_names/index.ts b/.old-already-moved/src/api/modules/wallet_names/index.ts similarity index 100% rename from src/api/modules/wallet_names/index.ts rename to .old-already-moved/src/api/modules/wallet_names/index.ts diff --git a/src/api/modules/wallet_names/wallet_names.background.ts b/.old-already-moved/src/api/modules/wallet_names/wallet_names.background.ts similarity index 100% rename from src/api/modules/wallet_names/wallet_names.background.ts rename to .old-already-moved/src/api/modules/wallet_names/wallet_names.background.ts diff --git a/src/api/modules/wallet_names/wallet_names.foreground.ts b/.old-already-moved/src/api/modules/wallet_names/wallet_names.foreground.ts similarity index 100% rename from src/api/modules/wallet_names/wallet_names.foreground.ts rename to .old-already-moved/src/api/modules/wallet_names/wallet_names.foreground.ts diff --git a/src/api/modules/wander_tier_info/index.ts b/.old-already-moved/src/api/modules/wander_tier_info/index.ts similarity index 100% rename from src/api/modules/wander_tier_info/index.ts rename to .old-already-moved/src/api/modules/wander_tier_info/index.ts diff --git a/src/api/modules/wander_tier_info/wander_tier_info.background.ts b/.old-already-moved/src/api/modules/wander_tier_info/wander_tier_info.background.ts similarity index 100% rename from src/api/modules/wander_tier_info/wander_tier_info.background.ts rename to .old-already-moved/src/api/modules/wander_tier_info/wander_tier_info.background.ts diff --git a/src/api/modules/wander_tier_info/wander_tier_info.foreground.ts b/.old-already-moved/src/api/modules/wander_tier_info/wander_tier_info.foreground.ts similarity index 100% rename from src/api/modules/wander_tier_info/wander_tier_info.foreground.ts rename to .old-already-moved/src/api/modules/wander_tier_info/wander_tier_info.foreground.ts diff --git a/src/applications/allowance.ts b/.old-already-moved/src/applications/allowance.ts similarity index 100% rename from src/applications/allowance.ts rename to .old-already-moved/src/applications/allowance.ts diff --git a/src/applications/application.ts b/.old-already-moved/src/applications/application.ts similarity index 100% rename from src/applications/application.ts rename to .old-already-moved/src/applications/application.ts diff --git a/src/applications/gateway.ts b/.old-already-moved/src/applications/gateway.ts similarity index 100% rename from src/applications/gateway.ts rename to .old-already-moved/src/applications/gateway.ts diff --git a/src/applications/index.ts b/.old-already-moved/src/applications/index.ts similarity index 100% rename from src/applications/index.ts rename to .old-already-moved/src/applications/index.ts diff --git a/src/applications/permissions.ts b/.old-already-moved/src/applications/permissions.ts similarity index 100% rename from src/applications/permissions.ts rename to .old-already-moved/src/applications/permissions.ts diff --git a/src/applications/tab.ts b/.old-already-moved/src/applications/tab.ts similarity index 100% rename from src/applications/tab.ts rename to .old-already-moved/src/applications/tab.ts diff --git a/src/applications/useActiveTab.ts b/.old-already-moved/src/applications/useActiveTab.ts similarity index 100% rename from src/applications/useActiveTab.ts rename to .old-already-moved/src/applications/useActiveTab.ts diff --git a/src/background.ts b/.old-already-moved/src/background.ts similarity index 100% rename from src/background.ts rename to .old-already-moved/src/background.ts diff --git a/src/components/AdaptiveBalanceDisplay.tsx b/.old-already-moved/src/components/AdaptiveBalanceDisplay.tsx similarity index 100% rename from src/components/AdaptiveBalanceDisplay.tsx rename to .old-already-moved/src/components/AdaptiveBalanceDisplay.tsx diff --git a/src/components/Avatar.tsx b/.old-already-moved/src/components/Avatar.tsx similarity index 100% rename from src/components/Avatar.tsx rename to .old-already-moved/src/components/Avatar.tsx diff --git a/src/components/Carousel.tsx b/.old-already-moved/src/components/Carousel.tsx similarity index 100% rename from src/components/Carousel.tsx rename to .old-already-moved/src/components/Carousel.tsx diff --git a/src/components/Checkbox.tsx b/.old-already-moved/src/components/Checkbox.tsx similarity index 100% rename from src/components/Checkbox.tsx rename to .old-already-moved/src/components/Checkbox.tsx diff --git a/src/components/CodeArea.tsx b/.old-already-moved/src/components/CodeArea.tsx similarity index 100% rename from src/components/CodeArea.tsx rename to .old-already-moved/src/components/CodeArea.tsx diff --git a/src/components/CopyToClipboard.tsx b/.old-already-moved/src/components/CopyToClipboard.tsx similarity index 100% rename from src/components/CopyToClipboard.tsx rename to .old-already-moved/src/components/CopyToClipboard.tsx diff --git a/src/components/Divider.tsx b/.old-already-moved/src/components/Divider.tsx similarity index 100% rename from src/components/Divider.tsx rename to .old-already-moved/src/components/Divider.tsx diff --git a/src/components/HeadAuth.tsx b/.old-already-moved/src/components/HeadAuth.tsx similarity index 100% rename from src/components/HeadAuth.tsx rename to .old-already-moved/src/components/HeadAuth.tsx diff --git a/src/components/HorizontalLine.tsx b/.old-already-moved/src/components/HorizontalLine.tsx similarity index 100% rename from src/components/HorizontalLine.tsx rename to .old-already-moved/src/components/HorizontalLine.tsx diff --git a/src/components/IconButton.tsx b/.old-already-moved/src/components/IconButton.tsx similarity index 100% rename from src/components/IconButton.tsx rename to .old-already-moved/src/components/IconButton.tsx diff --git a/src/components/IconText.tsx b/.old-already-moved/src/components/IconText.tsx similarity index 100% rename from src/components/IconText.tsx rename to .old-already-moved/src/components/IconText.tsx diff --git a/src/components/InputMenu.tsx b/.old-already-moved/src/components/InputMenu.tsx similarity index 100% rename from src/components/InputMenu.tsx rename to .old-already-moved/src/components/InputMenu.tsx diff --git a/src/components/Online.tsx b/.old-already-moved/src/components/Online.tsx similarity index 100% rename from src/components/Online.tsx rename to .old-already-moved/src/components/Online.tsx diff --git a/src/components/Pagination.tsx b/.old-already-moved/src/components/Pagination.tsx similarity index 100% rename from src/components/Pagination.tsx rename to .old-already-moved/src/components/Pagination.tsx diff --git a/src/components/Paragraph.tsx b/.old-already-moved/src/components/Paragraph.tsx similarity index 100% rename from src/components/Paragraph.tsx rename to .old-already-moved/src/components/Paragraph.tsx diff --git a/src/components/Progress.tsx b/.old-already-moved/src/components/Progress.tsx similarity index 100% rename from src/components/Progress.tsx rename to .old-already-moved/src/components/Progress.tsx diff --git a/src/components/QRCodeLoop.tsx b/.old-already-moved/src/components/QRCodeLoop.tsx similarity index 100% rename from src/components/QRCodeLoop.tsx rename to .old-already-moved/src/components/QRCodeLoop.tsx diff --git a/src/components/QRCodeWrapper.tsx b/.old-already-moved/src/components/QRCodeWrapper.tsx similarity index 85% rename from src/components/QRCodeWrapper.tsx rename to .old-already-moved/src/components/QRCodeWrapper.tsx index 2a25219f4..017019e3a 100644 --- a/src/components/QRCodeWrapper.tsx +++ b/.old-already-moved/src/components/QRCodeWrapper.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; export const QRCodeWrapper = styled.div<{ size?: number }>` display: flex; diff --git a/src/components/Recipient.tsx b/.old-already-moved/src/components/Recipient.tsx similarity index 100% rename from src/components/Recipient.tsx rename to .old-already-moved/src/components/Recipient.tsx diff --git a/src/components/SeedInput.tsx b/.old-already-moved/src/components/SeedInput.tsx similarity index 100% rename from src/components/SeedInput.tsx rename to .old-already-moved/src/components/SeedInput.tsx diff --git a/src/components/SendInput.tsx b/.old-already-moved/src/components/SendInput.tsx similarity index 100% rename from src/components/SendInput.tsx rename to .old-already-moved/src/components/SendInput.tsx diff --git a/src/components/SignDataItemsDetails.tsx b/.old-already-moved/src/components/SignDataItemsDetails.tsx similarity index 100% rename from src/components/SignDataItemsDetails.tsx rename to .old-already-moved/src/components/SignDataItemsDetails.tsx diff --git a/src/components/Skeleton.tsx b/.old-already-moved/src/components/Skeleton.tsx similarity index 100% rename from src/components/Skeleton.tsx rename to .old-already-moved/src/components/Skeleton.tsx diff --git a/src/components/Slider.tsx b/.old-already-moved/src/components/Slider.tsx similarity index 100% rename from src/components/Slider.tsx rename to .old-already-moved/src/components/Slider.tsx diff --git a/src/components/SliderMenu.tsx b/.old-already-moved/src/components/SliderMenu.tsx similarity index 100% rename from src/components/SliderMenu.tsx rename to .old-already-moved/src/components/SliderMenu.tsx diff --git a/src/components/Squircle.tsx b/.old-already-moved/src/components/Squircle.tsx similarity index 100% rename from src/components/Squircle.tsx rename to .old-already-moved/src/components/Squircle.tsx diff --git a/src/components/Tabs.tsx b/.old-already-moved/src/components/Tabs.tsx similarity index 100% rename from src/components/Tabs.tsx rename to .old-already-moved/src/components/Tabs.tsx diff --git a/src/components/ToggleSwitch.tsx b/.old-already-moved/src/components/ToggleSwitch.tsx similarity index 100% rename from src/components/ToggleSwitch.tsx rename to .old-already-moved/src/components/ToggleSwitch.tsx diff --git a/src/components/arlocal/InputWrapper.tsx b/.old-already-moved/src/components/arlocal/InputWrapper.tsx similarity index 100% rename from src/components/arlocal/InputWrapper.tsx rename to .old-already-moved/src/components/arlocal/InputWrapper.tsx diff --git a/src/components/arlocal/Mint.tsx b/.old-already-moved/src/components/arlocal/Mint.tsx similarity index 100% rename from src/components/arlocal/Mint.tsx rename to .old-already-moved/src/components/arlocal/Mint.tsx diff --git a/src/components/arlocal/Transaction.tsx b/.old-already-moved/src/components/arlocal/Transaction.tsx similarity index 100% rename from src/components/arlocal/Transaction.tsx rename to .old-already-moved/src/components/arlocal/Transaction.tsx diff --git a/src/components/arlocal/Tutorial.tsx b/.old-already-moved/src/components/arlocal/Tutorial.tsx similarity index 100% rename from src/components/arlocal/Tutorial.tsx rename to .old-already-moved/src/components/arlocal/Tutorial.tsx diff --git a/src/components/auth/App.tsx b/.old-already-moved/src/components/auth/App.tsx similarity index 100% rename from src/components/auth/App.tsx rename to .old-already-moved/src/components/auth/App.tsx diff --git a/src/components/auth/AuthButtons.tsx b/.old-already-moved/src/components/auth/AuthButtons.tsx similarity index 100% rename from src/components/auth/AuthButtons.tsx rename to .old-already-moved/src/components/auth/AuthButtons.tsx diff --git a/src/components/auth/CustomGatewayWarning.tsx b/.old-already-moved/src/components/auth/CustomGatewayWarning.tsx similarity index 100% rename from src/components/auth/CustomGatewayWarning.tsx rename to .old-already-moved/src/components/auth/CustomGatewayWarning.tsx diff --git a/src/components/auth/Label.tsx b/.old-already-moved/src/components/auth/Label.tsx similarity index 100% rename from src/components/auth/Label.tsx rename to .old-already-moved/src/components/auth/Label.tsx diff --git a/src/components/auth/Message.tsx b/.old-already-moved/src/components/auth/Message.tsx similarity index 100% rename from src/components/auth/Message.tsx rename to .old-already-moved/src/components/auth/Message.tsx diff --git a/src/components/auth/PermissionCheckbox.tsx b/.old-already-moved/src/components/auth/PermissionCheckbox.tsx similarity index 100% rename from src/components/auth/PermissionCheckbox.tsx rename to .old-already-moved/src/components/auth/PermissionCheckbox.tsx diff --git a/src/components/auth/Permissions.tsx b/.old-already-moved/src/components/auth/Permissions.tsx similarity index 100% rename from src/components/auth/Permissions.tsx rename to .old-already-moved/src/components/auth/Permissions.tsx diff --git a/src/components/auth/Wrapper.tsx b/.old-already-moved/src/components/auth/Wrapper.tsx similarity index 100% rename from src/components/auth/Wrapper.tsx rename to .old-already-moved/src/components/auth/Wrapper.tsx diff --git a/src/components/common/AnimatedStarContainer.tsx b/.old-already-moved/src/components/common/AnimatedStarContainer.tsx similarity index 100% rename from src/components/common/AnimatedStarContainer.tsx rename to .old-already-moved/src/components/common/AnimatedStarContainer.tsx diff --git a/src/components/common/Box.tsx b/.old-already-moved/src/components/common/Box.tsx similarity index 100% rename from src/components/common/Box.tsx rename to .old-already-moved/src/components/common/Box.tsx diff --git a/src/components/common/Flex.tsx b/.old-already-moved/src/components/common/Flex.tsx similarity index 100% rename from src/components/common/Flex.tsx rename to .old-already-moved/src/components/common/Flex.tsx diff --git a/src/components/common/IconButton.tsx b/.old-already-moved/src/components/common/IconButton.tsx similarity index 100% rename from src/components/common/IconButton.tsx rename to .old-already-moved/src/components/common/IconButton.tsx diff --git a/src/components/common/Image/Image.module.scss b/.old-already-moved/src/components/common/Image/Image.module.scss similarity index 100% rename from src/components/common/Image/Image.module.scss rename to .old-already-moved/src/components/common/Image/Image.module.scss diff --git a/src/components/common/Image/Image.tsx b/.old-already-moved/src/components/common/Image/Image.tsx similarity index 100% rename from src/components/common/Image/Image.tsx rename to .old-already-moved/src/components/common/Image/Image.tsx diff --git a/src/components/common/InputButton.tsx b/.old-already-moved/src/components/common/InputButton.tsx similarity index 100% rename from src/components/common/InputButton.tsx rename to .old-already-moved/src/components/common/InputButton.tsx diff --git a/src/components/common/Link.tsx b/.old-already-moved/src/components/common/Link.tsx similarity index 100% rename from src/components/common/Link.tsx rename to .old-already-moved/src/components/common/Link.tsx diff --git a/src/components/common/ParseTextWithLinks.tsx b/.old-already-moved/src/components/common/ParseTextWithLinks.tsx similarity index 100% rename from src/components/common/ParseTextWithLinks.tsx rename to .old-already-moved/src/components/common/ParseTextWithLinks.tsx diff --git a/src/components/dashboard/About.tsx b/.old-already-moved/src/components/dashboard/About.tsx similarity index 100% rename from src/components/dashboard/About.tsx rename to .old-already-moved/src/components/dashboard/About.tsx diff --git a/src/components/dashboard/Analytics.tsx b/.old-already-moved/src/components/dashboard/Analytics.tsx similarity index 100% rename from src/components/dashboard/Analytics.tsx rename to .old-already-moved/src/components/dashboard/Analytics.tsx diff --git a/src/components/dashboard/Applications.tsx b/.old-already-moved/src/components/dashboard/Applications.tsx similarity index 100% rename from src/components/dashboard/Applications.tsx rename to .old-already-moved/src/components/dashboard/Applications.tsx diff --git a/src/components/dashboard/Contacts.tsx b/.old-already-moved/src/components/dashboard/Contacts.tsx similarity index 100% rename from src/components/dashboard/Contacts.tsx rename to .old-already-moved/src/components/dashboard/Contacts.tsx diff --git a/src/components/dashboard/NotificationSettings.tsx b/.old-already-moved/src/components/dashboard/NotificationSettings.tsx similarity index 100% rename from src/components/dashboard/NotificationSettings.tsx rename to .old-already-moved/src/components/dashboard/NotificationSettings.tsx diff --git a/src/components/dashboard/ReorderIcon.tsx b/.old-already-moved/src/components/dashboard/ReorderIcon.tsx similarity index 100% rename from src/components/dashboard/ReorderIcon.tsx rename to .old-already-moved/src/components/dashboard/ReorderIcon.tsx diff --git a/src/components/dashboard/Reset.tsx b/.old-already-moved/src/components/dashboard/Reset.tsx similarity index 100% rename from src/components/dashboard/Reset.tsx rename to .old-already-moved/src/components/dashboard/Reset.tsx diff --git a/src/components/dashboard/SearchInput.tsx b/.old-already-moved/src/components/dashboard/SearchInput.tsx similarity index 100% rename from src/components/dashboard/SearchInput.tsx rename to .old-already-moved/src/components/dashboard/SearchInput.tsx diff --git a/src/components/dashboard/Setting.tsx b/.old-already-moved/src/components/dashboard/Setting.tsx similarity index 100% rename from src/components/dashboard/Setting.tsx rename to .old-already-moved/src/components/dashboard/Setting.tsx diff --git a/src/components/dashboard/SignSettings.tsx b/.old-already-moved/src/components/dashboard/SignSettings.tsx similarity index 100% rename from src/components/dashboard/SignSettings.tsx rename to .old-already-moved/src/components/dashboard/SignSettings.tsx diff --git a/src/components/dashboard/Tokens.tsx b/.old-already-moved/src/components/dashboard/Tokens.tsx similarity index 100% rename from src/components/dashboard/Tokens.tsx rename to .old-already-moved/src/components/dashboard/Tokens.tsx diff --git a/src/components/dashboard/Wallets.tsx b/.old-already-moved/src/components/dashboard/Wallets.tsx similarity index 100% rename from src/components/dashboard/Wallets.tsx rename to .old-already-moved/src/components/dashboard/Wallets.tsx diff --git a/src/components/dashboard/list/AppListItem.tsx b/.old-already-moved/src/components/dashboard/list/AppListItem.tsx similarity index 100% rename from src/components/dashboard/list/AppListItem.tsx rename to .old-already-moved/src/components/dashboard/list/AppListItem.tsx diff --git a/src/components/dashboard/list/BaseElement.tsx b/.old-already-moved/src/components/dashboard/list/BaseElement.tsx similarity index 100% rename from src/components/dashboard/list/BaseElement.tsx rename to .old-already-moved/src/components/dashboard/list/BaseElement.tsx diff --git a/src/components/dashboard/list/ContactListItem.tsx b/.old-already-moved/src/components/dashboard/list/ContactListItem.tsx similarity index 100% rename from src/components/dashboard/list/ContactListItem.tsx rename to .old-already-moved/src/components/dashboard/list/ContactListItem.tsx diff --git a/src/components/dashboard/list/SettingListItem.tsx b/.old-already-moved/src/components/dashboard/list/SettingListItem.tsx similarity index 100% rename from src/components/dashboard/list/SettingListItem.tsx rename to .old-already-moved/src/components/dashboard/list/SettingListItem.tsx diff --git a/src/components/dashboard/list/TokenListItem.tsx b/.old-already-moved/src/components/dashboard/list/TokenListItem.tsx similarity index 100% rename from src/components/dashboard/list/TokenListItem.tsx rename to .old-already-moved/src/components/dashboard/list/TokenListItem.tsx diff --git a/src/components/dashboard/list/WalletListItem.tsx b/.old-already-moved/src/components/dashboard/list/WalletListItem.tsx similarity index 100% rename from src/components/dashboard/list/WalletListItem.tsx rename to .old-already-moved/src/components/dashboard/list/WalletListItem.tsx diff --git a/src/components/dashboard/subsettings/AddContact.tsx b/.old-already-moved/src/components/dashboard/subsettings/AddContact.tsx similarity index 100% rename from src/components/dashboard/subsettings/AddContact.tsx rename to .old-already-moved/src/components/dashboard/subsettings/AddContact.tsx diff --git a/src/components/dashboard/subsettings/AddToken.tsx b/.old-already-moved/src/components/dashboard/subsettings/AddToken.tsx similarity index 100% rename from src/components/dashboard/subsettings/AddToken.tsx rename to .old-already-moved/src/components/dashboard/subsettings/AddToken.tsx diff --git a/src/components/dashboard/subsettings/AddWallet.tsx b/.old-already-moved/src/components/dashboard/subsettings/AddWallet.tsx similarity index 100% rename from src/components/dashboard/subsettings/AddWallet.tsx rename to .old-already-moved/src/components/dashboard/subsettings/AddWallet.tsx diff --git a/src/components/dashboard/subsettings/AppSettings.tsx b/.old-already-moved/src/components/dashboard/subsettings/AppSettings.tsx similarity index 100% rename from src/components/dashboard/subsettings/AppSettings.tsx rename to .old-already-moved/src/components/dashboard/subsettings/AppSettings.tsx diff --git a/src/components/dashboard/subsettings/ContactSettings.tsx b/.old-already-moved/src/components/dashboard/subsettings/ContactSettings.tsx similarity index 100% rename from src/components/dashboard/subsettings/ContactSettings.tsx rename to .old-already-moved/src/components/dashboard/subsettings/ContactSettings.tsx diff --git a/src/components/dashboard/subsettings/Help.tsx b/.old-already-moved/src/components/dashboard/subsettings/Help.tsx similarity index 100% rename from src/components/dashboard/subsettings/Help.tsx rename to .old-already-moved/src/components/dashboard/subsettings/Help.tsx diff --git a/src/components/dashboard/subsettings/TokenSettings.tsx b/.old-already-moved/src/components/dashboard/subsettings/TokenSettings.tsx similarity index 100% rename from src/components/dashboard/subsettings/TokenSettings.tsx rename to .old-already-moved/src/components/dashboard/subsettings/TokenSettings.tsx diff --git a/src/components/dashboard/subsettings/WalletSettings.tsx b/.old-already-moved/src/components/dashboard/subsettings/WalletSettings.tsx similarity index 100% rename from src/components/dashboard/subsettings/WalletSettings.tsx rename to .old-already-moved/src/components/dashboard/subsettings/WalletSettings.tsx diff --git a/src/components/dev/button/button.component.tsx b/.old-already-moved/src/components/dev/button/button.component.tsx similarity index 100% rename from src/components/dev/button/button.component.tsx rename to .old-already-moved/src/components/dev/button/button.component.tsx diff --git a/src/components/dev/button/button.module.scss b/.old-already-moved/src/components/dev/button/button.module.scss similarity index 100% rename from src/components/dev/button/button.module.scss rename to .old-already-moved/src/components/dev/button/button.module.scss diff --git a/src/components/dev/buttons/buttons.component.tsx b/.old-already-moved/src/components/dev/buttons/buttons.component.tsx similarity index 100% rename from src/components/dev/buttons/buttons.component.tsx rename to .old-already-moved/src/components/dev/buttons/buttons.component.tsx diff --git a/src/components/dev/buttons/buttons.module.scss b/.old-already-moved/src/components/dev/buttons/buttons.module.scss similarity index 100% rename from src/components/dev/buttons/buttons.module.scss rename to .old-already-moved/src/components/dev/buttons/buttons.module.scss diff --git a/src/components/dev/env-panel/EnvPanel.module.scss b/.old-already-moved/src/components/dev/env-panel/EnvPanel.module.scss similarity index 100% rename from src/components/dev/env-panel/EnvPanel.module.scss rename to .old-already-moved/src/components/dev/env-panel/EnvPanel.module.scss diff --git a/src/components/dev/env-panel/EnvPanel.tsx b/.old-already-moved/src/components/dev/env-panel/EnvPanel.tsx similarity index 98% rename from src/components/dev/env-panel/EnvPanel.tsx rename to .old-already-moved/src/components/dev/env-panel/EnvPanel.tsx index 8d0a50163..bb8bfaed6 100644 --- a/src/components/dev/env-panel/EnvPanel.tsx +++ b/.old-already-moved/src/components/dev/env-panel/EnvPanel.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from "react"; import { useLocation } from "~wallets/router/router.utils"; -import { EMBEDDED_SERVER_BASE_URL } from "~utils/embedded/iframe.utils"; +import { EMBEDDED_SERVER_BASE_URL } from "~utils/_embedded/iframe.utils"; import styles from "./EnvPanel.module.scss"; diff --git a/src/components/dev/figma-screen/figma-screen.component.tsx b/.old-already-moved/src/components/dev/figma-screen/figma-screen.component.tsx similarity index 100% rename from src/components/dev/figma-screen/figma-screen.component.tsx rename to .old-already-moved/src/components/dev/figma-screen/figma-screen.component.tsx diff --git a/src/components/dev/figma-screen/figma-screen.module.scss b/.old-already-moved/src/components/dev/figma-screen/figma-screen.module.scss similarity index 100% rename from src/components/dev/figma-screen/figma-screen.module.scss rename to .old-already-moved/src/components/dev/figma-screen/figma-screen.module.scss diff --git a/src/components/dev/resize-event-observer/ResizeEventObserver.module.scss b/.old-already-moved/src/components/dev/resize-event-observer/ResizeEventObserver.module.scss similarity index 100% rename from src/components/dev/resize-event-observer/ResizeEventObserver.module.scss rename to .old-already-moved/src/components/dev/resize-event-observer/ResizeEventObserver.module.scss diff --git a/src/components/dev/resize-event-observer/ResizeEventObserver.tsx b/.old-already-moved/src/components/dev/resize-event-observer/ResizeEventObserver.tsx similarity index 91% rename from src/components/dev/resize-event-observer/ResizeEventObserver.tsx rename to .old-already-moved/src/components/dev/resize-event-observer/ResizeEventObserver.tsx index 0855cfe48..35ac40a83 100644 --- a/src/components/dev/resize-event-observer/ResizeEventObserver.tsx +++ b/.old-already-moved/src/components/dev/resize-event-observer/ResizeEventObserver.tsx @@ -1,10 +1,10 @@ import React, { useCallback, useEffect, useRef } from "react"; import { useLocation } from "~wallets/router/router.utils"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; -import { locationToRouteType, routeTypeToPreferredLayout } from "~utils/embedded/utils/routes/embedded-routes.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; +import { locationToRouteType, routeTypeToPreferredLayout } from "~utils/_embedded/utils/routes/embedded-routes.utils"; import styles from "./ResizeEventObserver.module.scss"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; export interface ResizeEventObserverProps { containerRef: React.MutableRefObject; diff --git a/src/components/dev/spinner-cover/spinner-cover.component.tsx b/.old-already-moved/src/components/dev/spinner-cover/spinner-cover.component.tsx similarity index 100% rename from src/components/dev/spinner-cover/spinner-cover.component.tsx rename to .old-already-moved/src/components/dev/spinner-cover/spinner-cover.component.tsx diff --git a/src/components/dev/spinner-cover/spinner-cover.module.scss b/.old-already-moved/src/components/dev/spinner-cover/spinner-cover.module.scss similarity index 100% rename from src/components/dev/spinner-cover/spinner-cover.module.scss rename to .old-already-moved/src/components/dev/spinner-cover/spinner-cover.module.scss diff --git a/src/components/dev/spinner/spinner.component.tsx b/.old-already-moved/src/components/dev/spinner/spinner.component.tsx similarity index 100% rename from src/components/dev/spinner/spinner.component.tsx rename to .old-already-moved/src/components/dev/spinner/spinner.component.tsx diff --git a/src/components/dev/spinner/spinner.module.scss b/.old-already-moved/src/components/dev/spinner/spinner.module.scss similarity index 100% rename from src/components/dev/spinner/spinner.module.scss rename to .old-already-moved/src/components/dev/spinner/spinner.module.scss diff --git a/src/components/devtools/Connector.tsx b/.old-already-moved/src/components/devtools/Connector.tsx similarity index 100% rename from src/components/devtools/Connector.tsx rename to .old-already-moved/src/components/devtools/Connector.tsx diff --git a/src/components/devtools/NoWallets.tsx b/.old-already-moved/src/components/devtools/NoWallets.tsx similarity index 100% rename from src/components/devtools/NoWallets.tsx rename to .old-already-moved/src/components/devtools/NoWallets.tsx diff --git a/src/components/embed/assets/images/Socials.png b/.old-already-moved/src/components/embed/assets/images/Socials.png similarity index 100% rename from src/components/embed/assets/images/Socials.png rename to .old-already-moved/src/components/embed/assets/images/Socials.png diff --git a/src/components/embed/assets/images/qr-frame.svg b/.old-already-moved/src/components/embed/assets/images/qr-frame.svg similarity index 100% rename from src/components/embed/assets/images/qr-frame.svg rename to .old-already-moved/src/components/embed/assets/images/qr-frame.svg diff --git a/src/components/embed/assets/svg/check.svg b/.old-already-moved/src/components/embed/assets/svg/check.svg similarity index 100% rename from src/components/embed/assets/svg/check.svg rename to .old-already-moved/src/components/embed/assets/svg/check.svg diff --git a/src/components/embed/assets/svg/chevron-left.svg b/.old-already-moved/src/components/embed/assets/svg/chevron-left.svg similarity index 100% rename from src/components/embed/assets/svg/chevron-left.svg rename to .old-already-moved/src/components/embed/assets/svg/chevron-left.svg diff --git a/src/components/embed/assets/svg/copyable.svg b/.old-already-moved/src/components/embed/assets/svg/copyable.svg similarity index 100% rename from src/components/embed/assets/svg/copyable.svg rename to .old-already-moved/src/components/embed/assets/svg/copyable.svg diff --git a/src/components/embed/assets/svg/info.svg b/.old-already-moved/src/components/embed/assets/svg/info.svg similarity index 100% rename from src/components/embed/assets/svg/info.svg rename to .old-already-moved/src/components/embed/assets/svg/info.svg diff --git a/src/components/embed/assets/svg/key-icon.svg b/.old-already-moved/src/components/embed/assets/svg/key-icon.svg similarity index 100% rename from src/components/embed/assets/svg/key-icon.svg rename to .old-already-moved/src/components/embed/assets/svg/key-icon.svg diff --git a/src/components/embed/assets/svg/logos/Twitter.svg b/.old-already-moved/src/components/embed/assets/svg/logos/Twitter.svg similarity index 100% rename from src/components/embed/assets/svg/logos/Twitter.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/Twitter.svg diff --git a/src/components/embed/assets/svg/logos/apple.svg b/.old-already-moved/src/components/embed/assets/svg/logos/apple.svg similarity index 100% rename from src/components/embed/assets/svg/logos/apple.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/apple.svg diff --git a/src/components/embed/assets/svg/logos/arconnect.svg b/.old-already-moved/src/components/embed/assets/svg/logos/arconnect.svg similarity index 100% rename from src/components/embed/assets/svg/logos/arconnect.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/arconnect.svg diff --git a/src/components/embed/assets/svg/logos/dropbox.svg b/.old-already-moved/src/components/embed/assets/svg/logos/dropbox.svg similarity index 100% rename from src/components/embed/assets/svg/logos/dropbox.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/dropbox.svg diff --git a/src/components/embed/assets/svg/logos/facebook.svg b/.old-already-moved/src/components/embed/assets/svg/logos/facebook.svg similarity index 100% rename from src/components/embed/assets/svg/logos/facebook.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/facebook.svg diff --git a/src/components/embed/assets/svg/logos/google-drive.svg b/.old-already-moved/src/components/embed/assets/svg/logos/google-drive.svg similarity index 100% rename from src/components/embed/assets/svg/logos/google-drive.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/google-drive.svg diff --git a/src/components/embed/assets/svg/logos/google.svg b/.old-already-moved/src/components/embed/assets/svg/logos/google.svg similarity index 100% rename from src/components/embed/assets/svg/logos/google.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/google.svg diff --git a/src/components/embed/assets/svg/logos/socials.svg b/.old-already-moved/src/components/embed/assets/svg/logos/socials.svg similarity index 100% rename from src/components/embed/assets/svg/logos/socials.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/socials.svg diff --git a/src/components/embed/assets/svg/logos/wander-2.svg b/.old-already-moved/src/components/embed/assets/svg/logos/wander-2.svg similarity index 100% rename from src/components/embed/assets/svg/logos/wander-2.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/wander-2.svg diff --git a/src/components/embed/assets/svg/logos/wander.svg b/.old-already-moved/src/components/embed/assets/svg/logos/wander.svg similarity index 100% rename from src/components/embed/assets/svg/logos/wander.svg rename to .old-already-moved/src/components/embed/assets/svg/logos/wander.svg diff --git a/src/components/embed/assets/svg/qr-code.svg b/.old-already-moved/src/components/embed/assets/svg/qr-code.svg similarity index 100% rename from src/components/embed/assets/svg/qr-code.svg rename to .old-already-moved/src/components/embed/assets/svg/qr-code.svg diff --git a/src/components/embed/assets/svg/recoverIconHeader.svg b/.old-already-moved/src/components/embed/assets/svg/recoverIconHeader.svg similarity index 100% rename from src/components/embed/assets/svg/recoverIconHeader.svg rename to .old-already-moved/src/components/embed/assets/svg/recoverIconHeader.svg diff --git a/src/components/embed/assets/svg/seedphrase-icon.svg b/.old-already-moved/src/components/embed/assets/svg/seedphrase-icon.svg similarity index 100% rename from src/components/embed/assets/svg/seedphrase-icon.svg rename to .old-already-moved/src/components/embed/assets/svg/seedphrase-icon.svg diff --git a/src/components/embed/assets/svg/upload.svg b/.old-already-moved/src/components/embed/assets/svg/upload.svg similarity index 100% rename from src/components/embed/assets/svg/upload.svg rename to .old-already-moved/src/components/embed/assets/svg/upload.svg diff --git a/src/components/embed/assets/svg/wallet-icon.svg b/.old-already-moved/src/components/embed/assets/svg/wallet-icon.svg similarity index 100% rename from src/components/embed/assets/svg/wallet-icon.svg rename to .old-already-moved/src/components/embed/assets/svg/wallet-icon.svg diff --git a/src/components/embed/assets/svg/warning.svg b/.old-already-moved/src/components/embed/assets/svg/warning.svg similarity index 100% rename from src/components/embed/assets/svg/warning.svg rename to .old-already-moved/src/components/embed/assets/svg/warning.svg diff --git a/src/components/embed/assets/svg/webcam.svg b/.old-already-moved/src/components/embed/assets/svg/webcam.svg similarity index 100% rename from src/components/embed/assets/svg/webcam.svg rename to .old-already-moved/src/components/embed/assets/svg/webcam.svg diff --git a/src/components/embed/assets/svg/x-close.svg b/.old-already-moved/src/components/embed/assets/svg/x-close.svg similarity index 100% rename from src/components/embed/assets/svg/x-close.svg rename to .old-already-moved/src/components/embed/assets/svg/x-close.svg diff --git a/src/components/embed/auth/Message.tsx b/.old-already-moved/src/components/embed/auth/Message.tsx similarity index 100% rename from src/components/embed/auth/Message.tsx rename to .old-already-moved/src/components/embed/auth/Message.tsx diff --git a/src/components/embed/auth/SignDataItem.tsx b/.old-already-moved/src/components/embed/auth/SignDataItem.tsx similarity index 100% rename from src/components/embed/auth/SignDataItem.tsx rename to .old-already-moved/src/components/embed/auth/SignDataItem.tsx diff --git a/src/components/embed/auth/SignDataItemDetails.tsx b/.old-already-moved/src/components/embed/auth/SignDataItemDetails.tsx similarity index 100% rename from src/components/embed/auth/SignDataItemDetails.tsx rename to .old-already-moved/src/components/embed/auth/SignDataItemDetails.tsx diff --git a/src/components/embed/auth/TransactionMessage.tsx b/.old-already-moved/src/components/embed/auth/TransactionMessage.tsx similarity index 100% rename from src/components/embed/auth/TransactionMessage.tsx rename to .old-already-moved/src/components/embed/auth/TransactionMessage.tsx diff --git a/src/components/embed/auth/TransactionTag.tsx b/.old-already-moved/src/components/embed/auth/TransactionTag.tsx similarity index 100% rename from src/components/embed/auth/TransactionTag.tsx rename to .old-already-moved/src/components/embed/auth/TransactionTag.tsx diff --git a/src/components/embed/index.ts b/.old-already-moved/src/components/embed/index.ts similarity index 100% rename from src/components/embed/index.ts rename to .old-already-moved/src/components/embed/index.ts diff --git a/src/components/embed/styles/mixins.css b/.old-already-moved/src/components/embed/styles/mixins.css similarity index 100% rename from src/components/embed/styles/mixins.css rename to .old-already-moved/src/components/embed/styles/mixins.css diff --git a/src/components/embed/themes/theme-config.ts b/.old-already-moved/src/components/embed/themes/theme-config.ts similarity index 100% rename from src/components/embed/themes/theme-config.ts rename to .old-already-moved/src/components/embed/themes/theme-config.ts diff --git a/src/components/embed/tokens/base/borders.css b/.old-already-moved/src/components/embed/tokens/base/borders.css similarity index 100% rename from src/components/embed/tokens/base/borders.css rename to .old-already-moved/src/components/embed/tokens/base/borders.css diff --git a/src/components/embed/tokens/base/colors.css b/.old-already-moved/src/components/embed/tokens/base/colors.css similarity index 100% rename from src/components/embed/tokens/base/colors.css rename to .old-already-moved/src/components/embed/tokens/base/colors.css diff --git a/src/components/embed/tokens/base/index.css b/.old-already-moved/src/components/embed/tokens/base/index.css similarity index 100% rename from src/components/embed/tokens/base/index.css rename to .old-already-moved/src/components/embed/tokens/base/index.css diff --git a/src/components/embed/tokens/base/shadows.css b/.old-already-moved/src/components/embed/tokens/base/shadows.css similarity index 100% rename from src/components/embed/tokens/base/shadows.css rename to .old-already-moved/src/components/embed/tokens/base/shadows.css diff --git a/src/components/embed/tokens/base/spacing.css b/.old-already-moved/src/components/embed/tokens/base/spacing.css similarity index 100% rename from src/components/embed/tokens/base/spacing.css rename to .old-already-moved/src/components/embed/tokens/base/spacing.css diff --git a/src/components/embed/tokens/base/typography.css b/.old-already-moved/src/components/embed/tokens/base/typography.css similarity index 100% rename from src/components/embed/tokens/base/typography.css rename to .old-already-moved/src/components/embed/tokens/base/typography.css diff --git a/src/components/embed/tokens/brand-color.css b/.old-already-moved/src/components/embed/tokens/brand-color.css similarity index 100% rename from src/components/embed/tokens/brand-color.css rename to .old-already-moved/src/components/embed/tokens/brand-color.css diff --git a/src/components/embed/tokens/breakpoint.css b/.old-already-moved/src/components/embed/tokens/breakpoint.css similarity index 100% rename from src/components/embed/tokens/breakpoint.css rename to .old-already-moved/src/components/embed/tokens/breakpoint.css diff --git a/src/components/embed/tokens/height.css b/.old-already-moved/src/components/embed/tokens/height.css similarity index 100% rename from src/components/embed/tokens/height.css rename to .old-already-moved/src/components/embed/tokens/height.css diff --git a/src/components/embed/tokens/index.css b/.old-already-moved/src/components/embed/tokens/index.css similarity index 100% rename from src/components/embed/tokens/index.css rename to .old-already-moved/src/components/embed/tokens/index.css diff --git a/src/components/embed/tokens/semantic/index.css b/.old-already-moved/src/components/embed/tokens/semantic/index.css similarity index 100% rename from src/components/embed/tokens/semantic/index.css rename to .old-already-moved/src/components/embed/tokens/semantic/index.css diff --git a/src/components/embed/tokens/themes/dark.css b/.old-already-moved/src/components/embed/tokens/themes/dark.css similarity index 100% rename from src/components/embed/tokens/themes/dark.css rename to .old-already-moved/src/components/embed/tokens/themes/dark.css diff --git a/src/components/embed/tokens/themes/light.css b/.old-already-moved/src/components/embed/tokens/themes/light.css similarity index 100% rename from src/components/embed/tokens/themes/light.css rename to .old-already-moved/src/components/embed/tokens/themes/light.css diff --git a/src/components/embed/tokens/width.css b/.old-already-moved/src/components/embed/tokens/width.css similarity index 100% rename from src/components/embed/tokens/width.css rename to .old-already-moved/src/components/embed/tokens/width.css diff --git a/src/components/embed/tokens/z-index.css b/.old-already-moved/src/components/embed/tokens/z-index.css similarity index 100% rename from src/components/embed/tokens/z-index.css rename to .old-already-moved/src/components/embed/tokens/z-index.css diff --git a/src/components/embed/types/alignments.ts b/.old-already-moved/src/components/embed/types/alignments.ts similarity index 100% rename from src/components/embed/types/alignments.ts rename to .old-already-moved/src/components/embed/types/alignments.ts diff --git a/src/components/embed/types/index.ts b/.old-already-moved/src/components/embed/types/index.ts similarity index 100% rename from src/components/embed/types/index.ts rename to .old-already-moved/src/components/embed/types/index.ts diff --git a/src/components/embed/types/positions.ts b/.old-already-moved/src/components/embed/types/positions.ts similarity index 100% rename from src/components/embed/types/positions.ts rename to .old-already-moved/src/components/embed/types/positions.ts diff --git a/src/components/embed/types/shadows.ts b/.old-already-moved/src/components/embed/types/shadows.ts similarity index 100% rename from src/components/embed/types/shadows.ts rename to .old-already-moved/src/components/embed/types/shadows.ts diff --git a/src/components/embed/types/sizes.ts b/.old-already-moved/src/components/embed/types/sizes.ts similarity index 100% rename from src/components/embed/types/sizes.ts rename to .old-already-moved/src/components/embed/types/sizes.ts diff --git a/src/components/embed/types/types.ts b/.old-already-moved/src/components/embed/types/types.ts similarity index 100% rename from src/components/embed/types/types.ts rename to .old-already-moved/src/components/embed/types/types.ts diff --git a/src/components/embed/types/variants.ts b/.old-already-moved/src/components/embed/types/variants.ts similarity index 100% rename from src/components/embed/types/variants.ts rename to .old-already-moved/src/components/embed/types/variants.ts diff --git a/src/components/embed/ui/atoms/avatar/Avatar.module.css b/.old-already-moved/src/components/embed/ui/atoms/avatar/Avatar.module.css similarity index 100% rename from src/components/embed/ui/atoms/avatar/Avatar.module.css rename to .old-already-moved/src/components/embed/ui/atoms/avatar/Avatar.module.css diff --git a/src/components/embed/ui/atoms/avatar/Avatar.tsx b/.old-already-moved/src/components/embed/ui/atoms/avatar/Avatar.tsx similarity index 100% rename from src/components/embed/ui/atoms/avatar/Avatar.tsx rename to .old-already-moved/src/components/embed/ui/atoms/avatar/Avatar.tsx diff --git a/src/components/embed/ui/atoms/avatar/Avatar.types.ts b/.old-already-moved/src/components/embed/ui/atoms/avatar/Avatar.types.ts similarity index 100% rename from src/components/embed/ui/atoms/avatar/Avatar.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/avatar/Avatar.types.ts diff --git a/src/components/embed/ui/atoms/avatar/index.ts b/.old-already-moved/src/components/embed/ui/atoms/avatar/index.ts similarity index 100% rename from src/components/embed/ui/atoms/avatar/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/avatar/index.ts diff --git a/src/components/embed/ui/atoms/box/Box.module.css b/.old-already-moved/src/components/embed/ui/atoms/box/Box.module.css similarity index 100% rename from src/components/embed/ui/atoms/box/Box.module.css rename to .old-already-moved/src/components/embed/ui/atoms/box/Box.module.css diff --git a/src/components/embed/ui/atoms/box/Box.tsx b/.old-already-moved/src/components/embed/ui/atoms/box/Box.tsx similarity index 100% rename from src/components/embed/ui/atoms/box/Box.tsx rename to .old-already-moved/src/components/embed/ui/atoms/box/Box.tsx diff --git a/src/components/embed/ui/atoms/box/Box.types.ts b/.old-already-moved/src/components/embed/ui/atoms/box/Box.types.ts similarity index 100% rename from src/components/embed/ui/atoms/box/Box.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/box/Box.types.ts diff --git a/src/components/embed/ui/atoms/box/index.ts b/.old-already-moved/src/components/embed/ui/atoms/box/index.ts similarity index 100% rename from src/components/embed/ui/atoms/box/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/box/index.ts diff --git a/src/components/embed/ui/atoms/button/Button.module.scss b/.old-already-moved/src/components/embed/ui/atoms/button/Button.module.scss similarity index 100% rename from src/components/embed/ui/atoms/button/Button.module.scss rename to .old-already-moved/src/components/embed/ui/atoms/button/Button.module.scss diff --git a/src/components/embed/ui/atoms/button/Button.tsx b/.old-already-moved/src/components/embed/ui/atoms/button/Button.tsx similarity index 100% rename from src/components/embed/ui/atoms/button/Button.tsx rename to .old-already-moved/src/components/embed/ui/atoms/button/Button.tsx diff --git a/src/components/embed/ui/atoms/button/Button.types.ts b/.old-already-moved/src/components/embed/ui/atoms/button/Button.types.ts similarity index 100% rename from src/components/embed/ui/atoms/button/Button.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/button/Button.types.ts diff --git a/src/components/embed/ui/atoms/button/index.ts b/.old-already-moved/src/components/embed/ui/atoms/button/index.ts similarity index 100% rename from src/components/embed/ui/atoms/button/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/button/index.ts diff --git a/src/components/embed/ui/atoms/checkbox/Checkbox.module.css b/.old-already-moved/src/components/embed/ui/atoms/checkbox/Checkbox.module.css similarity index 100% rename from src/components/embed/ui/atoms/checkbox/Checkbox.module.css rename to .old-already-moved/src/components/embed/ui/atoms/checkbox/Checkbox.module.css diff --git a/src/components/embed/ui/atoms/checkbox/Checkbox.tsx b/.old-already-moved/src/components/embed/ui/atoms/checkbox/Checkbox.tsx similarity index 100% rename from src/components/embed/ui/atoms/checkbox/Checkbox.tsx rename to .old-already-moved/src/components/embed/ui/atoms/checkbox/Checkbox.tsx diff --git a/src/components/embed/ui/atoms/checkbox/Checkbox.types.ts b/.old-already-moved/src/components/embed/ui/atoms/checkbox/Checkbox.types.ts similarity index 100% rename from src/components/embed/ui/atoms/checkbox/Checkbox.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/checkbox/Checkbox.types.ts diff --git a/src/components/embed/ui/atoms/checkbox/index.ts b/.old-already-moved/src/components/embed/ui/atoms/checkbox/index.ts similarity index 100% rename from src/components/embed/ui/atoms/checkbox/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/checkbox/index.ts diff --git a/src/components/embed/ui/atoms/code-input/CodeInput.module.scss b/.old-already-moved/src/components/embed/ui/atoms/code-input/CodeInput.module.scss similarity index 100% rename from src/components/embed/ui/atoms/code-input/CodeInput.module.scss rename to .old-already-moved/src/components/embed/ui/atoms/code-input/CodeInput.module.scss diff --git a/src/components/embed/ui/atoms/code-input/CodeInput.tsx b/.old-already-moved/src/components/embed/ui/atoms/code-input/CodeInput.tsx similarity index 100% rename from src/components/embed/ui/atoms/code-input/CodeInput.tsx rename to .old-already-moved/src/components/embed/ui/atoms/code-input/CodeInput.tsx diff --git a/src/components/embed/ui/atoms/copyable/Copyable.module.css b/.old-already-moved/src/components/embed/ui/atoms/copyable/Copyable.module.css similarity index 100% rename from src/components/embed/ui/atoms/copyable/Copyable.module.css rename to .old-already-moved/src/components/embed/ui/atoms/copyable/Copyable.module.css diff --git a/src/components/embed/ui/atoms/copyable/Copyable.tsx b/.old-already-moved/src/components/embed/ui/atoms/copyable/Copyable.tsx similarity index 100% rename from src/components/embed/ui/atoms/copyable/Copyable.tsx rename to .old-already-moved/src/components/embed/ui/atoms/copyable/Copyable.tsx diff --git a/src/components/embed/ui/atoms/copyable/Copyable.types.ts b/.old-already-moved/src/components/embed/ui/atoms/copyable/Copyable.types.ts similarity index 100% rename from src/components/embed/ui/atoms/copyable/Copyable.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/copyable/Copyable.types.ts diff --git a/src/components/embed/ui/atoms/copyable/index.ts b/.old-already-moved/src/components/embed/ui/atoms/copyable/index.ts similarity index 100% rename from src/components/embed/ui/atoms/copyable/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/copyable/index.ts diff --git a/src/components/embed/ui/atoms/divider/Divider.module.css b/.old-already-moved/src/components/embed/ui/atoms/divider/Divider.module.css similarity index 100% rename from src/components/embed/ui/atoms/divider/Divider.module.css rename to .old-already-moved/src/components/embed/ui/atoms/divider/Divider.module.css diff --git a/src/components/embed/ui/atoms/divider/Divider.tsx b/.old-already-moved/src/components/embed/ui/atoms/divider/Divider.tsx similarity index 100% rename from src/components/embed/ui/atoms/divider/Divider.tsx rename to .old-already-moved/src/components/embed/ui/atoms/divider/Divider.tsx diff --git a/src/components/embed/ui/atoms/divider/Divider.types.ts b/.old-already-moved/src/components/embed/ui/atoms/divider/Divider.types.ts similarity index 100% rename from src/components/embed/ui/atoms/divider/Divider.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/divider/Divider.types.ts diff --git a/src/components/embed/ui/atoms/divider/index.ts b/.old-already-moved/src/components/embed/ui/atoms/divider/index.ts similarity index 100% rename from src/components/embed/ui/atoms/divider/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/divider/index.ts diff --git a/src/components/embed/ui/atoms/formatted-text/FormattedText.module.scss b/.old-already-moved/src/components/embed/ui/atoms/formatted-text/FormattedText.module.scss similarity index 100% rename from src/components/embed/ui/atoms/formatted-text/FormattedText.module.scss rename to .old-already-moved/src/components/embed/ui/atoms/formatted-text/FormattedText.module.scss diff --git a/src/components/embed/ui/atoms/formatted-text/FormattedText.tsx b/.old-already-moved/src/components/embed/ui/atoms/formatted-text/FormattedText.tsx similarity index 100% rename from src/components/embed/ui/atoms/formatted-text/FormattedText.tsx rename to .old-already-moved/src/components/embed/ui/atoms/formatted-text/FormattedText.tsx diff --git a/src/components/embed/ui/atoms/icon/Apple.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Apple.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Apple.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Apple.tsx diff --git a/src/components/embed/ui/atoms/icon/ArConnect.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/ArConnect.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/ArConnect.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/ArConnect.tsx diff --git a/src/components/embed/ui/atoms/icon/ArrowDown.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/ArrowDown.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/ArrowDown.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/ArrowDown.tsx diff --git a/src/components/embed/ui/atoms/icon/BackupComplete.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/BackupComplete.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/BackupComplete.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/BackupComplete.tsx diff --git a/src/components/embed/ui/atoms/icon/BuyWithCash.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/BuyWithCash.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/BuyWithCash.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/BuyWithCash.tsx diff --git a/src/components/embed/ui/atoms/icon/BuyWithCrypto.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/BuyWithCrypto.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/BuyWithCrypto.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/BuyWithCrypto.tsx diff --git a/src/components/embed/ui/atoms/icon/Camera.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Camera.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Camera.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Camera.tsx diff --git a/src/components/embed/ui/atoms/icon/Check.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Check.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Check.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Check.tsx diff --git a/src/components/embed/ui/atoms/icon/ChevronLeft.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/ChevronLeft.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/ChevronLeft.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/ChevronLeft.tsx diff --git a/src/components/embed/ui/atoms/icon/ChevronRight.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/ChevronRight.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/ChevronRight.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/ChevronRight.tsx diff --git a/src/components/embed/ui/atoms/icon/Coins.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Coins.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Coins.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Coins.tsx diff --git a/src/components/embed/ui/atoms/icon/CollapseIt.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/CollapseIt.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/CollapseIt.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/CollapseIt.tsx diff --git a/src/components/embed/ui/atoms/icon/CompassIcon.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/CompassIcon.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/CompassIcon.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/CompassIcon.tsx diff --git a/src/components/embed/ui/atoms/icon/ConnectWander.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/ConnectWander.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/ConnectWander.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/ConnectWander.tsx diff --git a/src/components/embed/ui/atoms/icon/Copyable.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Copyable.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Copyable.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Copyable.tsx diff --git a/src/components/embed/ui/atoms/icon/Download.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Download.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Download.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Download.tsx diff --git a/src/components/embed/ui/atoms/icon/Dropbox.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Dropbox.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Dropbox.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Dropbox.tsx diff --git a/src/components/embed/ui/atoms/icon/EUAFlag.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/EUAFlag.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/EUAFlag.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/EUAFlag.tsx diff --git a/src/components/embed/ui/atoms/icon/Error.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Error.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Error.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Error.tsx diff --git a/src/components/embed/ui/atoms/icon/ExpandIt.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/ExpandIt.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/ExpandIt.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/ExpandIt.tsx diff --git a/src/components/embed/ui/atoms/icon/Eye.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Eye.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Eye.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Eye.tsx diff --git a/src/components/embed/ui/atoms/icon/EyeOff.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/EyeOff.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/EyeOff.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/EyeOff.tsx diff --git a/src/components/embed/ui/atoms/icon/Facebook.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Facebook.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Facebook.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Facebook.tsx diff --git a/src/components/embed/ui/atoms/icon/GDrive.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/GDrive.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/GDrive.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/GDrive.tsx diff --git a/src/components/embed/ui/atoms/icon/Google.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Google.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Google.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Google.tsx diff --git a/src/components/embed/ui/atoms/icon/IconBase.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/IconBase.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/IconBase.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/IconBase.tsx diff --git a/src/components/embed/ui/atoms/icon/Info.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Info.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Info.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Info.tsx diff --git a/src/components/embed/ui/atoms/icon/Key.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Key.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Key.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Key.tsx diff --git a/src/components/embed/ui/atoms/icon/KeyShare.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/KeyShare.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/KeyShare.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/KeyShare.tsx diff --git a/src/components/embed/ui/atoms/icon/Minimize.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Minimize.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Minimize.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Minimize.tsx diff --git a/src/components/embed/ui/atoms/icon/OpenTab.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/OpenTab.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/OpenTab.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/OpenTab.tsx diff --git a/src/components/embed/ui/atoms/icon/PlusCircle.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/PlusCircle.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/PlusCircle.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/PlusCircle.tsx diff --git a/src/components/embed/ui/atoms/icon/ProtocolLand.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/ProtocolLand.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/ProtocolLand.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/ProtocolLand.tsx diff --git a/src/components/embed/ui/atoms/icon/QRCode.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/QRCode.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/QRCode.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/QRCode.tsx diff --git a/src/components/embed/ui/atoms/icon/Receipt.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Receipt.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Receipt.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Receipt.tsx diff --git a/src/components/embed/ui/atoms/icon/RecoverHeader.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/RecoverHeader.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/RecoverHeader.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/RecoverHeader.tsx diff --git a/src/components/embed/ui/atoms/icon/SeedPhrase.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/SeedPhrase.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/SeedPhrase.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/SeedPhrase.tsx diff --git a/src/components/embed/ui/atoms/icon/Socials.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Socials.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Socials.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Socials.tsx diff --git a/src/components/embed/ui/atoms/icon/Success.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Success.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Success.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Success.tsx diff --git a/src/components/embed/ui/atoms/icon/SuccessCheck.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/SuccessCheck.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/SuccessCheck.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/SuccessCheck.tsx diff --git a/src/components/embed/ui/atoms/icon/Twitter.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Twitter.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Twitter.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Twitter.tsx diff --git a/src/components/embed/ui/atoms/icon/Upload.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Upload.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Upload.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Upload.tsx diff --git a/src/components/embed/ui/atoms/icon/Wallet.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Wallet.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Wallet.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Wallet.tsx diff --git a/src/components/embed/ui/atoms/icon/Wander-2.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Wander-2.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Wander-2.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Wander-2.tsx diff --git a/src/components/embed/ui/atoms/icon/Wander.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Wander.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Wander.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Wander.tsx diff --git a/src/components/embed/ui/atoms/icon/Warning.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Warning.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Warning.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Warning.tsx diff --git a/src/components/embed/ui/atoms/icon/WarningCircled.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/WarningCircled.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/WarningCircled.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/WarningCircled.tsx diff --git a/src/components/embed/ui/atoms/icon/Webcam.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/Webcam.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/Webcam.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/Webcam.tsx diff --git a/src/components/embed/ui/atoms/icon/XClose.tsx b/.old-already-moved/src/components/embed/ui/atoms/icon/XClose.tsx similarity index 100% rename from src/components/embed/ui/atoms/icon/XClose.tsx rename to .old-already-moved/src/components/embed/ui/atoms/icon/XClose.tsx diff --git a/src/components/embed/ui/atoms/icon/index.ts b/.old-already-moved/src/components/embed/ui/atoms/icon/index.ts similarity index 100% rename from src/components/embed/ui/atoms/icon/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/icon/index.ts diff --git a/src/components/embed/ui/atoms/index.ts b/.old-already-moved/src/components/embed/ui/atoms/index.ts similarity index 100% rename from src/components/embed/ui/atoms/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/index.ts diff --git a/src/components/embed/ui/atoms/input-button/InputButton.module.scss b/.old-already-moved/src/components/embed/ui/atoms/input-button/InputButton.module.scss similarity index 100% rename from src/components/embed/ui/atoms/input-button/InputButton.module.scss rename to .old-already-moved/src/components/embed/ui/atoms/input-button/InputButton.module.scss diff --git a/src/components/embed/ui/atoms/input-button/InputButton.tsx b/.old-already-moved/src/components/embed/ui/atoms/input-button/InputButton.tsx similarity index 100% rename from src/components/embed/ui/atoms/input-button/InputButton.tsx rename to .old-already-moved/src/components/embed/ui/atoms/input-button/InputButton.tsx diff --git a/src/components/embed/ui/atoms/input/Input.module.css b/.old-already-moved/src/components/embed/ui/atoms/input/Input.module.css similarity index 100% rename from src/components/embed/ui/atoms/input/Input.module.css rename to .old-already-moved/src/components/embed/ui/atoms/input/Input.module.css diff --git a/src/components/embed/ui/atoms/input/Input.tsx b/.old-already-moved/src/components/embed/ui/atoms/input/Input.tsx similarity index 100% rename from src/components/embed/ui/atoms/input/Input.tsx rename to .old-already-moved/src/components/embed/ui/atoms/input/Input.tsx diff --git a/src/components/embed/ui/atoms/input/Input.types.ts b/.old-already-moved/src/components/embed/ui/atoms/input/Input.types.ts similarity index 100% rename from src/components/embed/ui/atoms/input/Input.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/input/Input.types.ts diff --git a/src/components/embed/ui/atoms/input/index.ts b/.old-already-moved/src/components/embed/ui/atoms/input/index.ts similarity index 100% rename from src/components/embed/ui/atoms/input/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/input/index.ts diff --git a/src/components/embed/ui/atoms/link/Link.module.css b/.old-already-moved/src/components/embed/ui/atoms/link/Link.module.css similarity index 100% rename from src/components/embed/ui/atoms/link/Link.module.css rename to .old-already-moved/src/components/embed/ui/atoms/link/Link.module.css diff --git a/src/components/embed/ui/atoms/link/Link.tsx b/.old-already-moved/src/components/embed/ui/atoms/link/Link.tsx similarity index 100% rename from src/components/embed/ui/atoms/link/Link.tsx rename to .old-already-moved/src/components/embed/ui/atoms/link/Link.tsx diff --git a/src/components/embed/ui/atoms/link/Link.types.ts b/.old-already-moved/src/components/embed/ui/atoms/link/Link.types.ts similarity index 100% rename from src/components/embed/ui/atoms/link/Link.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/link/Link.types.ts diff --git a/src/components/embed/ui/atoms/link/index.ts b/.old-already-moved/src/components/embed/ui/atoms/link/index.ts similarity index 100% rename from src/components/embed/ui/atoms/link/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/link/index.ts diff --git a/src/components/embed/ui/atoms/loading/Loading.module.css b/.old-already-moved/src/components/embed/ui/atoms/loading/Loading.module.css similarity index 100% rename from src/components/embed/ui/atoms/loading/Loading.module.css rename to .old-already-moved/src/components/embed/ui/atoms/loading/Loading.module.css diff --git a/src/components/embed/ui/atoms/loading/Loading.tsx b/.old-already-moved/src/components/embed/ui/atoms/loading/Loading.tsx similarity index 100% rename from src/components/embed/ui/atoms/loading/Loading.tsx rename to .old-already-moved/src/components/embed/ui/atoms/loading/Loading.tsx diff --git a/src/components/embed/ui/atoms/loading/Loading.types.ts b/.old-already-moved/src/components/embed/ui/atoms/loading/Loading.types.ts similarity index 100% rename from src/components/embed/ui/atoms/loading/Loading.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/loading/Loading.types.ts diff --git a/src/components/embed/ui/atoms/loading/index.ts b/.old-already-moved/src/components/embed/ui/atoms/loading/index.ts similarity index 100% rename from src/components/embed/ui/atoms/loading/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/loading/index.ts diff --git a/src/components/embed/ui/atoms/password-input/PasswordInput.tsx b/.old-already-moved/src/components/embed/ui/atoms/password-input/PasswordInput.tsx similarity index 100% rename from src/components/embed/ui/atoms/password-input/PasswordInput.tsx rename to .old-already-moved/src/components/embed/ui/atoms/password-input/PasswordInput.tsx diff --git a/src/components/embed/ui/atoms/password-input/index.ts b/.old-already-moved/src/components/embed/ui/atoms/password-input/index.ts similarity index 100% rename from src/components/embed/ui/atoms/password-input/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/password-input/index.ts diff --git a/src/components/embed/ui/atoms/radio/Radio.module.css b/.old-already-moved/src/components/embed/ui/atoms/radio/Radio.module.css similarity index 100% rename from src/components/embed/ui/atoms/radio/Radio.module.css rename to .old-already-moved/src/components/embed/ui/atoms/radio/Radio.module.css diff --git a/src/components/embed/ui/atoms/radio/Radio.tsx b/.old-already-moved/src/components/embed/ui/atoms/radio/Radio.tsx similarity index 100% rename from src/components/embed/ui/atoms/radio/Radio.tsx rename to .old-already-moved/src/components/embed/ui/atoms/radio/Radio.tsx diff --git a/src/components/embed/ui/atoms/radio/Radio.types.ts b/.old-already-moved/src/components/embed/ui/atoms/radio/Radio.types.ts similarity index 100% rename from src/components/embed/ui/atoms/radio/Radio.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/radio/Radio.types.ts diff --git a/src/components/embed/ui/atoms/radio/index.ts b/.old-already-moved/src/components/embed/ui/atoms/radio/index.ts similarity index 100% rename from src/components/embed/ui/atoms/radio/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/radio/index.ts diff --git a/src/components/embed/ui/atoms/row/Row.module.css b/.old-already-moved/src/components/embed/ui/atoms/row/Row.module.css similarity index 100% rename from src/components/embed/ui/atoms/row/Row.module.css rename to .old-already-moved/src/components/embed/ui/atoms/row/Row.module.css diff --git a/src/components/embed/ui/atoms/row/Row.tsx b/.old-already-moved/src/components/embed/ui/atoms/row/Row.tsx similarity index 100% rename from src/components/embed/ui/atoms/row/Row.tsx rename to .old-already-moved/src/components/embed/ui/atoms/row/Row.tsx diff --git a/src/components/embed/ui/atoms/row/Row.types.ts b/.old-already-moved/src/components/embed/ui/atoms/row/Row.types.ts similarity index 100% rename from src/components/embed/ui/atoms/row/Row.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/row/Row.types.ts diff --git a/src/components/embed/ui/atoms/row/index.ts b/.old-already-moved/src/components/embed/ui/atoms/row/index.ts similarity index 100% rename from src/components/embed/ui/atoms/row/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/row/index.ts diff --git a/src/components/embed/ui/atoms/secret-input/SecretInput.module.scss b/.old-already-moved/src/components/embed/ui/atoms/secret-input/SecretInput.module.scss similarity index 100% rename from src/components/embed/ui/atoms/secret-input/SecretInput.module.scss rename to .old-already-moved/src/components/embed/ui/atoms/secret-input/SecretInput.module.scss diff --git a/src/components/embed/ui/atoms/secret-input/SecretInput.tsx b/.old-already-moved/src/components/embed/ui/atoms/secret-input/SecretInput.tsx similarity index 100% rename from src/components/embed/ui/atoms/secret-input/SecretInput.tsx rename to .old-already-moved/src/components/embed/ui/atoms/secret-input/SecretInput.tsx diff --git a/src/components/embed/ui/atoms/seed-input/SeedInput.module.css b/.old-already-moved/src/components/embed/ui/atoms/seed-input/SeedInput.module.css similarity index 100% rename from src/components/embed/ui/atoms/seed-input/SeedInput.module.css rename to .old-already-moved/src/components/embed/ui/atoms/seed-input/SeedInput.module.css diff --git a/src/components/embed/ui/atoms/seed-input/SeedInput.tsx b/.old-already-moved/src/components/embed/ui/atoms/seed-input/SeedInput.tsx similarity index 100% rename from src/components/embed/ui/atoms/seed-input/SeedInput.tsx rename to .old-already-moved/src/components/embed/ui/atoms/seed-input/SeedInput.tsx diff --git a/src/components/embed/ui/atoms/seed-input/SeedInput.types.ts b/.old-already-moved/src/components/embed/ui/atoms/seed-input/SeedInput.types.ts similarity index 100% rename from src/components/embed/ui/atoms/seed-input/SeedInput.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/seed-input/SeedInput.types.ts diff --git a/src/components/embed/ui/atoms/seed-input/index.ts b/.old-already-moved/src/components/embed/ui/atoms/seed-input/index.ts similarity index 100% rename from src/components/embed/ui/atoms/seed-input/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/seed-input/index.ts diff --git a/src/components/embed/ui/atoms/spacer/Spacer.tsx b/.old-already-moved/src/components/embed/ui/atoms/spacer/Spacer.tsx similarity index 100% rename from src/components/embed/ui/atoms/spacer/Spacer.tsx rename to .old-already-moved/src/components/embed/ui/atoms/spacer/Spacer.tsx diff --git a/src/components/embed/ui/atoms/spacer/Spacer.types.ts b/.old-already-moved/src/components/embed/ui/atoms/spacer/Spacer.types.ts similarity index 100% rename from src/components/embed/ui/atoms/spacer/Spacer.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/spacer/Spacer.types.ts diff --git a/src/components/embed/ui/atoms/spacer/index.ts b/.old-already-moved/src/components/embed/ui/atoms/spacer/index.ts similarity index 100% rename from src/components/embed/ui/atoms/spacer/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/spacer/index.ts diff --git a/src/components/embed/ui/atoms/switch/Switch.module.css b/.old-already-moved/src/components/embed/ui/atoms/switch/Switch.module.css similarity index 100% rename from src/components/embed/ui/atoms/switch/Switch.module.css rename to .old-already-moved/src/components/embed/ui/atoms/switch/Switch.module.css diff --git a/src/components/embed/ui/atoms/switch/Switch.tsx b/.old-already-moved/src/components/embed/ui/atoms/switch/Switch.tsx similarity index 100% rename from src/components/embed/ui/atoms/switch/Switch.tsx rename to .old-already-moved/src/components/embed/ui/atoms/switch/Switch.tsx diff --git a/src/components/embed/ui/atoms/switch/Switch.types.ts b/.old-already-moved/src/components/embed/ui/atoms/switch/Switch.types.ts similarity index 100% rename from src/components/embed/ui/atoms/switch/Switch.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/switch/Switch.types.ts diff --git a/src/components/embed/ui/atoms/switch/index.ts b/.old-already-moved/src/components/embed/ui/atoms/switch/index.ts similarity index 100% rename from src/components/embed/ui/atoms/switch/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/switch/index.ts diff --git a/src/components/embed/ui/atoms/text-area/index.ts b/.old-already-moved/src/components/embed/ui/atoms/text-area/index.ts similarity index 100% rename from src/components/embed/ui/atoms/text-area/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/text-area/index.ts diff --git a/src/components/embed/ui/atoms/text-input/TextInput.module.scss b/.old-already-moved/src/components/embed/ui/atoms/text-input/TextInput.module.scss similarity index 100% rename from src/components/embed/ui/atoms/text-input/TextInput.module.scss rename to .old-already-moved/src/components/embed/ui/atoms/text-input/TextInput.module.scss diff --git a/src/components/embed/ui/atoms/text-input/TextInput.tsx b/.old-already-moved/src/components/embed/ui/atoms/text-input/TextInput.tsx similarity index 100% rename from src/components/embed/ui/atoms/text-input/TextInput.tsx rename to .old-already-moved/src/components/embed/ui/atoms/text-input/TextInput.tsx diff --git a/src/components/embed/ui/atoms/text-input/TextInput.types.ts b/.old-already-moved/src/components/embed/ui/atoms/text-input/TextInput.types.ts similarity index 100% rename from src/components/embed/ui/atoms/text-input/TextInput.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/text-input/TextInput.types.ts diff --git a/src/components/embed/ui/atoms/text-input/index.ts b/.old-already-moved/src/components/embed/ui/atoms/text-input/index.ts similarity index 100% rename from src/components/embed/ui/atoms/text-input/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/text-input/index.ts diff --git a/src/components/embed/ui/atoms/text/Text.module.css b/.old-already-moved/src/components/embed/ui/atoms/text/Text.module.css similarity index 100% rename from src/components/embed/ui/atoms/text/Text.module.css rename to .old-already-moved/src/components/embed/ui/atoms/text/Text.module.css diff --git a/src/components/embed/ui/atoms/text/Text.tsx b/.old-already-moved/src/components/embed/ui/atoms/text/Text.tsx similarity index 100% rename from src/components/embed/ui/atoms/text/Text.tsx rename to .old-already-moved/src/components/embed/ui/atoms/text/Text.tsx diff --git a/src/components/embed/ui/atoms/text/Text.types.ts b/.old-already-moved/src/components/embed/ui/atoms/text/Text.types.ts similarity index 100% rename from src/components/embed/ui/atoms/text/Text.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/text/Text.types.ts diff --git a/src/components/embed/ui/atoms/text/index.ts b/.old-already-moved/src/components/embed/ui/atoms/text/index.ts similarity index 100% rename from src/components/embed/ui/atoms/text/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/text/index.ts diff --git a/src/components/embed/ui/atoms/theme-setup/ThemeSetup.tsx b/.old-already-moved/src/components/embed/ui/atoms/theme-setup/ThemeSetup.tsx similarity index 100% rename from src/components/embed/ui/atoms/theme-setup/ThemeSetup.tsx rename to .old-already-moved/src/components/embed/ui/atoms/theme-setup/ThemeSetup.tsx diff --git a/src/components/embed/ui/atoms/upload/Upload.module.css b/.old-already-moved/src/components/embed/ui/atoms/upload/Upload.module.css similarity index 100% rename from src/components/embed/ui/atoms/upload/Upload.module.css rename to .old-already-moved/src/components/embed/ui/atoms/upload/Upload.module.css diff --git a/src/components/embed/ui/atoms/upload/Upload.tsx b/.old-already-moved/src/components/embed/ui/atoms/upload/Upload.tsx similarity index 100% rename from src/components/embed/ui/atoms/upload/Upload.tsx rename to .old-already-moved/src/components/embed/ui/atoms/upload/Upload.tsx diff --git a/src/components/embed/ui/atoms/upload/Upload.types.ts b/.old-already-moved/src/components/embed/ui/atoms/upload/Upload.types.ts similarity index 100% rename from src/components/embed/ui/atoms/upload/Upload.types.ts rename to .old-already-moved/src/components/embed/ui/atoms/upload/Upload.types.ts diff --git a/src/components/embed/ui/atoms/upload/index.ts b/.old-already-moved/src/components/embed/ui/atoms/upload/index.ts similarity index 100% rename from src/components/embed/ui/atoms/upload/index.ts rename to .old-already-moved/src/components/embed/ui/atoms/upload/index.ts diff --git a/src/components/embed/ui/index.ts b/.old-already-moved/src/components/embed/ui/index.ts similarity index 100% rename from src/components/embed/ui/index.ts rename to .old-already-moved/src/components/embed/ui/index.ts diff --git a/src/components/embed/ui/molecules/card/Card.module.css b/.old-already-moved/src/components/embed/ui/molecules/card/Card.module.css similarity index 100% rename from src/components/embed/ui/molecules/card/Card.module.css rename to .old-already-moved/src/components/embed/ui/molecules/card/Card.module.css diff --git a/src/components/embed/ui/molecules/card/Card.tsx b/.old-already-moved/src/components/embed/ui/molecules/card/Card.tsx similarity index 96% rename from src/components/embed/ui/molecules/card/Card.tsx rename to .old-already-moved/src/components/embed/ui/molecules/card/Card.tsx index 26dd4e796..fb2af5292 100644 --- a/src/components/embed/ui/molecules/card/Card.tsx +++ b/.old-already-moved/src/components/embed/ui/molecules/card/Card.tsx @@ -2,7 +2,7 @@ import React from "react"; import type { CardBaseProps } from "./Card.types"; import { Box, MinimizeIcon, ChevronLeft, Button } from "../../atoms"; import { Header } from "../header"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { useLocation } from "~wallets/router/router.utils"; import { Loading } from "@arconnect/components"; import { NoUnpartitionedStateBanner } from "~components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner"; diff --git a/src/components/embed/ui/molecules/card/Card.types.ts b/.old-already-moved/src/components/embed/ui/molecules/card/Card.types.ts similarity index 100% rename from src/components/embed/ui/molecules/card/Card.types.ts rename to .old-already-moved/src/components/embed/ui/molecules/card/Card.types.ts diff --git a/src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.module.scss b/.old-already-moved/src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.module.scss similarity index 100% rename from src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.module.scss rename to .old-already-moved/src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.module.scss diff --git a/src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.tsx b/.old-already-moved/src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.tsx similarity index 100% rename from src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.tsx rename to .old-already-moved/src/components/embed/ui/molecules/card/auth-request-card/AuthRequestCard.tsx diff --git a/src/components/embed/ui/molecules/card/default-card/DefaultCard.module.scss b/.old-already-moved/src/components/embed/ui/molecules/card/default-card/DefaultCard.module.scss similarity index 100% rename from src/components/embed/ui/molecules/card/default-card/DefaultCard.module.scss rename to .old-already-moved/src/components/embed/ui/molecules/card/default-card/DefaultCard.module.scss diff --git a/src/components/embed/ui/molecules/card/default-card/DefaultCard.tsx b/.old-already-moved/src/components/embed/ui/molecules/card/default-card/DefaultCard.tsx similarity index 100% rename from src/components/embed/ui/molecules/card/default-card/DefaultCard.tsx rename to .old-already-moved/src/components/embed/ui/molecules/card/default-card/DefaultCard.tsx diff --git a/src/components/embed/ui/molecules/card/index.ts b/.old-already-moved/src/components/embed/ui/molecules/card/index.ts similarity index 100% rename from src/components/embed/ui/molecules/card/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/card/index.ts diff --git a/src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.module.scss b/.old-already-moved/src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.module.scss similarity index 100% rename from src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.module.scss rename to .old-already-moved/src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.module.scss diff --git a/src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.tsx b/.old-already-moved/src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.tsx similarity index 100% rename from src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.tsx rename to .old-already-moved/src/components/embed/ui/molecules/card/onboarding-card/OnboardingCard.tsx diff --git a/src/components/embed/ui/molecules/dropdown/Dropdown.module.css b/.old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown.module.css similarity index 100% rename from src/components/embed/ui/molecules/dropdown/Dropdown.module.css rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown.module.css diff --git a/src/components/embed/ui/molecules/dropdown/Dropdown.tsx b/.old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown.tsx similarity index 100% rename from src/components/embed/ui/molecules/dropdown/Dropdown.tsx rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown.tsx diff --git a/src/components/embed/ui/molecules/dropdown/Dropdown.types.ts b/.old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown.types.ts similarity index 100% rename from src/components/embed/ui/molecules/dropdown/Dropdown.types.ts rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown.types.ts diff --git a/src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.css b/.old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.css similarity index 100% rename from src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.css rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.css diff --git a/src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.tsx b/.old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.tsx similarity index 100% rename from src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.tsx rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/Dropdown/Dropdown.tsx diff --git a/src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.css b/.old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.css similarity index 100% rename from src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.css rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.css diff --git a/src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.tsx b/.old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.tsx similarity index 100% rename from src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.tsx rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownButton/DropdownButton.tsx diff --git a/src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.css b/.old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.css similarity index 100% rename from src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.css rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.css diff --git a/src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.tsx b/.old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.tsx similarity index 100% rename from src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.tsx rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownContent/DropdownContent.tsx diff --git a/src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.css b/.old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.css similarity index 100% rename from src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.css rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.css diff --git a/src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.tsx b/.old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.tsx similarity index 100% rename from src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.tsx rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/DropdownItem/DropdownItem.tsx diff --git a/src/components/embed/ui/molecules/dropdown/index.ts b/.old-already-moved/src/components/embed/ui/molecules/dropdown/index.ts similarity index 100% rename from src/components/embed/ui/molecules/dropdown/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/dropdown/index.ts diff --git a/src/components/embed/ui/molecules/header/Header.module.css b/.old-already-moved/src/components/embed/ui/molecules/header/Header.module.css similarity index 100% rename from src/components/embed/ui/molecules/header/Header.module.css rename to .old-already-moved/src/components/embed/ui/molecules/header/Header.module.css diff --git a/src/components/embed/ui/molecules/header/Header.tsx b/.old-already-moved/src/components/embed/ui/molecules/header/Header.tsx similarity index 100% rename from src/components/embed/ui/molecules/header/Header.tsx rename to .old-already-moved/src/components/embed/ui/molecules/header/Header.tsx diff --git a/src/components/embed/ui/molecules/header/Header.types.ts b/.old-already-moved/src/components/embed/ui/molecules/header/Header.types.ts similarity index 100% rename from src/components/embed/ui/molecules/header/Header.types.ts rename to .old-already-moved/src/components/embed/ui/molecules/header/Header.types.ts diff --git a/src/components/embed/ui/molecules/header/index.ts b/.old-already-moved/src/components/embed/ui/molecules/header/index.ts similarity index 100% rename from src/components/embed/ui/molecules/header/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/header/index.ts diff --git a/src/components/embed/ui/molecules/index.ts b/.old-already-moved/src/components/embed/ui/molecules/index.ts similarity index 100% rename from src/components/embed/ui/molecules/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/index.ts diff --git a/src/components/embed/ui/molecules/popover/Popover.module.css b/.old-already-moved/src/components/embed/ui/molecules/popover/Popover.module.css similarity index 100% rename from src/components/embed/ui/molecules/popover/Popover.module.css rename to .old-already-moved/src/components/embed/ui/molecules/popover/Popover.module.css diff --git a/src/components/embed/ui/molecules/popover/Popover.tsx b/.old-already-moved/src/components/embed/ui/molecules/popover/Popover.tsx similarity index 100% rename from src/components/embed/ui/molecules/popover/Popover.tsx rename to .old-already-moved/src/components/embed/ui/molecules/popover/Popover.tsx diff --git a/src/components/embed/ui/molecules/popover/Popover.types.ts b/.old-already-moved/src/components/embed/ui/molecules/popover/Popover.types.ts similarity index 100% rename from src/components/embed/ui/molecules/popover/Popover.types.ts rename to .old-already-moved/src/components/embed/ui/molecules/popover/Popover.types.ts diff --git a/src/components/embed/ui/molecules/popover/index.ts b/.old-already-moved/src/components/embed/ui/molecules/popover/index.ts similarity index 100% rename from src/components/embed/ui/molecules/popover/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/popover/index.ts diff --git a/src/components/embed/ui/molecules/snackbar/Snackbar.module.scss b/.old-already-moved/src/components/embed/ui/molecules/snackbar/Snackbar.module.scss similarity index 100% rename from src/components/embed/ui/molecules/snackbar/Snackbar.module.scss rename to .old-already-moved/src/components/embed/ui/molecules/snackbar/Snackbar.module.scss diff --git a/src/components/embed/ui/molecules/snackbar/Snackbar.tsx b/.old-already-moved/src/components/embed/ui/molecules/snackbar/Snackbar.tsx similarity index 100% rename from src/components/embed/ui/molecules/snackbar/Snackbar.tsx rename to .old-already-moved/src/components/embed/ui/molecules/snackbar/Snackbar.tsx diff --git a/src/components/embed/ui/molecules/tabBar/TabBar.module.scss b/.old-already-moved/src/components/embed/ui/molecules/tabBar/TabBar.module.scss similarity index 100% rename from src/components/embed/ui/molecules/tabBar/TabBar.module.scss rename to .old-already-moved/src/components/embed/ui/molecules/tabBar/TabBar.module.scss diff --git a/src/components/embed/ui/molecules/tabBar/TabBar.tsx b/.old-already-moved/src/components/embed/ui/molecules/tabBar/TabBar.tsx similarity index 100% rename from src/components/embed/ui/molecules/tabBar/TabBar.tsx rename to .old-already-moved/src/components/embed/ui/molecules/tabBar/TabBar.tsx diff --git a/src/components/embed/ui/molecules/tabBar/TabBar.types.ts b/.old-already-moved/src/components/embed/ui/molecules/tabBar/TabBar.types.ts similarity index 100% rename from src/components/embed/ui/molecules/tabBar/TabBar.types.ts rename to .old-already-moved/src/components/embed/ui/molecules/tabBar/TabBar.types.ts diff --git a/src/components/embed/ui/molecules/tabBar/index.ts b/.old-already-moved/src/components/embed/ui/molecules/tabBar/index.ts similarity index 100% rename from src/components/embed/ui/molecules/tabBar/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/tabBar/index.ts diff --git a/src/components/embed/ui/molecules/theme-toggle/ThemeToggle.module.css b/.old-already-moved/src/components/embed/ui/molecules/theme-toggle/ThemeToggle.module.css similarity index 100% rename from src/components/embed/ui/molecules/theme-toggle/ThemeToggle.module.css rename to .old-already-moved/src/components/embed/ui/molecules/theme-toggle/ThemeToggle.module.css diff --git a/src/components/embed/ui/molecules/theme-toggle/ThemeToggle.tsx b/.old-already-moved/src/components/embed/ui/molecules/theme-toggle/ThemeToggle.tsx similarity index 100% rename from src/components/embed/ui/molecules/theme-toggle/ThemeToggle.tsx rename to .old-already-moved/src/components/embed/ui/molecules/theme-toggle/ThemeToggle.tsx diff --git a/src/components/embed/ui/molecules/theme-toggle/index.ts b/.old-already-moved/src/components/embed/ui/molecules/theme-toggle/index.ts similarity index 100% rename from src/components/embed/ui/molecules/theme-toggle/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/theme-toggle/index.ts diff --git a/src/components/embed/ui/molecules/tooltip/index.ts b/.old-already-moved/src/components/embed/ui/molecules/tooltip/index.ts similarity index 100% rename from src/components/embed/ui/molecules/tooltip/index.ts rename to .old-already-moved/src/components/embed/ui/molecules/tooltip/index.ts diff --git a/src/components/embed/ui/organisms/account-selector/AccountSelector.tsx b/.old-already-moved/src/components/embed/ui/organisms/account-selector/AccountSelector.tsx similarity index 100% rename from src/components/embed/ui/organisms/account-selector/AccountSelector.tsx rename to .old-already-moved/src/components/embed/ui/organisms/account-selector/AccountSelector.tsx diff --git a/src/components/embed/ui/organisms/index.ts b/.old-already-moved/src/components/embed/ui/organisms/index.ts similarity index 100% rename from src/components/embed/ui/organisms/index.ts rename to .old-already-moved/src/components/embed/ui/organisms/index.ts diff --git a/src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.module.css b/.old-already-moved/src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.module.css similarity index 100% rename from src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.module.css rename to .old-already-moved/src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.module.css diff --git a/src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.tsx b/.old-already-moved/src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.tsx similarity index 100% rename from src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.tsx rename to .old-already-moved/src/components/embed/ui/organisms/qr-loop-scanner/QRLoopScanner.tsx diff --git a/src/components/embed/ui/pages/index.ts b/.old-already-moved/src/components/embed/ui/pages/index.ts similarity index 100% rename from src/components/embed/ui/pages/index.ts rename to .old-already-moved/src/components/embed/ui/pages/index.ts diff --git a/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.module.scss b/.old-already-moved/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.module.scss similarity index 100% rename from src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.module.scss rename to .old-already-moved/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.module.scss diff --git a/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.tsx b/.old-already-moved/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.tsx similarity index 94% rename from src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.tsx rename to .old-already-moved/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.tsx index 907a7e695..0f73c2eef 100644 --- a/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.tsx +++ b/.old-already-moved/src/components/embed/ui/templates/no-unpartitioned-state-banner/NoUnpartitionedStateBanner.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { Link } from "~wallets/router/components/link/Link"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; import { AlertTriangle } from "@untitled-ui/icons-react"; diff --git a/src/components/embed/ui/templates/wander-footer/WanderFooter.module.scss b/.old-already-moved/src/components/embed/ui/templates/wander-footer/WanderFooter.module.scss similarity index 100% rename from src/components/embed/ui/templates/wander-footer/WanderFooter.module.scss rename to .old-already-moved/src/components/embed/ui/templates/wander-footer/WanderFooter.module.scss diff --git a/src/components/embed/ui/templates/wander-footer/WanderFooter.tsx b/.old-already-moved/src/components/embed/ui/templates/wander-footer/WanderFooter.tsx similarity index 100% rename from src/components/embed/ui/templates/wander-footer/WanderFooter.tsx rename to .old-already-moved/src/components/embed/ui/templates/wander-footer/WanderFooter.tsx diff --git a/src/components/embed/utils/embedded/theme-bridge.ts b/.old-already-moved/src/components/embed/utils/embedded/theme-bridge.ts similarity index 100% rename from src/components/embed/utils/embedded/theme-bridge.ts rename to .old-already-moved/src/components/embed/utils/embedded/theme-bridge.ts diff --git a/src/components/embed/utils/index.ts b/.old-already-moved/src/components/embed/utils/index.ts similarity index 100% rename from src/components/embed/utils/index.ts rename to .old-already-moved/src/components/embed/utils/index.ts diff --git a/src/components/hardware/AnimatedQRPlayer.tsx b/.old-already-moved/src/components/hardware/AnimatedQRPlayer.tsx similarity index 100% rename from src/components/hardware/AnimatedQRPlayer.tsx rename to .old-already-moved/src/components/hardware/AnimatedQRPlayer.tsx diff --git a/src/components/hardware/AnimatedQRScanner.tsx b/.old-already-moved/src/components/hardware/AnimatedQRScanner.tsx similarity index 100% rename from src/components/hardware/AnimatedQRScanner.tsx rename to .old-already-moved/src/components/hardware/AnimatedQRScanner.tsx diff --git a/src/components/hardware/HardwareWalletIcon.tsx b/.old-already-moved/src/components/hardware/HardwareWalletIcon.tsx similarity index 100% rename from src/components/hardware/HardwareWalletIcon.tsx rename to .old-already-moved/src/components/hardware/HardwareWalletIcon.tsx diff --git a/src/components/hardware/KeystoneButton.tsx b/.old-already-moved/src/components/hardware/KeystoneButton.tsx similarity index 100% rename from src/components/hardware/KeystoneButton.tsx rename to .old-already-moved/src/components/hardware/KeystoneButton.tsx diff --git a/src/components/help/HelpListItems.tsx b/.old-already-moved/src/components/help/HelpListItems.tsx similarity index 100% rename from src/components/help/HelpListItems.tsx rename to .old-already-moved/src/components/help/HelpListItems.tsx diff --git a/src/components/icons/CompassIcon.tsx b/.old-already-moved/src/components/icons/CompassIcon.tsx similarity index 100% rename from src/components/icons/CompassIcon.tsx rename to .old-already-moved/src/components/icons/CompassIcon.tsx diff --git a/src/components/icons/GithubIcon.tsx b/.old-already-moved/src/components/icons/GithubIcon.tsx similarity index 100% rename from src/components/icons/GithubIcon.tsx rename to .old-already-moved/src/components/icons/GithubIcon.tsx diff --git a/src/components/icons/NetworkErrorIcon.tsx b/.old-already-moved/src/components/icons/NetworkErrorIcon.tsx similarity index 100% rename from src/components/icons/NetworkErrorIcon.tsx rename to .old-already-moved/src/components/icons/NetworkErrorIcon.tsx diff --git a/src/components/icons/ReceiveIcon.tsx b/.old-already-moved/src/components/icons/ReceiveIcon.tsx similarity index 100% rename from src/components/icons/ReceiveIcon.tsx rename to .old-already-moved/src/components/icons/ReceiveIcon.tsx diff --git a/src/components/icons/TriangleIcon.tsx b/.old-already-moved/src/components/icons/TriangleIcon.tsx similarity index 100% rename from src/components/icons/TriangleIcon.tsx rename to .old-already-moved/src/components/icons/TriangleIcon.tsx diff --git a/src/components/icons/TwitterIcon.tsx b/.old-already-moved/src/components/icons/TwitterIcon.tsx similarity index 100% rename from src/components/icons/TwitterIcon.tsx rename to .old-already-moved/src/components/icons/TwitterIcon.tsx diff --git a/src/components/icons/VerifiedIcon.tsx b/.old-already-moved/src/components/icons/VerifiedIcon.tsx similarity index 100% rename from src/components/icons/VerifiedIcon.tsx rename to .old-already-moved/src/components/icons/VerifiedIcon.tsx diff --git a/src/components/icons/WarningIcon.tsx b/.old-already-moved/src/components/icons/WarningIcon.tsx similarity index 100% rename from src/components/icons/WarningIcon.tsx rename to .old-already-moved/src/components/icons/WarningIcon.tsx diff --git a/src/components/modals/Components.tsx b/.old-already-moved/src/components/modals/Components.tsx similarity index 100% rename from src/components/modals/Components.tsx rename to .old-already-moved/src/components/modals/Components.tsx diff --git a/src/components/modals/QRModal.tsx b/.old-already-moved/src/components/modals/QRModal.tsx similarity index 100% rename from src/components/modals/QRModal.tsx rename to .old-already-moved/src/components/modals/QRModal.tsx diff --git a/src/components/modals/WalletKeySizeErrorModal.tsx b/.old-already-moved/src/components/modals/WalletKeySizeErrorModal.tsx similarity index 100% rename from src/components/modals/WalletKeySizeErrorModal.tsx rename to .old-already-moved/src/components/modals/WalletKeySizeErrorModal.tsx diff --git a/src/components/page/common/Fallback/fallback.view.tsx b/.old-already-moved/src/components/page/common/Fallback/fallback.view.tsx similarity index 100% rename from src/components/page/common/Fallback/fallback.view.tsx rename to .old-already-moved/src/components/page/common/Fallback/fallback.view.tsx diff --git a/src/components/page/common/loading/loading.view.tsx b/.old-already-moved/src/components/page/common/loading/loading.view.tsx similarity index 100% rename from src/components/page/common/loading/loading.view.tsx rename to .old-already-moved/src/components/page/common/loading/loading.view.tsx diff --git a/src/components/page/page.component.tsx b/.old-already-moved/src/components/page/page.component.tsx similarity index 100% rename from src/components/page/page.component.tsx rename to .old-already-moved/src/components/page/page.component.tsx diff --git a/src/components/page/page.utils.tsx b/.old-already-moved/src/components/page/page.utils.tsx similarity index 100% rename from src/components/page/page.utils.tsx rename to .old-already-moved/src/components/page/page.utils.tsx diff --git a/src/components/popup/ActionButtons.tsx b/.old-already-moved/src/components/popup/ActionButtons.tsx similarity index 100% rename from src/components/popup/ActionButtons.tsx rename to .old-already-moved/src/components/popup/ActionButtons.tsx diff --git a/src/components/popup/AddressScanner.tsx b/.old-already-moved/src/components/popup/AddressScanner.tsx similarity index 100% rename from src/components/popup/AddressScanner.tsx rename to .old-already-moved/src/components/popup/AddressScanner.tsx diff --git a/src/components/popup/Article.tsx b/.old-already-moved/src/components/popup/Article.tsx similarity index 100% rename from src/components/popup/Article.tsx rename to .old-already-moved/src/components/popup/Article.tsx diff --git a/src/components/popup/Collectible.tsx b/.old-already-moved/src/components/popup/Collectible.tsx similarity index 100% rename from src/components/popup/Collectible.tsx rename to .old-already-moved/src/components/popup/Collectible.tsx diff --git a/src/components/popup/FeedFilterPopup.tsx b/.old-already-moved/src/components/popup/FeedFilterPopup.tsx similarity index 100% rename from src/components/popup/FeedFilterPopup.tsx rename to .old-already-moved/src/components/popup/FeedFilterPopup.tsx diff --git a/src/components/popup/Graph.tsx b/.old-already-moved/src/components/popup/Graph.tsx similarity index 100% rename from src/components/popup/Graph.tsx rename to .old-already-moved/src/components/popup/Graph.tsx diff --git a/src/components/popup/Head.tsx b/.old-already-moved/src/components/popup/Head.tsx similarity index 100% rename from src/components/popup/Head.tsx rename to .old-already-moved/src/components/popup/Head.tsx diff --git a/src/components/popup/HeadV2.tsx b/.old-already-moved/src/components/popup/HeadV2.tsx similarity index 98% rename from src/components/popup/HeadV2.tsx rename to .old-already-moved/src/components/popup/HeadV2.tsx index b123f76dd..ca960731d 100644 --- a/src/components/popup/HeadV2.tsx +++ b/.old-already-moved/src/components/popup/HeadV2.tsx @@ -15,7 +15,7 @@ import Squircle from "~components/Squircle"; import { useLocation } from "~wallets/router/router.utils"; import { ArrowNarrowLeft } from "@untitled-ui/icons-react"; import { MinimizeIcon } from "@iconicicons/react"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { useNameServiceProfile } from "~lib/nameservice"; import { concatGatewayURL } from "~gateways/utils"; import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; diff --git a/src/components/popup/Navigation.tsx b/.old-already-moved/src/components/popup/Navigation.tsx similarity index 99% rename from src/components/popup/Navigation.tsx rename to .old-already-moved/src/components/popup/Navigation.tsx index 77690c4f3..817b808b7 100644 --- a/src/components/popup/Navigation.tsx +++ b/.old-already-moved/src/components/popup/Navigation.tsx @@ -2,7 +2,7 @@ import { CurrencyDollarCircle, Grid01, Home05 } from "@untitled-ui/icons-react"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { useLocation } from "~wallets/router/router.utils"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; import { useStorage, ExtensionStorage } from "~utils/storage"; import type { WanderRoutePath } from "~wallets/router/router.types"; import HedgehogHeadIcon from "url:/assets/agents/images/hedgehog-head.svg"; diff --git a/src/components/popup/Title.tsx b/.old-already-moved/src/components/popup/Title.tsx similarity index 100% rename from src/components/popup/Title.tsx rename to .old-already-moved/src/components/popup/Title.tsx diff --git a/src/components/popup/Token.tsx b/.old-already-moved/src/components/popup/Token.tsx similarity index 100% rename from src/components/popup/Token.tsx rename to .old-already-moved/src/components/popup/Token.tsx diff --git a/src/components/popup/TokenLogo.tsx b/.old-already-moved/src/components/popup/TokenLogo.tsx similarity index 100% rename from src/components/popup/TokenLogo.tsx rename to .old-already-moved/src/components/popup/TokenLogo.tsx diff --git a/src/components/popup/WalletHeader.tsx b/.old-already-moved/src/components/popup/WalletHeader.tsx similarity index 100% rename from src/components/popup/WalletHeader.tsx rename to .old-already-moved/src/components/popup/WalletHeader.tsx diff --git a/src/components/popup/WalletMenu.tsx b/.old-already-moved/src/components/popup/WalletMenu.tsx similarity index 100% rename from src/components/popup/WalletMenu.tsx rename to .old-already-moved/src/components/popup/WalletMenu.tsx diff --git a/src/components/popup/WalletSwitcher.tsx b/.old-already-moved/src/components/popup/WalletSwitcher.tsx similarity index 100% rename from src/components/popup/WalletSwitcher.tsx rename to .old-already-moved/src/components/popup/WalletSwitcher.tsx diff --git a/src/components/popup/asset/Loading.tsx b/.old-already-moved/src/components/popup/asset/Loading.tsx similarity index 100% rename from src/components/popup/asset/Loading.tsx rename to .old-already-moved/src/components/popup/asset/Loading.tsx diff --git a/src/components/popup/asset/PeriodPicker.tsx b/.old-already-moved/src/components/popup/asset/PeriodPicker.tsx similarity index 100% rename from src/components/popup/asset/PeriodPicker.tsx rename to .old-already-moved/src/components/popup/asset/PeriodPicker.tsx diff --git a/src/components/popup/asset/Thumbnail.tsx b/.old-already-moved/src/components/popup/asset/Thumbnail.tsx similarity index 100% rename from src/components/popup/asset/Thumbnail.tsx rename to .old-already-moved/src/components/popup/asset/Thumbnail.tsx diff --git a/src/components/popup/chart/LineChart.tsx b/.old-already-moved/src/components/popup/chart/LineChart.tsx similarity index 100% rename from src/components/popup/chart/LineChart.tsx rename to .old-already-moved/src/components/popup/chart/LineChart.tsx diff --git a/src/components/popup/chart/PieChart.tsx b/.old-already-moved/src/components/popup/chart/PieChart.tsx similarity index 100% rename from src/components/popup/chart/PieChart.tsx rename to .old-already-moved/src/components/popup/chart/PieChart.tsx diff --git a/src/components/popup/earn/AddTokenPopup.tsx b/.old-already-moved/src/components/popup/earn/AddTokenPopup.tsx similarity index 100% rename from src/components/popup/earn/AddTokenPopup.tsx rename to .old-already-moved/src/components/popup/earn/AddTokenPopup.tsx diff --git a/src/components/popup/earn/EarnAOTokensDetectedNotice.tsx b/.old-already-moved/src/components/popup/earn/EarnAOTokensDetectedNotice.tsx similarity index 100% rename from src/components/popup/earn/EarnAOTokensDetectedNotice.tsx rename to .old-already-moved/src/components/popup/earn/EarnAOTokensDetectedNotice.tsx diff --git a/src/components/popup/earn/EarnDelegationNotice.tsx b/.old-already-moved/src/components/popup/earn/EarnDelegationNotice.tsx similarity index 100% rename from src/components/popup/earn/EarnDelegationNotice.tsx rename to .old-already-moved/src/components/popup/earn/EarnDelegationNotice.tsx diff --git a/src/components/popup/earn/EarnPopup.tsx b/.old-already-moved/src/components/popup/earn/EarnPopup.tsx similarity index 100% rename from src/components/popup/earn/EarnPopup.tsx rename to .old-already-moved/src/components/popup/earn/EarnPopup.tsx diff --git a/src/components/popup/earn/EarnTabs.tsx b/.old-already-moved/src/components/popup/earn/EarnTabs.tsx similarity index 100% rename from src/components/popup/earn/EarnTabs.tsx rename to .old-already-moved/src/components/popup/earn/EarnTabs.tsx diff --git a/src/components/popup/home/ActivityNotificationsNotice.tsx b/.old-already-moved/src/components/popup/home/ActivityNotificationsNotice.tsx similarity index 100% rename from src/components/popup/home/ActivityNotificationsNotice.tsx rename to .old-already-moved/src/components/popup/home/ActivityNotificationsNotice.tsx diff --git a/src/components/popup/home/AnalyticsConsent.tsx b/.old-already-moved/src/components/popup/home/AnalyticsConsent.tsx similarity index 100% rename from src/components/popup/home/AnalyticsConsent.tsx rename to .old-already-moved/src/components/popup/home/AnalyticsConsent.tsx diff --git a/src/components/popup/home/AoBanner.tsx b/.old-already-moved/src/components/popup/home/AoBanner.tsx similarity index 100% rename from src/components/popup/home/AoBanner.tsx rename to .old-already-moved/src/components/popup/home/AoBanner.tsx diff --git a/src/components/popup/home/AppIcon.tsx b/.old-already-moved/src/components/popup/home/AppIcon.tsx similarity index 100% rename from src/components/popup/home/AppIcon.tsx rename to .old-already-moved/src/components/popup/home/AppIcon.tsx diff --git a/src/components/popup/home/AstroBetaAccessAnnouncementPopup.tsx b/.old-already-moved/src/components/popup/home/AstroBetaAccessAnnouncementPopup.tsx similarity index 100% rename from src/components/popup/home/AstroBetaAccessAnnouncementPopup.tsx rename to .old-already-moved/src/components/popup/home/AstroBetaAccessAnnouncementPopup.tsx diff --git a/src/components/popup/home/Balance.tsx b/.old-already-moved/src/components/popup/home/Balance.tsx similarity index 97% rename from src/components/popup/home/Balance.tsx rename to .old-already-moved/src/components/popup/home/Balance.tsx index 6ce2d7583..79adedb6f 100644 --- a/src/components/popup/home/Balance.tsx +++ b/.old-already-moved/src/components/popup/home/Balance.tsx @@ -10,8 +10,8 @@ import { Text } from "@arconnect/components-rebrand"; import BigNumber from "bignumber.js"; import { useTotalFiatBalance } from "~tokens/hooks"; import NumberFlow from "@number-flow/react"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; import { useAsyncEffect } from "~utils/react/useAsyncEffect"; import TriangleIcon from "~components/icons/TriangleIcon"; diff --git a/src/components/popup/home/Collectibles.tsx b/.old-already-moved/src/components/popup/home/Collectibles.tsx similarity index 100% rename from src/components/popup/home/Collectibles.tsx rename to .old-already-moved/src/components/popup/home/Collectibles.tsx diff --git a/src/components/popup/home/KeystoneAnnouncementPopup.tsx b/.old-already-moved/src/components/popup/home/KeystoneAnnouncementPopup.tsx similarity index 100% rename from src/components/popup/home/KeystoneAnnouncementPopup.tsx rename to .old-already-moved/src/components/popup/home/KeystoneAnnouncementPopup.tsx diff --git a/src/components/popup/home/ManageAssets.tsx b/.old-already-moved/src/components/popup/home/ManageAssets.tsx similarity index 100% rename from src/components/popup/home/ManageAssets.tsx rename to .old-already-moved/src/components/popup/home/ManageAssets.tsx diff --git a/src/components/popup/home/NoBalance.tsx b/.old-already-moved/src/components/popup/home/NoBalance.tsx similarity index 100% rename from src/components/popup/home/NoBalance.tsx rename to .old-already-moved/src/components/popup/home/NoBalance.tsx diff --git a/src/components/popup/home/StargridAccessAnnouncementPopup.tsx b/.old-already-moved/src/components/popup/home/StargridAccessAnnouncementPopup.tsx similarity index 100% rename from src/components/popup/home/StargridAccessAnnouncementPopup.tsx rename to .old-already-moved/src/components/popup/home/StargridAccessAnnouncementPopup.tsx diff --git a/src/components/popup/home/Tabs.tsx b/.old-already-moved/src/components/popup/home/Tabs.tsx similarity index 100% rename from src/components/popup/home/Tabs.tsx rename to .old-already-moved/src/components/popup/home/Tabs.tsx diff --git a/src/components/popup/home/Tokens.tsx b/.old-already-moved/src/components/popup/home/Tokens.tsx similarity index 100% rename from src/components/popup/home/Tokens.tsx rename to .old-already-moved/src/components/popup/home/Tokens.tsx diff --git a/src/components/popup/home/Transactions.tsx b/.old-already-moved/src/components/popup/home/Transactions.tsx similarity index 99% rename from src/components/popup/home/Transactions.tsx rename to .old-already-moved/src/components/popup/home/Transactions.tsx index e547a597b..63dfb1309 100644 --- a/src/components/popup/home/Transactions.tsx +++ b/.old-already-moved/src/components/popup/home/Transactions.tsx @@ -12,7 +12,7 @@ import { AR_RECEIVER_QUERY, AR_SENT_QUERY, PRINT_ARWEAVE_QUERY, -} from "~notifications/utils"; +} from "~_notifications/utils"; import { printTxWorkingGateways, txHistoryGateways } from "~gateways/gateway"; import { ViewAll } from "../Title"; import { diff --git a/src/components/popup/home/WalletActions.tsx b/.old-already-moved/src/components/popup/home/WalletActions.tsx similarity index 100% rename from src/components/popup/home/WalletActions.tsx rename to .old-already-moved/src/components/popup/home/WalletActions.tsx diff --git a/src/components/popup/home/WandAnnouncementPopup.tsx b/.old-already-moved/src/components/popup/home/WandAnnouncementPopup.tsx similarity index 100% rename from src/components/popup/home/WandAnnouncementPopup.tsx rename to .old-already-moved/src/components/popup/home/WandAnnouncementPopup.tsx diff --git a/src/components/popup/list/SettingListItem.tsx b/.old-already-moved/src/components/popup/list/SettingListItem.tsx similarity index 100% rename from src/components/popup/list/SettingListItem.tsx rename to .old-already-moved/src/components/popup/list/SettingListItem.tsx diff --git a/src/components/popup/list/SubscriptionListItem.tsx b/.old-already-moved/src/components/popup/list/SubscriptionListItem.tsx similarity index 100% rename from src/components/popup/list/SubscriptionListItem.tsx rename to .old-already-moved/src/components/popup/list/SubscriptionListItem.tsx diff --git a/src/components/popup/list/TokenListItem.tsx b/.old-already-moved/src/components/popup/list/TokenListItem.tsx similarity index 100% rename from src/components/popup/list/TokenListItem.tsx rename to .old-already-moved/src/components/popup/list/TokenListItem.tsx diff --git a/src/components/popup/settings/BackupSeedphraseWarning.tsx b/.old-already-moved/src/components/popup/settings/BackupSeedphraseWarning.tsx similarity index 100% rename from src/components/popup/settings/BackupSeedphraseWarning.tsx rename to .old-already-moved/src/components/popup/settings/BackupSeedphraseWarning.tsx diff --git a/src/components/popup/settings/RevealRecoveryPhraseModal.tsx b/.old-already-moved/src/components/popup/settings/RevealRecoveryPhraseModal.tsx similarity index 100% rename from src/components/popup/settings/RevealRecoveryPhraseModal.tsx rename to .old-already-moved/src/components/popup/settings/RevealRecoveryPhraseModal.tsx diff --git a/src/components/popup/tier/CustomizableStars.tsx b/.old-already-moved/src/components/popup/tier/CustomizableStars.tsx similarity index 100% rename from src/components/popup/tier/CustomizableStars.tsx rename to .old-already-moved/src/components/popup/tier/CustomizableStars.tsx diff --git a/src/components/popup/tier/GetTokensButton.tsx b/.old-already-moved/src/components/popup/tier/GetTokensButton.tsx similarity index 100% rename from src/components/popup/tier/GetTokensButton.tsx rename to .old-already-moved/src/components/popup/tier/GetTokensButton.tsx diff --git a/src/components/popup/tier/ProgressBar.tsx b/.old-already-moved/src/components/popup/tier/ProgressBar.tsx similarity index 100% rename from src/components/popup/tier/ProgressBar.tsx rename to .old-already-moved/src/components/popup/tier/ProgressBar.tsx diff --git a/src/components/popup/tier/StarIcon.tsx b/.old-already-moved/src/components/popup/tier/StarIcon.tsx similarity index 100% rename from src/components/popup/tier/StarIcon.tsx rename to .old-already-moved/src/components/popup/tier/StarIcon.tsx diff --git a/src/components/popup/tier/TierButton.tsx b/.old-already-moved/src/components/popup/tier/TierButton.tsx similarity index 100% rename from src/components/popup/tier/TierButton.tsx rename to .old-already-moved/src/components/popup/tier/TierButton.tsx diff --git a/src/components/popup/tier/TierCard.tsx b/.old-already-moved/src/components/popup/tier/TierCard.tsx similarity index 100% rename from src/components/popup/tier/TierCard.tsx rename to .old-already-moved/src/components/popup/tier/TierCard.tsx diff --git a/src/components/popup/tier/TierProgress.tsx b/.old-already-moved/src/components/popup/tier/TierProgress.tsx similarity index 100% rename from src/components/popup/tier/TierProgress.tsx rename to .old-already-moved/src/components/popup/tier/TierProgress.tsx diff --git a/src/components/popup/tier/TierProgressPopup.tsx b/.old-already-moved/src/components/popup/tier/TierProgressPopup.tsx similarity index 100% rename from src/components/popup/tier/TierProgressPopup.tsx rename to .old-already-moved/src/components/popup/tier/TierProgressPopup.tsx diff --git a/src/components/popup/tier/TierTag.tsx b/.old-already-moved/src/components/popup/tier/TierTag.tsx similarity index 100% rename from src/components/popup/tier/TierTag.tsx rename to .old-already-moved/src/components/popup/tier/TierTag.tsx diff --git a/src/components/popup/tier/TierWrapper.tsx b/.old-already-moved/src/components/popup/tier/TierWrapper.tsx similarity index 100% rename from src/components/popup/tier/TierWrapper.tsx rename to .old-already-moved/src/components/popup/tier/TierWrapper.tsx diff --git a/src/components/popup/tier/TiersPopup.tsx b/.old-already-moved/src/components/popup/tier/TiersPopup.tsx similarity index 100% rename from src/components/popup/tier/TiersPopup.tsx rename to .old-already-moved/src/components/popup/tier/TiersPopup.tsx diff --git a/src/components/popup/tier/WanderIcon.tsx b/.old-already-moved/src/components/popup/tier/WanderIcon.tsx similarity index 100% rename from src/components/popup/tier/WanderIcon.tsx rename to .old-already-moved/src/components/popup/tier/WanderIcon.tsx diff --git a/src/components/popup/tokens/ActiveAgentsSlider.tsx b/.old-already-moved/src/components/popup/tokens/ActiveAgentsSlider.tsx similarity index 100% rename from src/components/popup/tokens/ActiveAgentsSlider.tsx rename to .old-already-moved/src/components/popup/tokens/ActiveAgentsSlider.tsx diff --git a/src/components/popup/tokens/ErrorMessages.tsx b/.old-already-moved/src/components/popup/tokens/ErrorMessages.tsx similarity index 100% rename from src/components/popup/tokens/ErrorMessages.tsx rename to .old-already-moved/src/components/popup/tokens/ErrorMessages.tsx diff --git a/src/components/popup/tokens/PriceChart.tsx b/.old-already-moved/src/components/popup/tokens/PriceChart.tsx similarity index 100% rename from src/components/popup/tokens/PriceChart.tsx rename to .old-already-moved/src/components/popup/tokens/PriceChart.tsx diff --git a/src/components/popup/tokens/PriceChartModal.tsx b/.old-already-moved/src/components/popup/tokens/PriceChartModal.tsx similarity index 100% rename from src/components/popup/tokens/PriceChartModal.tsx rename to .old-already-moved/src/components/popup/tokens/PriceChartModal.tsx diff --git a/src/components/popup/tokens/TokenActionButtons.tsx b/.old-already-moved/src/components/popup/tokens/TokenActionButtons.tsx similarity index 100% rename from src/components/popup/tokens/TokenActionButtons.tsx rename to .old-already-moved/src/components/popup/tokens/TokenActionButtons.tsx diff --git a/src/components/popup/tokens/TokenActivity.tsx b/.old-already-moved/src/components/popup/tokens/TokenActivity.tsx similarity index 100% rename from src/components/popup/tokens/TokenActivity.tsx rename to .old-already-moved/src/components/popup/tokens/TokenActivity.tsx diff --git a/src/components/popup/tokens/TokenInfo.tsx b/.old-already-moved/src/components/popup/tokens/TokenInfo.tsx similarity index 100% rename from src/components/popup/tokens/TokenInfo.tsx rename to .old-already-moved/src/components/popup/tokens/TokenInfo.tsx diff --git a/src/components/popup/tokens/TokenLinks.tsx b/.old-already-moved/src/components/popup/tokens/TokenLinks.tsx similarity index 100% rename from src/components/popup/tokens/TokenLinks.tsx rename to .old-already-moved/src/components/popup/tokens/TokenLinks.tsx diff --git a/src/components/welcome/Done.tsx b/.old-already-moved/src/components/welcome/Done.tsx similarity index 100% rename from src/components/welcome/Done.tsx rename to .old-already-moved/src/components/welcome/Done.tsx diff --git a/src/components/welcome/PasswordStrength.tsx b/.old-already-moved/src/components/welcome/PasswordStrength.tsx similarity index 98% rename from src/components/welcome/PasswordStrength.tsx rename to .old-already-moved/src/components/welcome/PasswordStrength.tsx index 3ca0fe6aa..2b9191649 100644 --- a/src/components/welcome/PasswordStrength.tsx +++ b/.old-already-moved/src/components/welcome/PasswordStrength.tsx @@ -4,7 +4,7 @@ import { useMemo } from "react"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { Check, X, AlertTriangle } from "@untitled-ui/icons-react"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; export interface PasswordStrengthProps { password: string; diff --git a/src/components/welcome/Screenshots.tsx b/.old-already-moved/src/components/welcome/Screenshots.tsx similarity index 100% rename from src/components/welcome/Screenshots.tsx rename to .old-already-moved/src/components/welcome/Screenshots.tsx diff --git a/src/components/welcome/StarIcon.tsx b/.old-already-moved/src/components/welcome/StarIcon.tsx similarity index 100% rename from src/components/welcome/StarIcon.tsx rename to .old-already-moved/src/components/welcome/StarIcon.tsx diff --git a/src/components/welcome/StarIcons.tsx b/.old-already-moved/src/components/welcome/StarIcons.tsx similarity index 100% rename from src/components/welcome/StarIcons.tsx rename to .old-already-moved/src/components/welcome/StarIcons.tsx diff --git a/src/components/welcome/Theme.tsx b/.old-already-moved/src/components/welcome/Theme.tsx similarity index 100% rename from src/components/welcome/Theme.tsx rename to .old-already-moved/src/components/welcome/Theme.tsx diff --git a/src/components/welcome/Wrapper.tsx b/.old-already-moved/src/components/welcome/Wrapper.tsx similarity index 100% rename from src/components/welcome/Wrapper.tsx rename to .old-already-moved/src/components/welcome/Wrapper.tsx diff --git a/src/components/welcome/generate/BackupWalletPage.tsx b/.old-already-moved/src/components/welcome/generate/BackupWalletPage.tsx similarity index 100% rename from src/components/welcome/generate/BackupWalletPage.tsx rename to .old-already-moved/src/components/welcome/generate/BackupWalletPage.tsx diff --git a/src/components/welcome/load/Migrate.tsx b/.old-already-moved/src/components/welcome/load/Migrate.tsx similarity index 100% rename from src/components/welcome/load/Migrate.tsx rename to .old-already-moved/src/components/welcome/load/Migrate.tsx diff --git a/src/components/welcome/load/QRLoopScanner.tsx b/.old-already-moved/src/components/welcome/load/QRLoopScanner.tsx similarity index 100% rename from src/components/welcome/load/QRLoopScanner.tsx rename to .old-already-moved/src/components/welcome/load/QRLoopScanner.tsx diff --git a/src/components/welcome/load/QRScanner.tsx b/.old-already-moved/src/components/welcome/load/QRScanner.tsx similarity index 100% rename from src/components/welcome/load/QRScanner.tsx rename to .old-already-moved/src/components/welcome/load/QRScanner.tsx diff --git a/src/components/welcome/load/Wallet.tsx b/.old-already-moved/src/components/welcome/load/Wallet.tsx similarity index 100% rename from src/components/welcome/load/Wallet.tsx rename to .old-already-moved/src/components/welcome/load/Wallet.tsx diff --git a/src/constants/api.ts b/.old-already-moved/src/constants/api.ts similarity index 100% rename from src/constants/api.ts rename to .old-already-moved/src/constants/api.ts diff --git a/src/contacts/hooks.ts b/.old-already-moved/src/contacts/hooks.ts similarity index 100% rename from src/contacts/hooks.ts rename to .old-already-moved/src/contacts/hooks.ts diff --git a/src/contents/api.ts b/.old-already-moved/src/contents/api.ts similarity index 100% rename from src/contents/api.ts rename to .old-already-moved/src/contents/api.ts diff --git a/src/contents/events.ts b/.old-already-moved/src/contents/events.ts similarity index 100% rename from src/contents/events.ts rename to .old-already-moved/src/contents/events.ts diff --git a/src/contents/injected/setup-wallet-sdk.injected-script.ts b/.old-already-moved/src/contents/injected/setup-wallet-sdk.injected-script.ts similarity index 100% rename from src/contents/injected/setup-wallet-sdk.injected-script.ts rename to .old-already-moved/src/contents/injected/setup-wallet-sdk.injected-script.ts diff --git a/src/devtools.tsx b/.old-already-moved/src/devtools.tsx similarity index 100% rename from src/devtools.tsx rename to .old-already-moved/src/devtools.tsx diff --git a/src/gateways/api.ts b/.old-already-moved/src/gateways/api.ts similarity index 100% rename from src/gateways/api.ts rename to .old-already-moved/src/gateways/api.ts diff --git a/src/gateways/ar_protocol.ts b/.old-already-moved/src/gateways/ar_protocol.ts similarity index 100% rename from src/gateways/ar_protocol.ts rename to .old-already-moved/src/gateways/ar_protocol.ts diff --git a/src/gateways/cache.ts b/.old-already-moved/src/gateways/cache.ts similarity index 100% rename from src/gateways/cache.ts rename to .old-already-moved/src/gateways/cache.ts diff --git a/src/gateways/gateway.ts b/.old-already-moved/src/gateways/gateway.ts similarity index 100% rename from src/gateways/gateway.ts rename to .old-already-moved/src/gateways/gateway.ts diff --git a/src/gateways/types.ts b/.old-already-moved/src/gateways/types.ts similarity index 100% rename from src/gateways/types.ts rename to .old-already-moved/src/gateways/types.ts diff --git a/src/gateways/utils.ts b/.old-already-moved/src/gateways/utils.ts similarity index 100% rename from src/gateways/utils.ts rename to .old-already-moved/src/gateways/utils.ts diff --git a/src/gateways/wayfinder.ts b/.old-already-moved/src/gateways/wayfinder.ts similarity index 100% rename from src/gateways/wayfinder.ts rename to .old-already-moved/src/gateways/wayfinder.ts diff --git a/src/iframe/README.md b/.old-already-moved/src/iframe/README.md similarity index 100% rename from src/iframe/README.md rename to .old-already-moved/src/iframe/README.md diff --git a/src/iframe/browser/action/action.mock.ts b/.old-already-moved/src/iframe/browser/action/action.mock.ts similarity index 100% rename from src/iframe/browser/action/action.mock.ts rename to .old-already-moved/src/iframe/browser/action/action.mock.ts diff --git a/src/iframe/browser/alarms/alarms.mock.ts b/.old-already-moved/src/iframe/browser/alarms/alarms.mock.ts similarity index 100% rename from src/iframe/browser/alarms/alarms.mock.ts rename to .old-already-moved/src/iframe/browser/alarms/alarms.mock.ts diff --git a/src/iframe/browser/i18n/i18n.mock.ts b/.old-already-moved/src/iframe/browser/i18n/i18n.mock.ts similarity index 100% rename from src/iframe/browser/i18n/i18n.mock.ts rename to .old-already-moved/src/iframe/browser/i18n/i18n.mock.ts diff --git a/src/iframe/browser/index.ts b/.old-already-moved/src/iframe/browser/index.ts similarity index 100% rename from src/iframe/browser/index.ts rename to .old-already-moved/src/iframe/browser/index.ts diff --git a/src/iframe/browser/runtime/runtime.mock.ts b/.old-already-moved/src/iframe/browser/runtime/runtime.mock.ts similarity index 100% rename from src/iframe/browser/runtime/runtime.mock.ts rename to .old-already-moved/src/iframe/browser/runtime/runtime.mock.ts diff --git a/src/iframe/browser/storage/storage.mock.ts b/.old-already-moved/src/iframe/browser/storage/storage.mock.ts similarity index 100% rename from src/iframe/browser/storage/storage.mock.ts rename to .old-already-moved/src/iframe/browser/storage/storage.mock.ts diff --git a/src/iframe/browser/tabs/tabs.mock.ts b/.old-already-moved/src/iframe/browser/tabs/tabs.mock.ts similarity index 90% rename from src/iframe/browser/tabs/tabs.mock.ts rename to .old-already-moved/src/iframe/browser/tabs/tabs.mock.ts index f68bfe678..1512f9241 100644 --- a/src/iframe/browser/tabs/tabs.mock.ts +++ b/.old-already-moved/src/iframe/browser/tabs/tabs.mock.ts @@ -1,5 +1,5 @@ -import { EMBEDDED_ANCESTOR_TAB_ID } from "~utils/embedded/embedded.constants"; -import { getEmbeddedAncestorOrigin } from "~utils/embedded/iframe.utils"; +import { EMBEDDED_ANCESTOR_TAB_ID } from "~utils/_embedded/embedded.constants"; +import { getEmbeddedAncestorOrigin } from "~utils/_embedded/iframe.utils"; import { isExternalURL } from "~utils/urls/isExternalURL"; export const tabs = { diff --git a/src/iframe/browser/windows/windows.mock.ts b/.old-already-moved/src/iframe/browser/windows/windows.mock.ts similarity index 92% rename from src/iframe/browser/windows/windows.mock.ts rename to .old-already-moved/src/iframe/browser/windows/windows.mock.ts index 9ccb2a865..9b1ba9048 100644 --- a/src/iframe/browser/windows/windows.mock.ts +++ b/.old-already-moved/src/iframe/browser/windows/windows.mock.ts @@ -1,4 +1,4 @@ -import { EMBEDDED_IFRAME_TAB_ID } from "~utils/embedded/embedded.constants"; +import { EMBEDDED_IFRAME_TAB_ID } from "~utils/_embedded/embedded.constants"; export const windows = { create: async ({ url }) => { diff --git a/src/iframe/iframe.tsx b/.old-already-moved/src/iframe/iframe.tsx similarity index 96% rename from src/iframe/iframe.tsx rename to .old-already-moved/src/iframe/iframe.tsx index 5118bea5a..eedf91c05 100644 --- a/src/iframe/iframe.tsx +++ b/.old-already-moved/src/iframe/iframe.tsx @@ -9,7 +9,7 @@ import { Router as Wouter } from "wouter"; import { IFRAME_ROUTES } from "~wallets/router/iframe/iframe.routes"; import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; import { useEmbeddedLocation } from "~wallets/router/iframe/iframe-router.hook"; -import { EmbeddedProvider } from "~utils/embedded/embedded.provider"; +import { EmbeddedProvider } from "~utils/_embedded/embedded.provider"; import { QueryClientProvider } from "@tanstack/react-query"; import { ThemeSetup } from "~components/embed/ui/atoms/theme-setup/ThemeSetup"; import { queryClient } from "~utils/tanstack"; diff --git a/src/iframe/main.tsx b/.old-already-moved/src/iframe/main.tsx similarity index 97% rename from src/iframe/main.tsx rename to .old-already-moved/src/iframe/main.tsx index f2a94fdc7..4a30a727d 100644 --- a/src/iframe/main.tsx +++ b/.old-already-moved/src/iframe/main.tsx @@ -6,8 +6,8 @@ import { EMBEDDED_ANCESTOR_ORIGIN, EMBEDDED_HIDE_BE, EMBEDDED_SERVER_BASE_URL, -} from "~utils/embedded/iframe.utils"; -import { getSupabaseClient } from "~utils/embedded/embedded.utils"; +} from "~utils/_embedded/iframe.utils"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; import { type OAuthSuccessMessage, type OAuthErrorMessage } from "~utils/authentication/authentication.types"; import { BACKGROUND_COLORS_BY_PROVIDER, diff --git a/src/iframe/storage/plasmo-storage/plasmo-storage.mock.ts b/.old-already-moved/src/iframe/storage/plasmo-storage/plasmo-storage.mock.ts similarity index 100% rename from src/iframe/storage/plasmo-storage/plasmo-storage.mock.ts rename to .old-already-moved/src/iframe/storage/plasmo-storage/plasmo-storage.mock.ts diff --git a/src/iframe/storage/storage-manager/storage-manager.ts b/.old-already-moved/src/iframe/storage/storage-manager/storage-manager.ts similarity index 100% rename from src/iframe/storage/storage-manager/storage-manager.ts rename to .old-already-moved/src/iframe/storage/storage-manager/storage-manager.ts diff --git a/src/iframe/storage/storage-manager/storage-manager.utils.ts b/.old-already-moved/src/iframe/storage/storage-manager/storage-manager.utils.ts similarity index 100% rename from src/iframe/storage/storage-manager/storage-manager.utils.ts rename to .old-already-moved/src/iframe/storage/storage-manager/storage-manager.utils.ts diff --git a/src/iframe/storage/unpartitioned-storage/local-storage.ts b/.old-already-moved/src/iframe/storage/unpartitioned-storage/local-storage.ts similarity index 98% rename from src/iframe/storage/unpartitioned-storage/local-storage.ts rename to .old-already-moved/src/iframe/storage/unpartitioned-storage/local-storage.ts index 46bd31871..49dcf9756 100644 --- a/src/iframe/storage/unpartitioned-storage/local-storage.ts +++ b/.old-already-moved/src/iframe/storage/unpartitioned-storage/local-storage.ts @@ -1,12 +1,12 @@ -import { isInsideIframe } from "~utils/embedded/iframe.utils"; +import { isInsideIframe } from "~utils/_embedded/iframe.utils"; import { EnhancedStorage } from "./unpartitioned-storage"; import Cookies from "js-cookie"; import type { CookieAttributes } from "node_modules/@types/js-cookie"; import { getUnpartitionedStateStatus } from "~iframe/storage/unpartitioned-storage/unpartitioned-storage.utils"; import { browserInfo } from "~utils/browser-info/browser-info.utils"; import { log, LOG_GROUP } from "~utils/log/log.utils"; -import { SUPABASE_AUTH_TOKEN_KEY_REGEXP } from "~utils/embedded/embedded.constants"; -import { DEVICE_NONCE_KEY } from "~utils/embedded/device-nonce/device-nonce.constants"; +import { SUPABASE_AUTH_TOKEN_KEY_REGEXP } from "~utils/_embedded/embedded.constants"; +import { DEVICE_NONCE_KEY } from "~utils/_embedded/device-nonce/device-nonce.constants"; export class LocalStorage { private static instance: LocalStorage | null = null; diff --git a/src/iframe/storage/unpartitioned-storage/unpartition-storage.test.ts b/.old-already-moved/src/iframe/storage/unpartitioned-storage/unpartition-storage.test.ts similarity index 100% rename from src/iframe/storage/unpartitioned-storage/unpartition-storage.test.ts rename to .old-already-moved/src/iframe/storage/unpartitioned-storage/unpartition-storage.test.ts diff --git a/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.ts b/.old-already-moved/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.ts similarity index 99% rename from src/iframe/storage/unpartitioned-storage/unpartitioned-storage.ts rename to .old-already-moved/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.ts index de175f8dd..bd8ac7db2 100644 --- a/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.ts +++ b/.old-already-moved/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.ts @@ -1,4 +1,4 @@ -import { isInsideIframe } from "~utils/embedded/iframe.utils"; +import { isInsideIframe } from "~utils/_embedded/iframe.utils"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import { getUnpartitionedStateStatus, diff --git a/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.utils.ts b/.old-already-moved/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.utils.ts similarity index 100% rename from src/iframe/storage/unpartitioned-storage/unpartitioned-storage.utils.ts rename to .old-already-moved/src/iframe/storage/unpartitioned-storage/unpartitioned-storage.utils.ts diff --git a/src/lib/ans.ts b/.old-already-moved/src/lib/ans.ts similarity index 100% rename from src/lib/ans.ts rename to .old-already-moved/src/lib/ans.ts diff --git a/src/lib/ao.ts b/.old-already-moved/src/lib/ao.ts similarity index 100% rename from src/lib/ao.ts rename to .old-already-moved/src/lib/ao.ts diff --git a/src/lib/arns.ts b/.old-already-moved/src/lib/arns.ts similarity index 100% rename from src/lib/arns.ts rename to .old-already-moved/src/lib/arns.ts diff --git a/src/lib/avatar.ts b/.old-already-moved/src/lib/avatar.ts similarity index 100% rename from src/lib/avatar.ts rename to .old-already-moved/src/lib/avatar.ts diff --git a/src/lib/coingecko.ts b/.old-already-moved/src/lib/coingecko.ts similarity index 99% rename from src/lib/coingecko.ts rename to .old-already-moved/src/lib/coingecko.ts index fe3d9aa76..71d2b27e0 100644 --- a/src/lib/coingecko.ts +++ b/.old-already-moved/src/lib/coingecko.ts @@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import BigNumber from "bignumber.js"; import redstone from "redstone-api"; import { getConversionRate } from "~utils/currency"; -import { CACHE_API } from "~constants/api"; +import { CACHE_API } from "~_constants/api"; import { withRetry } from "~utils/promises/retry"; import { ExtensionStorage, PersistentStorage } from "~utils/storage"; diff --git a/src/lib/nameservice.ts b/.old-already-moved/src/lib/nameservice.ts similarity index 100% rename from src/lib/nameservice.ts rename to .old-already-moved/src/lib/nameservice.ts diff --git a/src/lib/onramper.ts b/.old-already-moved/src/lib/onramper.ts similarity index 100% rename from src/lib/onramper.ts rename to .old-already-moved/src/lib/onramper.ts diff --git a/src/lib/permaweb_news.ts b/.old-already-moved/src/lib/permaweb_news.ts similarity index 100% rename from src/lib/permaweb_news.ts rename to .old-already-moved/src/lib/permaweb_news.ts diff --git a/src/lib/redstone.ts b/.old-already-moved/src/lib/redstone.ts similarity index 100% rename from src/lib/redstone.ts rename to .old-already-moved/src/lib/redstone.ts diff --git a/src/lib/transactions.ts b/.old-already-moved/src/lib/transactions.ts similarity index 99% rename from src/lib/transactions.ts rename to .old-already-moved/src/lib/transactions.ts index 76b6969d2..51fbf67bd 100644 --- a/src/lib/transactions.ts +++ b/.old-already-moved/src/lib/transactions.ts @@ -5,7 +5,7 @@ import BigNumber from "bignumber.js"; import browser from "webextension-polyfill"; import { balanceToFractioned, formatFiatBalance } from "~tokens/currency"; import { timeoutPromise } from "~utils/promises/timeout"; -import { TRANSFER_ERROR_QUERY } from "~notifications/utils"; +import { TRANSFER_ERROR_QUERY } from "~_notifications/utils"; import { gql } from "~gateways/api"; import { txHistoryGateways } from "~gateways/gateway"; import { retryWithDelay } from "~utils/promises/retry"; diff --git a/src/lib/types.ts b/.old-already-moved/src/lib/types.ts similarity index 100% rename from src/lib/types.ts rename to .old-already-moved/src/lib/types.ts diff --git a/src/lib/wayfinder.ts b/.old-already-moved/src/lib/wayfinder.ts similarity index 100% rename from src/lib/wayfinder.ts rename to .old-already-moved/src/lib/wayfinder.ts diff --git a/src/notifications/utils.ts b/.old-already-moved/src/notifications/utils.ts similarity index 100% rename from src/notifications/utils.ts rename to .old-already-moved/src/notifications/utils.ts diff --git a/src/popup.html b/.old-already-moved/src/popup.html similarity index 100% rename from src/popup.html rename to .old-already-moved/src/popup.html diff --git a/src/popup.tsx b/.old-already-moved/src/popup.tsx similarity index 100% rename from src/popup.tsx rename to .old-already-moved/src/popup.tsx diff --git a/src/routes/embedded/account/add-wallet/account-add-wallet.view.tsx b/.old-already-moved/src/routes/_embedded/account/add-wallet/account-add-wallet.view.tsx similarity index 97% rename from src/routes/embedded/account/add-wallet/account-add-wallet.view.tsx rename to .old-already-moved/src/routes/_embedded/account/add-wallet/account-add-wallet.view.tsx index 788129a32..b71c015d1 100644 --- a/src/routes/embedded/account/add-wallet/account-add-wallet.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/add-wallet/account-add-wallet.view.tsx @@ -2,7 +2,7 @@ import type { WalletSourceType } from "embed-api"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Box, Button, Card, KeyIcon, SeedIcon, WalletIcon } from "~components/embed/ui"; import { WanderFooter } from "~components/embed/ui/templates/wander-footer/WanderFooter"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useLocation } from "~wallets/router/router.utils"; export function AccountAddWalletEmbeddedView() { diff --git a/src/routes/embedded/account/backup-wallet/backup-full-wallet.view.tsx b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-full-wallet.view.tsx similarity index 97% rename from src/routes/embedded/account/backup-wallet/backup-full-wallet.view.tsx rename to .old-already-moved/src/routes/_embedded/account/backup-wallet/backup-full-wallet.view.tsx index 5bacdd12e..32a0970cc 100644 --- a/src/routes/embedded/account/backup-wallet/backup-full-wallet.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-full-wallet.view.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import { Flex } from "~components/common/Flex"; import { Button, Copyable, Snackbar, CheckIcon } from "~components/embed/ui"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { WalletUtils } from "~utils/wallets/wallets.utils"; import { Link } from "~wallets/router/components/link/Link"; import { useLocation } from "~wallets/router/router.utils"; diff --git a/src/routes/embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx similarity index 95% rename from src/routes/embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx rename to .old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx index a541a09d6..fd48885f6 100644 --- a/src/routes/embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { Button, Snackbar, WarningIcon } from "~components/embed/ui"; import { SecretInput } from "~components/embed/ui/atoms/secret-input/SecretInput"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useAsyncEffect } from "~utils/react/useAsyncEffect"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; import { useLocation } from "~wallets/router/router.utils"; diff --git a/src/routes/embedded/account/backup-wallet/backup-wallet-qrcode.tsx b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-qrcode.tsx similarity index 98% rename from src/routes/embedded/account/backup-wallet/backup-wallet-qrcode.tsx rename to .old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-qrcode.tsx index 8a38e3db6..6ec4cf963 100644 --- a/src/routes/embedded/account/backup-wallet/backup-wallet-qrcode.tsx +++ b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-qrcode.tsx @@ -5,7 +5,7 @@ import { Flex } from "~components/common/Flex"; import { CopyToClipboard } from "~components/CopyToClipboard"; import { Text } from "~components/embed/ui"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useLocation } from "~wallets/router/router.utils"; import type { LocalWallet } from "~wallets/wallets.types"; import browser from "webextension-polyfill"; diff --git a/src/routes/embedded/account/backup-wallet/backup-wallet-recovery-file.view.tsx b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-recovery-file.view.tsx similarity index 97% rename from src/routes/embedded/account/backup-wallet/backup-wallet-recovery-file.view.tsx rename to .old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-recovery-file.view.tsx index 13fe5a389..093fc01b8 100644 --- a/src/routes/embedded/account/backup-wallet/backup-wallet-recovery-file.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet-recovery-file.view.tsx @@ -5,7 +5,7 @@ import { toast } from "react-toastify"; import { Flex } from "~components/common/Flex"; import { Button, Copyable, Snackbar } from "~components/embed/ui"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { Link } from "~wallets/router/components/link/Link"; import { useLocation } from "~wallets/router/router.utils"; diff --git a/src/routes/embedded/account/backup-wallet/backup-wallet.view.tsx b/.old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet.view.tsx similarity index 100% rename from src/routes/embedded/account/backup-wallet/backup-wallet.view.tsx rename to .old-already-moved/src/routes/_embedded/account/backup-wallet/backup-wallet.view.tsx diff --git a/src/routes/embedded/account/change-password/account-change-password.view.tsx b/.old-already-moved/src/routes/_embedded/account/change-password/account-change-password.view.tsx similarity index 98% rename from src/routes/embedded/account/change-password/account-change-password.view.tsx rename to .old-already-moved/src/routes/_embedded/account/change-password/account-change-password.view.tsx index ffbb19583..eb69166fe 100644 --- a/src/routes/embedded/account/change-password/account-change-password.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/change-password/account-change-password.view.tsx @@ -1,8 +1,8 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { toast } from "react-toastify"; import { Text, Button } from "~components/embed"; import { useCallback, useEffect, useRef, useState } from "react"; -import { getSupabaseClient, signOut } from "~utils/embedded/embedded.utils"; +import { getSupabaseClient, signOut } from "~utils/_embedded/embedded.utils"; import { useLocation } from "~wallets/router/router.utils"; import PasswordStrength from "~components/welcome/PasswordStrength"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; diff --git a/src/routes/embedded/account/export-wallet/account-export-wallet.view.tsx b/.old-already-moved/src/routes/_embedded/account/export-wallet/account-export-wallet.view.tsx similarity index 97% rename from src/routes/embedded/account/export-wallet/account-export-wallet.view.tsx rename to .old-already-moved/src/routes/_embedded/account/export-wallet/account-export-wallet.view.tsx index 104368c00..3dc7d8b4b 100644 --- a/src/routes/embedded/account/export-wallet/account-export-wallet.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/export-wallet/account-export-wallet.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { Box, Button, Card, Copyable, KeyIcon, SeedIcon, Snackbar, WarningIcon } from "~components/embed/ui"; import copy from "copy-to-clipboard"; import { WalletUtils } from "~utils/wallets/wallets.utils"; diff --git a/src/routes/embedded/account/import-keyfile/account-import-keyfile.view.tsx b/.old-already-moved/src/routes/_embedded/account/import-keyfile/account-import-keyfile.view.tsx similarity index 97% rename from src/routes/embedded/account/import-keyfile/account-import-keyfile.view.tsx rename to .old-already-moved/src/routes/_embedded/account/import-keyfile/account-import-keyfile.view.tsx index f9b223ecc..5f3edc867 100644 --- a/src/routes/embedded/account/import-keyfile/account-import-keyfile.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/import-keyfile/account-import-keyfile.view.tsx @@ -2,7 +2,7 @@ import copy from "copy-to-clipboard"; import { useCallback, useEffect, useState } from "react"; import { Button, Card, Copyable, Row, Upload, WanderIcon, Text } from "~components/embed/ui"; import { WanderFooter } from "~components/embed/ui/templates/wander-footer/WanderFooter"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useLocation } from "~wallets/router/router.utils"; export function AccountImportKeyfileEmbeddedView() { diff --git a/src/routes/embedded/account/import-seedphrase/account-import-seedphrase.view.tsx b/.old-already-moved/src/routes/_embedded/account/import-seedphrase/account-import-seedphrase.view.tsx similarity index 97% rename from src/routes/embedded/account/import-seedphrase/account-import-seedphrase.view.tsx rename to .old-already-moved/src/routes/_embedded/account/import-seedphrase/account-import-seedphrase.view.tsx index c0258743f..00ece3291 100644 --- a/src/routes/embedded/account/import-seedphrase/account-import-seedphrase.view.tsx +++ b/.old-already-moved/src/routes/_embedded/account/import-seedphrase/account-import-seedphrase.view.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Button, Card, SeedInput } from "~components/embed/ui"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useLocation } from "~wallets/router/router.utils"; import { toast } from "react-toastify"; import { WanderFooter } from "~components/embed/ui/templates/wander-footer/WanderFooter"; diff --git a/src/routes/embedded/auth-request/connect/components/AppIcons.tsx b/.old-already-moved/src/routes/_embedded/auth-request/connect/components/AppIcons.tsx similarity index 100% rename from src/routes/embedded/auth-request/connect/components/AppIcons.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/connect/components/AppIcons.tsx diff --git a/src/routes/embedded/auth-request/connect/connect-custom.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/connect/connect-custom.view.tsx similarity index 100% rename from src/routes/embedded/auth-request/connect/connect-custom.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/connect/connect-custom.view.tsx diff --git a/src/routes/embedded/auth-request/connect/connect-settings.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/connect/connect-settings.view.tsx similarity index 100% rename from src/routes/embedded/auth-request/connect/connect-settings.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/connect/connect-settings.view.tsx diff --git a/src/routes/embedded/auth-request/connect/connect.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/connect/connect.view.tsx similarity index 98% rename from src/routes/embedded/auth-request/connect/connect.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/connect/connect.view.tsx index 2b7ca94ac..f96605c4d 100644 --- a/src/routes/embedded/auth-request/connect/connect.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth-request/connect/connect.view.tsx @@ -1,5 +1,5 @@ import { Button, Text, Row, Box, Avatar, WalletIcon } from "~components/embed/ui"; -import type { Wallet } from "~utils/embedded/embedded.types"; +import type { Wallet } from "~utils/_embedded/embedded.types"; import { formatAddress } from "~utils/format"; import { setActiveWallet, useActiveWallet } from "~wallets/hooks"; import { useLocation } from "~wallets/router/router.utils"; diff --git a/src/routes/embedded/auth-request/decrypt/decrypt.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/decrypt/decrypt.view.tsx similarity index 100% rename from src/routes/embedded/auth-request/decrypt/decrypt.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/decrypt/decrypt.view.tsx diff --git a/src/routes/embedded/auth-request/sign/batchSignDataItem.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/sign/batchSignDataItem.view.tsx similarity index 100% rename from src/routes/embedded/auth-request/sign/batchSignDataItem.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/sign/batchSignDataItem.view.tsx diff --git a/src/routes/embedded/auth-request/sign/sign-details.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/sign/sign-details.view.tsx similarity index 98% rename from src/routes/embedded/auth-request/sign/sign-details.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/sign/sign-details.view.tsx index a1b7d833c..977f1dc8b 100644 --- a/src/routes/embedded/auth-request/sign/sign-details.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth-request/sign/sign-details.view.tsx @@ -5,7 +5,7 @@ import type { DecodedTag } from "~api/modules/sign/tags"; import { Row, Text, Box, ChevronRight } from "~components/embed/ui"; import { defaultGateway } from "~gateways/gateway"; import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { humanizeTimestampTags } from "~utils/timestamp"; import { useLocation } from "~wallets/router/router.utils"; import { formatAddress } from "~utils/format"; diff --git a/src/routes/embedded/auth-request/sign/sign.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/sign/sign.view.tsx similarity index 100% rename from src/routes/embedded/auth-request/sign/sign.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/sign/sign.view.tsx diff --git a/src/routes/embedded/auth-request/sign/signDataItem.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/sign/signDataItem.view.tsx similarity index 100% rename from src/routes/embedded/auth-request/sign/signDataItem.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/sign/signDataItem.view.tsx diff --git a/src/routes/embedded/auth-request/signature/signature.view.tsx b/.old-already-moved/src/routes/_embedded/auth-request/signature/signature.view.tsx similarity index 100% rename from src/routes/embedded/auth-request/signature/signature.view.tsx rename to .old-already-moved/src/routes/_embedded/auth-request/signature/signature.view.tsx diff --git a/src/routes/embedded/auth/add-auth-provider/auth-add-auth-provider.view.tsx b/.old-already-moved/src/routes/_embedded/auth/add-auth-provider/auth-add-auth-provider.view.tsx similarity index 89% rename from src/routes/embedded/auth/add-auth-provider/auth-add-auth-provider.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/add-auth-provider/auth-add-auth-provider.view.tsx index 4730233f7..10ec47bd1 100644 --- a/src/routes/embedded/auth/add-auth-provider/auth-add-auth-provider.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/add-auth-provider/auth-add-auth-provider.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { DevFigmaScreen } from "~components/dev/figma-screen/figma-screen.component"; export function AuthAddAuthProviderEmbeddedView() { diff --git a/src/routes/embedded/auth/add-device/auth-add-device.view.tsx b/.old-already-moved/src/routes/_embedded/auth/add-device/auth-add-device.view.tsx similarity index 100% rename from src/routes/embedded/auth/add-device/auth-add-device.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/add-device/auth-add-device.view.tsx diff --git a/src/routes/embedded/auth/add-qrcode/add-qrcode.view.tsx b/.old-already-moved/src/routes/_embedded/auth/add-qrcode/add-qrcode.view.tsx similarity index 100% rename from src/routes/embedded/auth/add-qrcode/add-qrcode.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/add-qrcode/add-qrcode.view.tsx diff --git a/src/routes/embedded/auth/add-wallet/auth-add-wallet.view.tsx b/.old-already-moved/src/routes/_embedded/auth/add-wallet/auth-add-wallet.view.tsx similarity index 96% rename from src/routes/embedded/auth/add-wallet/auth-add-wallet.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/add-wallet/auth-add-wallet.view.tsx index eff1ce67c..e1034dd56 100644 --- a/src/routes/embedded/auth/add-wallet/auth-add-wallet.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/add-wallet/auth-add-wallet.view.tsx @@ -1,9 +1,9 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useCallback, useEffect, useState } from "react"; import { Button, KeyIcon, SeedIcon, WalletIcon } from "~components/embed"; import type { WalletSourceType } from "embed-api"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { signOut } from "~utils/embedded/embedded.utils"; +import { signOut } from "~utils/_embedded/embedded.utils"; import { QrCode02 } from "@untitled-ui/icons-react"; import { navigate } from "wouter/use-hash-location"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; diff --git a/src/routes/embedded/auth/auth-email-otp/auth-email-otp.view.tsx b/.old-already-moved/src/routes/_embedded/auth/auth-email-otp/auth-email-otp.view.tsx similarity index 98% rename from src/routes/embedded/auth/auth-email-otp/auth-email-otp.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/auth-email-otp/auth-email-otp.view.tsx index c6f18ab41..55660bce9 100644 --- a/src/routes/embedded/auth/auth-email-otp/auth-email-otp.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/auth-email-otp/auth-email-otp.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { toast } from "react-toastify"; import { Button, Text } from "~components/embed"; import React, { useCallback, useEffect, useRef, useState } from "react"; @@ -9,7 +9,7 @@ import { getFriendlyAuthErrorMessage } from "~utils/authentication/authenticatio import { Flex } from "~components/common/Flex"; import { CodeInput, type CodeInputHandle } from "~components/embed/ui/atoms/code-input/CodeInput"; import { useCooldownCallback } from "~utils/react/useCooldownCallback"; -import { getSupabaseClient } from "~utils/embedded/embedded.utils"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; import { StorageKeys } from "~utils/storage/storage.constants"; import { PersistentStorage, useStorage } from "~utils/storage"; import type { PreferredEmailAuth } from "~utils/auth/auth.types"; diff --git a/src/routes/embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx b/.old-already-moved/src/routes/_embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx similarity index 98% rename from src/routes/embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx index c1d190498..b8d95db48 100644 --- a/src/routes/embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { toast } from "react-toastify"; import { Button, Text, TextInput } from "~components/embed"; import React, { useCallback, useEffect, useRef, useState } from "react"; diff --git a/src/routes/embedded/auth/auth-email-sign-up/auth-email-sign-up.view.tsx b/.old-already-moved/src/routes/_embedded/auth/auth-email-sign-up/auth-email-sign-up.view.tsx similarity index 97% rename from src/routes/embedded/auth/auth-email-sign-up/auth-email-sign-up.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/auth-email-sign-up/auth-email-sign-up.view.tsx index b42a24470..e4ffafc09 100644 --- a/src/routes/embedded/auth/auth-email-sign-up/auth-email-sign-up.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/auth-email-sign-up/auth-email-sign-up.view.tsx @@ -1,8 +1,8 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { toast } from "react-toastify"; import { Button, TextInput } from "~components/embed"; import { useCallback, useEffect, useRef, useState } from "react"; -import { getSupabaseClient } from "~utils/embedded/embedded.utils"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; import { useLocation, useSearchParams } from "~wallets/router/router.utils"; import PasswordStrength from "~components/welcome/PasswordStrength"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; diff --git a/src/routes/embedded/auth/auth-email-verify/auth-email-verify.view.tsx b/.old-already-moved/src/routes/_embedded/auth/auth-email-verify/auth-email-verify.view.tsx similarity index 97% rename from src/routes/embedded/auth/auth-email-verify/auth-email-verify.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/auth-email-verify/auth-email-verify.view.tsx index fbaad557b..a4b8af19a 100644 --- a/src/routes/embedded/auth/auth-email-verify/auth-email-verify.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/auth-email-verify/auth-email-verify.view.tsx @@ -1,10 +1,10 @@ import { toast } from "react-toastify"; import { Text, Button } from "~components/embed"; import React, { useCallback, useEffect, useRef, useState } from "react"; -import { getSupabaseClient } from "~utils/embedded/embedded.utils"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; import { useLocation, useSearchParams } from "~wallets/router/router.utils"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; import { Flex } from "~components/common/Flex"; import { getFriendlyAuthErrorMessage } from "~utils/authentication/authentication.utils"; diff --git a/src/routes/embedded/auth/auth-more-providers/auth-more-providers.view.tsx b/.old-already-moved/src/routes/_embedded/auth/auth-more-providers/auth-more-providers.view.tsx similarity index 94% rename from src/routes/embedded/auth/auth-more-providers/auth-more-providers.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/auth-more-providers/auth-more-providers.view.tsx index dfe0e8531..a0f77a7df 100644 --- a/src/routes/embedded/auth/auth-more-providers/auth-more-providers.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/auth-more-providers/auth-more-providers.view.tsx @@ -1,11 +1,11 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { AppleIcon, Button, FacebookIcon, TwitterIcon } from "~components/embed"; import { useCallback, useState } from "react"; import { useLocation } from "~wallets/router/router.utils"; import { toast } from "react-toastify"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import type { OAutProviderType } from "~utils/embedded/embedded.types"; +import type { OAutProviderType } from "~utils/_embedded/embedded.types"; import { getFriendlyAuthErrorMessage } from "~utils/authentication/authentication.utils"; export function AuthMoreProvidersEmbeddedView() { diff --git a/src/routes/embedded/auth/auth/auth.view.tsx b/.old-already-moved/src/routes/_embedded/auth/auth/auth.view.tsx similarity index 96% rename from src/routes/embedded/auth/auth/auth.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/auth/auth.view.tsx index 4c3a99def..5c7595111 100644 --- a/src/routes/embedded/auth/auth/auth.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/auth/auth.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { toast } from "react-toastify"; import { Button, @@ -14,16 +14,16 @@ import { KeyIcon, } from "~components/embed"; import React, { useCallback, useRef, useState } from "react"; -import { getSupabaseClient } from "~utils/embedded/embedded.utils"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; import { useLocation, useSearchParams } from "~wallets/router/router.utils"; import { isValidEmail } from "~utils/email"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { sleep } from "~utils/promises/sleep"; -import { EMBEDDED_HIDE_BE, EMBEDDED_INJECTED_BE, isInsideIframe } from "~utils/embedded/iframe.utils"; +import { EMBEDDED_HIDE_BE, EMBEDDED_INJECTED_BE, isInsideIframe } from "~utils/_embedded/iframe.utils"; import { InputButton } from "~components/embed/ui/atoms/input-button/InputButton"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import type { OAutProviderType } from "~utils/embedded/embedded.types"; +import type { OAutProviderType } from "~utils/_embedded/embedded.types"; import { getFriendlyAuthErrorMessage } from "~utils/authentication/authentication.utils"; import { PersistentStorage, useStorage } from "~utils/storage"; import { StorageKeys } from "~utils/storage/storage.constants"; diff --git a/src/routes/embedded/auth/import-keyfile/auth-import-keyfile.view.tsx b/.old-already-moved/src/routes/_embedded/auth/import-keyfile/auth-import-keyfile.view.tsx similarity index 98% rename from src/routes/embedded/auth/import-keyfile/auth-import-keyfile.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/import-keyfile/auth-import-keyfile.view.tsx index 65c803810..692c0a0cf 100644 --- a/src/routes/embedded/auth/import-keyfile/auth-import-keyfile.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/import-keyfile/auth-import-keyfile.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useEffect, useState } from "react"; import { Row, Upload, Copyable, Button, Text, Snackbar } from "~components/embed"; import copy from "copy-to-clipboard"; diff --git a/.old-already-moved/src/routes/_embedded/auth/import-qrcode/auth-import-qrcode.tsx b/.old-already-moved/src/routes/_embedded/auth/import-qrcode/auth-import-qrcode.tsx new file mode 100644 index 000000000..950e681c0 --- /dev/null +++ b/.old-already-moved/src/routes/_embedded/auth/import-qrcode/auth-import-qrcode.tsx @@ -0,0 +1,15 @@ +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; +import { QrCodeScanEmbeddedView } from "./components/QrCodeEmbeddedView"; + +export function AuthImportQrCodeEmbeddedView() { + const { authStatus } = useEmbedded(); + + return ( + + ); +} diff --git a/src/routes/embedded/auth/import-qrcode/components/QrCodeEmbeddedView.tsx b/.old-already-moved/src/routes/_embedded/auth/import-qrcode/components/QrCodeEmbeddedView.tsx similarity index 98% rename from src/routes/embedded/auth/import-qrcode/components/QrCodeEmbeddedView.tsx rename to .old-already-moved/src/routes/_embedded/auth/import-qrcode/components/QrCodeEmbeddedView.tsx index 575d2a9d4..c3754c729 100644 --- a/src/routes/embedded/auth/import-qrcode/components/QrCodeEmbeddedView.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/import-qrcode/components/QrCodeEmbeddedView.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useCallback, useEffect, useState } from "react"; import { Row, Copyable, Button, CameraIcon, Box, QRLoopScanner } from "~components/embed"; import copy from "copy-to-clipboard"; diff --git a/src/routes/embedded/auth/import-qrcode/hooks/useWebcamPermission.tsx b/.old-already-moved/src/routes/_embedded/auth/import-qrcode/hooks/useWebcamPermission.tsx similarity index 100% rename from src/routes/embedded/auth/import-qrcode/hooks/useWebcamPermission.tsx rename to .old-already-moved/src/routes/_embedded/auth/import-qrcode/hooks/useWebcamPermission.tsx diff --git a/src/routes/embedded/auth/import-seedphrase/auth-import-seedphrase.view.tsx b/.old-already-moved/src/routes/_embedded/auth/import-seedphrase/auth-import-seedphrase.view.tsx similarity index 98% rename from src/routes/embedded/auth/import-seedphrase/auth-import-seedphrase.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/import-seedphrase/auth-import-seedphrase.view.tsx index 921fe1a7f..b9b34853f 100644 --- a/src/routes/embedded/auth/import-seedphrase/auth-import-seedphrase.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/import-seedphrase/auth-import-seedphrase.view.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { Button, Row, SeedInput, Copyable } from "~components/embed/ui"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useLocation } from "~wallets/router/router.utils"; export function AuthImportSeedphraseEmbeddedView() { diff --git a/src/routes/embedded/auth/qrcode-scanner/auth-qrcode-scanner.view.tsx b/.old-already-moved/src/routes/_embedded/auth/qrcode-scanner/auth-qrcode-scanner.view.tsx similarity index 100% rename from src/routes/embedded/auth/qrcode-scanner/auth-qrcode-scanner.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/qrcode-scanner/auth-qrcode-scanner.view.tsx diff --git a/src/routes/embedded/auth/recover-account/auth-recover-account.view.tsx b/.old-already-moved/src/routes/_embedded/auth/recover-account/auth-recover-account.view.tsx similarity index 96% rename from src/routes/embedded/auth/recover-account/auth-recover-account.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/recover-account/auth-recover-account.view.tsx index 75bda7322..fb75af1d9 100644 --- a/src/routes/embedded/auth/recover-account/auth-recover-account.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/recover-account/auth-recover-account.view.tsx @@ -5,8 +5,8 @@ import { InputButton } from "~components/embed/ui/atoms/input-button/InputButton import { QrCode02 } from "@untitled-ui/icons-react"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; import { isValidEmail } from "~utils/email"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; -import { getSupabaseClient } from "~utils/embedded/embedded.utils"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; import { useLocation, useSearchParams } from "~wallets/router/router.utils"; import { getFriendlyAuthErrorMessage } from "~utils/authentication/authentication.utils"; diff --git a/src/routes/embedded/auth/recover-account/confirm/auth-recover-confirm.view.tsx b/.old-already-moved/src/routes/_embedded/auth/recover-account/confirm/auth-recover-confirm.view.tsx similarity index 98% rename from src/routes/embedded/auth/recover-account/confirm/auth-recover-confirm.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/recover-account/confirm/auth-recover-confirm.view.tsx index 6472b861f..1a4fea6d6 100644 --- a/src/routes/embedded/auth/recover-account/confirm/auth-recover-confirm.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/recover-account/confirm/auth-recover-confirm.view.tsx @@ -2,7 +2,7 @@ import { useState, useCallback, useMemo, useRef, useEffect } from "react"; import { toast } from "react-toastify"; import { Button, Checkbox, RecoverHeaderIcon, Snackbar } from "~components/embed/ui"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { formatAddress } from "~utils/format"; import { withRetry } from "~utils/promises/retry"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; diff --git a/src/routes/embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx b/.old-already-moved/src/routes/_embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx similarity index 98% rename from src/routes/embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx index b8fef0e1b..f885583bf 100644 --- a/src/routes/embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useEffect, useState } from "react"; import { useLocation } from "~wallets/router/router.utils"; import { Row, Button, Copyable, Upload, Text, Snackbar, RecoverHeaderIcon } from "~components/embed"; diff --git a/src/routes/embedded/auth/recover-account/otp/auth-recover-account-otp.view.tsx b/.old-already-moved/src/routes/_embedded/auth/recover-account/otp/auth-recover-account-otp.view.tsx similarity index 97% rename from src/routes/embedded/auth/recover-account/otp/auth-recover-account-otp.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/recover-account/otp/auth-recover-account-otp.view.tsx index 748550c6e..650dae250 100644 --- a/src/routes/embedded/auth/recover-account/otp/auth-recover-account-otp.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/recover-account/otp/auth-recover-account-otp.view.tsx @@ -2,8 +2,8 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "react-toastify"; import { Button, Text, RecoverHeaderIcon } from "~components/embed/ui"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; -import { getSupabaseClient } from "~utils/embedded/embedded.utils"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; import { useLocation, useSearchParams } from "~wallets/router/router.utils"; import { Flex } from "~components/common/Flex"; diff --git a/src/routes/embedded/auth/recover-account/qrcode/auth-recover-account-qrcode.view.tsx b/.old-already-moved/src/routes/_embedded/auth/recover-account/qrcode/auth-recover-account-qrcode.view.tsx similarity index 100% rename from src/routes/embedded/auth/recover-account/qrcode/auth-recover-account-qrcode.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/recover-account/qrcode/auth-recover-account-qrcode.view.tsx diff --git a/src/routes/embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx b/.old-already-moved/src/routes/_embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx similarity index 98% rename from src/routes/embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx index 06f7fb78d..c4a69f156 100644 --- a/src/routes/embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useLocation } from "~wallets/router/router.utils"; import { Copyable, Row, Button, SeedInput, RecoverHeaderIcon } from "~components/embed/ui"; diff --git a/src/routes/embedded/auth/recover-account/select-account/auth-recover-account-select.view.tsx b/.old-already-moved/src/routes/_embedded/auth/recover-account/select-account/auth-recover-account-select.view.tsx similarity index 97% rename from src/routes/embedded/auth/recover-account/select-account/auth-recover-account-select.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/recover-account/select-account/auth-recover-account-select.view.tsx index 76681eafb..a5390bb9e 100644 --- a/src/routes/embedded/auth/recover-account/select-account/auth-recover-account-select.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/recover-account/select-account/auth-recover-account-select.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useCallback, useState } from "react"; import { Box, Text, Button, RecoverHeaderIcon } from "~components/embed/ui"; import type { RecoverableAccount } from "embed-api"; diff --git a/src/routes/embedded/auth/restore-shares/auth-restore-shares.view.tsx b/.old-already-moved/src/routes/_embedded/auth/restore-shares/auth-restore-shares.view.tsx similarity index 97% rename from src/routes/embedded/auth/restore-shares/auth-restore-shares.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/restore-shares/auth-restore-shares.view.tsx index 96a74c9ce..f8bf88a96 100644 --- a/src/routes/embedded/auth/restore-shares/auth-restore-shares.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/restore-shares/auth-restore-shares.view.tsx @@ -3,8 +3,8 @@ import { useMemo } from "react"; import { Button, WalletIcon, SeedIcon, KeyIcon, Snackbar, type SnackbarVariant, Divider } from "~components/embed/ui"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; import { formatDate } from "~utils/agents/utils"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; -import { signOut } from "~utils/embedded/embedded.utils"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; +import { signOut } from "~utils/_embedded/embedded.utils"; import browser from "webextension-polyfill"; export function AuthRestoreSharesEmbeddedView() { diff --git a/src/routes/embedded/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view.tsx b/.old-already-moved/src/routes/_embedded/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view.tsx similarity index 100% rename from src/routes/embedded/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view.tsx diff --git a/src/routes/embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx b/.old-already-moved/src/routes/_embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx similarity index 98% rename from src/routes/embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx index 42f64d48f..27c93d5e2 100644 --- a/src/routes/embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useEffect, useState } from "react"; import { toast } from "react-toastify"; import { Upload, Button, Copyable, Row, Text, Snackbar } from "~components/embed/ui"; diff --git a/src/routes/embedded/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view.tsx b/.old-already-moved/src/routes/_embedded/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view.tsx similarity index 100% rename from src/routes/embedded/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view.tsx diff --git a/.old-already-moved/src/routes/_embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx b/.old-already-moved/src/routes/_embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx new file mode 100644 index 000000000..73f96979f --- /dev/null +++ b/.old-already-moved/src/routes/_embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx @@ -0,0 +1,5 @@ +import { AuthRestoreSharesKeyfileEmbeddedView } from "~routes/_embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view"; + +export function AuthRestoreSharesRecoveryFileEmbeddedView() { + return ; +} diff --git a/src/routes/embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx b/.old-already-moved/src/routes/_embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx similarity index 98% rename from src/routes/embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx rename to .old-already-moved/src/routes/_embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx index 4ce3d8a85..b6264f387 100644 --- a/src/routes/embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx +++ b/.old-already-moved/src/routes/_embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { useCallback, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { Button, SeedInput, Row, Copyable } from "~components/embed/ui"; diff --git a/src/routes/embedded/support/unpartitioned-state/unpartitioned-state.module.scss b/.old-already-moved/src/routes/_embedded/support/unpartitioned-state/unpartitioned-state.module.scss similarity index 100% rename from src/routes/embedded/support/unpartitioned-state/unpartitioned-state.module.scss rename to .old-already-moved/src/routes/_embedded/support/unpartitioned-state/unpartitioned-state.module.scss diff --git a/src/routes/embedded/support/unpartitioned-state/unpartitioned-state.view.tsx b/.old-already-moved/src/routes/_embedded/support/unpartitioned-state/unpartitioned-state.view.tsx similarity index 99% rename from src/routes/embedded/support/unpartitioned-state/unpartitioned-state.view.tsx rename to .old-already-moved/src/routes/_embedded/support/unpartitioned-state/unpartitioned-state.view.tsx index c20681ab7..098fa1d15 100644 --- a/src/routes/embedded/support/unpartitioned-state/unpartitioned-state.view.tsx +++ b/.old-already-moved/src/routes/_embedded/support/unpartitioned-state/unpartitioned-state.view.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import React, { useCallback, useEffect, useState } from "react"; import { useLocation } from "~wallets/router/router.utils"; import { OnboardingCard } from "~components/embed/ui/molecules/card/onboarding-card/OnboardingCard"; @@ -12,7 +12,7 @@ import { useInterval } from "@swyg/corre"; import { HAS_ADVANCED_STORAGE_API } from "~iframe/storage/unpartitioned-storage/unpartitioned-storage.utils"; import { browserInfo } from "~utils/browser-info/browser-info.utils"; import { COULD_NOT_ACCESS_UNPARTITIONED_STATE_ERR_MESSAGE } from "~iframe/storage/unpartitioned-storage/unpartitioned-storage"; -import { signOut } from "~utils/embedded/embedded.utils"; +import { signOut } from "~utils/_embedded/embedded.utils"; // Logos downloaded from https://github.com/alrra/browser-logos import chromeLogoSrc from "url:assets/icons/browsers/chrome-logo.png"; diff --git a/src/routes/embedded/wallet/buy/buy.cash.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/buy.cash.view.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/buy.cash.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/buy.cash.view.tsx diff --git a/src/routes/embedded/wallet/buy/buy.container.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/buy.container.view.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/buy.container.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/buy.container.view.tsx diff --git a/src/routes/embedded/wallet/buy/buy.input.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/buy.input.view.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/buy.input.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/buy.input.view.tsx diff --git a/src/routes/embedded/wallet/buy/buy.module.scss b/.old-already-moved/src/routes/_embedded/wallet/buy/buy.module.scss similarity index 100% rename from src/routes/embedded/wallet/buy/buy.module.scss rename to .old-already-moved/src/routes/_embedded/wallet/buy/buy.module.scss diff --git a/src/routes/embedded/wallet/buy/buy.success.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/buy.success.view.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/buy.success.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/buy.success.view.tsx diff --git a/src/routes/embedded/wallet/buy/components/selector/CurrencySelector.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/components/selector/CurrencySelector.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/components/selector/CurrencySelector.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/components/selector/CurrencySelector.tsx diff --git a/src/routes/embedded/wallet/buy/components/selector/PaymentSelector.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/components/selector/PaymentSelector.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/components/selector/PaymentSelector.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/components/selector/PaymentSelector.tsx diff --git a/src/routes/embedded/wallet/buy/components/selector/SelectorContainer.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/components/selector/SelectorContainer.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/components/selector/SelectorContainer.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/components/selector/SelectorContainer.tsx diff --git a/src/routes/embedded/wallet/buy/components/selector/SelectorItem.tsx b/.old-already-moved/src/routes/_embedded/wallet/buy/components/selector/SelectorItem.tsx similarity index 100% rename from src/routes/embedded/wallet/buy/components/selector/SelectorItem.tsx rename to .old-already-moved/src/routes/_embedded/wallet/buy/components/selector/SelectorItem.tsx diff --git a/src/routes/embedded/wallet/buy/components/selector/index.ts b/.old-already-moved/src/routes/_embedded/wallet/buy/components/selector/index.ts similarity index 100% rename from src/routes/embedded/wallet/buy/components/selector/index.ts rename to .old-already-moved/src/routes/_embedded/wallet/buy/components/selector/index.ts diff --git a/src/routes/embedded/wallet/deposit/deposit.container.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/deposit/deposit.container.view.tsx similarity index 100% rename from src/routes/embedded/wallet/deposit/deposit.container.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/deposit/deposit.container.view.tsx diff --git a/src/routes/embedded/wallet/home/actions/action-item.module.scss b/.old-already-moved/src/routes/_embedded/wallet/home/actions/action-item.module.scss similarity index 100% rename from src/routes/embedded/wallet/home/actions/action-item.module.scss rename to .old-already-moved/src/routes/_embedded/wallet/home/actions/action-item.module.scss diff --git a/src/routes/embedded/wallet/home/actions/action-item.tsx b/.old-already-moved/src/routes/_embedded/wallet/home/actions/action-item.tsx similarity index 100% rename from src/routes/embedded/wallet/home/actions/action-item.tsx rename to .old-already-moved/src/routes/_embedded/wallet/home/actions/action-item.tsx diff --git a/src/routes/embedded/wallet/home/actions/actions.container.tsx b/.old-already-moved/src/routes/_embedded/wallet/home/actions/actions.container.tsx similarity index 86% rename from src/routes/embedded/wallet/home/actions/actions.container.tsx rename to .old-already-moved/src/routes/_embedded/wallet/home/actions/actions.container.tsx index 0076bfc7d..fc1fbaef9 100644 --- a/src/routes/embedded/wallet/home/actions/actions.container.tsx +++ b/.old-already-moved/src/routes/_embedded/wallet/home/actions/actions.container.tsx @@ -1,9 +1,9 @@ import { CoinsIcon, ReceiptIcon, XClose, Box } from "~components/embed/ui"; import browser from "~iframe/browser"; -import { ActionItem } from "~routes/embedded/wallet/home/actions/action-item"; -import { signOut } from "~utils/embedded/embedded.utils"; +import { ActionItem } from "~routes/_embedded/wallet/home/actions/action-item"; +import { signOut } from "~utils/_embedded/embedded.utils"; import { Lock01 } from "@untitled-ui/icons-react"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; export function WalletHomeActions() { const { user } = useEmbedded(); diff --git a/src/routes/embedded/wallet/home/assets/asset-item.module.scss b/.old-already-moved/src/routes/_embedded/wallet/home/assets/asset-item.module.scss similarity index 100% rename from src/routes/embedded/wallet/home/assets/asset-item.module.scss rename to .old-already-moved/src/routes/_embedded/wallet/home/assets/asset-item.module.scss diff --git a/src/routes/embedded/wallet/home/assets/asset-item.tsx b/.old-already-moved/src/routes/_embedded/wallet/home/assets/asset-item.tsx similarity index 100% rename from src/routes/embedded/wallet/home/assets/asset-item.tsx rename to .old-already-moved/src/routes/_embedded/wallet/home/assets/asset-item.tsx diff --git a/src/routes/embedded/wallet/home/assets/assets.container.tsx b/.old-already-moved/src/routes/_embedded/wallet/home/assets/assets.container.tsx similarity index 100% rename from src/routes/embedded/wallet/home/assets/assets.container.tsx rename to .old-already-moved/src/routes/_embedded/wallet/home/assets/assets.container.tsx diff --git a/src/routes/embedded/wallet/home/balance.container.tsx b/.old-already-moved/src/routes/_embedded/wallet/home/balance.container.tsx similarity index 100% rename from src/routes/embedded/wallet/home/balance.container.tsx rename to .old-already-moved/src/routes/_embedded/wallet/home/balance.container.tsx diff --git a/src/routes/embedded/wallet/home/wallet.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/home/wallet.view.tsx similarity index 98% rename from src/routes/embedded/wallet/home/wallet.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/home/wallet.view.tsx index d157a7a84..701f61d98 100644 --- a/src/routes/embedded/wallet/home/wallet.view.tsx +++ b/.old-already-moved/src/routes/_embedded/wallet/home/wallet.view.tsx @@ -10,7 +10,7 @@ import { WalletHomeActions } from "./actions/actions.container"; import { WalletHomeAssets } from "./assets/assets.container"; import { useBalanceSortedTokens } from "~/tokens/hooks"; import { WalletHomeBalance } from "./balance.container"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; import browser from "webextension-polyfill"; diff --git a/src/routes/embedded/wallet/receive/options/receive.module.scss b/.old-already-moved/src/routes/_embedded/wallet/receive/options/receive.module.scss similarity index 100% rename from src/routes/embedded/wallet/receive/options/receive.module.scss rename to .old-already-moved/src/routes/_embedded/wallet/receive/options/receive.module.scss diff --git a/src/routes/embedded/wallet/receive/options/receive.options.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/receive/options/receive.options.view.tsx similarity index 100% rename from src/routes/embedded/wallet/receive/options/receive.options.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/receive/options/receive.options.view.tsx diff --git a/src/routes/embedded/wallet/receive/receive.view.css b/.old-already-moved/src/routes/_embedded/wallet/receive/receive.view.css similarity index 100% rename from src/routes/embedded/wallet/receive/receive.view.css rename to .old-already-moved/src/routes/_embedded/wallet/receive/receive.view.css diff --git a/src/routes/embedded/wallet/receive/receive.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/receive/receive.view.tsx similarity index 96% rename from src/routes/embedded/wallet/receive/receive.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/receive/receive.view.tsx index 0dec7a879..9f3874c5b 100644 --- a/src/routes/embedded/wallet/receive/receive.view.tsx +++ b/.old-already-moved/src/routes/_embedded/wallet/receive/receive.view.tsx @@ -4,7 +4,7 @@ import "./receive.view.css"; import { Card, Text, Copyable, Box } from "~components/embed/ui"; import { useActiveWallet } from "~wallets/hooks"; import { useLocation } from "~wallets/router/router.utils"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; import copy from "copy-to-clipboard"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; diff --git a/src/routes/embedded/wallet/transactions-history/transactions-history.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/transactions-history/transactions-history.view.tsx similarity index 100% rename from src/routes/embedded/wallet/transactions-history/transactions-history.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/transactions-history/transactions-history.view.tsx diff --git a/src/routes/embedded/wallet/transactions/components/TransactionGroup.tsx b/.old-already-moved/src/routes/_embedded/wallet/transactions/components/TransactionGroup.tsx similarity index 100% rename from src/routes/embedded/wallet/transactions/components/TransactionGroup.tsx rename to .old-already-moved/src/routes/_embedded/wallet/transactions/components/TransactionGroup.tsx diff --git a/src/routes/embedded/wallet/transactions/components/TransactionItem.tsx b/.old-already-moved/src/routes/_embedded/wallet/transactions/components/TransactionItem.tsx similarity index 100% rename from src/routes/embedded/wallet/transactions/components/TransactionItem.tsx rename to .old-already-moved/src/routes/_embedded/wallet/transactions/components/TransactionItem.tsx diff --git a/src/routes/embedded/wallet/transactions/transaction-complete.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/transactions/transaction-complete.view.tsx similarity index 95% rename from src/routes/embedded/wallet/transactions/transaction-complete.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/transactions/transaction-complete.view.tsx index 87b6f7e33..0e76020e7 100644 --- a/src/routes/embedded/wallet/transactions/transaction-complete.view.tsx +++ b/.old-already-moved/src/routes/_embedded/wallet/transactions/transaction-complete.view.tsx @@ -4,7 +4,7 @@ import type { WanderRoutePath, CommonRouteProps } from "~wallets/router/router.t import Lottie from "react-lottie"; import checkmarkAnimationData from "assets/lotties/checkmark.json"; import { Box, Card, XClose, Text, Button } from "~components/embed/ui"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; export interface TransactionCompletedParams { diff --git a/src/routes/embedded/wallet/transactions/transactions.view.tsx b/.old-already-moved/src/routes/_embedded/wallet/transactions/transactions.view.tsx similarity index 100% rename from src/routes/embedded/wallet/transactions/transactions.view.tsx rename to .old-already-moved/src/routes/_embedded/wallet/transactions/transactions.view.tsx diff --git a/src/routes/auth/allowance.tsx b/.old-already-moved/src/routes/auth/allowance.tsx similarity index 100% rename from src/routes/auth/allowance.tsx rename to .old-already-moved/src/routes/auth/allowance.tsx diff --git a/src/routes/auth/batchSignDataItem.tsx b/.old-already-moved/src/routes/auth/batchSignDataItem.tsx similarity index 100% rename from src/routes/auth/batchSignDataItem.tsx rename to .old-already-moved/src/routes/auth/batchSignDataItem.tsx diff --git a/src/routes/auth/connect.tsx b/.old-already-moved/src/routes/auth/connect.tsx similarity index 100% rename from src/routes/auth/connect.tsx rename to .old-already-moved/src/routes/auth/connect.tsx diff --git a/src/routes/auth/decrypt.tsx b/.old-already-moved/src/routes/auth/decrypt.tsx similarity index 100% rename from src/routes/auth/decrypt.tsx rename to .old-already-moved/src/routes/auth/decrypt.tsx diff --git a/src/routes/auth/loading.tsx b/.old-already-moved/src/routes/auth/loading.tsx similarity index 100% rename from src/routes/auth/loading.tsx rename to .old-already-moved/src/routes/auth/loading.tsx diff --git a/src/routes/auth/sign.tsx b/.old-already-moved/src/routes/auth/sign.tsx similarity index 100% rename from src/routes/auth/sign.tsx rename to .old-already-moved/src/routes/auth/sign.tsx diff --git a/src/routes/auth/signDataItem.tsx b/.old-already-moved/src/routes/auth/signDataItem.tsx similarity index 100% rename from src/routes/auth/signDataItem.tsx rename to .old-already-moved/src/routes/auth/signDataItem.tsx diff --git a/src/routes/auth/signKeystone.tsx b/.old-already-moved/src/routes/auth/signKeystone.tsx similarity index 100% rename from src/routes/auth/signKeystone.tsx rename to .old-already-moved/src/routes/auth/signKeystone.tsx diff --git a/src/routes/auth/signature.tsx b/.old-already-moved/src/routes/auth/signature.tsx similarity index 100% rename from src/routes/auth/signature.tsx rename to .old-already-moved/src/routes/auth/signature.tsx diff --git a/src/routes/auth/subscription.tsx b/.old-already-moved/src/routes/auth/subscription.tsx similarity index 100% rename from src/routes/auth/subscription.tsx rename to .old-already-moved/src/routes/auth/subscription.tsx diff --git a/src/routes/auth/unlock.tsx b/.old-already-moved/src/routes/auth/unlock.tsx similarity index 100% rename from src/routes/auth/unlock.tsx rename to .old-already-moved/src/routes/auth/unlock.tsx diff --git a/src/routes/dashboard/dashboard.constants.ts b/.old-already-moved/src/routes/dashboard/dashboard.constants.ts similarity index 98% rename from src/routes/dashboard/dashboard.constants.ts rename to .old-already-moved/src/routes/dashboard/dashboard.constants.ts index 96bad4920..d44b9e23c 100644 --- a/src/routes/dashboard/dashboard.constants.ts +++ b/.old-already-moved/src/routes/dashboard/dashboard.constants.ts @@ -32,7 +32,7 @@ import { HelpDashboardView } from "~components/dashboard/subsettings/Help"; import { SignSettingsDashboardView } from "~components/dashboard/SignSettings"; import { ResetDashboardView } from "~components/dashboard/Reset"; import { AnalyticsSettingsDashboardView } from "~components/dashboard/Analytics"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; export interface DashboardRouteConfig extends Omit { name: string; diff --git a/src/routes/dashboard/index.tsx b/.old-already-moved/src/routes/dashboard/index.tsx similarity index 100% rename from src/routes/dashboard/index.tsx rename to .old-already-moved/src/routes/dashboard/index.tsx diff --git a/src/routes/popup/agents/ao-yield/agent-activated.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/agent-activated.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/agent-activated.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/agent-activated.tsx diff --git a/src/routes/popup/agents/ao-yield/agent-history.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/agent-history.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/agent-history.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/agent-history.tsx diff --git a/src/routes/popup/agents/ao-yield/agent-info.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/agent-info.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/agent-info.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/agent-info.tsx diff --git a/src/routes/popup/agents/ao-yield/agent-transaction-history.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/agent-transaction-history.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/agent-transaction-history.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/agent-transaction-history.tsx diff --git a/src/routes/popup/agents/ao-yield/confirm-agent.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/confirm-agent.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/confirm-agent.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/confirm-agent.tsx diff --git a/src/routes/popup/agents/ao-yield/create-agent.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/create-agent.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/create-agent.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/create-agent.tsx diff --git a/src/routes/popup/agents/ao-yield/edit-agent.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/edit-agent.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/edit-agent.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/edit-agent.tsx diff --git a/src/routes/popup/agents/ao-yield/manage-agent.tsx b/.old-already-moved/src/routes/popup/agents/ao-yield/manage-agent.tsx similarity index 100% rename from src/routes/popup/agents/ao-yield/manage-agent.tsx rename to .old-already-moved/src/routes/popup/agents/ao-yield/manage-agent.tsx diff --git a/src/routes/popup/agents/components/AODelegationModal.tsx b/.old-already-moved/src/routes/popup/agents/components/AODelegationModal.tsx similarity index 100% rename from src/routes/popup/agents/components/AODelegationModal.tsx rename to .old-already-moved/src/routes/popup/agents/components/AODelegationModal.tsx diff --git a/src/routes/popup/agents/components/AOMintingPausedListItem.tsx b/.old-already-moved/src/routes/popup/agents/components/AOMintingPausedListItem.tsx similarity index 100% rename from src/routes/popup/agents/components/AOMintingPausedListItem.tsx rename to .old-already-moved/src/routes/popup/agents/components/AOMintingPausedListItem.tsx diff --git a/src/routes/popup/agents/components/AOMintingStatusModal.tsx b/.old-already-moved/src/routes/popup/agents/components/AOMintingStatusModal.tsx similarity index 100% rename from src/routes/popup/agents/components/AOMintingStatusModal.tsx rename to .old-already-moved/src/routes/popup/agents/components/AOMintingStatusModal.tsx diff --git a/src/routes/popup/agents/components/AOYieldAgentListItem.tsx b/.old-already-moved/src/routes/popup/agents/components/AOYieldAgentListItem.tsx similarity index 100% rename from src/routes/popup/agents/components/AOYieldAgentListItem.tsx rename to .old-already-moved/src/routes/popup/agents/components/AOYieldAgentListItem.tsx diff --git a/src/routes/popup/agents/components/CreateWanderAgentCTA.tsx b/.old-already-moved/src/routes/popup/agents/components/CreateWanderAgentCTA.tsx similarity index 100% rename from src/routes/popup/agents/components/CreateWanderAgentCTA.tsx rename to .old-already-moved/src/routes/popup/agents/components/CreateWanderAgentCTA.tsx diff --git a/src/routes/popup/agents/components/LiquidOpsAgentListItem.tsx b/.old-already-moved/src/routes/popup/agents/components/LiquidOpsAgentListItem.tsx similarity index 100% rename from src/routes/popup/agents/components/LiquidOpsAgentListItem.tsx rename to .old-already-moved/src/routes/popup/agents/components/LiquidOpsAgentListItem.tsx diff --git a/src/routes/popup/agents/components/StatusLabel.tsx b/.old-already-moved/src/routes/popup/agents/components/StatusLabel.tsx similarity index 100% rename from src/routes/popup/agents/components/StatusLabel.tsx rename to .old-already-moved/src/routes/popup/agents/components/StatusLabel.tsx diff --git a/src/routes/popup/agents/components/SvgImage.tsx b/.old-already-moved/src/routes/popup/agents/components/SvgImage.tsx similarity index 100% rename from src/routes/popup/agents/components/SvgImage.tsx rename to .old-already-moved/src/routes/popup/agents/components/SvgImage.tsx diff --git a/src/routes/popup/agents/components/WanderAgentExplainerPopup.tsx b/.old-already-moved/src/routes/popup/agents/components/WanderAgentExplainerPopup.tsx similarity index 100% rename from src/routes/popup/agents/components/WanderAgentExplainerPopup.tsx rename to .old-already-moved/src/routes/popup/agents/components/WanderAgentExplainerPopup.tsx diff --git a/src/routes/popup/agents/components/ao-yield/AgentCancelModal.tsx b/.old-already-moved/src/routes/popup/agents/components/ao-yield/AgentCancelModal.tsx similarity index 100% rename from src/routes/popup/agents/components/ao-yield/AgentCancelModal.tsx rename to .old-already-moved/src/routes/popup/agents/components/ao-yield/AgentCancelModal.tsx diff --git a/src/routes/popup/agents/components/ao-yield/AssetSelectorModal.tsx b/.old-already-moved/src/routes/popup/agents/components/ao-yield/AssetSelectorModal.tsx similarity index 100% rename from src/routes/popup/agents/components/ao-yield/AssetSelectorModal.tsx rename to .old-already-moved/src/routes/popup/agents/components/ao-yield/AssetSelectorModal.tsx diff --git a/src/routes/popup/agents/components/ao-yield/CustomSelect.tsx b/.old-already-moved/src/routes/popup/agents/components/ao-yield/CustomSelect.tsx similarity index 100% rename from src/routes/popup/agents/components/ao-yield/CustomSelect.tsx rename to .old-already-moved/src/routes/popup/agents/components/ao-yield/CustomSelect.tsx diff --git a/src/routes/popup/agents/components/ao-yield/DateSelectorModal.tsx b/.old-already-moved/src/routes/popup/agents/components/ao-yield/DateSelectorModal.tsx similarity index 100% rename from src/routes/popup/agents/components/ao-yield/DateSelectorModal.tsx rename to .old-already-moved/src/routes/popup/agents/components/ao-yield/DateSelectorModal.tsx diff --git a/src/routes/popup/agents/components/ao-yield/SlippageSelectorModal.tsx b/.old-already-moved/src/routes/popup/agents/components/ao-yield/SlippageSelectorModal.tsx similarity index 100% rename from src/routes/popup/agents/components/ao-yield/SlippageSelectorModal.tsx rename to .old-already-moved/src/routes/popup/agents/components/ao-yield/SlippageSelectorModal.tsx diff --git a/src/routes/popup/agents/components/ao-yield/agent-info.tsx b/.old-already-moved/src/routes/popup/agents/components/ao-yield/agent-info.tsx similarity index 100% rename from src/routes/popup/agents/components/ao-yield/agent-info.tsx rename to .old-already-moved/src/routes/popup/agents/components/ao-yield/agent-info.tsx diff --git a/src/routes/popup/agents/components/liquidops/Agent.tsx b/.old-already-moved/src/routes/popup/agents/components/liquidops/Agent.tsx similarity index 100% rename from src/routes/popup/agents/components/liquidops/Agent.tsx rename to .old-already-moved/src/routes/popup/agents/components/liquidops/Agent.tsx diff --git a/src/routes/popup/agents/components/liquidops/AgentStats.tsx b/.old-already-moved/src/routes/popup/agents/components/liquidops/AgentStats.tsx similarity index 100% rename from src/routes/popup/agents/components/liquidops/AgentStats.tsx rename to .old-already-moved/src/routes/popup/agents/components/liquidops/AgentStats.tsx diff --git a/src/routes/popup/agents/components/liquidops/Info.tsx b/.old-already-moved/src/routes/popup/agents/components/liquidops/Info.tsx similarity index 100% rename from src/routes/popup/agents/components/liquidops/Info.tsx rename to .old-already-moved/src/routes/popup/agents/components/liquidops/Info.tsx diff --git a/src/routes/popup/agents/index.tsx b/.old-already-moved/src/routes/popup/agents/index.tsx similarity index 100% rename from src/routes/popup/agents/index.tsx rename to .old-already-moved/src/routes/popup/agents/index.tsx diff --git a/src/routes/popup/agents/liquidops/agent.tsx b/.old-already-moved/src/routes/popup/agents/liquidops/agent.tsx similarity index 100% rename from src/routes/popup/agents/liquidops/agent.tsx rename to .old-already-moved/src/routes/popup/agents/liquidops/agent.tsx diff --git a/src/routes/popup/agents/liquidops/agents.tsx b/.old-already-moved/src/routes/popup/agents/liquidops/agents.tsx similarity index 100% rename from src/routes/popup/agents/liquidops/agents.tsx rename to .old-already-moved/src/routes/popup/agents/liquidops/agents.tsx diff --git a/src/routes/popup/agents/liquidops/confirm.tsx b/.old-already-moved/src/routes/popup/agents/liquidops/confirm.tsx similarity index 100% rename from src/routes/popup/agents/liquidops/confirm.tsx rename to .old-already-moved/src/routes/popup/agents/liquidops/confirm.tsx diff --git a/src/routes/popup/agents/liquidops/depositwithdraw.tsx b/.old-already-moved/src/routes/popup/agents/liquidops/depositwithdraw.tsx similarity index 100% rename from src/routes/popup/agents/liquidops/depositwithdraw.tsx rename to .old-already-moved/src/routes/popup/agents/liquidops/depositwithdraw.tsx diff --git a/src/routes/popup/agents/liquidops/result.tsx b/.old-already-moved/src/routes/popup/agents/liquidops/result.tsx similarity index 100% rename from src/routes/popup/agents/liquidops/result.tsx rename to .old-already-moved/src/routes/popup/agents/liquidops/result.tsx diff --git a/src/routes/popup/agents/liquidops/utils/LiquidOps.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/LiquidOps.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/LiquidOps.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/LiquidOps.ts diff --git a/src/routes/popup/agents/liquidops/utils/format.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/format.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/format.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/format.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/actions/useLend.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/actions/useLend.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/actions/useLend.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/actions/useLend.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useAPYOrder.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useAPYOrder.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useAPYOrder.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useAPYOrder.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useAvailableTokens.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useAvailableTokens.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useAvailableTokens.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useAvailableTokens.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useEarnings.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useEarnings.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useEarnings.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useEarnings.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useGateway.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useGateway.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useGateway.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useGateway.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useLOAssetBalance.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useLOAssetBalance.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useLOAssetBalance.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useLOAssetBalance.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useLOOTokenBalance.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useLOOTokenBalance.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useLOOTokenBalance.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useLOOTokenBalance.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useLOSupplyAPY.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useLOSupplyAPY.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useLOSupplyAPY.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useLOSupplyAPY.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useOExchangeRate.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useOExchangeRate.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useOExchangeRate.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useOExchangeRate.ts diff --git a/src/routes/popup/agents/liquidops/utils/hooks/useTokenStatus.ts b/.old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useTokenStatus.ts similarity index 100% rename from src/routes/popup/agents/liquidops/utils/hooks/useTokenStatus.ts rename to .old-already-moved/src/routes/popup/agents/liquidops/utils/hooks/useTokenStatus.ts diff --git a/src/routes/popup/announcement.tsx b/.old-already-moved/src/routes/popup/announcement.tsx similarity index 100% rename from src/routes/popup/announcement.tsx rename to .old-already-moved/src/routes/popup/announcement.tsx diff --git a/src/routes/popup/collectible/[id].tsx b/.old-already-moved/src/routes/popup/collectible/[id].tsx similarity index 100% rename from src/routes/popup/collectible/[id].tsx rename to .old-already-moved/src/routes/popup/collectible/[id].tsx diff --git a/src/routes/popup/collectibles.tsx b/.old-already-moved/src/routes/popup/collectibles.tsx similarity index 100% rename from src/routes/popup/collectibles.tsx rename to .old-already-moved/src/routes/popup/collectibles.tsx diff --git a/src/routes/popup/confirm.tsx b/.old-already-moved/src/routes/popup/confirm.tsx similarity index 100% rename from src/routes/popup/confirm.tsx rename to .old-already-moved/src/routes/popup/confirm.tsx diff --git a/src/routes/popup/earn/allocation-set.tsx b/.old-already-moved/src/routes/popup/earn/allocation-set.tsx similarity index 100% rename from src/routes/popup/earn/allocation-set.tsx rename to .old-already-moved/src/routes/popup/earn/allocation-set.tsx diff --git a/src/routes/popup/earn/index.tsx b/.old-already-moved/src/routes/popup/earn/index.tsx similarity index 100% rename from src/routes/popup/earn/index.tsx rename to .old-already-moved/src/routes/popup/earn/index.tsx diff --git a/src/routes/popup/earn/manage.tsx b/.old-already-moved/src/routes/popup/earn/manage.tsx similarity index 100% rename from src/routes/popup/earn/manage.tsx rename to .old-already-moved/src/routes/popup/earn/manage.tsx diff --git a/src/routes/popup/gettingStarted.tsx b/.old-already-moved/src/routes/popup/gettingStarted.tsx similarity index 100% rename from src/routes/popup/gettingStarted.tsx rename to .old-already-moved/src/routes/popup/gettingStarted.tsx diff --git a/src/routes/popup/index.tsx b/.old-already-moved/src/routes/popup/index.tsx similarity index 100% rename from src/routes/popup/index.tsx rename to .old-already-moved/src/routes/popup/index.tsx diff --git a/src/routes/popup/notification/[id].tsx b/.old-already-moved/src/routes/popup/notification/[id].tsx similarity index 100% rename from src/routes/popup/notification/[id].tsx rename to .old-already-moved/src/routes/popup/notification/[id].tsx diff --git a/src/routes/popup/passwordPopup.tsx b/.old-already-moved/src/routes/popup/passwordPopup.tsx similarity index 100% rename from src/routes/popup/passwordPopup.tsx rename to .old-already-moved/src/routes/popup/passwordPopup.tsx diff --git a/src/routes/popup/pending.tsx b/.old-already-moved/src/routes/popup/pending.tsx similarity index 100% rename from src/routes/popup/pending.tsx rename to .old-already-moved/src/routes/popup/pending.tsx diff --git a/src/routes/popup/purchase.tsx b/.old-already-moved/src/routes/popup/purchase.tsx similarity index 100% rename from src/routes/popup/purchase.tsx rename to .old-already-moved/src/routes/popup/purchase.tsx diff --git a/src/routes/popup/receive.tsx b/.old-already-moved/src/routes/popup/receive.tsx similarity index 100% rename from src/routes/popup/receive.tsx rename to .old-already-moved/src/routes/popup/receive.tsx diff --git a/src/routes/popup/send/amount.tsx b/.old-already-moved/src/routes/popup/send/amount.tsx similarity index 100% rename from src/routes/popup/send/amount.tsx rename to .old-already-moved/src/routes/popup/send/amount.tsx diff --git a/src/routes/popup/send/announcement.tsx b/.old-already-moved/src/routes/popup/send/announcement.tsx similarity index 100% rename from src/routes/popup/send/announcement.tsx rename to .old-already-moved/src/routes/popup/send/announcement.tsx diff --git a/src/routes/popup/send/auth.tsx b/.old-already-moved/src/routes/popup/send/auth.tsx similarity index 100% rename from src/routes/popup/send/auth.tsx rename to .old-already-moved/src/routes/popup/send/auth.tsx diff --git a/src/routes/popup/send/completed.tsx b/.old-already-moved/src/routes/popup/send/completed.tsx similarity index 100% rename from src/routes/popup/send/completed.tsx rename to .old-already-moved/src/routes/popup/send/completed.tsx diff --git a/src/routes/popup/send/confirm.tsx b/.old-already-moved/src/routes/popup/send/confirm.tsx similarity index 100% rename from src/routes/popup/send/confirm.tsx rename to .old-already-moved/src/routes/popup/send/confirm.tsx diff --git a/src/routes/popup/send/index.tsx b/.old-already-moved/src/routes/popup/send/index.tsx similarity index 100% rename from src/routes/popup/send/index.tsx rename to .old-already-moved/src/routes/popup/send/index.tsx diff --git a/src/routes/popup/send/note.tsx b/.old-already-moved/src/routes/popup/send/note.tsx similarity index 100% rename from src/routes/popup/send/note.tsx rename to .old-already-moved/src/routes/popup/send/note.tsx diff --git a/src/routes/popup/settings/apps/[url]/index.tsx b/.old-already-moved/src/routes/popup/settings/apps/[url]/index.tsx similarity index 100% rename from src/routes/popup/settings/apps/[url]/index.tsx rename to .old-already-moved/src/routes/popup/settings/apps/[url]/index.tsx diff --git a/src/routes/popup/settings/apps/[url]/permissions.tsx b/.old-already-moved/src/routes/popup/settings/apps/[url]/permissions.tsx similarity index 100% rename from src/routes/popup/settings/apps/[url]/permissions.tsx rename to .old-already-moved/src/routes/popup/settings/apps/[url]/permissions.tsx diff --git a/src/routes/popup/settings/apps/index.tsx b/.old-already-moved/src/routes/popup/settings/apps/index.tsx similarity index 100% rename from src/routes/popup/settings/apps/index.tsx rename to .old-already-moved/src/routes/popup/settings/apps/index.tsx diff --git a/src/routes/popup/settings/contacts/[address]/index.tsx b/.old-already-moved/src/routes/popup/settings/contacts/[address]/index.tsx similarity index 100% rename from src/routes/popup/settings/contacts/[address]/index.tsx rename to .old-already-moved/src/routes/popup/settings/contacts/[address]/index.tsx diff --git a/src/routes/popup/settings/contacts/index.tsx b/.old-already-moved/src/routes/popup/settings/contacts/index.tsx similarity index 100% rename from src/routes/popup/settings/contacts/index.tsx rename to .old-already-moved/src/routes/popup/settings/contacts/index.tsx diff --git a/src/routes/popup/settings/contacts/new.tsx b/.old-already-moved/src/routes/popup/settings/contacts/new.tsx similarity index 100% rename from src/routes/popup/settings/contacts/new.tsx rename to .old-already-moved/src/routes/popup/settings/contacts/new.tsx diff --git a/src/routes/popup/settings/help/index.tsx b/.old-already-moved/src/routes/popup/settings/help/index.tsx similarity index 100% rename from src/routes/popup/settings/help/index.tsx rename to .old-already-moved/src/routes/popup/settings/help/index.tsx diff --git a/src/routes/popup/settings/index.tsx b/.old-already-moved/src/routes/popup/settings/index.tsx similarity index 99% rename from src/routes/popup/settings/index.tsx rename to .old-already-moved/src/routes/popup/settings/index.tsx index 9523816fa..0ac6e78a6 100644 --- a/src/routes/popup/settings/index.tsx +++ b/.old-already-moved/src/routes/popup/settings/index.tsx @@ -19,7 +19,7 @@ import type { StoredWallet } from "~wallets"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { NoAvatarIcon } from "~components/Avatar"; -import { signOut } from "~utils/embedded/embedded.utils"; +import { signOut } from "~utils/_embedded/embedded.utils"; import { BackupSeedphraseWarning } from "~components/popup/settings/BackupSeedphraseWarning"; import { WalletName } from "~components/dashboard/list/WalletListItem"; diff --git a/src/routes/popup/settings/notifications/index.tsx b/.old-already-moved/src/routes/popup/settings/notifications/index.tsx similarity index 100% rename from src/routes/popup/settings/notifications/index.tsx rename to .old-already-moved/src/routes/popup/settings/notifications/index.tsx diff --git a/src/routes/popup/settings/tokens/[id]/index.tsx b/.old-already-moved/src/routes/popup/settings/tokens/[id]/index.tsx similarity index 100% rename from src/routes/popup/settings/tokens/[id]/index.tsx rename to .old-already-moved/src/routes/popup/settings/tokens/[id]/index.tsx diff --git a/src/routes/popup/settings/tokens/index.tsx b/.old-already-moved/src/routes/popup/settings/tokens/index.tsx similarity index 100% rename from src/routes/popup/settings/tokens/index.tsx rename to .old-already-moved/src/routes/popup/settings/tokens/index.tsx diff --git a/src/routes/popup/settings/tokens/new.tsx b/.old-already-moved/src/routes/popup/settings/tokens/new.tsx similarity index 100% rename from src/routes/popup/settings/tokens/new.tsx rename to .old-already-moved/src/routes/popup/settings/tokens/new.tsx diff --git a/src/routes/popup/settings/wallets/[address]/export.tsx b/.old-already-moved/src/routes/popup/settings/wallets/[address]/export.tsx similarity index 100% rename from src/routes/popup/settings/wallets/[address]/export.tsx rename to .old-already-moved/src/routes/popup/settings/wallets/[address]/export.tsx diff --git a/src/routes/popup/settings/wallets/[address]/index.tsx b/.old-already-moved/src/routes/popup/settings/wallets/[address]/index.tsx similarity index 100% rename from src/routes/popup/settings/wallets/[address]/index.tsx rename to .old-already-moved/src/routes/popup/settings/wallets/[address]/index.tsx diff --git a/src/routes/popup/settings/wallets/[address]/qr.tsx b/.old-already-moved/src/routes/popup/settings/wallets/[address]/qr.tsx similarity index 100% rename from src/routes/popup/settings/wallets/[address]/qr.tsx rename to .old-already-moved/src/routes/popup/settings/wallets/[address]/qr.tsx diff --git a/src/routes/popup/settings/wallets/[address]/recovery-phrase.tsx b/.old-already-moved/src/routes/popup/settings/wallets/[address]/recovery-phrase.tsx similarity index 100% rename from src/routes/popup/settings/wallets/[address]/recovery-phrase.tsx rename to .old-already-moved/src/routes/popup/settings/wallets/[address]/recovery-phrase.tsx diff --git a/src/routes/popup/settings/wallets/index.tsx b/.old-already-moved/src/routes/popup/settings/wallets/index.tsx similarity index 100% rename from src/routes/popup/settings/wallets/index.tsx rename to .old-already-moved/src/routes/popup/settings/wallets/index.tsx diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/.old-already-moved/src/routes/popup/subscriptions/subscriptionDetails.tsx similarity index 100% rename from src/routes/popup/subscriptions/subscriptionDetails.tsx rename to .old-already-moved/src/routes/popup/subscriptions/subscriptionDetails.tsx diff --git a/src/routes/popup/subscriptions/subscriptionManagement.tsx b/.old-already-moved/src/routes/popup/subscriptions/subscriptionManagement.tsx similarity index 100% rename from src/routes/popup/subscriptions/subscriptionManagement.tsx rename to .old-already-moved/src/routes/popup/subscriptions/subscriptionManagement.tsx diff --git a/src/routes/popup/subscriptions/subscriptionPayment.tsx b/.old-already-moved/src/routes/popup/subscriptions/subscriptionPayment.tsx similarity index 100% rename from src/routes/popup/subscriptions/subscriptionPayment.tsx rename to .old-already-moved/src/routes/popup/subscriptions/subscriptionPayment.tsx diff --git a/src/routes/popup/subscriptions/subscriptions.tsx b/.old-already-moved/src/routes/popup/subscriptions/subscriptions.tsx similarity index 100% rename from src/routes/popup/subscriptions/subscriptions.tsx rename to .old-already-moved/src/routes/popup/subscriptions/subscriptions.tsx diff --git a/src/routes/popup/tier.tsx b/.old-already-moved/src/routes/popup/tier.tsx similarity index 100% rename from src/routes/popup/tier.tsx rename to .old-already-moved/src/routes/popup/tier.tsx diff --git a/src/routes/popup/tokens/index.tsx b/.old-already-moved/src/routes/popup/tokens/index.tsx similarity index 100% rename from src/routes/popup/tokens/index.tsx rename to .old-already-moved/src/routes/popup/tokens/index.tsx diff --git a/src/routes/popup/tokens/token-detail.tsx b/.old-already-moved/src/routes/popup/tokens/token-detail.tsx similarity index 100% rename from src/routes/popup/tokens/token-detail.tsx rename to .old-already-moved/src/routes/popup/tokens/token-detail.tsx diff --git a/src/routes/popup/transaction/[id].tsx b/.old-already-moved/src/routes/popup/transaction/[id].tsx similarity index 100% rename from src/routes/popup/transaction/[id].tsx rename to .old-already-moved/src/routes/popup/transaction/[id].tsx diff --git a/src/routes/popup/transaction/transactions.tsx b/.old-already-moved/src/routes/popup/transaction/transactions.tsx similarity index 100% rename from src/routes/popup/transaction/transactions.tsx rename to .old-already-moved/src/routes/popup/transaction/transactions.tsx diff --git a/src/routes/popup/unlock.tsx b/.old-already-moved/src/routes/popup/unlock.tsx similarity index 100% rename from src/routes/popup/unlock.tsx rename to .old-already-moved/src/routes/popup/unlock.tsx diff --git a/src/routes/welcome/PinExtension.tsx b/.old-already-moved/src/routes/welcome/PinExtension.tsx similarity index 100% rename from src/routes/welcome/PinExtension.tsx rename to .old-already-moved/src/routes/welcome/PinExtension.tsx diff --git a/src/routes/welcome/UpdateSplash.tsx b/.old-already-moved/src/routes/welcome/UpdateSplash.tsx similarity index 100% rename from src/routes/welcome/UpdateSplash.tsx rename to .old-already-moved/src/routes/welcome/UpdateSplash.tsx diff --git a/src/routes/welcome/WanderLoading.tsx b/.old-already-moved/src/routes/welcome/WanderLoading.tsx similarity index 100% rename from src/routes/welcome/WanderLoading.tsx rename to .old-already-moved/src/routes/welcome/WanderLoading.tsx diff --git a/src/routes/welcome/generate/account.tsx b/.old-already-moved/src/routes/welcome/generate/account.tsx similarity index 100% rename from src/routes/welcome/generate/account.tsx rename to .old-already-moved/src/routes/welcome/generate/account.tsx diff --git a/src/routes/welcome/generate/backup.tsx b/.old-already-moved/src/routes/welcome/generate/backup.tsx similarity index 100% rename from src/routes/welcome/generate/backup.tsx rename to .old-already-moved/src/routes/welcome/generate/backup.tsx diff --git a/src/routes/welcome/generate/confirm.tsx b/.old-already-moved/src/routes/welcome/generate/confirm.tsx similarity index 100% rename from src/routes/welcome/generate/confirm.tsx rename to .old-already-moved/src/routes/welcome/generate/confirm.tsx diff --git a/src/routes/welcome/generate/done.tsx b/.old-already-moved/src/routes/welcome/generate/done.tsx similarity index 100% rename from src/routes/welcome/generate/done.tsx rename to .old-already-moved/src/routes/welcome/generate/done.tsx diff --git a/src/routes/welcome/generate/loading.tsx b/.old-already-moved/src/routes/welcome/generate/loading.tsx similarity index 100% rename from src/routes/welcome/generate/loading.tsx rename to .old-already-moved/src/routes/welcome/generate/loading.tsx diff --git a/src/routes/welcome/generate/permissions.tsx b/.old-already-moved/src/routes/welcome/generate/permissions.tsx similarity index 100% rename from src/routes/welcome/generate/permissions.tsx rename to .old-already-moved/src/routes/welcome/generate/permissions.tsx diff --git a/src/routes/welcome/gettingStarted.tsx b/.old-already-moved/src/routes/welcome/gettingStarted.tsx similarity index 100% rename from src/routes/welcome/gettingStarted.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted.tsx diff --git a/src/routes/welcome/gettingStarted/connect.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/connect.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/connect.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/connect.tsx diff --git a/src/routes/welcome/gettingStarted/enableNotifications.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/enableNotifications.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/enableNotifications.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/enableNotifications.tsx diff --git a/src/routes/welcome/gettingStarted/explore.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/explore.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/explore.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/explore.tsx diff --git a/src/routes/welcome/gettingStarted/onramp.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/onramp.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/onramp.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/onramp.tsx diff --git a/src/routes/welcome/gettingStarted/personalize.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/personalize.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/personalize.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/personalize.tsx diff --git a/src/routes/welcome/gettingStarted/pin.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/pin.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/pin.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/pin.tsx diff --git a/src/routes/welcome/gettingStarted/tokens.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/tokens.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/tokens.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/tokens.tsx diff --git a/src/routes/welcome/gettingStarted/welcome.tsx b/.old-already-moved/src/routes/welcome/gettingStarted/welcome.tsx similarity index 100% rename from src/routes/welcome/gettingStarted/welcome.tsx rename to .old-already-moved/src/routes/welcome/gettingStarted/welcome.tsx diff --git a/src/routes/welcome/index.tsx b/.old-already-moved/src/routes/welcome/index.tsx similarity index 100% rename from src/routes/welcome/index.tsx rename to .old-already-moved/src/routes/welcome/index.tsx diff --git a/src/routes/welcome/load/done.tsx b/.old-already-moved/src/routes/welcome/load/done.tsx similarity index 100% rename from src/routes/welcome/load/done.tsx rename to .old-already-moved/src/routes/welcome/load/done.tsx diff --git a/src/routes/welcome/load/options.tsx b/.old-already-moved/src/routes/welcome/load/options.tsx similarity index 100% rename from src/routes/welcome/load/options.tsx rename to .old-already-moved/src/routes/welcome/load/options.tsx diff --git a/src/routes/welcome/load/password.tsx b/.old-already-moved/src/routes/welcome/load/password.tsx similarity index 100% rename from src/routes/welcome/load/password.tsx rename to .old-already-moved/src/routes/welcome/load/password.tsx diff --git a/src/routes/welcome/load/theme.tsx b/.old-already-moved/src/routes/welcome/load/theme.tsx similarity index 100% rename from src/routes/welcome/load/theme.tsx rename to .old-already-moved/src/routes/welcome/load/theme.tsx diff --git a/src/routes/welcome/load/wallets.tsx b/.old-already-moved/src/routes/welcome/load/wallets.tsx similarity index 100% rename from src/routes/welcome/load/wallets.tsx rename to .old-already-moved/src/routes/welcome/load/wallets.tsx diff --git a/src/routes/welcome/setup.tsx b/.old-already-moved/src/routes/welcome/setup.tsx similarity index 100% rename from src/routes/welcome/setup.tsx rename to .old-already-moved/src/routes/welcome/setup.tsx diff --git a/src/sdk-entrypoint/sdk-entrypoint.ts b/.old-already-moved/src/sdk-entrypoint/sdk-entrypoint.ts similarity index 100% rename from src/sdk-entrypoint/sdk-entrypoint.ts rename to .old-already-moved/src/sdk-entrypoint/sdk-entrypoint.ts diff --git a/src/settings/hook.ts b/.old-already-moved/src/settings/hook.ts similarity index 100% rename from src/settings/hook.ts rename to .old-already-moved/src/settings/hook.ts diff --git a/src/settings/index.ts b/.old-already-moved/src/settings/index.ts similarity index 100% rename from src/settings/index.ts rename to .old-already-moved/src/settings/index.ts diff --git a/src/settings/setting.ts b/.old-already-moved/src/settings/setting.ts similarity index 100% rename from src/settings/setting.ts rename to .old-already-moved/src/settings/setting.ts diff --git a/src/subscriptions/index.ts b/.old-already-moved/src/subscriptions/index.ts similarity index 100% rename from src/subscriptions/index.ts rename to .old-already-moved/src/subscriptions/index.ts diff --git a/src/subscriptions/payments.ts b/.old-already-moved/src/subscriptions/payments.ts similarity index 100% rename from src/subscriptions/payments.ts rename to .old-already-moved/src/subscriptions/payments.ts diff --git a/src/subscriptions/subscription.ts b/.old-already-moved/src/subscriptions/subscription.ts similarity index 100% rename from src/subscriptions/subscription.ts rename to .old-already-moved/src/subscriptions/subscription.ts diff --git a/src/tabs/arlocal.html b/.old-already-moved/src/tabs/arlocal.html similarity index 100% rename from src/tabs/arlocal.html rename to .old-already-moved/src/tabs/arlocal.html diff --git a/src/tabs/arlocal.tsx b/.old-already-moved/src/tabs/arlocal.tsx similarity index 100% rename from src/tabs/arlocal.tsx rename to .old-already-moved/src/tabs/arlocal.tsx diff --git a/src/tabs/auth.html b/.old-already-moved/src/tabs/auth.html similarity index 100% rename from src/tabs/auth.html rename to .old-already-moved/src/tabs/auth.html diff --git a/src/tabs/auth.tsx b/.old-already-moved/src/tabs/auth.tsx similarity index 100% rename from src/tabs/auth.tsx rename to .old-already-moved/src/tabs/auth.tsx diff --git a/src/tabs/dashboard.html b/.old-already-moved/src/tabs/dashboard.html similarity index 100% rename from src/tabs/dashboard.html rename to .old-already-moved/src/tabs/dashboard.html diff --git a/src/tabs/dashboard.tsx b/.old-already-moved/src/tabs/dashboard.tsx similarity index 100% rename from src/tabs/dashboard.tsx rename to .old-already-moved/src/tabs/dashboard.tsx diff --git a/src/tabs/devtools.html b/.old-already-moved/src/tabs/devtools.html similarity index 100% rename from src/tabs/devtools.html rename to .old-already-moved/src/tabs/devtools.html diff --git a/src/tabs/devtools.tsx b/.old-already-moved/src/tabs/devtools.tsx similarity index 100% rename from src/tabs/devtools.tsx rename to .old-already-moved/src/tabs/devtools.tsx diff --git a/src/tabs/fullscreen.html b/.old-already-moved/src/tabs/fullscreen.html similarity index 100% rename from src/tabs/fullscreen.html rename to .old-already-moved/src/tabs/fullscreen.html diff --git a/src/tabs/fullscreen.tsx b/.old-already-moved/src/tabs/fullscreen.tsx similarity index 100% rename from src/tabs/fullscreen.tsx rename to .old-already-moved/src/tabs/fullscreen.tsx diff --git a/src/tabs/welcome.html b/.old-already-moved/src/tabs/welcome.html similarity index 100% rename from src/tabs/welcome.html rename to .old-already-moved/src/tabs/welcome.html diff --git a/src/tabs/welcome.tsx b/.old-already-moved/src/tabs/welcome.tsx similarity index 100% rename from src/tabs/welcome.tsx rename to .old-already-moved/src/tabs/welcome.tsx diff --git a/src/tokens/aoTokens/ao.constants.ts b/.old-already-moved/src/tokens/aoTokens/ao.constants.ts similarity index 100% rename from src/tokens/aoTokens/ao.constants.ts rename to .old-already-moved/src/tokens/aoTokens/ao.constants.ts diff --git a/src/tokens/aoTokens/ao.ts b/.old-already-moved/src/tokens/aoTokens/ao.ts similarity index 99% rename from src/tokens/aoTokens/ao.ts rename to .old-already-moved/src/tokens/aoTokens/ao.ts index 419f39573..b74c40d11 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/.old-already-moved/src/tokens/aoTokens/ao.ts @@ -13,7 +13,7 @@ import { isNetworkError, NetworkError, BalanceFetchError } from "~utils/error/er import activeAddress from "~api/modules/active_address"; import { findGateway } from "~gateways/wayfinder"; import BigNumber from "bignumber.js"; -import { CACHE_API } from "~constants/api"; +import { CACHE_API } from "~_constants/api"; import Arweave from "arweave"; import { queryClient } from "~utils/tanstack"; import { diff --git a/src/tokens/aoTokens/config.ts b/.old-already-moved/src/tokens/aoTokens/config.ts similarity index 100% rename from src/tokens/aoTokens/config.ts rename to .old-already-moved/src/tokens/aoTokens/config.ts diff --git a/src/tokens/aoTokens/router.ts b/.old-already-moved/src/tokens/aoTokens/router.ts similarity index 100% rename from src/tokens/aoTokens/router.ts rename to .old-already-moved/src/tokens/aoTokens/router.ts diff --git a/src/tokens/aoTokens/sync.ts b/.old-already-moved/src/tokens/aoTokens/sync.ts similarity index 100% rename from src/tokens/aoTokens/sync.ts rename to .old-already-moved/src/tokens/aoTokens/sync.ts diff --git a/src/tokens/currency.ts b/.old-already-moved/src/tokens/currency.ts similarity index 100% rename from src/tokens/currency.ts rename to .old-already-moved/src/tokens/currency.ts diff --git a/src/tokens/hooks/index.ts b/.old-already-moved/src/tokens/hooks/index.ts similarity index 100% rename from src/tokens/hooks/index.ts rename to .old-already-moved/src/tokens/hooks/index.ts diff --git a/src/tokens/hooks/useFormattedTokenBalance.ts b/.old-already-moved/src/tokens/hooks/useFormattedTokenBalance.ts similarity index 100% rename from src/tokens/hooks/useFormattedTokenBalance.ts rename to .old-already-moved/src/tokens/hooks/useFormattedTokenBalance.ts diff --git a/src/tokens/hooks/useMarketStats.ts b/.old-already-moved/src/tokens/hooks/useMarketStats.ts similarity index 100% rename from src/tokens/hooks/useMarketStats.ts rename to .old-already-moved/src/tokens/hooks/useMarketStats.ts diff --git a/src/tokens/hooks/useTokenMarketData.ts b/.old-already-moved/src/tokens/hooks/useTokenMarketData.ts similarity index 100% rename from src/tokens/hooks/useTokenMarketData.ts rename to .old-already-moved/src/tokens/hooks/useTokenMarketData.ts diff --git a/src/tokens/hooks/useTokenPriceChange.ts b/.old-already-moved/src/tokens/hooks/useTokenPriceChange.ts similarity index 100% rename from src/tokens/hooks/useTokenPriceChange.ts rename to .old-already-moved/src/tokens/hooks/useTokenPriceChange.ts diff --git a/src/tokens/index.ts b/.old-already-moved/src/tokens/index.ts similarity index 100% rename from src/tokens/index.ts rename to .old-already-moved/src/tokens/index.ts diff --git a/src/tokens/knownTokens.ts b/.old-already-moved/src/tokens/knownTokens.ts similarity index 100% rename from src/tokens/knownTokens.ts rename to .old-already-moved/src/tokens/knownTokens.ts diff --git a/src/tokens/token.ts b/.old-already-moved/src/tokens/token.ts similarity index 100% rename from src/tokens/token.ts rename to .old-already-moved/src/tokens/token.ts diff --git a/src/utils/embedded/device-nonce/device-nonce.constants.ts b/.old-already-moved/src/utils/_embedded/device-nonce/device-nonce.constants.ts similarity index 100% rename from src/utils/embedded/device-nonce/device-nonce.constants.ts rename to .old-already-moved/src/utils/_embedded/device-nonce/device-nonce.constants.ts diff --git a/src/utils/embedded/device-nonce/device-nonce.utils.ts b/.old-already-moved/src/utils/_embedded/device-nonce/device-nonce.utils.ts similarity index 94% rename from src/utils/embedded/device-nonce/device-nonce.utils.ts rename to .old-already-moved/src/utils/_embedded/device-nonce/device-nonce.utils.ts index 63be9acef..a216123e8 100644 --- a/src/utils/embedded/device-nonce/device-nonce.utils.ts +++ b/.old-already-moved/src/utils/_embedded/device-nonce/device-nonce.utils.ts @@ -1,7 +1,7 @@ import { nanoid } from "nanoid"; import { LocalStorage } from "~iframe/storage/unpartitioned-storage/local-storage"; -import { DEVICE_NONCE_KEY } from "~utils/embedded/device-nonce/device-nonce.constants"; -import { setDeviceNonceHeader } from "~utils/embedded/embedded.utils"; +import { DEVICE_NONCE_KEY } from "~utils/_embedded/device-nonce/device-nonce.constants"; +import { setDeviceNonceHeader } from "~utils/_embedded/embedded.utils"; import { log, LOG_GROUP } from "~utils/log/log.utils"; const INVALID_DEVICE_NONCE_ERR_MSG = "Invalid deviceNonce"; diff --git a/src/utils/embedded/embedded.constants.ts b/.old-already-moved/src/utils/_embedded/embedded.constants.ts similarity index 91% rename from src/utils/embedded/embedded.constants.ts rename to .old-already-moved/src/utils/_embedded/embedded.constants.ts index 9dd221283..ccbdbddbc 100644 --- a/src/utils/embedded/embedded.constants.ts +++ b/.old-already-moved/src/utils/_embedded/embedded.constants.ts @@ -1,5 +1,5 @@ import type { AuthProviderType } from "embed-api"; -import type { AuthStatus, EmbeddedSdkAuthStatus } from "~utils/embedded/embedded.types"; +import type { AuthStatus, EmbeddedSdkAuthStatus } from "~utils/_embedded/embedded.types"; // TODO: Move to `embed-api` export const SUPABASE_AUTH_TOKEN_KEY_REGEXP = /^sb\-\w+\-auth\-token$/; diff --git a/src/utils/embedded/embedded.context.ts b/.old-already-moved/src/utils/_embedded/embedded.context.ts similarity index 93% rename from src/utils/embedded/embedded.context.ts rename to .old-already-moved/src/utils/_embedded/embedded.context.ts index 99b60a853..00a30e415 100644 --- a/src/utils/embedded/embedded.context.ts +++ b/.old-already-moved/src/utils/_embedded/embedded.context.ts @@ -1,7 +1,7 @@ import { createContext } from "react"; import { getUnpartitionedStateStatus } from "~iframe/storage/unpartitioned-storage/unpartitioned-storage.utils"; -import type { EmbeddedContextAuth, EmbeddedContextData, EmbeddedContextState } from "~utils/embedded/embedded.types"; -import { INITIAL_ANON_SESSION } from "~utils/embedded/session/session.utils"; +import type { EmbeddedContextAuth, EmbeddedContextData, EmbeddedContextState } from "~utils/_embedded/embedded.types"; +import { INITIAL_ANON_SESSION } from "~utils/_embedded/session/session.utils"; export const EMBEDDED_CONTEXT_INITIAL_STATE = { currentWalletId: "", diff --git a/.old-already-moved/src/utils/_embedded/embedded.hooks.ts b/.old-already-moved/src/utils/_embedded/embedded.hooks.ts new file mode 100644 index 000000000..894fdbb26 --- /dev/null +++ b/.old-already-moved/src/utils/_embedded/embedded.hooks.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { EmbeddedContext } from "~utils/_embedded/embedded.context"; + +export function useEmbedded() { + return useContext(EmbeddedContext); +} diff --git a/src/utils/embedded/embedded.provider.tsx b/.old-already-moved/src/utils/_embedded/embedded.provider.tsx similarity index 99% rename from src/utils/embedded/embedded.provider.tsx rename to .old-already-moved/src/utils/_embedded/embedded.provider.tsx index d752e357e..b84f72f96 100644 --- a/src/utils/embedded/embedded.provider.tsx +++ b/.old-already-moved/src/utils/_embedded/embedded.provider.tsx @@ -24,14 +24,14 @@ import type { Wallet, OAutProviderType, AuthEmailParams, -} from "~utils/embedded/embedded.types"; +} from "~utils/_embedded/embedded.types"; import { setAuthTokenHeader, getSupabaseClient, signOut, getBackupsNeededAndMessage, checkStoredSupabaseAuthToken, -} from "~utils/embedded/embedded.utils"; +} from "~utils/_embedded/embedded.utils"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import { AuthProviderType, @@ -43,14 +43,14 @@ import { type SupabaseSession, } from "embed-api"; import { AuthenticationService } from "~utils/authentication/authentication.service"; -import { EMBEDDED_FEATURE_FLAGS, EMBEDDED_SDK_AUTH_STATUS_BY_AUTH_STATUS } from "~utils/embedded/embedded.constants"; -import { isTempWalletPromiseExpired } from "~utils/embedded/utils/wallets/embedded-wallets.utils"; +import { EMBEDDED_FEATURE_FLAGS, EMBEDDED_SDK_AUTH_STATUS_BY_AUTH_STATUS } from "~utils/_embedded/embedded.constants"; +import { isTempWalletPromiseExpired } from "~utils/_embedded/utils/wallets/embedded-wallets.utils"; import copy from "copy-to-clipboard"; import { getAuthProviderTypeFromSupabaseUser, getUserDetailsFromSupabaseUser, postEmbeddedMessage, -} from "~utils/embedded/utils/messages/embedded-messages.utils"; +} from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { ExtensionStorage, PersistentStorage, useStorage } from "~utils/storage"; import { StorageKeys } from "~utils/storage/storage.constants"; import { @@ -72,13 +72,13 @@ import { useAsyncEffect } from "~utils/react/useAsyncEffect"; import { isomorphicOnMessage } from "~isomorphic-messaging"; import { useTheme } from "~utils/theme/theme.hook"; import { withRetry } from "~utils/promises/retry"; -import { createAnonSession, INITIAL_ANON_SESSION, parseSupabaseSession } from "~utils/embedded/session/session.utils"; +import { createAnonSession, INITIAL_ANON_SESSION, parseSupabaseSession } from "~utils/_embedded/session/session.utils"; import { useLocation } from "~wallets/router/router.utils"; import { EMBEDDED_CONTEXT_INITIAL_AUTH, EMBEDDED_CONTEXT_INITIAL_STATE, EmbeddedContext, -} from "~utils/embedded/embedded.context"; +} from "~utils/_embedded/embedded.context"; export function EmbeddedProvider({ children }: EmbeddedProviderProps) { const [embeddedContextState, setEmbeddedContextState] = diff --git a/src/utils/embedded/embedded.types.ts b/.old-already-moved/src/utils/_embedded/embedded.types.ts similarity index 100% rename from src/utils/embedded/embedded.types.ts rename to .old-already-moved/src/utils/_embedded/embedded.types.ts diff --git a/src/utils/embedded/embedded.utils.ts b/.old-already-moved/src/utils/_embedded/embedded.utils.ts similarity index 97% rename from src/utils/embedded/embedded.utils.ts rename to .old-already-moved/src/utils/_embedded/embedded.utils.ts index e9b235b7a..ec14a25ff 100644 --- a/src/utils/embedded/embedded.utils.ts +++ b/.old-already-moved/src/utils/_embedded/embedded.utils.ts @@ -1,10 +1,10 @@ import { createSupabaseClient, createTRPCClient } from "embed-api"; -import { IS_EMBEDDED_APP, SUPABASE_AUTH_TOKEN_KEY_REGEXP } from "~utils/embedded/embedded.constants"; +import { IS_EMBEDDED_APP, SUPABASE_AUTH_TOKEN_KEY_REGEXP } from "~utils/_embedded/embedded.constants"; import { LocalStorage } from "~iframe/storage/unpartitioned-storage/local-storage"; import { isInsideIframe, EMBEDDED_CLIENT_ID, EMBEDDED_ANCESTOR_ORIGIN, EMBEDDED_SERVER_BASE_URL } from "./iframe.utils"; import { ExtensionStorage } from "~utils/storage"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; -import type { Wallet } from "~utils/embedded/embedded.types"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; +import type { Wallet } from "~utils/_embedded/embedded.types"; export function getBackupsNeededAndMessage(wallets: Wallet[]) { const backupsNeeded = wallets.filter((wallet) => { diff --git a/src/utils/embedded/iframe.utils.ts b/.old-already-moved/src/utils/_embedded/iframe.utils.ts similarity index 100% rename from src/utils/embedded/iframe.utils.ts rename to .old-already-moved/src/utils/_embedded/iframe.utils.ts diff --git a/.old-already-moved/src/utils/_embedded/session/session.utils.ts b/.old-already-moved/src/utils/_embedded/session/session.utils.ts new file mode 100644 index 000000000..fc20f9d16 --- /dev/null +++ b/.old-already-moved/src/utils/_embedded/session/session.utils.ts @@ -0,0 +1,138 @@ +import { + createAnonSession as createAnonSessionFromHeaders, + type AuthProviderType, + type DbSession, + type SupabaseJwtPayload, + type SupabaseSession, + type SupabaseUser, +} from "embed-api"; +import { jwtDecode } from "jwt-decode"; +import { getDeviceNonce } from "~utils/_embedded/device-nonce/device-nonce.utils"; +import { getSupabaseClient } from "~utils/_embedded/embedded.utils"; +import { getAuthProviderTypeFromSupabaseUser } from "~utils/_embedded/utils/messages/embedded-messages.utils"; + +let hasSessionBeenRefreshed = false; + +async function verifySessionSync(dbSession: DbSession, decodedSession: SupabaseJwtPayload): Promise { + let shouldRefreshSession = false; + + if (dbSession.deviceNonce !== decodedSession.sessionData.deviceNonce) { + shouldRefreshSession = true; + + if (process.env.NODE_ENV === "development") + console.warn( + `⚠️ The current session's deviceNonce (${dbSession.deviceNonce}) doesn't match the decoded session's deviceNonce (${decodedSession.sessionData.deviceNonce}):`, + { dbSession, decodedSession }, + ); + } + + if (dbSession.userAgent !== decodedSession.sessionData.userAgent) { + shouldRefreshSession = true; + + if (process.env.NODE_ENV === "development") + console.warn( + `⚠️ The current session's userAgent (${dbSession.userAgent}) doesn't match the decoded session's userAgent (${decodedSession.sessionData.userAgent}):`, + { dbSession, decodedSession }, + ); + } + + if (dbSession.userId !== decodedSession.sub) { + shouldRefreshSession = true; + + if (process.env.NODE_ENV === "development") + console.warn( + `⚠️ The current session's userId (${dbSession.userId}) doesn't match the decoded session's sub (${decodedSession.sub}):`, + { dbSession, decodedSession }, + ); + } + + if (!shouldRefreshSession && process.env.NODE_ENV === "development") { + console.info(`✅ The current session's matches the decoded session:`, { dbSession, decodedSession }); + } + + if (shouldRefreshSession && !hasSessionBeenRefreshed) { + hasSessionBeenRefreshed = true; + + if (process.env.NODE_ENV === "development") console.info("🔁 Refreshing session..."); + + try { + const supabase = await getSupabaseClient(); + const refreshedSessionResponse = await supabase.auth.refreshSession(); + + if (process.env.NODE_ENV === "development") console.info("🔁 Refreshed session =", refreshedSessionResponse); + } catch (err) { + console.warn("Error refreshing session:", err); + } + } +} + +export interface ParseSupabaseSessionsReturn { + accessToken: string | null; + user: SupabaseUser | null; + authProviderType: AuthProviderType | null; + session: DbSession | null; +} + +export async function parseSupabaseSession(supabaseSession?: SupabaseSession): Promise { + const accessToken = supabaseSession?.access_token ?? null; + const user = (supabaseSession?.user as SupabaseUser) ?? null; + const authProviderType = getAuthProviderTypeFromSupabaseUser(user); + + if (process.env.NODE_ENV === "development" && user && authProviderType === null) { + alert( + `authProviderType = ${authProviderType}. Something wasn't properly mapped in AUTH_PROVIDER_TYPE_BY_PROVIDER_STR.`, + ); + } + + if (!accessToken || !user) { + const anonSession = await createAnonSession(); + + return { + accessToken: null, + user: null, + authProviderType: null, + session: anonSession, + }; + } + + const decodedSession = jwtDecode(accessToken); + const { sessionData } = decodedSession; + const deviceNonce = await getDeviceNonce(); + const userAgent = navigator.userAgent; + const userId = user.id; + const session: DbSession = { + id: decodedSession.session_id, + createdAt: sessionData.createdAt, + updatedAt: sessionData.updatedAt, + deviceNonce, + ip: sessionData.ip, + userAgent, + userId, + }; + + verifySessionSync(session, decodedSession); + + return { + accessToken, + user, + authProviderType, + session, + }; +} + +export const INITIAL_ANON_SESSION = createAnonSessionFromHeaders({ + userAgent: navigator.userAgent, + deviceNonce: "", + ip: "", +}); + +export async function createAnonSession() { + const deviceNonce = await getDeviceNonce(); + const userAgent = navigator.userAgent; + + return createAnonSessionFromHeaders({ + userAgent, + deviceNonce, + ip: "", + }); +} diff --git a/src/utils/embedded/utils/logo/logo.utils.ts b/.old-already-moved/src/utils/_embedded/utils/logo/logo.utils.ts similarity index 100% rename from src/utils/embedded/utils/logo/logo.utils.ts rename to .old-already-moved/src/utils/_embedded/utils/logo/logo.utils.ts diff --git a/.old-already-moved/src/utils/_embedded/utils/messages/embedded-messages.types.ts b/.old-already-moved/src/utils/_embedded/utils/messages/embedded-messages.types.ts new file mode 100644 index 000000000..e667c233c --- /dev/null +++ b/.old-already-moved/src/utils/_embedded/utils/messages/embedded-messages.types.ts @@ -0,0 +1,93 @@ +import type { AuthProviderType } from "embed-api"; +import type { EmbeddedLayout, RouteType } from "~utils/_embedded/utils/routes/embedded-routes.utils"; + +export type EmbeddedMessageId = + | "embedded_auth" + | "embedded_backup" + | "embedded_open" + | "embedded_close" + | "embedded_resize" + | "embedded_balance" + | "embedded_request"; + +export interface EmbeddedUserDetails { + id: string; + email: null | string; + phone: null | string; + username: null | string; + name: null | string; + fullName: null | string; + picture: null | string; + confirmed: boolean; + emailConfirmed: boolean; + phoneConfirmed: boolean; + createdAt: Date; +} + +export interface EmbeddedAuthNativeMessageData { + authType: "NATIVE_WALLET"; + authStatus: null; + userDetails: null; +} + +export interface EmbeddedAuthNoAuthMessageData { + authType: null; + authStatus: "not-authenticated"; + userDetails: null; +} + +export interface EmbeddedAuthLoadingMessageData { + authType?: AuthProviderType; + authStatus: "loading"; + userDetails: EmbeddedUserDetails; +} +export interface EmbeddedAuthCompletedMessageData { + authType: AuthProviderType; + authStatus: "onboarding" | "authenticated"; + userDetails: EmbeddedUserDetails; +} + +export type EmbeddedAuthMessageData = + | EmbeddedAuthNativeMessageData + | EmbeddedAuthNoAuthMessageData + | EmbeddedAuthLoadingMessageData + | EmbeddedAuthCompletedMessageData; + +export interface EmbeddedBackupMessageData { + backupsNeeded: number; + backupMessage?: string; +} + +export interface EmbeddedResizeMessageData { + routeType: RouteType; + preferredLayoutType: EmbeddedLayout; + width?: number; + height: number; +} + +export interface EmbeddedBalanceMessageData { + amount: number; + currency: "USD" | "EUR"; // TODO: Replace with a type that includes all options in the settings? + formattedBalance: string; +} + +export interface EmbeddedRequestMessageData { + pendingRequests: number; + hasNewConnectRequest: boolean; +} + +export interface EmbeddedMessageMap { + embedded_auth: EmbeddedAuthMessageData; + embedded_backup: EmbeddedBackupMessageData; + embedded_open: void; + embedded_close: void; + embedded_resize: EmbeddedResizeMessageData; + embedded_balance: EmbeddedBalanceMessageData; + embedded_request: EmbeddedRequestMessageData; +} + +export interface EmbeddedMessage { + id: string; + type: K; + data: EmbeddedMessageMap[K]; +} diff --git a/src/utils/embedded/utils/messages/embedded-messages.utils.ts b/.old-already-moved/src/utils/_embedded/utils/messages/embedded-messages.utils.ts similarity index 95% rename from src/utils/embedded/utils/messages/embedded-messages.utils.ts rename to .old-already-moved/src/utils/_embedded/utils/messages/embedded-messages.utils.ts index c80891d76..5b339d588 100644 --- a/src/utils/embedded/utils/messages/embedded-messages.utils.ts +++ b/.old-already-moved/src/utils/_embedded/utils/messages/embedded-messages.utils.ts @@ -1,14 +1,14 @@ import type { AuthProviderType, SupabaseUser } from "embed-api"; import { nanoid } from "nanoid"; -import { getEmbeddedAncestorOrigin, isInsideIframe } from "~utils/embedded/iframe.utils"; -import { AUTH_PROVIDER_TYPE_BY_PROVIDER_STR } from "~utils/embedded/embedded.constants"; +import { getEmbeddedAncestorOrigin, isInsideIframe } from "~utils/_embedded/iframe.utils"; +import { AUTH_PROVIDER_TYPE_BY_PROVIDER_STR } from "~utils/_embedded/embedded.constants"; import type { EmbeddedAuthMessageData, EmbeddedMessage, EmbeddedMessageId, EmbeddedMessageMap, EmbeddedUserDetails, -} from "~utils/embedded/utils/messages/embedded-messages.types"; +} from "~utils/_embedded/utils/messages/embedded-messages.types"; const EMBEDDED_MESSAGE_IDS = [ "embedded_auth", diff --git a/src/utils/embedded/utils/routes/embedded-routes.utils.ts b/.old-already-moved/src/utils/_embedded/utils/routes/embedded-routes.utils.ts similarity index 94% rename from src/utils/embedded/utils/routes/embedded-routes.utils.ts rename to .old-already-moved/src/utils/_embedded/utils/routes/embedded-routes.utils.ts index 5d7d0d074..8aec8188b 100644 --- a/src/utils/embedded/utils/routes/embedded-routes.utils.ts +++ b/.old-already-moved/src/utils/_embedded/utils/routes/embedded-routes.utils.ts @@ -1,4 +1,4 @@ -import type { AuthStatus } from "~utils/embedded/embedded.types"; +import type { AuthStatus } from "~utils/_embedded/embedded.types"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; import type { WanderRoutePath } from "~wallets/router/router.types"; diff --git a/.old-already-moved/src/utils/_embedded/utils/trpc/trpc.utils.ts b/.old-already-moved/src/utils/_embedded/utils/trpc/trpc.utils.ts new file mode 100644 index 000000000..991ec8745 --- /dev/null +++ b/.old-already-moved/src/utils/_embedded/utils/trpc/trpc.utils.ts @@ -0,0 +1,41 @@ +import type { HttpStatusCode } from "axios"; +import type { trpcVanilla } from "~utils/_embedded/embedded.utils"; + +export interface TRPCErrorData { + code: string; + httpStatus: HttpStatusCode; + stack: string; + path: keyof typeof trpcVanilla; +} + +export interface TRPCError { + code: number; + message: string; + data: TRPCErrorData; +} + +export class TRPCClientError extends Error { + meta: { + response: Response; + responseJSON: any; + }; + + shape: TRPCError; + + data: TRPCErrorData; +} + +export function isTRPCClientError(err: unknown): err is TRPCClientError { + const data = (err ? (err as any).data || {} : {}) as TRPCErrorData; + + return ( + err instanceof Error && + err.hasOwnProperty("meta") && + err.hasOwnProperty("shape") && + err.hasOwnProperty("data") && + typeof data.code === "string" && + typeof data.httpStatus === "number" && + typeof data.stack === "string" && + typeof data.path === "string" + ); +} diff --git a/src/utils/embedded/utils/useUnpartitionedStateCheck.ts b/.old-already-moved/src/utils/_embedded/utils/useUnpartitionedStateCheck.ts similarity index 100% rename from src/utils/embedded/utils/useUnpartitionedStateCheck.ts rename to .old-already-moved/src/utils/_embedded/utils/useUnpartitionedStateCheck.ts diff --git a/.old-already-moved/src/utils/_embedded/utils/wallets/embedded-wallets.utils.ts b/.old-already-moved/src/utils/_embedded/utils/wallets/embedded-wallets.utils.ts new file mode 100644 index 000000000..7dd5d736b --- /dev/null +++ b/.old-already-moved/src/utils/_embedded/utils/wallets/embedded-wallets.utils.ts @@ -0,0 +1,7 @@ +import type { TempWalletPromise } from "~utils/_embedded/embedded.types"; + +const FIVE_MINS_IN_MS = 5 * 60 * 1000; + +export function isTempWalletPromiseExpired(tempWalletPromise: TempWalletPromise) { + return Date.now() - tempWalletPromise.createdAt >= FIVE_MINS_IN_MS; +} diff --git a/src/utils/agents/constants.ts b/.old-already-moved/src/utils/agents/constants.ts similarity index 100% rename from src/utils/agents/constants.ts rename to .old-already-moved/src/utils/agents/constants.ts diff --git a/src/utils/agents/deploy.ts b/.old-already-moved/src/utils/agents/deploy.ts similarity index 100% rename from src/utils/agents/deploy.ts rename to .old-already-moved/src/utils/agents/deploy.ts diff --git a/src/utils/agents/hooks.ts b/.old-already-moved/src/utils/agents/hooks.ts similarity index 100% rename from src/utils/agents/hooks.ts rename to .old-already-moved/src/utils/agents/hooks.ts diff --git a/src/utils/agents/queries.ts b/.old-already-moved/src/utils/agents/queries.ts similarity index 100% rename from src/utils/agents/queries.ts rename to .old-already-moved/src/utils/agents/queries.ts diff --git a/src/utils/agents/swap.ts b/.old-already-moved/src/utils/agents/swap.ts similarity index 100% rename from src/utils/agents/swap.ts rename to .old-already-moved/src/utils/agents/swap.ts diff --git a/src/utils/agents/sync.ts b/.old-already-moved/src/utils/agents/sync.ts similarity index 98% rename from src/utils/agents/sync.ts rename to .old-already-moved/src/utils/agents/sync.ts index fa083f56e..7fec90489 100644 --- a/src/utils/agents/sync.ts +++ b/.old-already-moved/src/utils/agents/sync.ts @@ -10,7 +10,7 @@ import { } from "./constants"; import browser from "webextension-polyfill"; import type { AOYieldAgent } from "./types"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; import { pLimit } from "plimit-lit"; import { ExtensionStorage } from "~utils/storage"; import { queryClient } from "~utils/tanstack"; diff --git a/src/utils/agents/types.ts b/.old-already-moved/src/utils/agents/types.ts similarity index 100% rename from src/utils/agents/types.ts rename to .old-already-moved/src/utils/agents/types.ts diff --git a/src/utils/agents/utils/date.utils.ts b/.old-already-moved/src/utils/agents/utils/date.utils.ts similarity index 100% rename from src/utils/agents/utils/date.utils.ts rename to .old-already-moved/src/utils/agents/utils/date.utils.ts diff --git a/src/utils/agents/utils/index.ts b/.old-already-moved/src/utils/agents/utils/index.ts similarity index 100% rename from src/utils/agents/utils/index.ts rename to .old-already-moved/src/utils/agents/utils/index.ts diff --git a/src/utils/analytics.ts b/.old-already-moved/src/utils/analytics.ts similarity index 100% rename from src/utils/analytics.ts rename to .old-already-moved/src/utils/analytics.ts diff --git a/src/utils/announcements.ts b/.old-already-moved/src/utils/announcements.ts similarity index 100% rename from src/utils/announcements.ts rename to .old-already-moved/src/utils/announcements.ts diff --git a/src/utils/apps.ts b/.old-already-moved/src/utils/apps.ts similarity index 100% rename from src/utils/apps.ts rename to .old-already-moved/src/utils/apps.ts diff --git a/src/utils/array.ts b/.old-already-moved/src/utils/array.ts similarity index 100% rename from src/utils/array.ts rename to .old-already-moved/src/utils/array.ts diff --git a/src/utils/assertions.ts b/.old-already-moved/src/utils/assertions.ts similarity index 100% rename from src/utils/assertions.ts rename to .old-already-moved/src/utils/assertions.ts diff --git a/src/utils/auth/auth.constants.ts b/.old-already-moved/src/utils/auth/auth.constants.ts similarity index 100% rename from src/utils/auth/auth.constants.ts rename to .old-already-moved/src/utils/auth/auth.constants.ts diff --git a/src/utils/auth/auth.hooks.ts b/.old-already-moved/src/utils/auth/auth.hooks.ts similarity index 100% rename from src/utils/auth/auth.hooks.ts rename to .old-already-moved/src/utils/auth/auth.hooks.ts diff --git a/src/utils/auth/auth.provider.tsx b/.old-already-moved/src/utils/auth/auth.provider.tsx similarity index 99% rename from src/utils/auth/auth.provider.tsx rename to .old-already-moved/src/utils/auth/auth.provider.tsx index e4a0e0148..f0bc72d86 100644 --- a/src/utils/auth/auth.provider.tsx +++ b/.old-already-moved/src/utils/auth/auth.provider.tsx @@ -16,9 +16,9 @@ import { isomorphicOnMessage } from "~isomorphic-messaging"; import type { IBridgeMessage } from "@arconnect/webext-bridge"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import { isError } from "~utils/error/error.utils"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { getDecryptionKey } from "~wallets/auth"; -import { addSignOutListener, removeSignOutListener } from "~utils/embedded/embedded.utils"; +import { addSignOutListener, removeSignOutListener } from "~utils/_embedded/embedded.utils"; interface AuthRequestsContextState { authRequests: AuthRequest[]; diff --git a/src/utils/auth/auth.types.ts b/.old-already-moved/src/utils/auth/auth.types.ts similarity index 100% rename from src/utils/auth/auth.types.ts rename to .old-already-moved/src/utils/auth/auth.types.ts diff --git a/src/utils/auth/auth.utils.ts b/.old-already-moved/src/utils/auth/auth.utils.ts similarity index 99% rename from src/utils/auth/auth.utils.ts rename to .old-already-moved/src/utils/auth/auth.utils.ts index ce419438d..c2254489c 100644 --- a/src/utils/auth/auth.utils.ts +++ b/.old-already-moved/src/utils/auth/auth.utils.ts @@ -20,7 +20,7 @@ import { } from "~utils/auth/auth.constants"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import { isError } from "~utils/error/error.utils"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; const popupMutex = new Mutex(); diff --git a/src/utils/authentication/authentication.service.ts b/.old-already-moved/src/utils/authentication/authentication.service.ts similarity index 97% rename from src/utils/authentication/authentication.service.ts rename to .old-already-moved/src/utils/authentication/authentication.service.ts index 664f06b93..2d32bf244 100644 --- a/src/utils/authentication/authentication.service.ts +++ b/.old-already-moved/src/utils/authentication/authentication.service.ts @@ -1,11 +1,11 @@ -import { getSupabaseClient, trpcVanilla } from "~utils/embedded/embedded.utils"; +import { getSupabaseClient, trpcVanilla } from "~utils/_embedded/embedded.utils"; import type { Session } from "@supabase/supabase-js"; import type { AuthSignInWithPasswordParams, AuthVerifyOtpParams, OAutProviderType, -} from "~utils/embedded/embedded.types"; -import { isInsideIframe } from "~utils/embedded/iframe.utils"; +} from "~utils/_embedded/embedded.types"; +import { isInsideIframe } from "~utils/_embedded/iframe.utils"; import { POPUP_CHECK_INTERVAL_MS, POPUP_AUTHENTICATION_TIMEOUT_MS, diff --git a/src/utils/authentication/authentication.types.ts b/.old-already-moved/src/utils/authentication/authentication.types.ts similarity index 100% rename from src/utils/authentication/authentication.types.ts rename to .old-already-moved/src/utils/authentication/authentication.types.ts diff --git a/src/utils/authentication/authentication.utils.ts b/.old-already-moved/src/utils/authentication/authentication.utils.ts similarity index 98% rename from src/utils/authentication/authentication.utils.ts rename to .old-already-moved/src/utils/authentication/authentication.utils.ts index c74773ecc..e31f31c7e 100644 --- a/src/utils/authentication/authentication.utils.ts +++ b/.old-already-moved/src/utils/authentication/authentication.utils.ts @@ -6,8 +6,7 @@ import { type OAuthErrorMessage, type OAuthResultMessage, type OAuthSuccessMessage, - type SupabaseProvider, -} from "~utils/authentication/authentication.types"; +} from "./authentication.types"; export const MIN_SUPABASE_PASSWORD_LENGTH = 6 as const; diff --git a/src/utils/balances.ts b/.old-already-moved/src/utils/balances.ts similarity index 100% rename from src/utils/balances.ts rename to .old-already-moved/src/utils/balances.ts diff --git a/src/utils/browser-info/browser-info.utils.tsx b/.old-already-moved/src/utils/browser-info/browser-info.utils.tsx similarity index 100% rename from src/utils/browser-info/browser-info.utils.tsx rename to .old-already-moved/src/utils/browser-info/browser-info.utils.tsx diff --git a/src/utils/context_menus.ts b/.old-already-moved/src/utils/context_menus.ts similarity index 100% rename from src/utils/context_menus.ts rename to .old-already-moved/src/utils/context_menus.ts diff --git a/src/utils/crypto/crypto.utils.ts b/.old-already-moved/src/utils/crypto/crypto.utils.ts similarity index 100% rename from src/utils/crypto/crypto.utils.ts rename to .old-already-moved/src/utils/crypto/crypto.utils.ts diff --git a/src/utils/currency.ts b/.old-already-moved/src/utils/currency.ts similarity index 100% rename from src/utils/currency.ts rename to .old-already-moved/src/utils/currency.ts diff --git a/src/utils/data_item.ts b/.old-already-moved/src/utils/data_item.ts similarity index 100% rename from src/utils/data_item.ts rename to .old-already-moved/src/utils/data_item.ts diff --git a/src/utils/email.ts b/.old-already-moved/src/utils/email.ts similarity index 100% rename from src/utils/email.ts rename to .old-already-moved/src/utils/email.ts diff --git a/src/utils/error/ErrorBoundary/errorBoundary.tsx b/.old-already-moved/src/utils/error/ErrorBoundary/errorBoundary.tsx similarity index 100% rename from src/utils/error/ErrorBoundary/errorBoundary.tsx rename to .old-already-moved/src/utils/error/ErrorBoundary/errorBoundary.tsx diff --git a/src/utils/error/error.utils.ts b/.old-already-moved/src/utils/error/error.utils.ts similarity index 100% rename from src/utils/error/error.utils.ts rename to .old-already-moved/src/utils/error/error.utils.ts diff --git a/src/utils/events.ts b/.old-already-moved/src/utils/events.ts similarity index 100% rename from src/utils/events.ts rename to .old-already-moved/src/utils/events.ts diff --git a/src/utils/fair_launch/fair_launch.alarms.ts b/.old-already-moved/src/utils/fair_launch/fair_launch.alarms.ts similarity index 100% rename from src/utils/fair_launch/fair_launch.alarms.ts rename to .old-already-moved/src/utils/fair_launch/fair_launch.alarms.ts diff --git a/src/utils/fair_launch/fair_launch.constants.ts b/.old-already-moved/src/utils/fair_launch/fair_launch.constants.ts similarity index 100% rename from src/utils/fair_launch/fair_launch.constants.ts rename to .old-already-moved/src/utils/fair_launch/fair_launch.constants.ts diff --git a/src/utils/fair_launch/fair_launch.hooks.ts b/.old-already-moved/src/utils/fair_launch/fair_launch.hooks.ts similarity index 100% rename from src/utils/fair_launch/fair_launch.hooks.ts rename to .old-already-moved/src/utils/fair_launch/fair_launch.hooks.ts diff --git a/src/utils/fair_launch/fair_launch.types.ts b/.old-already-moved/src/utils/fair_launch/fair_launch.types.ts similarity index 100% rename from src/utils/fair_launch/fair_launch.types.ts rename to .old-already-moved/src/utils/fair_launch/fair_launch.types.ts diff --git a/src/utils/fair_launch/fair_launch.utils.ts b/.old-already-moved/src/utils/fair_launch/fair_launch.utils.ts similarity index 99% rename from src/utils/fair_launch/fair_launch.utils.ts rename to .old-already-moved/src/utils/fair_launch/fair_launch.utils.ts index 7c6437fe9..6f1ef8933 100644 --- a/src/utils/fair_launch/fair_launch.utils.ts +++ b/.old-already-moved/src/utils/fair_launch/fair_launch.utils.ts @@ -8,7 +8,7 @@ import { isLocalWallet } from "~utils/assertions"; import { freeDecryptedWallet } from "~wallets/encryption"; import { queryClient } from "~utils/tanstack"; import BigNumber from "bignumber.js"; -import { CACHE_API } from "~constants/api"; +import { CACHE_API } from "~_constants/api"; import { getAoTokens } from "~tokens"; import { ExtensionStorage } from "~utils/storage"; import { LOG_GROUP, log } from "~utils/log/log.utils"; diff --git a/src/utils/file.ts b/.old-already-moved/src/utils/file.ts similarity index 97% rename from src/utils/file.ts rename to .old-already-moved/src/utils/file.ts index bcfa5b7a4..6acea1c2e 100644 --- a/src/utils/file.ts +++ b/.old-already-moved/src/utils/file.ts @@ -1,5 +1,5 @@ import type { JWKInterface } from "arweave/web/lib/wallet"; -import type { RecoveryJSON } from "~utils/embedded/embedded.types"; +import type { RecoveryJSON } from "~utils/_embedded/embedded.types"; /** * Read file content as binary diff --git a/src/utils/format.ts b/.old-already-moved/src/utils/format.ts similarity index 100% rename from src/utils/format.ts rename to .old-already-moved/src/utils/format.ts diff --git a/src/utils/icon.ts b/.old-already-moved/src/utils/icon.ts similarity index 97% rename from src/utils/icon.ts rename to .old-already-moved/src/utils/icon.ts index 3efe1d84f..44c922218 100644 --- a/src/utils/icon.ts +++ b/.old-already-moved/src/utils/icon.ts @@ -36,7 +36,7 @@ import devLocked256 from "url:/assets/icons/locked/logo256.development.png"; import browser from "webextension-polyfill"; import { ExtensionStorage } from "./storage"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; interface LogosBySize { 64: string; diff --git a/src/utils/inactivity/inactivity.constants.ts b/.old-already-moved/src/utils/inactivity/inactivity.constants.ts similarity index 100% rename from src/utils/inactivity/inactivity.constants.ts rename to .old-already-moved/src/utils/inactivity/inactivity.constants.ts diff --git a/src/utils/inactivity/inactivity.hooks.ts b/.old-already-moved/src/utils/inactivity/inactivity.hooks.ts similarity index 100% rename from src/utils/inactivity/inactivity.hooks.ts rename to .old-already-moved/src/utils/inactivity/inactivity.hooks.ts diff --git a/src/utils/inactivity/inactivity.manager.ts b/.old-already-moved/src/utils/inactivity/inactivity.manager.ts similarity index 100% rename from src/utils/inactivity/inactivity.manager.ts rename to .old-already-moved/src/utils/inactivity/inactivity.manager.ts diff --git a/src/utils/inactivity/inactivity.types.ts b/.old-already-moved/src/utils/inactivity/inactivity.types.ts similarity index 100% rename from src/utils/inactivity/inactivity.types.ts rename to .old-already-moved/src/utils/inactivity/inactivity.types.ts diff --git a/src/utils/inactivity/inactivity.utils.ts b/.old-already-moved/src/utils/inactivity/inactivity.utils.ts similarity index 100% rename from src/utils/inactivity/inactivity.utils.ts rename to .old-already-moved/src/utils/inactivity/inactivity.utils.ts diff --git a/src/utils/log/log.utils.ts b/.old-already-moved/src/utils/log/log.utils.ts similarity index 100% rename from src/utils/log/log.utils.ts rename to .old-already-moved/src/utils/log/log.utils.ts diff --git a/src/utils/messaging/common/messaging.utils.ts b/.old-already-moved/src/utils/messaging/common/messaging.utils.ts similarity index 100% rename from src/utils/messaging/common/messaging.utils.ts rename to .old-already-moved/src/utils/messaging/common/messaging.utils.ts diff --git a/src/utils/messaging/messaging.types.ts b/.old-already-moved/src/utils/messaging/messaging.types.ts similarity index 100% rename from src/utils/messaging/messaging.types.ts rename to .old-already-moved/src/utils/messaging/messaging.types.ts diff --git a/src/utils/messaging/strategies/extension/extension-chunking.strategy.ts b/.old-already-moved/src/utils/messaging/strategies/extension/extension-chunking.strategy.ts similarity index 100% rename from src/utils/messaging/strategies/extension/extension-chunking.strategy.ts rename to .old-already-moved/src/utils/messaging/strategies/extension/extension-chunking.strategy.ts diff --git a/src/utils/messaging/strategies/extension/extension-messaging.strategy.ts b/.old-already-moved/src/utils/messaging/strategies/extension/extension-messaging.strategy.ts similarity index 100% rename from src/utils/messaging/strategies/extension/extension-messaging.strategy.ts rename to .old-already-moved/src/utils/messaging/strategies/extension/extension-messaging.strategy.ts diff --git a/src/utils/messaging/strategies/iframe/iframe-chunking.strategy.ts b/.old-already-moved/src/utils/messaging/strategies/iframe/iframe-chunking.strategy.ts similarity index 100% rename from src/utils/messaging/strategies/iframe/iframe-chunking.strategy.ts rename to .old-already-moved/src/utils/messaging/strategies/iframe/iframe-chunking.strategy.ts diff --git a/src/utils/messaging/strategies/iframe/iframe-messaging.strategy.ts b/.old-already-moved/src/utils/messaging/strategies/iframe/iframe-messaging.strategy.ts similarity index 99% rename from src/utils/messaging/strategies/iframe/iframe-messaging.strategy.ts rename to .old-already-moved/src/utils/messaging/strategies/iframe/iframe-messaging.strategy.ts index f2ee2c1e3..cb416fd8a 100644 --- a/src/utils/messaging/strategies/iframe/iframe-messaging.strategy.ts +++ b/.old-already-moved/src/utils/messaging/strategies/iframe/iframe-messaging.strategy.ts @@ -1,8 +1,8 @@ import type { ProtocolMap, RuntimeContext } from "@arconnect/webext-bridge"; import { nanoid } from "nanoid"; import type { ApiCall, ApiResponse } from "shim"; -import { EMBEDDED_ANCESTOR_TAB_ID, EMBEDDED_IFRAME_TAB_ID } from "~utils/embedded/embedded.constants"; -import { getEmbeddedAncestorOrigin, isInsideIframe } from "~utils/embedded/iframe.utils"; +import { EMBEDDED_ANCESTOR_TAB_ID, EMBEDDED_IFRAME_TAB_ID } from "~utils/_embedded/embedded.constants"; +import { getEmbeddedAncestorOrigin, isInsideIframe } from "~utils/_embedded/iframe.utils"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import type { MessageData, MessageID, OnMessageCallback } from "~utils/messaging/messaging.types"; diff --git a/src/utils/misc.ts b/.old-already-moved/src/utils/misc.ts similarity index 100% rename from src/utils/misc.ts rename to .old-already-moved/src/utils/misc.ts diff --git a/src/utils/multi_sort.ts b/.old-already-moved/src/utils/multi_sort.ts similarity index 100% rename from src/utils/multi_sort.ts rename to .old-already-moved/src/utils/multi_sort.ts diff --git a/src/utils/mutex.ts b/.old-already-moved/src/utils/mutex.ts similarity index 100% rename from src/utils/mutex.ts rename to .old-already-moved/src/utils/mutex.ts diff --git a/src/utils/notifications.ts b/.old-already-moved/src/utils/notifications.ts similarity index 100% rename from src/utils/notifications.ts rename to .old-already-moved/src/utils/notifications.ts diff --git a/src/utils/otp/otp.utils.ts b/.old-already-moved/src/utils/otp/otp.utils.ts similarity index 100% rename from src/utils/otp/otp.utils.ts rename to .old-already-moved/src/utils/otp/otp.utils.ts diff --git a/src/utils/pretty_date.ts b/.old-already-moved/src/utils/pretty_date.ts similarity index 100% rename from src/utils/pretty_date.ts rename to .old-already-moved/src/utils/pretty_date.ts diff --git a/src/utils/promises/isPromise.ts b/.old-already-moved/src/utils/promises/isPromise.ts similarity index 100% rename from src/utils/promises/isPromise.ts rename to .old-already-moved/src/utils/promises/isPromise.ts diff --git a/src/utils/promises/resolvable.ts b/.old-already-moved/src/utils/promises/resolvable.ts similarity index 100% rename from src/utils/promises/resolvable.ts rename to .old-already-moved/src/utils/promises/resolvable.ts diff --git a/src/utils/promises/retry.ts b/.old-already-moved/src/utils/promises/retry.ts similarity index 100% rename from src/utils/promises/retry.ts rename to .old-already-moved/src/utils/promises/retry.ts diff --git a/src/utils/promises/sleep.ts b/.old-already-moved/src/utils/promises/sleep.ts similarity index 100% rename from src/utils/promises/sleep.ts rename to .old-already-moved/src/utils/promises/sleep.ts diff --git a/src/utils/promises/timeout.ts b/.old-already-moved/src/utils/promises/timeout.ts similarity index 100% rename from src/utils/promises/timeout.ts rename to .old-already-moved/src/utils/promises/timeout.ts diff --git a/src/utils/ramps.ts b/.old-already-moved/src/utils/ramps.ts similarity index 100% rename from src/utils/ramps.ts rename to .old-already-moved/src/utils/ramps.ts diff --git a/src/utils/react/useAsyncEffect.ts b/.old-already-moved/src/utils/react/useAsyncEffect.ts similarity index 100% rename from src/utils/react/useAsyncEffect.ts rename to .old-already-moved/src/utils/react/useAsyncEffect.ts diff --git a/src/utils/react/useCooldownCallback.ts b/.old-already-moved/src/utils/react/useCooldownCallback.ts similarity index 100% rename from src/utils/react/useCooldownCallback.ts rename to .old-already-moved/src/utils/react/useCooldownCallback.ts diff --git a/src/utils/runtime.ts b/.old-already-moved/src/utils/runtime.ts similarity index 100% rename from src/utils/runtime.ts rename to .old-already-moved/src/utils/runtime.ts diff --git a/src/utils/signer.utils.ts b/.old-already-moved/src/utils/signer.utils.ts similarity index 100% rename from src/utils/signer.utils.ts rename to .old-already-moved/src/utils/signer.utils.ts diff --git a/src/utils/storage.ts b/.old-already-moved/src/utils/storage.ts similarity index 97% rename from src/utils/storage.ts rename to .old-already-moved/src/utils/storage.ts index 158749c01..e4bca6b66 100644 --- a/src/utils/storage.ts +++ b/.old-already-moved/src/utils/storage.ts @@ -4,7 +4,7 @@ import { Storage } from "@plasmohq/storage"; import { useStorage as usePlasmoStorage } from "@plasmohq/storage/hook"; import { useMemo } from "react"; import { StorageMock, type StorageMockInterface } from "~iframe/storage/plasmo-storage/plasmo-storage.mock"; -import { IS_EMBEDDED_APP } from "./embedded/embedded.constants"; +import { IS_EMBEDDED_APP } from "./_embedded/embedded.constants"; /** * Default extension storage: diff --git a/src/utils/storage.utils.ts b/.old-already-moved/src/utils/storage.utils.ts similarity index 100% rename from src/utils/storage.utils.ts rename to .old-already-moved/src/utils/storage.utils.ts diff --git a/src/utils/storage/storage.constants.ts b/.old-already-moved/src/utils/storage/storage.constants.ts similarity index 100% rename from src/utils/storage/storage.constants.ts rename to .old-already-moved/src/utils/storage/storage.constants.ts diff --git a/src/utils/supported_currencies.ts b/.old-already-moved/src/utils/supported_currencies.ts similarity index 100% rename from src/utils/supported_currencies.ts rename to .old-already-moved/src/utils/supported_currencies.ts diff --git a/src/utils/tanstack.ts b/.old-already-moved/src/utils/tanstack.ts similarity index 100% rename from src/utils/tanstack.ts rename to .old-already-moved/src/utils/tanstack.ts diff --git a/src/utils/theme.ts b/.old-already-moved/src/utils/theme.ts similarity index 100% rename from src/utils/theme.ts rename to .old-already-moved/src/utils/theme.ts diff --git a/src/utils/theme/observer/theme-observer.component.tsx b/.old-already-moved/src/utils/theme/observer/theme-observer.component.tsx similarity index 100% rename from src/utils/theme/observer/theme-observer.component.tsx rename to .old-already-moved/src/utils/theme/observer/theme-observer.component.tsx diff --git a/src/utils/theme/styled-components/styled-components.provider.tsx b/.old-already-moved/src/utils/theme/styled-components/styled-components.provider.tsx similarity index 100% rename from src/utils/theme/styled-components/styled-components.provider.tsx rename to .old-already-moved/src/utils/theme/styled-components/styled-components.provider.tsx diff --git a/src/utils/theme/theme.constants.ts b/.old-already-moved/src/utils/theme/theme.constants.ts similarity index 100% rename from src/utils/theme/theme.constants.ts rename to .old-already-moved/src/utils/theme/theme.constants.ts diff --git a/src/utils/theme/theme.context.tsx b/.old-already-moved/src/utils/theme/theme.context.tsx similarity index 100% rename from src/utils/theme/theme.context.tsx rename to .old-already-moved/src/utils/theme/theme.context.tsx diff --git a/src/utils/theme/theme.hook.ts b/.old-already-moved/src/utils/theme/theme.hook.ts similarity index 100% rename from src/utils/theme/theme.hook.ts rename to .old-already-moved/src/utils/theme/theme.hook.ts diff --git a/src/utils/theme/theme.provider.tsx b/.old-already-moved/src/utils/theme/theme.provider.tsx similarity index 98% rename from src/utils/theme/theme.provider.tsx rename to .old-already-moved/src/utils/theme/theme.provider.tsx index a78d49735..b19012516 100644 --- a/src/utils/theme/theme.provider.tsx +++ b/.old-already-moved/src/utils/theme/theme.provider.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useRef, useMemo, type PropsWithChildren } from "react"; import { themeTokens, commonTokens } from "../../components/embed/themes/theme-config"; import useSetting from "~settings/hook"; -import { isInsideIframe } from "~utils/embedded/iframe.utils"; +import { isInsideIframe } from "~utils/_embedded/iframe.utils"; import { useAsyncEffect } from "~utils/react/useAsyncEffect"; import { LocalStorage } from "~iframe/storage/unpartitioned-storage/local-storage"; import { IS_FOCUS_ACTIVE_CLS, THEME_MODES } from "~utils/theme/theme.constants"; @@ -9,7 +9,7 @@ import type { ThemeMode, ThemeContextState, ThemeContextValue } from "~utils/the import { resolveThemeMode, darkModePreference } from "~utils/theme/theme.utils"; import { ThemeContext } from "~utils/theme/theme.context"; import { MotionGlobalConfig } from "framer-motion"; -import { postEmbeddedMessage } from "~utils/embedded/utils/messages/embedded-messages.utils"; +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; import { ARCONNECT_THEME_BACKGROUND_COLOR, ARCONNECT_THEME_TEXT_COLOR } from "~utils/storage.utils"; export function ThemeProvider({ children }: PropsWithChildren<{}>) { diff --git a/src/utils/theme/theme.types.ts b/.old-already-moved/src/utils/theme/theme.types.ts similarity index 100% rename from src/utils/theme/theme.types.ts rename to .old-already-moved/src/utils/theme/theme.types.ts diff --git a/src/utils/theme/theme.utils.ts b/.old-already-moved/src/utils/theme/theme.utils.ts similarity index 100% rename from src/utils/theme/theme.utils.ts rename to .old-already-moved/src/utils/theme/theme.utils.ts diff --git a/src/utils/tier/alarms.ts b/.old-already-moved/src/utils/tier/alarms.ts similarity index 100% rename from src/utils/tier/alarms.ts rename to .old-already-moved/src/utils/tier/alarms.ts diff --git a/src/utils/tier/carousel.ts b/.old-already-moved/src/utils/tier/carousel.ts similarity index 100% rename from src/utils/tier/carousel.ts rename to .old-already-moved/src/utils/tier/carousel.ts diff --git a/src/utils/tier/constants.ts b/.old-already-moved/src/utils/tier/constants.ts similarity index 100% rename from src/utils/tier/constants.ts rename to .old-already-moved/src/utils/tier/constants.ts diff --git a/src/utils/tier/hooks.ts b/.old-already-moved/src/utils/tier/hooks.ts similarity index 100% rename from src/utils/tier/hooks.ts rename to .old-already-moved/src/utils/tier/hooks.ts diff --git a/src/utils/tier/types.ts b/.old-already-moved/src/utils/tier/types.ts similarity index 100% rename from src/utils/tier/types.ts rename to .old-already-moved/src/utils/tier/types.ts diff --git a/src/utils/tier/utils.ts b/.old-already-moved/src/utils/tier/utils.ts similarity index 100% rename from src/utils/tier/utils.ts rename to .old-already-moved/src/utils/tier/utils.ts diff --git a/src/utils/timestamp.ts b/.old-already-moved/src/utils/timestamp.ts similarity index 100% rename from src/utils/timestamp.ts rename to .old-already-moved/src/utils/timestamp.ts diff --git a/src/utils/transactions.ts b/.old-already-moved/src/utils/transactions.ts similarity index 100% rename from src/utils/transactions.ts rename to .old-already-moved/src/utils/transactions.ts diff --git a/src/utils/transak/transak.alarms.ts b/.old-already-moved/src/utils/transak/transak.alarms.ts similarity index 100% rename from src/utils/transak/transak.alarms.ts rename to .old-already-moved/src/utils/transak/transak.alarms.ts diff --git a/src/utils/transak/transak.constants.ts b/.old-already-moved/src/utils/transak/transak.constants.ts similarity index 100% rename from src/utils/transak/transak.constants.ts rename to .old-already-moved/src/utils/transak/transak.constants.ts diff --git a/src/utils/transak/transak.hooks.ts b/.old-already-moved/src/utils/transak/transak.hooks.ts similarity index 99% rename from src/utils/transak/transak.hooks.ts rename to .old-already-moved/src/utils/transak/transak.hooks.ts index ae22aa42f..0ea3c8bc3 100644 --- a/src/utils/transak/transak.hooks.ts +++ b/.old-already-moved/src/utils/transak/transak.hooks.ts @@ -7,7 +7,7 @@ import { useStorage, ExtensionStorage } from "~utils/storage"; import { useLocation } from "~wallets/router/router.utils"; import type { WanderRoutePath } from "~wallets/router/router.types"; import getSymbolFromCurrency from "currency-symbol-map"; -import { IS_EMBEDDED_APP } from "~utils/embedded/embedded.constants"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; import { useActiveTier } from "~utils/tier/hooks"; import { TierTypes } from "~utils/tier/constants"; import { scheduleTransakPurchaseAlarm } from "./transak.alarms"; diff --git a/src/utils/transak/transak.queries.ts b/.old-already-moved/src/utils/transak/transak.queries.ts similarity index 100% rename from src/utils/transak/transak.queries.ts rename to .old-already-moved/src/utils/transak/transak.queries.ts diff --git a/src/utils/transak/transak.utils.ts b/.old-already-moved/src/utils/transak/transak.utils.ts similarity index 100% rename from src/utils/transak/transak.utils.ts rename to .old-already-moved/src/utils/transak/transak.utils.ts diff --git a/src/utils/upload/wallet/use-wallet-upload.hook.ts b/.old-already-moved/src/utils/upload/wallet/use-wallet-upload.hook.ts similarity index 97% rename from src/utils/upload/wallet/use-wallet-upload.hook.ts rename to .old-already-moved/src/utils/upload/wallet/use-wallet-upload.hook.ts index 82b9591ef..a164f1678 100644 --- a/src/utils/upload/wallet/use-wallet-upload.hook.ts +++ b/.old-already-moved/src/utils/upload/wallet/use-wallet-upload.hook.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from "react"; -import type { RecoveryJSON, TempWallet, Wallet } from "~utils/embedded/embedded.types"; +import type { RecoveryJSON, TempWallet, Wallet } from "~utils/_embedded/embedded.types"; import type { JWKInterface } from "arweave/web/lib/wallet"; import { WalletUtils } from "~utils/wallets/wallets.utils"; diff --git a/src/utils/urls/getAppIconPlaceholder.ts b/.old-already-moved/src/utils/urls/getAppIconPlaceholder.ts similarity index 100% rename from src/utils/urls/getAppIconPlaceholder.ts rename to .old-already-moved/src/utils/urls/getAppIconPlaceholder.ts diff --git a/src/utils/urls/isExternalURL.ts b/.old-already-moved/src/utils/urls/isExternalURL.ts similarity index 100% rename from src/utils/urls/isExternalURL.ts rename to .old-already-moved/src/utils/urls/isExternalURL.ts diff --git a/src/utils/urls/isGateway.ts b/.old-already-moved/src/utils/urls/isGateway.ts similarity index 100% rename from src/utils/urls/isGateway.ts rename to .old-already-moved/src/utils/urls/isGateway.ts diff --git a/src/utils/urls/isURL.ts b/.old-already-moved/src/utils/urls/isURL.ts similarity index 100% rename from src/utils/urls/isURL.ts rename to .old-already-moved/src/utils/urls/isURL.ts diff --git a/src/utils/wallets/wallets.constants.ts b/.old-already-moved/src/utils/wallets/wallets.constants.ts similarity index 100% rename from src/utils/wallets/wallets.constants.ts rename to .old-already-moved/src/utils/wallets/wallets.constants.ts diff --git a/src/utils/wallets/wallets.hooks.ts b/.old-already-moved/src/utils/wallets/wallets.hooks.ts similarity index 100% rename from src/utils/wallets/wallets.hooks.ts rename to .old-already-moved/src/utils/wallets/wallets.hooks.ts diff --git a/src/utils/wallets/wallets.provider.tsx b/.old-already-moved/src/utils/wallets/wallets.provider.tsx similarity index 100% rename from src/utils/wallets/wallets.provider.tsx rename to .old-already-moved/src/utils/wallets/wallets.provider.tsx diff --git a/src/utils/wallets/wallets.service.ts b/.old-already-moved/src/utils/wallets/wallets.service.ts similarity index 97% rename from src/utils/wallets/wallets.service.ts rename to .old-already-moved/src/utils/wallets/wallets.service.ts index e010d34ed..0448a9065 100644 --- a/src/utils/wallets/wallets.service.ts +++ b/.old-already-moved/src/utils/wallets/wallets.service.ts @@ -1,7 +1,7 @@ import { solveChallenge, ErrorMessages, type DbSession } from "embed-api"; -import type { Wallet, WalletActivationStatus } from "~utils/embedded/embedded.types"; -import { trpcVanilla } from "~utils/embedded/embedded.utils"; -import { isTRPCClientError } from "~utils/embedded/utils/trpc/trpc.utils"; +import type { Wallet, WalletActivationStatus } from "~utils/_embedded/embedded.types"; +import { trpcVanilla } from "~utils/_embedded/embedded.utils"; +import { isTRPCClientError } from "~utils/_embedded/utils/trpc/trpc.utils"; import { WalletUtils } from "~utils/wallets/wallets.utils"; import { getWallets, removeWallet } from "~wallets"; import type { JWKInterface } from "arweave/web/lib/wallet"; diff --git a/src/utils/wallets/wallets.utils.ts b/.old-already-moved/src/utils/wallets/wallets.utils.ts similarity index 99% rename from src/utils/wallets/wallets.utils.ts rename to .old-already-moved/src/utils/wallets/wallets.utils.ts index fdb02157d..d1e2f6431 100644 --- a/src/utils/wallets/wallets.utils.ts +++ b/.old-already-moved/src/utils/wallets/wallets.utils.ts @@ -9,8 +9,8 @@ import { setDecryptionKey } from "~wallets/auth"; import { INVALID_DEVICE_SHARES_INFO_ERR_MSG } from "~utils/wallets/wallets.constants"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import type NodeForge from "node-forge"; -import type { RecoveryJSON, Wallet } from "~utils/embedded/embedded.types"; -import { EMBEDDED_FEATURE_FLAGS } from "~utils/embedded/embedded.constants"; +import type { RecoveryJSON, Wallet } from "~utils/_embedded/embedded.types"; +import { EMBEDDED_FEATURE_FLAGS } from "~utils/_embedded/embedded.constants"; import { LocalStorage } from "~iframe/storage/unpartitioned-storage/local-storage"; import { random, pki, util } from "node-forge"; import { ed25519 } from "@noble/curves/ed25519.js"; diff --git a/src/wallets/auth.ts b/.old-already-moved/src/wallets/auth.ts similarity index 100% rename from src/wallets/auth.ts rename to .old-already-moved/src/wallets/auth.ts diff --git a/src/wallets/encryption.ts b/.old-already-moved/src/wallets/encryption.ts similarity index 100% rename from src/wallets/encryption.ts rename to .old-already-moved/src/wallets/encryption.ts diff --git a/src/wallets/generator.ts b/.old-already-moved/src/wallets/generator.ts similarity index 100% rename from src/wallets/generator.ts rename to .old-already-moved/src/wallets/generator.ts diff --git a/src/wallets/hardware/index.ts b/.old-already-moved/src/wallets/hardware/index.ts similarity index 100% rename from src/wallets/hardware/index.ts rename to .old-already-moved/src/wallets/hardware/index.ts diff --git a/src/wallets/hardware/keystone.ts b/.old-already-moved/src/wallets/hardware/keystone.ts similarity index 100% rename from src/wallets/hardware/keystone.ts rename to .old-already-moved/src/wallets/hardware/keystone.ts diff --git a/src/wallets/hooks.ts b/.old-already-moved/src/wallets/hooks.ts similarity index 99% rename from src/wallets/hooks.ts rename to .old-already-moved/src/wallets/hooks.ts index 901311569..be8250dfa 100644 --- a/src/wallets/hooks.ts +++ b/.old-already-moved/src/wallets/hooks.ts @@ -31,7 +31,7 @@ import { AO_SENT_QUERY_FOR_TOKEN_WITH_CURSOR, AO_RECEIVER_QUERY_FOR_TOKEN_WITH_CURSOR, AO_LIQUIDOPS_RECEIVER_QUERY_FOR_TOKEN_WITH_CURSOR, -} from "~notifications/utils"; +} from "~_notifications/utils"; import { gql } from "~gateways/api"; import BigNumber from "bignumber.js"; import { useAsyncEffect } from "~utils/react/useAsyncEffect"; diff --git a/src/wallets/index.ts b/.old-already-moved/src/wallets/index.ts similarity index 100% rename from src/wallets/index.ts rename to .old-already-moved/src/wallets/index.ts diff --git a/src/wallets/router/auth/auth-router.hook.ts b/.old-already-moved/src/wallets/router/auth/auth-router.hook.ts similarity index 100% rename from src/wallets/router/auth/auth-router.hook.ts rename to .old-already-moved/src/wallets/router/auth/auth-router.hook.ts diff --git a/src/wallets/router/auth/auth.embed.routes.ts b/.old-already-moved/src/wallets/router/auth/auth.embed.routes.ts similarity index 82% rename from src/wallets/router/auth/auth.embed.routes.ts rename to .old-already-moved/src/wallets/router/auth/auth.embed.routes.ts index af5a3658d..d1ba84ea1 100644 --- a/src/wallets/router/auth/auth.embed.routes.ts +++ b/.old-already-moved/src/wallets/router/auth/auth.embed.routes.ts @@ -5,15 +5,15 @@ import { SubscriptionAuthRequestView } from "~routes/auth/subscription"; import { UnlockAuthRequestView } from "~routes/auth/unlock"; import { getExtensionOverrides } from "~wallets/router/extension/extension.routes"; import type { RouteConfig } from "~wallets/router/router.types"; -import { EmbeddedConnectAuthRequestView } from "~routes/embedded/auth-request/connect/connect.view"; -import { EmbeddedSignDataAuthRequestView } from "~routes/embedded/auth-request/sign/signDataItem.view"; -import { EmbeddedDecryptAuthRequestView } from "~routes/embedded/auth-request/decrypt/decrypt.view"; -import { EmbeddedSignatureAuthRequestView } from "~routes/embedded/auth-request/signature/signature.view"; -import { EmbeddedSignAuthRequestView } from "~routes/embedded/auth-request/sign/sign.view"; -import { EmbeddedBatchSignDataItemAuthRequestView } from "~routes/embedded/auth-request/sign/batchSignDataItem.view"; -import { EmbeddedConnectSettingsAuthRequestView } from "~routes/embedded/auth-request/connect/connect-settings.view"; -import { EmbeddedConnectCustomAuthRequestView } from "~routes/embedded/auth-request/connect/connect-custom.view"; -import { EmbeddedSignDetailsAuthRequestView } from "~routes/embedded/auth-request/sign/sign-details.view"; +import { EmbeddedConnectAuthRequestView } from "~routes/_embedded/auth-request/connect/connect.view"; +import { EmbeddedSignDataAuthRequestView } from "~routes/_embedded/auth-request/sign/signDataItem.view"; +import { EmbeddedDecryptAuthRequestView } from "~routes/_embedded/auth-request/decrypt/decrypt.view"; +import { EmbeddedSignatureAuthRequestView } from "~routes/_embedded/auth-request/signature/signature.view"; +import { EmbeddedSignAuthRequestView } from "~routes/_embedded/auth-request/sign/sign.view"; +import { EmbeddedBatchSignDataItemAuthRequestView } from "~routes/_embedded/auth-request/sign/batchSignDataItem.view"; +import { EmbeddedConnectSettingsAuthRequestView } from "~routes/_embedded/auth-request/connect/connect-settings.view"; +import { EmbeddedConnectCustomAuthRequestView } from "~routes/_embedded/auth-request/connect/connect-custom.view"; +import { EmbeddedSignDetailsAuthRequestView } from "~routes/_embedded/auth-request/sign/sign-details.view"; export type ConnectAuthRoutePath = | "/auth-request" diff --git a/src/wallets/router/auth/auth.routes.ts b/.old-already-moved/src/wallets/router/auth/auth.routes.ts similarity index 100% rename from src/wallets/router/auth/auth.routes.ts rename to .old-already-moved/src/wallets/router/auth/auth.routes.ts diff --git a/src/wallets/router/components/link/Link.module.scss b/.old-already-moved/src/wallets/router/components/link/Link.module.scss similarity index 100% rename from src/wallets/router/components/link/Link.module.scss rename to .old-already-moved/src/wallets/router/components/link/Link.module.scss diff --git a/src/wallets/router/components/link/Link.tsx b/.old-already-moved/src/wallets/router/components/link/Link.tsx similarity index 100% rename from src/wallets/router/components/link/Link.tsx rename to .old-already-moved/src/wallets/router/components/link/Link.tsx diff --git a/src/wallets/router/components/redirect/Redirect.tsx b/.old-already-moved/src/wallets/router/components/redirect/Redirect.tsx similarity index 100% rename from src/wallets/router/components/redirect/Redirect.tsx rename to .old-already-moved/src/wallets/router/components/redirect/Redirect.tsx diff --git a/src/wallets/router/dashboard/dashboard.routes.ts b/.old-already-moved/src/wallets/router/dashboard/dashboard.routes.ts similarity index 100% rename from src/wallets/router/dashboard/dashboard.routes.ts rename to .old-already-moved/src/wallets/router/dashboard/dashboard.routes.ts diff --git a/src/wallets/router/extension/extension-router.hook.ts b/.old-already-moved/src/wallets/router/extension/extension-router.hook.ts similarity index 100% rename from src/wallets/router/extension/extension-router.hook.ts rename to .old-already-moved/src/wallets/router/extension/extension-router.hook.ts diff --git a/src/wallets/router/extension/extension.routes.tsx b/.old-already-moved/src/wallets/router/extension/extension.routes.tsx similarity index 100% rename from src/wallets/router/extension/extension.routes.tsx rename to .old-already-moved/src/wallets/router/extension/extension.routes.tsx diff --git a/src/wallets/router/iframe/iframe-router.hook.ts b/.old-already-moved/src/wallets/router/iframe/iframe-router.hook.ts similarity index 97% rename from src/wallets/router/iframe/iframe-router.hook.ts rename to .old-already-moved/src/wallets/router/iframe/iframe-router.hook.ts index 2bab87f3c..236026324 100644 --- a/src/wallets/router/iframe/iframe-router.hook.ts +++ b/.old-already-moved/src/wallets/router/iframe/iframe-router.hook.ts @@ -1,6 +1,6 @@ import { useHashLocation } from "wouter/use-hash-location"; -import { useEmbedded } from "~utils/embedded/embedded.hooks"; -import type { AuthStatus } from "~utils/embedded/embedded.types"; +import { useEmbedded } from "~utils/_embedded/embedded.hooks"; +import type { AuthStatus } from "~utils/_embedded/embedded.types"; import { useAuthRequestsLocation } from "~wallets/router/auth/auth-router.hook"; import type { ExtensionRouteOverride } from "~wallets/router/extension/extension.routes"; import { EmbeddedPaths } from "~wallets/router/iframe/iframe.routes"; diff --git a/src/wallets/router/iframe/iframe.routes.tsx b/.old-already-moved/src/wallets/router/iframe/iframe.routes.tsx similarity index 69% rename from src/wallets/router/iframe/iframe.routes.tsx rename to .old-already-moved/src/wallets/router/iframe/iframe.routes.tsx index bdb9bad4e..1dc2c95d1 100644 --- a/src/wallets/router/iframe/iframe.routes.tsx +++ b/.old-already-moved/src/wallets/router/iframe/iframe.routes.tsx @@ -4,68 +4,68 @@ import type { RouteConfig } from "~wallets/router/router.types"; import { isRouteOverride } from "~wallets/router/router.utils"; // Support Views: -import { UnpartitionedStateMissingEmbeddedView } from "~routes/embedded/support/unpartitioned-state/unpartitioned-state.view"; +import { UnpartitionedStateMissingEmbeddedView } from "~routes/_embedded/support/unpartitioned-state/unpartitioned-state.view"; // Authentication Views: -import { AuthEmbeddedView } from "~routes/embedded/auth/auth/auth.view"; -import { AuthEmailOtpEmbeddedView } from "~routes/embedded/auth/auth-email-otp/auth-email-otp.view"; -import { AuthEmailSignInPasswordEmbeddedView } from "~routes/embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view"; -import { AuthEmailVerifyEmbeddedView } from "~routes/embedded/auth/auth-email-verify/auth-email-verify.view"; -import { AuthMoreProvidersEmbeddedView } from "~routes/embedded/auth/auth-more-providers/auth-more-providers.view"; -import { AuthAddWalletEmbeddedView } from "~routes/embedded/auth/add-wallet/auth-add-wallet.view"; -import { AuthImportSeedphraseEmbeddedView } from "~routes/embedded/auth/import-seedphrase/auth-import-seedphrase.view"; -import { AuthImportKeyfileEmbeddedView } from "~routes/embedded/auth/import-keyfile/auth-import-keyfile.view"; -import { AuthImportQrCodeEmbeddedView } from "~routes/embedded/auth/import-qrcode/auth-import-qrcode"; +import { AuthEmbeddedView } from "~routes/_embedded/auth/auth/auth.view"; +import { AuthEmailOtpEmbeddedView } from "~routes/_embedded/auth/auth-email-otp/auth-email-otp.view"; +import { AuthEmailSignInPasswordEmbeddedView } from "~routes/_embedded/auth/auth-email-sign-in-password/auth-email-sign-in-password.view"; +import { AuthEmailVerifyEmbeddedView } from "~routes/_embedded/auth/auth-email-verify/auth-email-verify.view"; +import { AuthMoreProvidersEmbeddedView } from "~routes/_embedded/auth/auth-more-providers/auth-more-providers.view"; +import { AuthAddWalletEmbeddedView } from "~routes/_embedded/auth/add-wallet/auth-add-wallet.view"; +import { AuthImportSeedphraseEmbeddedView } from "~routes/_embedded/auth/import-seedphrase/auth-import-seedphrase.view"; +import { AuthImportKeyfileEmbeddedView } from "~routes/_embedded/auth/import-keyfile/auth-import-keyfile.view"; +import { AuthImportQrCodeEmbeddedView } from "~routes/_embedded/auth/import-qrcode/auth-import-qrcode"; // Authentication Linking Views: -import { AuthAddDeviceEmbeddedView } from "~routes/embedded/auth/add-device/auth-add-device.view"; -import { AuthAddAuthProviderEmbeddedView } from "~routes/embedded/auth/add-auth-provider/auth-add-auth-provider.view"; +import { AuthAddDeviceEmbeddedView } from "~routes/_embedded/auth/add-device/auth-add-device.view"; +import { AuthAddAuthProviderEmbeddedView } from "~routes/_embedded/auth/add-auth-provider/auth-add-auth-provider.view"; // Shares Views: -import { AuthRestoreSharesEmbeddedView } from "~routes/embedded/auth/restore-shares/auth-restore-shares.view"; -import { AuthRestoreSharesCreateConfirmationEmbeddedView } from "~routes/embedded/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view"; -import { AuthRestoreSharesRecoveryFileEmbeddedView } from "~routes/embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view"; -import { AuthRestoreSharesSeedPhraseEmbeddedView } from "~routes/embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view"; -import { AuthRestoreSharesKeyfileEmbeddedView } from "~routes/embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view"; -import { AuthRestoreSharesQrCodeEmbeddedView } from "~routes/embedded/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view"; +import { AuthRestoreSharesEmbeddedView } from "~routes/_embedded/auth/restore-shares/auth-restore-shares.view"; +import { AuthRestoreSharesCreateConfirmationEmbeddedView } from "~routes/_embedded/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view"; +import { AuthRestoreSharesRecoveryFileEmbeddedView } from "~routes/_embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view"; +import { AuthRestoreSharesSeedPhraseEmbeddedView } from "~routes/_embedded/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view"; +import { AuthRestoreSharesKeyfileEmbeddedView } from "~routes/_embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view"; +import { AuthRestoreSharesQrCodeEmbeddedView } from "~routes/_embedded/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view"; // Account Recovery Views: -import { AuthRecoverAccountEmbeddedView } from "~routes/embedded/auth/recover-account/auth-recover-account.view"; -import { AuthRecoverAccountOtpEmbeddedView } from "~routes/embedded/auth/recover-account/otp/auth-recover-account-otp.view"; -import { AuthRecoverAccountSeedphraseEmbeddedView } from "~routes/embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view"; -import { AuthRecoverAccountKeyfileEmbeddedView } from "~routes/embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view"; -import { AuthRecoverAccountQrCodeEmbeddedView } from "~routes/embedded/auth/recover-account/qrcode/auth-recover-account-qrcode.view"; -import { AuthRecoverAccountSelectEmbeddedView } from "~routes/embedded/auth/recover-account/select-account/auth-recover-account-select.view"; -import { AuthRecoverAccountConfirmEmbeddedView } from "~routes/embedded/auth/recover-account/confirm/auth-recover-confirm.view"; +import { AuthRecoverAccountEmbeddedView } from "~routes/_embedded/auth/recover-account/auth-recover-account.view"; +import { AuthRecoverAccountOtpEmbeddedView } from "~routes/_embedded/auth/recover-account/otp/auth-recover-account-otp.view"; +import { AuthRecoverAccountSeedphraseEmbeddedView } from "~routes/_embedded/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view"; +import { AuthRecoverAccountKeyfileEmbeddedView } from "~routes/_embedded/auth/recover-account/keyfile/auth-recover-account-keyfile.view"; +import { AuthRecoverAccountQrCodeEmbeddedView } from "~routes/_embedded/auth/recover-account/qrcode/auth-recover-account-qrcode.view"; +import { AuthRecoverAccountSelectEmbeddedView } from "~routes/_embedded/auth/recover-account/select-account/auth-recover-account-select.view"; +import { AuthRecoverAccountConfirmEmbeddedView } from "~routes/_embedded/auth/recover-account/confirm/auth-recover-confirm.view"; // Account Management Views: -import { AccountChangePasswordEmbeddedView } from "~routes/embedded/account/change-password/account-change-password.view"; +import { AccountChangePasswordEmbeddedView } from "~routes/_embedded/account/change-password/account-change-password.view"; // Account Wallet Views: -import { AccountAddWalletEmbeddedView } from "~routes/embedded/account/add-wallet/account-add-wallet.view"; -import { AccountImportSeedphraseEmbeddedView } from "~routes/embedded/account/import-seedphrase/account-import-seedphrase.view"; -import { AccountImportKeyfileEmbeddedView } from "~routes/embedded/account/import-keyfile/account-import-keyfile.view"; +import { AccountAddWalletEmbeddedView } from "~routes/_embedded/account/add-wallet/account-add-wallet.view"; +import { AccountImportSeedphraseEmbeddedView } from "~routes/_embedded/account/import-seedphrase/account-import-seedphrase.view"; +import { AccountImportKeyfileEmbeddedView } from "~routes/_embedded/account/import-keyfile/account-import-keyfile.view"; // Account Backup Views: -import { AccountBackupWalletEmbeddedView } from "~routes/embedded/account/backup-wallet/backup-wallet.view"; -import { AccountBackupCopySeedphraseEmbeddedView } from "~routes/embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view"; -import { AccountBackupFullWalletEmbeddedView } from "~routes/embedded/account/backup-wallet/backup-full-wallet.view"; -import { AccountBackupWalletRecoveryFileEmbeddedView } from "~routes/embedded/account/backup-wallet/backup-wallet-recovery-file.view"; -import { AccountBackupWalletQrCodeEmbeddedView } from "~routes/embedded/account/backup-wallet/backup-wallet-qrcode"; -import { AccountExportWalletEmbeddedView } from "~routes/embedded/account/export-wallet/account-export-wallet.view"; +import { AccountBackupWalletEmbeddedView } from "~routes/_embedded/account/backup-wallet/backup-wallet.view"; +import { AccountBackupCopySeedphraseEmbeddedView } from "~routes/_embedded/account/backup-wallet/backup-wallet-copy-seedphrase.view"; +import { AccountBackupFullWalletEmbeddedView } from "~routes/_embedded/account/backup-wallet/backup-full-wallet.view"; +import { AccountBackupWalletRecoveryFileEmbeddedView } from "~routes/_embedded/account/backup-wallet/backup-wallet-recovery-file.view"; +import { AccountBackupWalletQrCodeEmbeddedView } from "~routes/_embedded/account/backup-wallet/backup-wallet-qrcode"; +import { AccountExportWalletEmbeddedView } from "~routes/_embedded/account/export-wallet/account-export-wallet.view"; // Wallet Views: -import { WalletHomeEmbeddedView } from "~routes/embedded/wallet/home/wallet.view"; -import { WalletReceiveEmbeddedView } from "~routes/embedded/wallet/receive/receive.view"; -import { WalletTransactionsEmbeddedView } from "~routes/embedded/wallet/transactions/transactions.view"; -import { WalletTransactionsHistoryEmbeddedView } from "~routes/embedded/wallet/transactions-history/transactions-history.view"; -import { WalletTransactionCompleteEmbeddedView } from "~routes/embedded/wallet/transactions/transaction-complete.view"; -import { WalletBuyEmbeddedView } from "~routes/embedded/wallet/buy/buy.container.view"; -import { WalletBuyCashEmbeddedView } from "~routes/embedded/wallet/buy/buy.cash.view"; -import { WalletReceiveOptionsEmbeddedView } from "~routes/embedded/wallet/receive/options/receive.options.view"; -import { WalletDepositTokensEmbeddedView } from "~routes/embedded/wallet/deposit/deposit.container.view"; -import { WalletBuyInputEmbeddedView } from "~routes/embedded/wallet/buy/buy.input.view"; -import { WalletBuySuccessEmbeddedView } from "~routes/embedded/wallet/buy/buy.success.view"; +import { WalletHomeEmbeddedView } from "~routes/_embedded/wallet/home/wallet.view"; +import { WalletReceiveEmbeddedView } from "~routes/_embedded/wallet/receive/receive.view"; +import { WalletTransactionsEmbeddedView } from "~routes/_embedded/wallet/transactions/transactions.view"; +import { WalletTransactionsHistoryEmbeddedView } from "~routes/_embedded/wallet/transactions-history/transactions-history.view"; +import { WalletTransactionCompleteEmbeddedView } from "~routes/_embedded/wallet/transactions/transaction-complete.view"; +import { WalletBuyEmbeddedView } from "~routes/_embedded/wallet/buy/buy.container.view"; +import { WalletBuyCashEmbeddedView } from "~routes/_embedded/wallet/buy/buy.cash.view"; +import { WalletReceiveOptionsEmbeddedView } from "~routes/_embedded/wallet/receive/options/receive.options.view"; +import { WalletDepositTokensEmbeddedView } from "~routes/_embedded/wallet/deposit/deposit.container.view"; +import { WalletBuyInputEmbeddedView } from "~routes/_embedded/wallet/buy/buy.input.view"; +import { WalletBuySuccessEmbeddedView } from "~routes/_embedded/wallet/buy/buy.success.view"; /** * Developers can manually navigate to these flows: diff --git a/src/wallets/router/popup/popup.routes.ts b/.old-already-moved/src/wallets/router/popup/popup.routes.ts similarity index 100% rename from src/wallets/router/popup/popup.routes.ts rename to .old-already-moved/src/wallets/router/popup/popup.routes.ts diff --git a/src/wallets/router/router.types.ts b/.old-already-moved/src/wallets/router/router.types.ts similarity index 100% rename from src/wallets/router/router.types.ts rename to .old-already-moved/src/wallets/router/router.types.ts diff --git a/src/wallets/router/router.utils.ts b/.old-already-moved/src/wallets/router/router.utils.ts similarity index 100% rename from src/wallets/router/router.utils.ts rename to .old-already-moved/src/wallets/router/router.utils.ts diff --git a/src/wallets/router/routes.component.tsx b/.old-already-moved/src/wallets/router/routes.component.tsx similarity index 100% rename from src/wallets/router/routes.component.tsx rename to .old-already-moved/src/wallets/router/routes.component.tsx diff --git a/src/wallets/router/welcome/welcome.routes.ts b/.old-already-moved/src/wallets/router/welcome/welcome.routes.ts similarity index 100% rename from src/wallets/router/welcome/welcome.routes.ts rename to .old-already-moved/src/wallets/router/welcome/welcome.routes.ts diff --git a/src/wallets/setup/non/non-wallet-setup.hook.ts b/.old-already-moved/src/wallets/setup/non/non-wallet-setup.hook.ts similarity index 100% rename from src/wallets/setup/non/non-wallet-setup.hook.ts rename to .old-already-moved/src/wallets/setup/non/non-wallet-setup.hook.ts diff --git a/src/wallets/wallets.types.ts b/.old-already-moved/src/wallets/wallets.types.ts similarity index 100% rename from src/wallets/wallets.types.ts rename to .old-already-moved/src/wallets/wallets.types.ts diff --git a/src/wallets/wallets.utils.ts b/.old-already-moved/src/wallets/wallets.utils.ts similarity index 100% rename from src/wallets/wallets.utils.ts rename to .old-already-moved/src/wallets/wallets.utils.ts diff --git a/wander-connect-sdk/DEVELOPMENT.md b/.old-already-moved/wander-connect-sdk/DEVELOPMENT.md similarity index 100% rename from wander-connect-sdk/DEVELOPMENT.md rename to .old-already-moved/wander-connect-sdk/DEVELOPMENT.md diff --git a/wander-connect-sdk/README.md b/.old-already-moved/wander-connect-sdk/README.md similarity index 100% rename from wander-connect-sdk/README.md rename to .old-already-moved/wander-connect-sdk/README.md diff --git a/wander-connect-sdk/package.json b/.old-already-moved/wander-connect-sdk/package.json similarity index 100% rename from wander-connect-sdk/package.json rename to .old-already-moved/wander-connect-sdk/package.json diff --git a/wander-connect-sdk/src/components/button/button.component.ts b/.old-already-moved/wander-connect-sdk/src/components/button/button.component.ts similarity index 100% rename from wander-connect-sdk/src/components/button/button.component.ts rename to .old-already-moved/wander-connect-sdk/src/components/button/button.component.ts diff --git a/wander-connect-sdk/src/components/button/button.template.ts b/.old-already-moved/wander-connect-sdk/src/components/button/button.template.ts similarity index 100% rename from wander-connect-sdk/src/components/button/button.template.ts rename to .old-already-moved/wander-connect-sdk/src/components/button/button.template.ts diff --git a/wander-connect-sdk/src/components/iframe/iframe.component.ts b/.old-already-moved/wander-connect-sdk/src/components/iframe/iframe.component.ts similarity index 100% rename from wander-connect-sdk/src/components/iframe/iframe.component.ts rename to .old-already-moved/wander-connect-sdk/src/components/iframe/iframe.component.ts diff --git a/wander-connect-sdk/src/components/iframe/iframe.template.ts b/.old-already-moved/wander-connect-sdk/src/components/iframe/iframe.template.ts similarity index 100% rename from wander-connect-sdk/src/components/iframe/iframe.template.ts rename to .old-already-moved/wander-connect-sdk/src/components/iframe/iframe.template.ts diff --git a/wander-connect-sdk/src/index.ts b/.old-already-moved/wander-connect-sdk/src/index.ts similarity index 100% rename from wander-connect-sdk/src/index.ts rename to .old-already-moved/wander-connect-sdk/src/index.ts diff --git a/wander-connect-sdk/src/types/global.d.ts b/.old-already-moved/wander-connect-sdk/src/types/global.d.ts similarity index 100% rename from wander-connect-sdk/src/types/global.d.ts rename to .old-already-moved/wander-connect-sdk/src/types/global.d.ts diff --git a/wander-connect-sdk/src/utils/auth/auth.constants.ts b/.old-already-moved/wander-connect-sdk/src/utils/auth/auth.constants.ts similarity index 100% rename from wander-connect-sdk/src/utils/auth/auth.constants.ts rename to .old-already-moved/wander-connect-sdk/src/utils/auth/auth.constants.ts diff --git a/wander-connect-sdk/src/utils/deep-clone/deep-clone.utils.ts b/.old-already-moved/wander-connect-sdk/src/utils/deep-clone/deep-clone.utils.ts similarity index 100% rename from wander-connect-sdk/src/utils/deep-clone/deep-clone.utils.ts rename to .old-already-moved/wander-connect-sdk/src/utils/deep-clone/deep-clone.utils.ts diff --git a/wander-connect-sdk/src/utils/layout/layout.utils.ts b/.old-already-moved/wander-connect-sdk/src/utils/layout/layout.utils.ts similarity index 100% rename from wander-connect-sdk/src/utils/layout/layout.utils.ts rename to .old-already-moved/wander-connect-sdk/src/utils/layout/layout.utils.ts diff --git a/wander-connect-sdk/src/utils/message/message.types.ts b/.old-already-moved/wander-connect-sdk/src/utils/message/message.types.ts similarity index 100% rename from wander-connect-sdk/src/utils/message/message.types.ts rename to .old-already-moved/wander-connect-sdk/src/utils/message/message.types.ts diff --git a/wander-connect-sdk/src/utils/message/message.utils.ts b/.old-already-moved/wander-connect-sdk/src/utils/message/message.utils.ts similarity index 100% rename from wander-connect-sdk/src/utils/message/message.utils.ts rename to .old-already-moved/wander-connect-sdk/src/utils/message/message.utils.ts diff --git a/wander-connect-sdk/src/utils/styles/styles.utils.ts b/.old-already-moved/wander-connect-sdk/src/utils/styles/styles.utils.ts similarity index 100% rename from wander-connect-sdk/src/utils/styles/styles.utils.ts rename to .old-already-moved/wander-connect-sdk/src/utils/styles/styles.utils.ts diff --git a/wander-connect-sdk/src/utils/url/url.utils.ts b/.old-already-moved/wander-connect-sdk/src/utils/url/url.utils.ts similarity index 100% rename from wander-connect-sdk/src/utils/url/url.utils.ts rename to .old-already-moved/wander-connect-sdk/src/utils/url/url.utils.ts diff --git a/wander-connect-sdk/src/wander-connect.ts b/.old-already-moved/wander-connect-sdk/src/wander-connect.ts similarity index 100% rename from wander-connect-sdk/src/wander-connect.ts rename to .old-already-moved/wander-connect-sdk/src/wander-connect.ts diff --git a/wander-connect-sdk/src/wander-connect.types.ts b/.old-already-moved/wander-connect-sdk/src/wander-connect.types.ts similarity index 100% rename from wander-connect-sdk/src/wander-connect.types.ts rename to .old-already-moved/wander-connect-sdk/src/wander-connect.types.ts diff --git a/wander-connect-sdk/tsconfig.json b/.old-already-moved/wander-connect-sdk/tsconfig.json similarity index 100% rename from wander-connect-sdk/tsconfig.json rename to .old-already-moved/wander-connect-sdk/tsconfig.json diff --git a/wander-connect-sdk/tsup.config.ts b/.old-already-moved/wander-connect-sdk/tsup.config.ts similarity index 100% rename from wander-connect-sdk/tsup.config.ts rename to .old-already-moved/wander-connect-sdk/tsup.config.ts diff --git a/.changeset/README.md b/.old/.changeset/README.md similarity index 100% rename from .changeset/README.md rename to .old/.changeset/README.md diff --git a/.changeset/config.json b/.old/.changeset/config.json similarity index 100% rename from .changeset/config.json rename to .old/.changeset/config.json diff --git a/.changeset/large-rats-share.md b/.old/.changeset/large-rats-share.md similarity index 100% rename from .changeset/large-rats-share.md rename to .old/.changeset/large-rats-share.md diff --git a/.github/CONTRIBUTING.md b/.old/.github/CONTRIBUTING.md similarity index 100% rename from .github/CONTRIBUTING.md rename to .old/.github/CONTRIBUTING.md diff --git a/.github/README.md b/.old/.github/README.md similarity index 100% rename from .github/README.md rename to .old/.github/README.md diff --git a/.github/wander-banner.png b/.old/.github/wander-banner.png similarity index 100% rename from .github/wander-banner.png rename to .old/.github/wander-banner.png diff --git a/.github/wander-connect-banner.png b/.old/.github/wander-connect-banner.png similarity index 100% rename from .github/wander-connect-banner.png rename to .old/.github/wander-connect-banner.png diff --git a/.github/workflows/codeql.yml b/.old/.github/workflows/codeql.yml similarity index 100% rename from .github/workflows/codeql.yml rename to .old/.github/workflows/codeql.yml diff --git a/.github/workflows/create-be-build.yml b/.old/.github/workflows/create-be-build.yml similarity index 100% rename from .github/workflows/create-be-build.yml rename to .old/.github/workflows/create-be-build.yml diff --git a/.github/workflows/release.yml b/.old/.github/workflows/release.yml similarity index 100% rename from .github/workflows/release.yml rename to .old/.github/workflows/release.yml diff --git a/.github/workflows/submit-beta.yml b/.old/.github/workflows/submit-beta.yml similarity index 100% rename from .github/workflows/submit-beta.yml rename to .old/.github/workflows/submit-beta.yml diff --git a/.github/workflows/submit.yml b/.old/.github/workflows/submit.yml similarity index 100% rename from .github/workflows/submit.yml rename to .old/.github/workflows/submit.yml diff --git a/.github/workflows/version.yml b/.old/.github/workflows/version.yml similarity index 100% rename from .github/workflows/version.yml rename to .old/.github/workflows/version.yml diff --git a/.old/.gitignore b/.old/.gitignore new file mode 100644 index 000000000..6e53c1a29 --- /dev/null +++ b/.old/.gitignore @@ -0,0 +1,47 @@ +# dependencies +node_modules +/.pnp +.pnp.js + +# testing +/coverage + +#cache +.turbo +.next +.vercel + +# misc +.DS_Store +*.pem +.vscode + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + + +# Exclude all .env files +.env +.env* +# Except for .env.example +!.env.example + +out/ +build/ +dist/ +sdk-dist +wander-connect-sdk/wallet-api-dist/ +# plasmo - https://www.plasmo.com +.plasmo + +# bpp - http://bpp.browser.market/ +keys.json + +# typescript +.tsbuildinfo + +# temporary remove development icon +assets/icon.development.png diff --git a/.husky/.gitignore b/.old/.husky/.gitignore similarity index 100% rename from .husky/.gitignore rename to .old/.husky/.gitignore diff --git a/.husky/pre-commit b/.old/.husky/pre-commit similarity index 100% rename from .husky/pre-commit rename to .old/.husky/pre-commit diff --git a/.old/__tsconfig.json b/.old/__tsconfig.json new file mode 100644 index 000000000..802f6e9dc --- /dev/null +++ b/.old/__tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "plasmo/templates/tsconfig.base", + "exclude": ["node_modules", "wander-connect-sdk"], + "include": [ + ".plasmo/index.d.ts", + ".plasmo/**/*", + "./**/*.ts", + "./**/*.tsx", + "./shim.d.ts", + "vite.config.js", + "vite.config.js" + ], + "compilerOptions": { + "paths": { + "~*": ["../.old-already-moved/src/*"], + "~isomorphic-messaging": ["../.old-already-moved/src/utils/messaging/strategies/extension/extension-messaging.strategy.ts"], + "~isomorphic-chunking": ["../.old-already-moved/src/utils/messaging/strategies/extension/extension-chunking.strategy.ts"] + }, + "resolveJsonModule": true, + "baseUrl": ".", + "jsx": "react-jsx", + "moduleResolution": "bundler" + } +} diff --git a/assets-beta/icon512.png b/.old/assets-beta/icon512.png similarity index 100% rename from assets-beta/icon512.png rename to .old/assets-beta/icon512.png diff --git a/assets-beta/icons/icon.png b/.old/assets-beta/icons/icon.png similarity index 100% rename from assets-beta/icons/icon.png rename to .old/assets-beta/icons/icon.png diff --git a/assets-beta/icons/locked/logo128.png b/.old/assets-beta/icons/locked/logo128.png similarity index 100% rename from assets-beta/icons/locked/logo128.png rename to .old/assets-beta/icons/locked/logo128.png diff --git a/assets-beta/icons/locked/logo256.png b/.old/assets-beta/icons/locked/logo256.png similarity index 100% rename from assets-beta/icons/locked/logo256.png rename to .old/assets-beta/icons/locked/logo256.png diff --git a/assets-beta/icons/locked/logo64.png b/.old/assets-beta/icons/locked/logo64.png similarity index 100% rename from assets-beta/icons/locked/logo64.png rename to .old/assets-beta/icons/locked/logo64.png diff --git a/assets-beta/icons/offline/logo128.png b/.old/assets-beta/icons/offline/logo128.png similarity index 100% rename from assets-beta/icons/offline/logo128.png rename to .old/assets-beta/icons/offline/logo128.png diff --git a/assets-beta/icons/offline/logo256.png b/.old/assets-beta/icons/offline/logo256.png similarity index 100% rename from assets-beta/icons/offline/logo256.png rename to .old/assets-beta/icons/offline/logo256.png diff --git a/assets-beta/icons/offline/logo64.png b/.old/assets-beta/icons/offline/logo64.png similarity index 100% rename from assets-beta/icons/offline/logo64.png rename to .old/assets-beta/icons/offline/logo64.png diff --git a/assets-beta/icons/online/logo128.png b/.old/assets-beta/icons/online/logo128.png similarity index 100% rename from assets-beta/icons/online/logo128.png rename to .old/assets-beta/icons/online/logo128.png diff --git a/assets-beta/icons/online/logo256.png b/.old/assets-beta/icons/online/logo256.png similarity index 100% rename from assets-beta/icons/online/logo256.png rename to .old/assets-beta/icons/online/logo256.png diff --git a/assets-beta/icons/online/logo64.png b/.old/assets-beta/icons/online/logo64.png similarity index 100% rename from assets-beta/icons/online/logo64.png rename to .old/assets-beta/icons/online/logo64.png diff --git a/assets/_locales/en/messages.json b/.old/assets/_locales/en/messages.json similarity index 100% rename from assets/_locales/en/messages.json rename to .old/assets/_locales/en/messages.json diff --git a/assets/_locales/zh_CN/messages.json b/.old/assets/_locales/zh_CN/messages.json similarity index 100% rename from assets/_locales/zh_CN/messages.json rename to .old/assets/_locales/zh_CN/messages.json diff --git a/assets/agents/contracts/ao-yield-agent.lua b/.old/assets/agents/contracts/ao-yield-agent.lua similarity index 100% rename from assets/agents/contracts/ao-yield-agent.lua rename to .old/assets/agents/contracts/ao-yield-agent.lua diff --git a/assets/agents/failure.svg b/.old/assets/agents/failure.svg similarity index 100% rename from assets/agents/failure.svg rename to .old/assets/agents/failure.svg diff --git a/assets/agents/hedgehog-failure.svg b/.old/assets/agents/hedgehog-failure.svg similarity index 100% rename from assets/agents/hedgehog-failure.svg rename to .old/assets/agents/hedgehog-failure.svg diff --git a/assets/agents/hedgehog-success.svg b/.old/assets/agents/hedgehog-success.svg similarity index 100% rename from assets/agents/hedgehog-success.svg rename to .old/assets/agents/hedgehog-success.svg diff --git a/assets/agents/images/agent-activated.svg b/.old/assets/agents/images/agent-activated.svg similarity index 100% rename from assets/agents/images/agent-activated.svg rename to .old/assets/agents/images/agent-activated.svg diff --git a/assets/agents/images/agent-not-activated.svg b/.old/assets/agents/images/agent-not-activated.svg similarity index 100% rename from assets/agents/images/agent-not-activated.svg rename to .old/assets/agents/images/agent-not-activated.svg diff --git a/assets/agents/images/alert-success.svg b/.old/assets/agents/images/alert-success.svg similarity index 100% rename from assets/agents/images/alert-success.svg rename to .old/assets/agents/images/alert-success.svg diff --git a/assets/agents/images/alert-triangle.svg b/.old/assets/agents/images/alert-triangle.svg similarity index 100% rename from assets/agents/images/alert-triangle.svg rename to .old/assets/agents/images/alert-triangle.svg diff --git a/assets/agents/images/alert-warning.svg b/.old/assets/agents/images/alert-warning.svg similarity index 100% rename from assets/agents/images/alert-warning.svg rename to .old/assets/agents/images/alert-warning.svg diff --git a/assets/agents/images/confirm-agent.svg b/.old/assets/agents/images/confirm-agent.svg similarity index 100% rename from assets/agents/images/confirm-agent.svg rename to .old/assets/agents/images/confirm-agent.svg diff --git a/assets/agents/images/hedgehog-head.svg b/.old/assets/agents/images/hedgehog-head.svg similarity index 100% rename from assets/agents/images/hedgehog-head.svg rename to .old/assets/agents/images/hedgehog-head.svg diff --git a/assets/agents/images/hedgehog-popup.svg b/.old/assets/agents/images/hedgehog-popup.svg similarity index 100% rename from assets/agents/images/hedgehog-popup.svg rename to .old/assets/agents/images/hedgehog-popup.svg diff --git a/assets/agents/success.svg b/.old/assets/agents/success.svg similarity index 100% rename from assets/agents/success.svg rename to .old/assets/agents/success.svg diff --git a/assets/animation/arweave.png b/.old/assets/animation/arweave.png similarity index 100% rename from assets/animation/arweave.png rename to .old/assets/animation/arweave.png diff --git a/assets/animation/hedgehog.png b/.old/assets/animation/hedgehog.png similarity index 100% rename from assets/animation/hedgehog.png rename to .old/assets/animation/hedgehog.png diff --git a/assets/animation/usd.png b/.old/assets/animation/usd.png similarity index 100% rename from assets/animation/usd.png rename to .old/assets/animation/usd.png diff --git a/assets/ar/ar-logo-dark.svg b/.old/assets/ar/ar-logo-dark.svg similarity index 100% rename from assets/ar/ar-logo-dark.svg rename to .old/assets/ar/ar-logo-dark.svg diff --git a/assets/ar/ar-logo-light.svg b/.old/assets/ar/ar-logo-light.svg similarity index 100% rename from assets/ar/ar-logo-light.svg rename to .old/assets/ar/ar-logo-light.svg diff --git a/assets/ar/no_funds.png b/.old/assets/ar/no_funds.png similarity index 100% rename from assets/ar/no_funds.png rename to .old/assets/ar/no_funds.png diff --git a/assets/ecosystem/aftrmarket.png b/.old/assets/ecosystem/aftrmarket.png similarity index 100% rename from assets/ecosystem/aftrmarket.png rename to .old/assets/ecosystem/aftrmarket.png diff --git a/assets/ecosystem/alex.svg b/.old/assets/ecosystem/alex.svg similarity index 100% rename from assets/ecosystem/alex.svg rename to .old/assets/ecosystem/alex.svg diff --git a/assets/ecosystem/amex.svg b/.old/assets/ecosystem/amex.svg similarity index 100% rename from assets/ecosystem/amex.svg rename to .old/assets/ecosystem/amex.svg diff --git a/assets/ecosystem/amplify.png b/.old/assets/ecosystem/amplify.png similarity index 100% rename from assets/ecosystem/amplify.png rename to .old/assets/ecosystem/amplify.png diff --git a/assets/ecosystem/ans-logo.svg b/.old/assets/ecosystem/ans-logo.svg similarity index 100% rename from assets/ecosystem/ans-logo.svg rename to .old/assets/ecosystem/ans-logo.svg diff --git a/assets/ecosystem/ao-arconnect.svg b/.old/assets/ecosystem/ao-arconnect.svg similarity index 100% rename from assets/ecosystem/ao-arconnect.svg rename to .old/assets/ecosystem/ao-arconnect.svg diff --git a/assets/ecosystem/ao-logo.svg b/.old/assets/ecosystem/ao-logo.svg similarity index 100% rename from assets/ecosystem/ao-logo.svg rename to .old/assets/ecosystem/ao-logo.svg diff --git a/assets/ecosystem/ao-token-logo.png b/.old/assets/ecosystem/ao-token-logo.png similarity index 100% rename from assets/ecosystem/ao-token-logo.png rename to .old/assets/ecosystem/ao-token-logo.png diff --git a/assets/ecosystem/aoclassicblackjack.png b/.old/assets/ecosystem/aoclassicblackjack.png similarity index 100% rename from assets/ecosystem/aoclassicblackjack.png rename to .old/assets/ecosystem/aoclassicblackjack.png diff --git a/assets/ecosystem/aocraft.png b/.old/assets/ecosystem/aocraft.png similarity index 100% rename from assets/ecosystem/aocraft.png rename to .old/assets/ecosystem/aocraft.png diff --git a/assets/ecosystem/aoctionhouse.png b/.old/assets/ecosystem/aoctionhouse.png similarity index 100% rename from assets/ecosystem/aoctionhouse.png rename to .old/assets/ecosystem/aoctionhouse.png diff --git a/assets/ecosystem/aolink.svg b/.old/assets/ecosystem/aolink.svg similarity index 100% rename from assets/ecosystem/aolink.svg rename to .old/assets/ecosystem/aolink.svg diff --git a/assets/ecosystem/aolotto.png b/.old/assets/ecosystem/aolotto.png similarity index 100% rename from assets/ecosystem/aolotto.png rename to .old/assets/ecosystem/aolotto.png diff --git a/assets/ecosystem/aomint.svg b/.old/assets/ecosystem/aomint.svg similarity index 100% rename from assets/ecosystem/aomint.svg rename to .old/assets/ecosystem/aomint.svg diff --git a/assets/ecosystem/aosllama.png b/.old/assets/ecosystem/aosllama.png similarity index 100% rename from assets/ecosystem/aosllama.png rename to .old/assets/ecosystem/aosllama.png diff --git a/assets/ecosystem/aostore.png b/.old/assets/ecosystem/aostore.png similarity index 100% rename from assets/ecosystem/aostore.png rename to .old/assets/ecosystem/aostore.png diff --git a/assets/ecosystem/aotrends.png b/.old/assets/ecosystem/aotrends.png similarity index 100% rename from assets/ecosystem/aotrends.png rename to .old/assets/ecosystem/aotrends.png diff --git a/assets/ecosystem/aowar.png b/.old/assets/ecosystem/aowar.png similarity index 100% rename from assets/ecosystem/aowar.png rename to .old/assets/ecosystem/aowar.png diff --git a/assets/ecosystem/aox.png b/.old/assets/ecosystem/aox.png similarity index 100% rename from assets/ecosystem/aox.png rename to .old/assets/ecosystem/aox.png diff --git a/assets/ecosystem/apple-pay.svg b/.old/assets/ecosystem/apple-pay.svg similarity index 100% rename from assets/ecosystem/apple-pay.svg rename to .old/assets/ecosystem/apple-pay.svg diff --git a/assets/ecosystem/apus.png b/.old/assets/ecosystem/apus.png similarity index 100% rename from assets/ecosystem/apus.png rename to .old/assets/ecosystem/apus.png diff --git a/assets/ecosystem/ar-logo.svg b/.old/assets/ecosystem/ar-logo.svg similarity index 100% rename from assets/ecosystem/ar-logo.svg rename to .old/assets/ecosystem/ar-logo.svg diff --git a/assets/ecosystem/arbit.png b/.old/assets/ecosystem/arbit.png similarity index 100% rename from assets/ecosystem/arbit.png rename to .old/assets/ecosystem/arbit.png diff --git a/assets/ecosystem/arconnect.png b/.old/assets/ecosystem/arconnect.png similarity index 100% rename from assets/ecosystem/arconnect.png rename to .old/assets/ecosystem/arconnect.png diff --git a/assets/ecosystem/arconnect.svg b/.old/assets/ecosystem/arconnect.svg similarity index 100% rename from assets/ecosystem/arconnect.svg rename to .old/assets/ecosystem/arconnect.svg diff --git a/assets/ecosystem/ardrive.png b/.old/assets/ecosystem/ardrive.png similarity index 100% rename from assets/ecosystem/ardrive.png rename to .old/assets/ecosystem/ardrive.png diff --git a/assets/ecosystem/ardrive.svg b/.old/assets/ecosystem/ardrive.svg similarity index 100% rename from assets/ecosystem/ardrive.svg rename to .old/assets/ecosystem/ardrive.svg diff --git a/assets/ecosystem/arfleet.png b/.old/assets/ecosystem/arfleet.png similarity index 100% rename from assets/ecosystem/arfleet.png rename to .old/assets/ecosystem/arfleet.png diff --git a/assets/ecosystem/argo.png b/.old/assets/ecosystem/argo.png similarity index 100% rename from assets/ecosystem/argo.png rename to .old/assets/ecosystem/argo.png diff --git a/assets/ecosystem/argora.png b/.old/assets/ecosystem/argora.png similarity index 100% rename from assets/ecosystem/argora.png rename to .old/assets/ecosystem/argora.png diff --git a/assets/ecosystem/ario.svg b/.old/assets/ecosystem/ario.svg similarity index 100% rename from assets/ecosystem/ario.svg rename to .old/assets/ecosystem/ario.svg diff --git a/assets/ecosystem/arlink.svg b/.old/assets/ecosystem/arlink.svg similarity index 100% rename from assets/ecosystem/arlink.svg rename to .old/assets/ecosystem/arlink.svg diff --git a/assets/ecosystem/arns.svg b/.old/assets/ecosystem/arns.svg similarity index 100% rename from assets/ecosystem/arns.svg rename to .old/assets/ecosystem/arns.svg diff --git a/assets/ecosystem/arswap.png b/.old/assets/ecosystem/arswap.png similarity index 100% rename from assets/ecosystem/arswap.png rename to .old/assets/ecosystem/arswap.png diff --git a/assets/ecosystem/artbycity.png b/.old/assets/ecosystem/artbycity.png similarity index 100% rename from assets/ecosystem/artbycity.png rename to .old/assets/ecosystem/artbycity.png diff --git a/assets/ecosystem/arverify.svg b/.old/assets/ecosystem/arverify.svg similarity index 100% rename from assets/ecosystem/arverify.svg rename to .old/assets/ecosystem/arverify.svg diff --git a/assets/ecosystem/arweaveafrica.png b/.old/assets/ecosystem/arweaveafrica.png similarity index 100% rename from assets/ecosystem/arweaveafrica.png rename to .old/assets/ecosystem/arweaveafrica.png diff --git a/assets/ecosystem/arweavecommunity.svg b/.old/assets/ecosystem/arweavecommunity.svg similarity index 100% rename from assets/ecosystem/arweavecommunity.svg rename to .old/assets/ecosystem/arweavecommunity.svg diff --git a/assets/ecosystem/arweavenews.jpeg b/.old/assets/ecosystem/arweavenews.jpeg similarity index 100% rename from assets/ecosystem/arweavenews.jpeg rename to .old/assets/ecosystem/arweavenews.jpeg diff --git a/assets/ecosystem/arweaveoasis.png b/.old/assets/ecosystem/arweaveoasis.png similarity index 100% rename from assets/ecosystem/arweaveoasis.png rename to .old/assets/ecosystem/arweaveoasis.png diff --git a/assets/ecosystem/arweavephilippines.png b/.old/assets/ecosystem/arweavephilippines.png similarity index 100% rename from assets/ecosystem/arweavephilippines.png rename to .old/assets/ecosystem/arweavephilippines.png diff --git a/assets/ecosystem/arweavewalletkit.svg b/.old/assets/ecosystem/arweavewalletkit.svg similarity index 100% rename from assets/ecosystem/arweavewalletkit.svg rename to .old/assets/ecosystem/arweavewalletkit.svg diff --git a/assets/ecosystem/arwiki.png b/.old/assets/ecosystem/arwiki.png similarity index 100% rename from assets/ecosystem/arwiki.png rename to .old/assets/ecosystem/arwiki.png diff --git a/assets/ecosystem/astro.png b/.old/assets/ecosystem/astro.png similarity index 100% rename from assets/ecosystem/astro.png rename to .old/assets/ecosystem/astro.png diff --git a/assets/ecosystem/astro.svg b/.old/assets/ecosystem/astro.svg similarity index 100% rename from assets/ecosystem/astro.svg rename to .old/assets/ecosystem/astro.svg diff --git a/assets/ecosystem/autonomous-dca-agent.png b/.old/assets/ecosystem/autonomous-dca-agent.png similarity index 100% rename from assets/ecosystem/autonomous-dca-agent.png rename to .old/assets/ecosystem/autonomous-dca-agent.png diff --git a/assets/ecosystem/bark.png b/.old/assets/ecosystem/bark.png similarity index 100% rename from assets/ecosystem/bark.png rename to .old/assets/ecosystem/bark.png diff --git a/assets/ecosystem/bark.svg b/.old/assets/ecosystem/bark.svg similarity index 100% rename from assets/ecosystem/bark.svg rename to .old/assets/ecosystem/bark.svg diff --git a/assets/ecosystem/basejump.svg b/.old/assets/ecosystem/basejump.svg similarity index 100% rename from assets/ecosystem/basejump.svg rename to .old/assets/ecosystem/basejump.svg diff --git a/assets/ecosystem/bazar.png b/.old/assets/ecosystem/bazar.png similarity index 100% rename from assets/ecosystem/bazar.png rename to .old/assets/ecosystem/bazar.png diff --git a/assets/ecosystem/bazar.svg b/.old/assets/ecosystem/bazar.svg similarity index 100% rename from assets/ecosystem/bazar.svg rename to .old/assets/ecosystem/bazar.svg diff --git a/assets/ecosystem/bazarmash.png b/.old/assets/ecosystem/bazarmash.png similarity index 100% rename from assets/ecosystem/bazarmash.png rename to .old/assets/ecosystem/bazarmash.png diff --git a/assets/ecosystem/betteridea.png b/.old/assets/ecosystem/betteridea.png similarity index 100% rename from assets/ecosystem/betteridea.png rename to .old/assets/ecosystem/betteridea.png diff --git a/assets/ecosystem/betteridea.svg b/.old/assets/ecosystem/betteridea.svg similarity index 100% rename from assets/ecosystem/betteridea.svg rename to .old/assets/ecosystem/betteridea.svg diff --git a/assets/ecosystem/bodhi.svg b/.old/assets/ecosystem/bodhi.svg similarity index 100% rename from assets/ecosystem/bodhi.svg rename to .old/assets/ecosystem/bodhi.svg diff --git a/assets/ecosystem/botega.svg b/.old/assets/ecosystem/botega.svg similarity index 100% rename from assets/ecosystem/botega.svg rename to .old/assets/ecosystem/botega.svg diff --git a/assets/ecosystem/casinao.svg b/.old/assets/ecosystem/casinao.svg similarity index 100% rename from assets/ecosystem/casinao.svg rename to .old/assets/ecosystem/casinao.svg diff --git a/assets/ecosystem/checkmynft.png b/.old/assets/ecosystem/checkmynft.png similarity index 100% rename from assets/ecosystem/checkmynft.png rename to .old/assets/ecosystem/checkmynft.png diff --git a/assets/ecosystem/coinmaker.svg b/.old/assets/ecosystem/coinmaker.svg similarity index 100% rename from assets/ecosystem/coinmaker.svg rename to .old/assets/ecosystem/coinmaker.svg diff --git a/assets/ecosystem/coinmarketcap.svg b/.old/assets/ecosystem/coinmarketcap.svg similarity index 100% rename from assets/ecosystem/coinmarketcap.svg rename to .old/assets/ecosystem/coinmarketcap.svg diff --git a/assets/ecosystem/communityxyz.png b/.old/assets/ecosystem/communityxyz.png similarity index 100% rename from assets/ecosystem/communityxyz.png rename to .old/assets/ecosystem/communityxyz.png diff --git a/assets/ecosystem/credit-debit.svg b/.old/assets/ecosystem/credit-debit.svg similarity index 100% rename from assets/ecosystem/credit-debit.svg rename to .old/assets/ecosystem/credit-debit.svg diff --git a/assets/ecosystem/ctrlplay.png b/.old/assets/ecosystem/ctrlplay.png similarity index 100% rename from assets/ecosystem/ctrlplay.png rename to .old/assets/ecosystem/ctrlplay.png diff --git a/assets/ecosystem/cyberweavers.png b/.old/assets/ecosystem/cyberweavers.png similarity index 100% rename from assets/ecosystem/cyberweavers.png rename to .old/assets/ecosystem/cyberweavers.png diff --git a/assets/ecosystem/dataos.svg b/.old/assets/ecosystem/dataos.svg similarity index 100% rename from assets/ecosystem/dataos.svg rename to .old/assets/ecosystem/dataos.svg diff --git a/assets/ecosystem/decentland.png b/.old/assets/ecosystem/decentland.png similarity index 100% rename from assets/ecosystem/decentland.png rename to .old/assets/ecosystem/decentland.png diff --git a/assets/ecosystem/decentramind.png b/.old/assets/ecosystem/decentramind.png similarity index 100% rename from assets/ecosystem/decentramind.png rename to .old/assets/ecosystem/decentramind.png diff --git a/assets/ecosystem/dexi.svg b/.old/assets/ecosystem/dexi.svg similarity index 100% rename from assets/ecosystem/dexi.svg rename to .old/assets/ecosystem/dexi.svg diff --git a/assets/ecosystem/dimensionlife.png b/.old/assets/ecosystem/dimensionlife.png similarity index 100% rename from assets/ecosystem/dimensionlife.png rename to .old/assets/ecosystem/dimensionlife.png diff --git a/assets/ecosystem/dumdumdum.png b/.old/assets/ecosystem/dumdumdum.png similarity index 100% rename from assets/ecosystem/dumdumdum.png rename to .old/assets/ecosystem/dumdumdum.png diff --git a/assets/ecosystem/dumdumup.png b/.old/assets/ecosystem/dumdumup.png similarity index 100% rename from assets/ecosystem/dumdumup.png rename to .old/assets/ecosystem/dumdumup.png diff --git a/assets/ecosystem/dumverse.png b/.old/assets/ecosystem/dumverse.png similarity index 100% rename from assets/ecosystem/dumverse.png rename to .old/assets/ecosystem/dumverse.png diff --git a/assets/ecosystem/ecclesia.jpeg b/.old/assets/ecosystem/ecclesia.jpeg similarity index 100% rename from assets/ecosystem/ecclesia.jpeg rename to .old/assets/ecosystem/ecclesia.jpeg diff --git a/assets/ecosystem/echo.svg b/.old/assets/ecosystem/echo.svg similarity index 100% rename from assets/ecosystem/echo.svg rename to .old/assets/ecosystem/echo.svg diff --git a/assets/ecosystem/elyssium.png b/.old/assets/ecosystem/elyssium.png similarity index 100% rename from assets/ecosystem/elyssium.png rename to .old/assets/ecosystem/elyssium.png diff --git a/assets/ecosystem/evermore.png b/.old/assets/ecosystem/evermore.png similarity index 100% rename from assets/ecosystem/evermore.png rename to .old/assets/ecosystem/evermore.png diff --git a/assets/ecosystem/everpay.svg b/.old/assets/ecosystem/everpay.svg similarity index 100% rename from assets/ecosystem/everpay.svg rename to .old/assets/ecosystem/everpay.svg diff --git a/assets/ecosystem/example.png b/.old/assets/ecosystem/example.png similarity index 100% rename from assets/ecosystem/example.png rename to .old/assets/ecosystem/example.png diff --git a/assets/ecosystem/exp-token-logo.png b/.old/assets/ecosystem/exp-token-logo.png similarity index 100% rename from assets/ecosystem/exp-token-logo.png rename to .old/assets/ecosystem/exp-token-logo.png diff --git a/assets/ecosystem/g-pay.svg b/.old/assets/ecosystem/g-pay.svg similarity index 100% rename from assets/ecosystem/g-pay.svg rename to .old/assets/ecosystem/g-pay.svg diff --git a/assets/ecosystem/gatherchat.png b/.old/assets/ecosystem/gatherchat.png similarity index 100% rename from assets/ecosystem/gatherchat.png rename to .old/assets/ecosystem/gatherchat.png diff --git a/assets/ecosystem/gitcoin.svg b/.old/assets/ecosystem/gitcoin.svg similarity index 100% rename from assets/ecosystem/gitcoin.svg rename to .old/assets/ecosystem/gitcoin.svg diff --git a/assets/ecosystem/glass.png b/.old/assets/ecosystem/glass.png similarity index 100% rename from assets/ecosystem/glass.png rename to .old/assets/ecosystem/glass.png diff --git a/assets/ecosystem/google-pay.svg b/.old/assets/ecosystem/google-pay.svg similarity index 100% rename from assets/ecosystem/google-pay.svg rename to .old/assets/ecosystem/google-pay.svg diff --git a/assets/ecosystem/hangout.svg b/.old/assets/ecosystem/hangout.svg similarity index 100% rename from assets/ecosystem/hangout.svg rename to .old/assets/ecosystem/hangout.svg diff --git a/assets/ecosystem/happyfarm.png b/.old/assets/ecosystem/happyfarm.png similarity index 100% rename from assets/ecosystem/happyfarm.png rename to .old/assets/ecosystem/happyfarm.png diff --git a/assets/ecosystem/kyve.svg b/.old/assets/ecosystem/kyve.svg similarity index 100% rename from assets/ecosystem/kyve.svg rename to .old/assets/ecosystem/kyve.svg diff --git a/assets/ecosystem/liquidops.svg b/.old/assets/ecosystem/liquidops.svg similarity index 100% rename from assets/ecosystem/liquidops.svg rename to .old/assets/ecosystem/liquidops.svg diff --git a/assets/ecosystem/llama.png b/.old/assets/ecosystem/llama.png similarity index 100% rename from assets/ecosystem/llama.png rename to .old/assets/ecosystem/llama.png diff --git a/assets/ecosystem/llamaland.png b/.old/assets/ecosystem/llamaland.png similarity index 100% rename from assets/ecosystem/llamaland.png rename to .old/assets/ecosystem/llamaland.png diff --git a/assets/ecosystem/longview.svg b/.old/assets/ecosystem/longview.svg similarity index 100% rename from assets/ecosystem/longview.svg rename to .old/assets/ecosystem/longview.svg diff --git a/assets/ecosystem/mastercard.svg b/.old/assets/ecosystem/mastercard.svg similarity index 100% rename from assets/ecosystem/mastercard.svg rename to .old/assets/ecosystem/mastercard.svg diff --git a/assets/ecosystem/metalinks.png b/.old/assets/ecosystem/metalinks.png similarity index 100% rename from assets/ecosystem/metalinks.png rename to .old/assets/ecosystem/metalinks.png diff --git a/assets/ecosystem/metaweave.png b/.old/assets/ecosystem/metaweave.png similarity index 100% rename from assets/ecosystem/metaweave.png rename to .old/assets/ecosystem/metaweave.png diff --git a/assets/ecosystem/mirror.jpeg b/.old/assets/ecosystem/mirror.jpeg similarity index 100% rename from assets/ecosystem/mirror.jpeg rename to .old/assets/ecosystem/mirror.jpeg diff --git a/assets/ecosystem/nestland.jpeg b/.old/assets/ecosystem/nestland.jpeg similarity index 100% rename from assets/ecosystem/nestland.jpeg rename to .old/assets/ecosystem/nestland.jpeg diff --git a/assets/ecosystem/notifications-promo.svg b/.old/assets/ecosystem/notifications-promo.svg similarity index 100% rename from assets/ecosystem/notifications-promo.svg rename to .old/assets/ecosystem/notifications-promo.svg diff --git a/assets/ecosystem/odysee.png b/.old/assets/ecosystem/odysee.png similarity index 100% rename from assets/ecosystem/odysee.png rename to .old/assets/ecosystem/odysee.png diff --git a/assets/ecosystem/odysee.svg b/.old/assets/ecosystem/odysee.svg similarity index 100% rename from assets/ecosystem/odysee.svg rename to .old/assets/ecosystem/odysee.svg diff --git a/assets/ecosystem/orbit.svg b/.old/assets/ecosystem/orbit.svg similarity index 100% rename from assets/ecosystem/orbit.svg rename to .old/assets/ecosystem/orbit.svg diff --git a/assets/ecosystem/outcome.svg b/.old/assets/ecosystem/outcome.svg similarity index 100% rename from assets/ecosystem/outcome.svg rename to .old/assets/ecosystem/outcome.svg diff --git a/assets/ecosystem/permabot.png b/.old/assets/ecosystem/permabot.png similarity index 100% rename from assets/ecosystem/permabot.png rename to .old/assets/ecosystem/permabot.png diff --git a/assets/ecosystem/permadao.svg b/.old/assets/ecosystem/permadao.svg similarity index 100% rename from assets/ecosystem/permadao.svg rename to .old/assets/ecosystem/permadao.svg diff --git a/assets/ecosystem/permafacts.svg b/.old/assets/ecosystem/permafacts.svg similarity index 100% rename from assets/ecosystem/permafacts.svg rename to .old/assets/ecosystem/permafacts.svg diff --git a/assets/ecosystem/permapages.svg b/.old/assets/ecosystem/permapages.svg similarity index 100% rename from assets/ecosystem/permapages.svg rename to .old/assets/ecosystem/permapages.svg diff --git a/assets/ecosystem/permaswap.svg b/.old/assets/ecosystem/permaswap.svg similarity index 100% rename from assets/ecosystem/permaswap.svg rename to .old/assets/ecosystem/permaswap.svg diff --git a/assets/ecosystem/perplex.svg b/.old/assets/ecosystem/perplex.svg similarity index 100% rename from assets/ecosystem/perplex.svg rename to .old/assets/ecosystem/perplex.svg diff --git a/assets/ecosystem/pet-or-rekt.png b/.old/assets/ecosystem/pet-or-rekt.png similarity index 100% rename from assets/ecosystem/pet-or-rekt.png rename to .old/assets/ecosystem/pet-or-rekt.png diff --git a/assets/ecosystem/pi-token-logo.png b/.old/assets/ecosystem/pi-token-logo.png similarity index 100% rename from assets/ecosystem/pi-token-logo.png rename to .old/assets/ecosystem/pi-token-logo.png diff --git a/assets/ecosystem/pianity.png b/.old/assets/ecosystem/pianity.png similarity index 100% rename from assets/ecosystem/pianity.png rename to .old/assets/ecosystem/pianity.png diff --git a/assets/ecosystem/pocketnetwork.png b/.old/assets/ecosystem/pocketnetwork.png similarity index 100% rename from assets/ecosystem/pocketnetwork.png rename to .old/assets/ecosystem/pocketnetwork.png diff --git a/assets/ecosystem/protocolland.svg b/.old/assets/ecosystem/protocolland.svg similarity index 100% rename from assets/ecosystem/protocolland.svg rename to .old/assets/ecosystem/protocolland.svg diff --git a/assets/ecosystem/publish-logo.svg b/.old/assets/ecosystem/publish-logo.svg similarity index 100% rename from assets/ecosystem/publish-logo.svg rename to .old/assets/ecosystem/publish-logo.svg diff --git a/assets/ecosystem/quantum-logo.svg b/.old/assets/ecosystem/quantum-logo.svg similarity index 100% rename from assets/ecosystem/quantum-logo.svg rename to .old/assets/ecosystem/quantum-logo.svg diff --git a/assets/ecosystem/quantum.svg b/.old/assets/ecosystem/quantum.svg similarity index 100% rename from assets/ecosystem/quantum.svg rename to .old/assets/ecosystem/quantum.svg diff --git a/assets/ecosystem/redstone.svg b/.old/assets/ecosystem/redstone.svg similarity index 100% rename from assets/ecosystem/redstone.svg rename to .old/assets/ecosystem/redstone.svg diff --git a/assets/ecosystem/rimbox.png b/.old/assets/ecosystem/rimbox.png similarity index 100% rename from assets/ecosystem/rimbox.png rename to .old/assets/ecosystem/rimbox.png diff --git a/assets/ecosystem/sarcophagus.png b/.old/assets/ecosystem/sarcophagus.png similarity index 100% rename from assets/ecosystem/sarcophagus.png rename to .old/assets/ecosystem/sarcophagus.png diff --git a/assets/ecosystem/stampprotocol.png b/.old/assets/ecosystem/stampprotocol.png similarity index 100% rename from assets/ecosystem/stampprotocol.png rename to .old/assets/ecosystem/stampprotocol.png diff --git a/assets/ecosystem/switch-vertical.svg b/.old/assets/ecosystem/switch-vertical.svg similarity index 100% rename from assets/ecosystem/switch-vertical.svg rename to .old/assets/ecosystem/switch-vertical.svg diff --git a/assets/ecosystem/tauoracle.png b/.old/assets/ecosystem/tauoracle.png similarity index 100% rename from assets/ecosystem/tauoracle.png rename to .old/assets/ecosystem/tauoracle.png diff --git a/assets/ecosystem/tesser.svg b/.old/assets/ecosystem/tesser.svg similarity index 100% rename from assets/ecosystem/tesser.svg rename to .old/assets/ecosystem/tesser.svg diff --git a/assets/ecosystem/tiny4vr.png b/.old/assets/ecosystem/tiny4vr.png similarity index 100% rename from assets/ecosystem/tiny4vr.png rename to .old/assets/ecosystem/tiny4vr.png diff --git a/assets/ecosystem/tracki.png b/.old/assets/ecosystem/tracki.png similarity index 100% rename from assets/ecosystem/tracki.png rename to .old/assets/ecosystem/tracki.png diff --git a/assets/ecosystem/trunk.png b/.old/assets/ecosystem/trunk.png similarity index 100% rename from assets/ecosystem/trunk.png rename to .old/assets/ecosystem/trunk.png diff --git a/assets/ecosystem/typr.png b/.old/assets/ecosystem/typr.png similarity index 100% rename from assets/ecosystem/typr.png rename to .old/assets/ecosystem/typr.png diff --git a/assets/ecosystem/usda.svg b/.old/assets/ecosystem/usda.svg similarity index 100% rename from assets/ecosystem/usda.svg rename to .old/assets/ecosystem/usda.svg diff --git a/assets/ecosystem/velocity.svg b/.old/assets/ecosystem/velocity.svg similarity index 100% rename from assets/ecosystem/velocity.svg rename to .old/assets/ecosystem/velocity.svg diff --git a/assets/ecosystem/verto.png b/.old/assets/ecosystem/verto.png similarity index 100% rename from assets/ecosystem/verto.png rename to .old/assets/ecosystem/verto.png diff --git a/assets/ecosystem/viewblock.png b/.old/assets/ecosystem/viewblock.png similarity index 100% rename from assets/ecosystem/viewblock.png rename to .old/assets/ecosystem/viewblock.png diff --git a/assets/ecosystem/viewblock.svg b/.old/assets/ecosystem/viewblock.svg similarity index 100% rename from assets/ecosystem/viewblock.svg rename to .old/assets/ecosystem/viewblock.svg diff --git a/assets/ecosystem/visa.svg b/.old/assets/ecosystem/visa.svg similarity index 100% rename from assets/ecosystem/visa.svg rename to .old/assets/ecosystem/visa.svg diff --git a/assets/ecosystem/vouchdao.png b/.old/assets/ecosystem/vouchdao.png similarity index 100% rename from assets/ecosystem/vouchdao.png rename to .old/assets/ecosystem/vouchdao.png diff --git a/assets/ecosystem/wander.svg b/.old/assets/ecosystem/wander.svg similarity index 100% rename from assets/ecosystem/wander.svg rename to .old/assets/ecosystem/wander.svg diff --git a/assets/ecosystem/war.png b/.old/assets/ecosystem/war.png similarity index 100% rename from assets/ecosystem/war.png rename to .old/assets/ecosystem/war.png diff --git a/assets/ecosystem/warp.svg b/.old/assets/ecosystem/warp.svg similarity index 100% rename from assets/ecosystem/warp.svg rename to .old/assets/ecosystem/warp.svg diff --git a/assets/ecosystem/weavechat.png b/.old/assets/ecosystem/weavechat.png similarity index 100% rename from assets/ecosystem/weavechat.png rename to .old/assets/ecosystem/weavechat.png diff --git a/assets/ecosystem/weavedb.svg b/.old/assets/ecosystem/weavedb.svg similarity index 100% rename from assets/ecosystem/weavedb.svg rename to .old/assets/ecosystem/weavedb.svg diff --git a/assets/ecosystem/weaveevm.png b/.old/assets/ecosystem/weaveevm.png similarity index 100% rename from assets/ecosystem/weaveevm.png rename to .old/assets/ecosystem/weaveevm.png diff --git a/assets/ecosystem/weavers.png b/.old/assets/ecosystem/weavers.png similarity index 100% rename from assets/ecosystem/weavers.png rename to .old/assets/ecosystem/weavers.png diff --git a/assets/ecosystem/weve.png b/.old/assets/ecosystem/weve.png similarity index 100% rename from assets/ecosystem/weve.png rename to .old/assets/ecosystem/weve.png diff --git a/assets/ecosystem/wisdomwizards.png b/.old/assets/ecosystem/wisdomwizards.png similarity index 100% rename from assets/ecosystem/wisdomwizards.png rename to .old/assets/ecosystem/wisdomwizards.png diff --git a/assets/ecosystem/wndr-token-logo.svg b/.old/assets/ecosystem/wndr-token-logo.svg similarity index 100% rename from assets/ecosystem/wndr-token-logo.svg rename to .old/assets/ecosystem/wndr-token-logo.svg diff --git a/assets/ecosystem/wusdc.svg b/.old/assets/ecosystem/wusdc.svg similarity index 100% rename from assets/ecosystem/wusdc.svg rename to .old/assets/ecosystem/wusdc.svg diff --git a/assets/ecosystem/yallajamel.png b/.old/assets/ecosystem/yallajamel.png similarity index 100% rename from assets/ecosystem/yallajamel.png rename to .old/assets/ecosystem/yallajamel.png diff --git a/assets/fonts/PlusJakartaSans-Bold.ttf b/.old/assets/fonts/PlusJakartaSans-Bold.ttf similarity index 100% rename from assets/fonts/PlusJakartaSans-Bold.ttf rename to .old/assets/fonts/PlusJakartaSans-Bold.ttf diff --git a/assets/fonts/PlusJakartaSans-ExtraBold.ttf b/.old/assets/fonts/PlusJakartaSans-ExtraBold.ttf similarity index 100% rename from assets/fonts/PlusJakartaSans-ExtraBold.ttf rename to .old/assets/fonts/PlusJakartaSans-ExtraBold.ttf diff --git a/assets/fonts/PlusJakartaSans-ExtraLight.ttf b/.old/assets/fonts/PlusJakartaSans-ExtraLight.ttf similarity index 100% rename from assets/fonts/PlusJakartaSans-ExtraLight.ttf rename to .old/assets/fonts/PlusJakartaSans-ExtraLight.ttf diff --git a/assets/fonts/PlusJakartaSans-Light.ttf b/.old/assets/fonts/PlusJakartaSans-Light.ttf similarity index 100% rename from assets/fonts/PlusJakartaSans-Light.ttf rename to .old/assets/fonts/PlusJakartaSans-Light.ttf diff --git a/assets/fonts/PlusJakartaSans-Medium.ttf b/.old/assets/fonts/PlusJakartaSans-Medium.ttf similarity index 100% rename from assets/fonts/PlusJakartaSans-Medium.ttf rename to .old/assets/fonts/PlusJakartaSans-Medium.ttf diff --git a/assets/fonts/PlusJakartaSans-Regular.ttf b/.old/assets/fonts/PlusJakartaSans-Regular.ttf similarity index 100% rename from assets/fonts/PlusJakartaSans-Regular.ttf rename to .old/assets/fonts/PlusJakartaSans-Regular.ttf diff --git a/assets/fonts/PlusJakartaSans-SemiBold.ttf b/.old/assets/fonts/PlusJakartaSans-SemiBold.ttf similarity index 100% rename from assets/fonts/PlusJakartaSans-SemiBold.ttf rename to .old/assets/fonts/PlusJakartaSans-SemiBold.ttf diff --git a/assets/fonts/Satoshi-Bold.ttf b/.old/assets/fonts/Satoshi-Bold.ttf similarity index 100% rename from assets/fonts/Satoshi-Bold.ttf rename to .old/assets/fonts/Satoshi-Bold.ttf diff --git a/assets/fonts/Satoshi-Medium.ttf b/.old/assets/fonts/Satoshi-Medium.ttf similarity index 100% rename from assets/fonts/Satoshi-Medium.ttf rename to .old/assets/fonts/Satoshi-Medium.ttf diff --git a/assets/fonts/Satoshi-Variable.ttf b/.old/assets/fonts/Satoshi-Variable.ttf similarity index 100% rename from assets/fonts/Satoshi-Variable.ttf rename to .old/assets/fonts/Satoshi-Variable.ttf diff --git a/assets/fonts/Satoshi-VariableItalic.ttf b/.old/assets/fonts/Satoshi-VariableItalic.ttf similarity index 100% rename from assets/fonts/Satoshi-VariableItalic.ttf rename to .old/assets/fonts/Satoshi-VariableItalic.ttf diff --git a/assets/fonts/Tomorrow-BoldItalic.ttf b/.old/assets/fonts/Tomorrow-BoldItalic.ttf similarity index 100% rename from assets/fonts/Tomorrow-BoldItalic.ttf rename to .old/assets/fonts/Tomorrow-BoldItalic.ttf diff --git a/assets/fonts/Tomorrow-Medium.ttf b/.old/assets/fonts/Tomorrow-Medium.ttf similarity index 100% rename from assets/fonts/Tomorrow-Medium.ttf rename to .old/assets/fonts/Tomorrow-Medium.ttf diff --git a/assets/forge/prime.worker.min.js b/.old/assets/forge/prime.worker.min.js similarity index 100% rename from assets/forge/prime.worker.min.js rename to .old/assets/forge/prime.worker.min.js diff --git a/assets/hardware/keystone.png b/.old/assets/hardware/keystone.png similarity index 100% rename from assets/hardware/keystone.png rename to .old/assets/hardware/keystone.png diff --git a/assets/icon-embed.svg b/.old/assets/icon-embed.svg similarity index 100% rename from assets/icon-embed.svg rename to .old/assets/icon-embed.svg diff --git a/assets/icon.svg b/.old/assets/icon.svg similarity index 100% rename from assets/icon.svg rename to .old/assets/icon.svg diff --git a/assets/icon512.development.png b/.old/assets/icon512.development.png similarity index 100% rename from assets/icon512.development.png rename to .old/assets/icon512.development.png diff --git a/assets/icon512.png b/.old/assets/icon512.png similarity index 100% rename from assets/icon512.png rename to .old/assets/icon512.png diff --git a/assets/icons/browsers/brave-logo.png b/.old/assets/icons/browsers/brave-logo.png similarity index 100% rename from assets/icons/browsers/brave-logo.png rename to .old/assets/icons/browsers/brave-logo.png diff --git a/assets/icons/browsers/chrome-logo.png b/.old/assets/icons/browsers/chrome-logo.png similarity index 100% rename from assets/icons/browsers/chrome-logo.png rename to .old/assets/icons/browsers/chrome-logo.png diff --git a/assets/icons/browsers/edge-logo.png b/.old/assets/icons/browsers/edge-logo.png similarity index 100% rename from assets/icons/browsers/edge-logo.png rename to .old/assets/icons/browsers/edge-logo.png diff --git a/assets/icons/browsers/firefox-logo.png b/.old/assets/icons/browsers/firefox-logo.png similarity index 100% rename from assets/icons/browsers/firefox-logo.png rename to .old/assets/icons/browsers/firefox-logo.png diff --git a/assets/icons/browsers/opera-logo.png b/.old/assets/icons/browsers/opera-logo.png similarity index 100% rename from assets/icons/browsers/opera-logo.png rename to .old/assets/icons/browsers/opera-logo.png diff --git a/assets/icons/browsers/safari-logo.png b/.old/assets/icons/browsers/safari-logo.png similarity index 100% rename from assets/icons/browsers/safari-logo.png rename to .old/assets/icons/browsers/safari-logo.png diff --git a/assets/icons/icon.development.png b/.old/assets/icons/icon.development.png similarity index 100% rename from assets/icons/icon.development.png rename to .old/assets/icons/icon.development.png diff --git a/assets/icons/icon.png b/.old/assets/icons/icon.png similarity index 100% rename from assets/icons/icon.png rename to .old/assets/icons/icon.png diff --git a/assets/icons/locked/logo128.development.png b/.old/assets/icons/locked/logo128.development.png similarity index 100% rename from assets/icons/locked/logo128.development.png rename to .old/assets/icons/locked/logo128.development.png diff --git a/assets/icons/locked/logo128.png b/.old/assets/icons/locked/logo128.png similarity index 100% rename from assets/icons/locked/logo128.png rename to .old/assets/icons/locked/logo128.png diff --git a/assets/icons/locked/logo256.development.png b/.old/assets/icons/locked/logo256.development.png similarity index 100% rename from assets/icons/locked/logo256.development.png rename to .old/assets/icons/locked/logo256.development.png diff --git a/assets/icons/locked/logo256.png b/.old/assets/icons/locked/logo256.png similarity index 100% rename from assets/icons/locked/logo256.png rename to .old/assets/icons/locked/logo256.png diff --git a/assets/icons/locked/logo64.development.png b/.old/assets/icons/locked/logo64.development.png similarity index 100% rename from assets/icons/locked/logo64.development.png rename to .old/assets/icons/locked/logo64.development.png diff --git a/assets/icons/locked/logo64.png b/.old/assets/icons/locked/logo64.png similarity index 100% rename from assets/icons/locked/logo64.png rename to .old/assets/icons/locked/logo64.png diff --git a/assets/icons/offline/logo128.development.png b/.old/assets/icons/offline/logo128.development.png similarity index 100% rename from assets/icons/offline/logo128.development.png rename to .old/assets/icons/offline/logo128.development.png diff --git a/assets/icons/offline/logo128.png b/.old/assets/icons/offline/logo128.png similarity index 100% rename from assets/icons/offline/logo128.png rename to .old/assets/icons/offline/logo128.png diff --git a/assets/icons/offline/logo256.development.png b/.old/assets/icons/offline/logo256.development.png similarity index 100% rename from assets/icons/offline/logo256.development.png rename to .old/assets/icons/offline/logo256.development.png diff --git a/assets/icons/offline/logo256.png b/.old/assets/icons/offline/logo256.png similarity index 100% rename from assets/icons/offline/logo256.png rename to .old/assets/icons/offline/logo256.png diff --git a/assets/icons/offline/logo64.development.png b/.old/assets/icons/offline/logo64.development.png similarity index 100% rename from assets/icons/offline/logo64.development.png rename to .old/assets/icons/offline/logo64.development.png diff --git a/assets/icons/offline/logo64.png b/.old/assets/icons/offline/logo64.png similarity index 100% rename from assets/icons/offline/logo64.png rename to .old/assets/icons/offline/logo64.png diff --git a/assets/icons/online/logo128.development.png b/.old/assets/icons/online/logo128.development.png similarity index 100% rename from assets/icons/online/logo128.development.png rename to .old/assets/icons/online/logo128.development.png diff --git a/assets/icons/online/logo128.png b/.old/assets/icons/online/logo128.png similarity index 100% rename from assets/icons/online/logo128.png rename to .old/assets/icons/online/logo128.png diff --git a/assets/icons/online/logo256.development.png b/.old/assets/icons/online/logo256.development.png similarity index 100% rename from assets/icons/online/logo256.development.png rename to .old/assets/icons/online/logo256.development.png diff --git a/assets/icons/online/logo256.png b/.old/assets/icons/online/logo256.png similarity index 100% rename from assets/icons/online/logo256.png rename to .old/assets/icons/online/logo256.png diff --git a/assets/icons/online/logo64.development.png b/.old/assets/icons/online/logo64.development.png similarity index 100% rename from assets/icons/online/logo64.development.png rename to .old/assets/icons/online/logo64.development.png diff --git a/assets/icons/online/logo64.png b/.old/assets/icons/online/logo64.png similarity index 100% rename from assets/icons/online/logo64.png rename to .old/assets/icons/online/logo64.png diff --git a/assets/images/astro-beta-announcement/astro_beta_announcement_bg.png b/.old/assets/images/astro-beta-announcement/astro_beta_announcement_bg.png similarity index 100% rename from assets/images/astro-beta-announcement/astro_beta_announcement_bg.png rename to .old/assets/images/astro-beta-announcement/astro_beta_announcement_bg.png diff --git a/assets/images/astro-beta-announcement/astro_logo.png b/.old/assets/images/astro-beta-announcement/astro_logo.png similarity index 100% rename from assets/images/astro-beta-announcement/astro_logo.png rename to .old/assets/images/astro-beta-announcement/astro_logo.png diff --git a/assets/images/earn/earn-popup.svg b/.old/assets/images/earn/earn-popup.svg similarity index 100% rename from assets/images/earn/earn-popup.svg rename to .old/assets/images/earn/earn-popup.svg diff --git a/assets/images/stargrid-announcement/samurai.png b/.old/assets/images/stargrid-announcement/samurai.png similarity index 100% rename from assets/images/stargrid-announcement/samurai.png rename to .old/assets/images/stargrid-announcement/samurai.png diff --git a/assets/images/stargrid-announcement/space_emperor_tromp.png b/.old/assets/images/stargrid-announcement/space_emperor_tromp.png similarity index 100% rename from assets/images/stargrid-announcement/space_emperor_tromp.png rename to .old/assets/images/stargrid-announcement/space_emperor_tromp.png diff --git a/assets/images/stargrid-announcement/stargrid_announcement_bg.png b/.old/assets/images/stargrid-announcement/stargrid_announcement_bg.png similarity index 100% rename from assets/images/stargrid-announcement/stargrid_announcement_bg.png rename to .old/assets/images/stargrid-announcement/stargrid_announcement_bg.png diff --git a/assets/images/stargrid-announcement/stargrid_logo.svg b/.old/assets/images/stargrid-announcement/stargrid_logo.svg similarity index 100% rename from assets/images/stargrid-announcement/stargrid_logo.svg rename to .old/assets/images/stargrid-announcement/stargrid_logo.svg diff --git a/assets/images/tier/core_card_bg.png b/.old/assets/images/tier/core_card_bg.png similarity index 100% rename from assets/images/tier/core_card_bg.png rename to .old/assets/images/tier/core_card_bg.png diff --git a/assets/images/tier/core_card_bg_light.png b/.old/assets/images/tier/core_card_bg_light.png similarity index 100% rename from assets/images/tier/core_card_bg_light.png rename to .old/assets/images/tier/core_card_bg_light.png diff --git a/assets/images/tier/core_carousel_bg.png b/.old/assets/images/tier/core_carousel_bg.png similarity index 100% rename from assets/images/tier/core_carousel_bg.png rename to .old/assets/images/tier/core_carousel_bg.png diff --git a/assets/images/tier/core_carousel_bg_light.png b/.old/assets/images/tier/core_carousel_bg_light.png similarity index 100% rename from assets/images/tier/core_carousel_bg_light.png rename to .old/assets/images/tier/core_carousel_bg_light.png diff --git a/assets/images/tier/core_header_glow.svg b/.old/assets/images/tier/core_header_glow.svg similarity index 100% rename from assets/images/tier/core_header_glow.svg rename to .old/assets/images/tier/core_header_glow.svg diff --git a/assets/images/tier/edge_header_glow.svg b/.old/assets/images/tier/edge_header_glow.svg similarity index 100% rename from assets/images/tier/edge_header_glow.svg rename to .old/assets/images/tier/edge_header_glow.svg diff --git a/assets/images/tier/elite_card_bg.png b/.old/assets/images/tier/elite_card_bg.png similarity index 100% rename from assets/images/tier/elite_card_bg.png rename to .old/assets/images/tier/elite_card_bg.png diff --git a/assets/images/tier/elite_card_bg_light.png b/.old/assets/images/tier/elite_card_bg_light.png similarity index 100% rename from assets/images/tier/elite_card_bg_light.png rename to .old/assets/images/tier/elite_card_bg_light.png diff --git a/assets/images/tier/elite_carousel_bg.png b/.old/assets/images/tier/elite_carousel_bg.png similarity index 100% rename from assets/images/tier/elite_carousel_bg.png rename to .old/assets/images/tier/elite_carousel_bg.png diff --git a/assets/images/tier/elite_carousel_bg_light.png b/.old/assets/images/tier/elite_carousel_bg_light.png similarity index 100% rename from assets/images/tier/elite_carousel_bg_light.png rename to .old/assets/images/tier/elite_carousel_bg_light.png diff --git a/assets/images/tier/plus_card_bg.png b/.old/assets/images/tier/plus_card_bg.png similarity index 100% rename from assets/images/tier/plus_card_bg.png rename to .old/assets/images/tier/plus_card_bg.png diff --git a/assets/images/tier/plus_card_bg_light.png b/.old/assets/images/tier/plus_card_bg_light.png similarity index 100% rename from assets/images/tier/plus_card_bg_light.png rename to .old/assets/images/tier/plus_card_bg_light.png diff --git a/assets/images/tier/plus_carousel_bg.png b/.old/assets/images/tier/plus_carousel_bg.png similarity index 100% rename from assets/images/tier/plus_carousel_bg.png rename to .old/assets/images/tier/plus_carousel_bg.png diff --git a/assets/images/tier/plus_carousel_bg_light.png b/.old/assets/images/tier/plus_carousel_bg_light.png similarity index 100% rename from assets/images/tier/plus_carousel_bg_light.png rename to .old/assets/images/tier/plus_carousel_bg_light.png diff --git a/assets/images/tier/powerups.svg b/.old/assets/images/tier/powerups.svg similarity index 100% rename from assets/images/tier/powerups.svg rename to .old/assets/images/tier/powerups.svg diff --git a/assets/images/tier/prime_card_bg.png b/.old/assets/images/tier/prime_card_bg.png similarity index 100% rename from assets/images/tier/prime_card_bg.png rename to .old/assets/images/tier/prime_card_bg.png diff --git a/assets/images/tier/prime_card_bg_light.png b/.old/assets/images/tier/prime_card_bg_light.png similarity index 100% rename from assets/images/tier/prime_card_bg_light.png rename to .old/assets/images/tier/prime_card_bg_light.png diff --git a/assets/images/tier/prime_carousel_bg.png b/.old/assets/images/tier/prime_carousel_bg.png similarity index 100% rename from assets/images/tier/prime_carousel_bg.png rename to .old/assets/images/tier/prime_carousel_bg.png diff --git a/assets/images/tier/prime_carousel_bg_light.png b/.old/assets/images/tier/prime_carousel_bg_light.png similarity index 100% rename from assets/images/tier/prime_carousel_bg_light.png rename to .old/assets/images/tier/prime_carousel_bg_light.png diff --git a/assets/images/tier/prime_header_glow.svg b/.old/assets/images/tier/prime_header_glow.svg similarity index 100% rename from assets/images/tier/prime_header_glow.svg rename to .old/assets/images/tier/prime_header_glow.svg diff --git a/assets/images/tier/rays_core.png b/.old/assets/images/tier/rays_core.png similarity index 100% rename from assets/images/tier/rays_core.png rename to .old/assets/images/tier/rays_core.png diff --git a/assets/images/tier/rays_elite.png b/.old/assets/images/tier/rays_elite.png similarity index 100% rename from assets/images/tier/rays_elite.png rename to .old/assets/images/tier/rays_elite.png diff --git a/assets/images/tier/rays_plus.png b/.old/assets/images/tier/rays_plus.png similarity index 100% rename from assets/images/tier/rays_plus.png rename to .old/assets/images/tier/rays_plus.png diff --git a/assets/images/tier/rays_prime.png b/.old/assets/images/tier/rays_prime.png similarity index 100% rename from assets/images/tier/rays_prime.png rename to .old/assets/images/tier/rays_prime.png diff --git a/assets/images/tier/rays_select.png b/.old/assets/images/tier/rays_select.png similarity index 100% rename from assets/images/tier/rays_select.png rename to .old/assets/images/tier/rays_select.png diff --git a/assets/images/tier/reserve_header_glow.svg b/.old/assets/images/tier/reserve_header_glow.svg similarity index 100% rename from assets/images/tier/reserve_header_glow.svg rename to .old/assets/images/tier/reserve_header_glow.svg diff --git a/assets/images/tier/select_card_bg.png b/.old/assets/images/tier/select_card_bg.png similarity index 100% rename from assets/images/tier/select_card_bg.png rename to .old/assets/images/tier/select_card_bg.png diff --git a/assets/images/tier/select_card_bg_light.png b/.old/assets/images/tier/select_card_bg_light.png similarity index 100% rename from assets/images/tier/select_card_bg_light.png rename to .old/assets/images/tier/select_card_bg_light.png diff --git a/assets/images/tier/select_carousel_bg.png b/.old/assets/images/tier/select_carousel_bg.png similarity index 100% rename from assets/images/tier/select_carousel_bg.png rename to .old/assets/images/tier/select_carousel_bg.png diff --git a/assets/images/tier/select_carousel_bg_light.png b/.old/assets/images/tier/select_carousel_bg_light.png similarity index 100% rename from assets/images/tier/select_carousel_bg_light.png rename to .old/assets/images/tier/select_carousel_bg_light.png diff --git a/assets/images/tier/select_header_glow.svg b/.old/assets/images/tier/select_header_glow.svg similarity index 100% rename from assets/images/tier/select_header_glow.svg rename to .old/assets/images/tier/select_header_glow.svg diff --git a/assets/images/tier/stars.png b/.old/assets/images/tier/stars.png similarity index 100% rename from assets/images/tier/stars.png rename to .old/assets/images/tier/stars.png diff --git a/assets/images/tokens/loading-token.svg b/.old/assets/images/tokens/loading-token.svg similarity index 100% rename from assets/images/tokens/loading-token.svg rename to .old/assets/images/tokens/loading-token.svg diff --git a/assets/images/wand-announcement/exclusive_features.png b/.old/assets/images/wand-announcement/exclusive_features.png similarity index 100% rename from assets/images/wand-announcement/exclusive_features.png rename to .old/assets/images/wand-announcement/exclusive_features.png diff --git a/assets/images/wand-announcement/powerups.png b/.old/assets/images/wand-announcement/powerups.png similarity index 100% rename from assets/images/wand-announcement/powerups.png rename to .old/assets/images/wand-announcement/powerups.png diff --git a/assets/images/wand-announcement/wand_announcement_bg.svg b/.old/assets/images/wand-announcement/wand_announcement_bg.svg similarity index 100% rename from assets/images/wand-announcement/wand_announcement_bg.svg rename to .old/assets/images/wand-announcement/wand_announcement_bg.svg diff --git a/assets/images/wand-announcement/zero_fees.png b/.old/assets/images/wand-announcement/zero_fees.png similarity index 100% rename from assets/images/wand-announcement/zero_fees.png rename to .old/assets/images/wand-announcement/zero_fees.png diff --git a/assets/lotties/checkmark.json b/.old/assets/lotties/checkmark.json similarity index 100% rename from assets/lotties/checkmark.json rename to .old/assets/lotties/checkmark.json diff --git a/assets/lotties/wander-loading-default-light.json b/.old/assets/lotties/wander-loading-default-light.json similarity index 100% rename from assets/lotties/wander-loading-default-light.json rename to .old/assets/lotties/wander-loading-default-light.json diff --git a/assets/lotties/wander-loading-default.json b/.old/assets/lotties/wander-loading-default.json similarity index 100% rename from assets/lotties/wander-loading-default.json rename to .old/assets/lotties/wander-loading-default.json diff --git a/assets/lotties/wander-loading-hover-light.json b/.old/assets/lotties/wander-loading-hover-light.json similarity index 100% rename from assets/lotties/wander-loading-hover-light.json rename to .old/assets/lotties/wander-loading-hover-light.json diff --git a/assets/lotties/wander-loading-hover.json b/.old/assets/lotties/wander-loading-hover.json similarity index 100% rename from assets/lotties/wander-loading-hover.json rename to .old/assets/lotties/wander-loading-hover.json diff --git a/assets/lotties/wander-logo-light.json b/.old/assets/lotties/wander-logo-light.json similarity index 100% rename from assets/lotties/wander-logo-light.json rename to .old/assets/lotties/wander-logo-light.json diff --git a/assets/lotties/wander-logo.json b/.old/assets/lotties/wander-logo.json similarity index 100% rename from assets/lotties/wander-logo.json rename to .old/assets/lotties/wander-logo.json diff --git a/assets/lotties/wander-token-announcement.json b/.old/assets/lotties/wander-token-announcement.json similarity index 100% rename from assets/lotties/wander-token-announcement.json rename to .old/assets/lotties/wander-token-announcement.json diff --git a/assets/lotties/wander-token-bg-loop.json b/.old/assets/lotties/wander-token-bg-loop.json similarity index 100% rename from assets/lotties/wander-token-bg-loop.json rename to .old/assets/lotties/wander-token-bg-loop.json diff --git a/assets/placeholder.png b/.old/assets/placeholder.png similarity index 100% rename from assets/placeholder.png rename to .old/assets/placeholder.png diff --git a/assets/popup.css b/.old/assets/popup.css similarity index 100% rename from assets/popup.css rename to .old/assets/popup.css diff --git a/assets/popup.js b/.old/assets/popup.js similarity index 100% rename from assets/popup.js rename to .old/assets/popup.js diff --git a/assets/routes/account.png b/.old/assets/routes/account.png similarity index 100% rename from assets/routes/account.png rename to .old/assets/routes/account.png diff --git a/assets/routes/auth-request.png b/.old/assets/routes/auth-request.png similarity index 100% rename from assets/routes/auth-request.png rename to .old/assets/routes/auth-request.png diff --git a/assets/routes/auth.png b/.old/assets/routes/auth.png similarity index 100% rename from assets/routes/auth.png rename to .old/assets/routes/auth.png diff --git a/assets/routes/default.png b/.old/assets/routes/default.png similarity index 100% rename from assets/routes/default.png rename to .old/assets/routes/default.png diff --git a/assets/routes/settings.png b/.old/assets/routes/settings.png similarity index 100% rename from assets/routes/settings.png rename to .old/assets/routes/settings.png diff --git a/assets/screenshots/collectible.png b/.old/assets/screenshots/collectible.png similarity index 100% rename from assets/screenshots/collectible.png rename to .old/assets/screenshots/collectible.png diff --git a/assets/screenshots/explore.png b/.old/assets/screenshots/explore.png similarity index 100% rename from assets/screenshots/explore.png rename to .old/assets/screenshots/explore.png diff --git a/assets/screenshots/home.png b/.old/assets/screenshots/home.png similarity index 100% rename from assets/screenshots/home.png rename to .old/assets/screenshots/home.png diff --git a/assets/screenshots/unpartitioned-state/brave-1-dark.png b/.old/assets/screenshots/unpartitioned-state/brave-1-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/brave-1-dark.png rename to .old/assets/screenshots/unpartitioned-state/brave-1-dark.png diff --git a/assets/screenshots/unpartitioned-state/brave-1-light.png b/.old/assets/screenshots/unpartitioned-state/brave-1-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/brave-1-light.png rename to .old/assets/screenshots/unpartitioned-state/brave-1-light.png diff --git a/assets/screenshots/unpartitioned-state/brave-2-dark.png b/.old/assets/screenshots/unpartitioned-state/brave-2-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/brave-2-dark.png rename to .old/assets/screenshots/unpartitioned-state/brave-2-dark.png diff --git a/assets/screenshots/unpartitioned-state/brave-2-light.png b/.old/assets/screenshots/unpartitioned-state/brave-2-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/brave-2-light.png rename to .old/assets/screenshots/unpartitioned-state/brave-2-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-1-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-1-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-1-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-1-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-1-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-1-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-1-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-1-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-2-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-2-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-2-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-2-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-2-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-2-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-2-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-2-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-3-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-3-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-3-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-3-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-3-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-3-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-3-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-3-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-4-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-4-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-4-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-4-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-4-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-4-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-4-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-4-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-5-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-5-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-5-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-5-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-5-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-5-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-5-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-5-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-android-1-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-android-1-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-android-1-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-android-1-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-android-1-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-android-1-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-android-1-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-android-1-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-android-2-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-android-2-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-android-2-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-android-2-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-android-2-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-android-2-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-android-2-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-android-2-light.png diff --git a/assets/screenshots/unpartitioned-state/chrome-android-3-dark.png b/.old/assets/screenshots/unpartitioned-state/chrome-android-3-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-android-3-dark.png rename to .old/assets/screenshots/unpartitioned-state/chrome-android-3-dark.png diff --git a/assets/screenshots/unpartitioned-state/chrome-android-3-light.png b/.old/assets/screenshots/unpartitioned-state/chrome-android-3-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/chrome-android-3-light.png rename to .old/assets/screenshots/unpartitioned-state/chrome-android-3-light.png diff --git a/assets/screenshots/unpartitioned-state/edge-1-dark.png b/.old/assets/screenshots/unpartitioned-state/edge-1-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-1-dark.png rename to .old/assets/screenshots/unpartitioned-state/edge-1-dark.png diff --git a/assets/screenshots/unpartitioned-state/edge-1-light.png b/.old/assets/screenshots/unpartitioned-state/edge-1-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-1-light.png rename to .old/assets/screenshots/unpartitioned-state/edge-1-light.png diff --git a/assets/screenshots/unpartitioned-state/edge-2-dark.png b/.old/assets/screenshots/unpartitioned-state/edge-2-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-2-dark.png rename to .old/assets/screenshots/unpartitioned-state/edge-2-dark.png diff --git a/assets/screenshots/unpartitioned-state/edge-2-light.png b/.old/assets/screenshots/unpartitioned-state/edge-2-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-2-light.png rename to .old/assets/screenshots/unpartitioned-state/edge-2-light.png diff --git a/assets/screenshots/unpartitioned-state/edge-3-dark.png b/.old/assets/screenshots/unpartitioned-state/edge-3-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-3-dark.png rename to .old/assets/screenshots/unpartitioned-state/edge-3-dark.png diff --git a/assets/screenshots/unpartitioned-state/edge-3-light.png b/.old/assets/screenshots/unpartitioned-state/edge-3-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-3-light.png rename to .old/assets/screenshots/unpartitioned-state/edge-3-light.png diff --git a/assets/screenshots/unpartitioned-state/edge-4-dark.png b/.old/assets/screenshots/unpartitioned-state/edge-4-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-4-dark.png rename to .old/assets/screenshots/unpartitioned-state/edge-4-dark.png diff --git a/assets/screenshots/unpartitioned-state/edge-4-light.png b/.old/assets/screenshots/unpartitioned-state/edge-4-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-4-light.png rename to .old/assets/screenshots/unpartitioned-state/edge-4-light.png diff --git a/assets/screenshots/unpartitioned-state/edge-5-dark.png b/.old/assets/screenshots/unpartitioned-state/edge-5-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-5-dark.png rename to .old/assets/screenshots/unpartitioned-state/edge-5-dark.png diff --git a/assets/screenshots/unpartitioned-state/edge-5-light.png b/.old/assets/screenshots/unpartitioned-state/edge-5-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/edge-5-light.png rename to .old/assets/screenshots/unpartitioned-state/edge-5-light.png diff --git a/assets/screenshots/unpartitioned-state/in-app-1-dark.png b/.old/assets/screenshots/unpartitioned-state/in-app-1-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/in-app-1-dark.png rename to .old/assets/screenshots/unpartitioned-state/in-app-1-dark.png diff --git a/assets/screenshots/unpartitioned-state/in-app-1-light.png b/.old/assets/screenshots/unpartitioned-state/in-app-1-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/in-app-1-light.png rename to .old/assets/screenshots/unpartitioned-state/in-app-1-light.png diff --git a/assets/screenshots/unpartitioned-state/in-app-2-dark.png b/.old/assets/screenshots/unpartitioned-state/in-app-2-dark.png similarity index 100% rename from assets/screenshots/unpartitioned-state/in-app-2-dark.png rename to .old/assets/screenshots/unpartitioned-state/in-app-2-dark.png diff --git a/assets/screenshots/unpartitioned-state/in-app-2-light.png b/.old/assets/screenshots/unpartitioned-state/in-app-2-light.png similarity index 100% rename from assets/screenshots/unpartitioned-state/in-app-2-light.png rename to .old/assets/screenshots/unpartitioned-state/in-app-2-light.png diff --git a/assets/screenshots/unpartitioned-state/screenshot-instructions.md b/.old/assets/screenshots/unpartitioned-state/screenshot-instructions.md similarity index 100% rename from assets/screenshots/unpartitioned-state/screenshot-instructions.md rename to .old/assets/screenshots/unpartitioned-state/screenshot-instructions.md diff --git a/assets/setup/arrow.svg b/.old/assets/setup/arrow.svg similarity index 100% rename from assets/setup/arrow.svg rename to .old/assets/setup/arrow.svg diff --git a/assets/setup/browser-extension.png b/.old/assets/setup/browser-extension.png similarity index 100% rename from assets/setup/browser-extension.png rename to .old/assets/setup/browser-extension.png diff --git a/assets/setup/buy_tour.png b/.old/assets/setup/buy_tour.png similarity index 100% rename from assets/setup/buy_tour.png rename to .old/assets/setup/buy_tour.png diff --git a/assets/setup/buy_tour_light.png b/.old/assets/setup/buy_tour_light.png similarity index 100% rename from assets/setup/buy_tour_light.png rename to .old/assets/setup/buy_tour_light.png diff --git a/assets/setup/discord-logo.svg b/.old/assets/setup/discord-logo.svg similarity index 100% rename from assets/setup/discord-logo.svg rename to .old/assets/setup/discord-logo.svg diff --git a/assets/setup/explore_tour.png b/.old/assets/setup/explore_tour.png similarity index 100% rename from assets/setup/explore_tour.png rename to .old/assets/setup/explore_tour.png diff --git a/assets/setup/explore_tour_light.png b/.old/assets/setup/explore_tour_light.png similarity index 100% rename from assets/setup/explore_tour_light.png rename to .old/assets/setup/explore_tour_light.png diff --git a/assets/setup/github-logo.svg b/.old/assets/setup/github-logo.svg similarity index 100% rename from assets/setup/github-logo.svg rename to .old/assets/setup/github-logo.svg diff --git a/assets/setup/info-logo.svg b/.old/assets/setup/info-logo.svg similarity index 100% rename from assets/setup/info-logo.svg rename to .old/assets/setup/info-logo.svg diff --git a/assets/setup/keystone.svg b/.old/assets/setup/keystone.svg similarity index 100% rename from assets/setup/keystone.svg rename to .old/assets/setup/keystone.svg diff --git a/assets/setup/notifications-example.png b/.old/assets/setup/notifications-example.png similarity index 100% rename from assets/setup/notifications-example.png rename to .old/assets/setup/notifications-example.png diff --git a/assets/setup/notifications-example.svg b/.old/assets/setup/notifications-example.svg similarity index 100% rename from assets/setup/notifications-example.svg rename to .old/assets/setup/notifications-example.svg diff --git a/assets/setup/pin-example.png b/.old/assets/setup/pin-example.png similarity index 100% rename from assets/setup/pin-example.png rename to .old/assets/setup/pin-example.png diff --git a/assets/setup/pin-example.svg b/.old/assets/setup/pin-example.svg similarity index 100% rename from assets/setup/pin-example.svg rename to .old/assets/setup/pin-example.svg diff --git a/assets/setup/pin-extension.png b/.old/assets/setup/pin-extension.png similarity index 100% rename from assets/setup/pin-extension.png rename to .old/assets/setup/pin-extension.png diff --git a/assets/setup/send_tour.png b/.old/assets/setup/send_tour.png similarity index 100% rename from assets/setup/send_tour.png rename to .old/assets/setup/send_tour.png diff --git a/assets/setup/send_tour_light.png b/.old/assets/setup/send_tour_light.png similarity index 100% rename from assets/setup/send_tour_light.png rename to .old/assets/setup/send_tour_light.png diff --git a/assets/setup/twitter-logo.svg b/.old/assets/setup/twitter-logo.svg similarity index 100% rename from assets/setup/twitter-logo.svg rename to .old/assets/setup/twitter-logo.svg diff --git a/assets/setup/wallet.svg b/.old/assets/setup/wallet.svg similarity index 100% rename from assets/setup/wallet.svg rename to .old/assets/setup/wallet.svg diff --git a/assets/setup/x-logo.svg b/.old/assets/setup/x-logo.svg similarity index 100% rename from assets/setup/x-logo.svg rename to .old/assets/setup/x-logo.svg diff --git a/.old/package.json b/.old/package.json new file mode 100644 index 000000000..86402aadf --- /dev/null +++ b/.old/package.json @@ -0,0 +1,182 @@ +{ + "name": "wander", + "displayName": "Wander", + "version": "1.25.0", + "description": "__MSG_extensionDescription__", + "author": "th8ta", + "packageManager": "yarn@1.22.18", + "homepage": "https://www.wander.app", + "scripts": { + "dev:chrome": "yarn patch:math-intrinsics && cross-env PARCEL_WORKER_BACKEND=process plasmo dev --verbose", + "build:chrome": "yarn patch:math-intrinsics && cross-env PARCEL_WORKER_BACKEND=process plasmo build --no-hoist", + "dev:firefox": "plasmo dev --target=firefox-mv2", + "build:firefox": "plasmo build --target=firefox-mv2 --no-hoist", + "dev:iframe": "vite", + "build:iframe": "vite build && yarn build:assets", + "preview:iframe": "vite preview", + "dev:sdk": "cd ./wander-connect-sdk/ && pnpm dev", + "build:sdk": "cd ./wander-connect-sdk/ && pnpm build", + "build:assets": "cp -r assets/forge dist/assets/forge && cp -r assets/animation dist/assets/animation", + "build:wallet-api": "vite build --config vite.wallet-config.js", + "clean": "rm -rf .plasmo build dist", + "nuke": "yarn clean && rm -rf yarn.lock node_modules", + "fmt": "prettier --write .", + "fmt:check": "prettier --check .", + "prepare": "husky", + "test": "vitest --environment jsdom", + "patch:math-intrinsics": "node patches/patch-math-intrinsics.js" + }, + "manifest": { + "host_permissions": [ + "*://*/*" + ], + "default_locale": "en", + "permissions": [ + "alarms", + "contextMenus", + "tabs", + "webNavigation", + "notifications" + ], + "web_accessible_resources": [ + { + "resources": [ + "/redirect/**" + ], + "matches": [ + "https://*/*" + ] + }, + { + "resources": [ + "assets/animation/*.png" + ], + "matches": [ + "*://*/*" + ] + } + ] + }, + "dependencies": { + "@ar.io/sdk": "^3.13.0", + "@arconnect/components": "^1.0.0", + "@arconnect/components-rebrand": "https://github.com/wanderwallet/components-rebrand.git#master", + "@arconnect/keystone-sdk": "^0.0.5", + "@arconnect/webext-bridge": "^5.0.6", + "@dha-team/arbundles": "^1.0.2", + "@iconicicons/react": "^1.5.0", + "@keystonehq/arweave-keyring": "^0.1.1-alpha.0", + "@keystonehq/bc-ur-registry-arweave": "^0.1.1-alpha.0", + "@ngraveio/bc-ur": "^1.1.6", + "@noble/curves": "^1.9.2", + "@number-flow/react": "^0.5.5", + "@permaweb/aoconnect": "0.0.55", + "@plasmohq/storage": "^1.7.2", + "@segment/analytics-next": "^1.53.2", + "@swyg/corre": "^1.0.4", + "@tanstack/react-query": "^5.64.1", + "@untitled-ui/icons-react": "^0.1.1", + "@vitejs/plugin-react": "^4.3.4", + "ao-tokens": "^0.0.6", + "ar-gql": "3.1.1", + "arweave": "^1.15.7", + "axios": "^1.7.2", + "bignumber.js": "^9.1.2", + "bip39-web-crypto": "^4.0.1", + "check-password-strength": "^3.0.0", + "clsx": "^2.1.1", + "copy-to-clipboard": "^3.3.2", + "currency-symbol-map": "^5.1.0", + "dayjs": "^1.11.6", + "embed-api": "https://github.com/wanderwallet/embed-api#1b838e69570bb115b308404dac21dfe2ac7b2397", + "embla-carousel-react": "^8.6.0", + "framer-motion": "^11.11.7", + "human-crypto-keys": "^0.1.4", + "js-confetti": "^0.12.0", + "js-cookie": "^3.0.5", + "jwt-decode": "^4.0.0", + "liquidops": "^1.2.2", + "lodash.throttle": "^4.1.1", + "mitt": "^3.0.0", + "nanoid": "5.0.4", + "node-forge": "^1.3.1", + "node-stdlib-browser": "^1.3.0", + "plimit-lit": "^3.0.1", + "pretty-bytes": "^7.0.0", + "qr-scanner": "^1.4.2", + "qrcode.react": "^4.2.0", + "qrloop": "^1.4.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-fast-marquee": "^1.3.5", + "react-input-autosize": "^3.0.0", + "react-lottie": "^1.2.10", + "react-qr-reader": "^2.2.1", + "react-ssr-prepass": "^1.6.0", + "react-toastify": "^11.0.5", + "recharts": "^3.1.0", + "redstone-api": "^0.4.13", + "rss-parser": "^3.12.0", + "shamir-secret-sharing": "^0.0.4", + "styled-components": "^5.3.6", + "typed-assert": "^1.0.9", + "vite-plugin-dts": "^4.5.3", + "webextension-polyfill": "^0.12.0", + "wouter": "^3.3.5" + }, + "devDependencies": { + "@changesets/cli": "^2.27.1", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^9.2.6", + "@types/chrome": "^0.0.319", + "@types/human-crypto-keys": "^0.1.0", + "@types/js-cookie": "^3.0.6", + "@types/lodash.throttle": "^4.1.9", + "@types/node-forge": "^1.3.11", + "@types/react": "^18.0.18", + "@types/react-dom": "^18.0.6", + "@types/react-input-autosize": "^2.2.4", + "@types/react-lottie": "^1.2.10", + "@types/react-qr-reader": "^2.1.4", + "@types/serviceworker": "^0.0.133", + "@types/styled-components": "^5.1.26", + "@types/webextension-polyfill": "^0.12.3", + "browserify-zlib": "^0.2.0", + "constants-browserify": "^1.0.0", + "cross-env": "^7.0.3", + "crypto-browserify": "^3.12.0", + "https-browserify": "^1.0.0", + "husky": "^9.1.7", + "jsdom": "^26.0.0", + "lint-staged": ">=10", + "path-browserify": "^1.0.1", + "plasmo": "0.90.5", + "postcss": "^8.0.0", + "postcss-modules": "^4.3.0", + "prettier": "^3.5.3", + "querystring-es3": "^0.2.1", + "semantic-release": "^23.0.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "timers-browserify": "^2.0.12", + "typescript": "^5.8.3", + "url": "^0.11.0", + "vite": "^6.2.5", + "vite-plugin-circular-dependency": "^0.5.0", + "vite-plugin-node-stdlib-browser": "^0.2.1", + "vitest": "^3.1.3", + "vm-browserify": "^1.1.2" + }, + "resolutions": { + "@dha-team/arbundles/arweave": "^1.15.7", + "**/msgpackr": ">=1.10.1", + "human-crypto-keys/node-forge": "^1.3.1", + "human-crypto-keys/crypto-key-composer/node-forge": "^1.3.1", + "axios": "^1.7.2", + "rollup": "4.43.0", + "@inquirer/core/wrap-ansi/string-width": "4.2.3" + }, + "lint-staged": { + "*.{js,ts,tsx,html,css,scss,md,json}": "prettier --write" + } +} diff --git a/patches/patch-math-intrinsics.js b/.old/patches/patch-math-intrinsics.js similarity index 100% rename from patches/patch-math-intrinsics.js rename to .old/patches/patch-math-intrinsics.js diff --git a/release.config.js b/.old/release.config.js similarity index 100% rename from release.config.js rename to .old/release.config.js diff --git a/shim.d.ts b/.old/shim.d.ts similarity index 97% rename from shim.d.ts rename to .old/shim.d.ts index b65677437..b0e1bb039 100644 --- a/shim.d.ts +++ b/.old/shim.d.ts @@ -1,9 +1,9 @@ import type { DisplayTheme } from "@arconnect/components-rebrand"; import type { Chunk } from "~api/modules/sign/chunks"; -import type { InjectedEvents } from "~utils/events"; +import type { InjectedEvents } from "../.old-already-moved/src/utils/events"; import "styled-components"; import type { AuthRequestMessageData, AuthResult } from "~utils/auth/auth.types"; -import { EmbeddedMessage, EmbeddedCall } from "~utils/embedded/utils/messages/embedded-messages.types.ts"; +import { EmbeddedMessage, EmbeddedCall } from "~utils/_embedded/utils/messages/embedded-messages.types"; import type { ThemeMode } from "~utils/theme/theme.hook"; import type { DirectAccess } from "~wallets/router/iframe/iframe.routes"; diff --git a/.old/src/_api/foreground/foreground-setup-embedded-wallet-sdk.ts b/.old/src/_api/foreground/foreground-setup-embedded-wallet-sdk.ts new file mode 100644 index 000000000..75fd21a27 --- /dev/null +++ b/.old/src/_api/foreground/foreground-setup-embedded-wallet-sdk.ts @@ -0,0 +1,117 @@ +import type { ApiCall, Event } from "shim"; +import type { MittInjectedEvents } from "~utils/events"; +import { nanoid } from "nanoid"; +import { foregroundModules, type ForegroundModule } from "~api/foreground/foreground-modules"; +import mitt from "mitt"; +import { log, LOG_GROUP } from "~utils/log/log.utils"; +import { version } from "../../../../.old/package.json"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; +import { isApiErrorResponse } from "~utils/messaging/common/messaging.utils"; +import { isomorphicSendMessage } from "~isomorphic-messaging"; +import { setEmbeddedTargetIframe } from "~utils/messaging/strategies/iframe/iframe-messaging.strategy"; +// import { version as sdkVersion } from "../../../wander-connect-sdk/package.json"; + +export function injectWanderConnectWalletAPI(targetWindowOrIframe: Window | HTMLIFrameElement = window) { + log(LOG_GROUP.SETUP, "injectWanderConnectWalletAPI()"); + + if (!(targetWindowOrIframe instanceof HTMLIFrameElement)) { + throw new Error("Target for Wander Embedded must be an IFRAME element."); + } + + setEmbeddedTargetIframe(targetWindowOrIframe); + + /** Init events */ + const events = mitt(); + + // TODO: Can we get the right type here?: + const walletAPI = { + walletName: IS_EMBEDDED_APP ? "Wander Connect" : "ArConnect", + walletVersion: version, + events, + } as const; + + for (const mod of foregroundModules) { + walletAPI[mod.functionName] = (...params: any[]) => { + return callForegroundThenBackground(mod, params); + }; + } + + /** + * Utility function to handle the actual "call-foreground-then-background" flow. + * This can be reused by the Proxy handler below. + */ + async function callForegroundThenBackground( + foregroundModule: string | ForegroundModule, + params: any[], + ): Promise { + return new Promise(async (resolve, reject) => { + // 1. Find out what function are we calling: + + const functionName = typeof foregroundModule === "string" ? foregroundModule : foregroundModule.functionName; + + // 2. Get & prepare its params: + // For `sign` and `dispatch`, this is where the params are send in chunks. + const functionParams = typeof foregroundModule === "string" ? params : await foregroundModule.function(...params); + + // TODO: Use a default function for those that do not have/need one and + // see if chunking can be done automatically or if it is needed at all: + + // 3. Prepare the message & payload to send: + const callID = nanoid(); + const data: ApiCall = { + app: IS_EMBEDDED_APP ? "wanderEmbedded" : "wander", + version, + callID, + type: `api_${functionName}`, + data: { + params: functionParams, + }, + }; + + // 4. Send message to background script (Wander BE) or to the iframe window (Wander Embedded): + + log(LOG_GROUP.API, `${data.type} (${data.callID})...`); + + // send call to the background + const res = await isomorphicSendMessage({ + destination: "background", + messageId: data.type === "chunk" ? "chunk" : "api_call", + data, + }); + + // TODO: If the call above fails, this API call never gets a response. Add timeout? + + log(LOG_GROUP.API, `${data.type} (${data.callID}) =`, res); + + // check for errors + if (isApiErrorResponse(res)) { + return reject(res.data); + } + + const finalizerFn = typeof foregroundModule === "string" ? null : foregroundModule.finalizer; + + // call the finalizer function if it exists + if (finalizerFn) { + try { + const finalizerResult = await finalizerFn(res.data, functionParams, params); + + // TODO: This is a bad check because the result could be falsy: + // if the finalizer transforms data + // update the result + if (finalizerResult) { + res.data = finalizerResult; + } + } catch (err) { + reject(err); + + return; + } + } + + resolve(res.data); + }); + } + + // @ts-expect-error + window.arweaveWallet = walletAPI; +} diff --git a/.old/src/_api/foreground/foreground-setup-wallet-sdk.ts b/.old/src/_api/foreground/foreground-setup-wallet-sdk.ts new file mode 100644 index 000000000..b0799214c --- /dev/null +++ b/.old/src/_api/foreground/foreground-setup-wallet-sdk.ts @@ -0,0 +1,197 @@ +import type { ApiCall, ApiResponse, Event } from "shim"; +import type { MittInjectedEvents } from "~utils/events"; +import { nanoid } from "nanoid"; +import { foregroundModules, type ForegroundModule } from "~api/foreground/foreground-modules"; +import mitt from "mitt"; +import { log, LOG_GROUP } from "~utils/log/log.utils"; +import { version } from "../../../../.old/package.json"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; +import { isApiErrorResponse } from "~utils/messaging/common/messaging.utils"; +// import { version as sdkVersion } from "../../../wander-connect-sdk/package.json"; + +export async function injectWanderWalletAPI(targetWindow: Window = window, embeddedOrigin?: string) { + log(LOG_GROUP.SETUP, "injectWanderWalletAPI()"); + + /** Init events */ + const events = mitt(); + + // TODO: Can we get the right type here?: + const walletAPI = { + walletName: IS_EMBEDDED_APP ? "Wander Connect" : "ArConnect", + walletVersion: version, + events, + } as const; + + for (const mod of foregroundModules) { + walletAPI[mod.functionName] = (...params: any[]) => { + return callForegroundThenBackground(mod, params); + }; + } + + /** + * Utility function to handle the actual "call-foreground-then-background" flow. + * This can be reused by the Proxy handler below. + */ + async function callForegroundThenBackground( + foregroundModule: string | ForegroundModule, + params: any[], + ): Promise { + return new Promise(async (resolve, reject) => { + // 1. Find out what function are we calling: + + const functionName = typeof foregroundModule === "string" ? foregroundModule : foregroundModule.functionName; + + // 2. Get & prepare its params: + // For `sign` and `dispatch`, this is where the params are send in chunks. + const functionParams = typeof foregroundModule === "string" ? params : await foregroundModule.function(...params); + + // TODO: Use a default function for those that do not have/need one and + // see if chunking can be done automatically or if it is needed at all: + + // 3. Prepare the message & payload to send: + const callID = nanoid(); + const data: ApiCall = { + app: IS_EMBEDDED_APP ? "wanderEmbedded" : "wander", + version, + callID, + type: `api_${functionName}`, + data: { + params: functionParams, + }, + }; + + // 4. Send message to background script (Wander BE) or to the iframe window (Wander Embedded): + + const targetOrigin = IS_EMBEDDED_APP ? embeddedOrigin : window.location.origin; + + targetWindow.postMessage(data, targetOrigin); + + // TODO: Note this is replacing the following from `api.content-script.ts`, so the logic to await and get the response is missing with just the + // one-line change above. + // + // const res = await sendMessage( + // data.type === "chunk" ? "chunk" : "api_call", + // data, + // "background" + // ); + // + // window.postMessage(res, window.location.origin); + + // TODO: Replace `postMessage` with `isomorphicSendMessage`, which should be updated to handle + // chunking automatically based on data size, rather than relying on `sendChunk` to be called from + // the foreground scripts manually. + + // 5. Wait for result from background: + window.addEventListener("message", callback); + + // TODO: Declare outside (factory) to facilitate testing? + async function callback(e: MessageEvent) { + // TODO: Make sure the response comes from targetWindow. + // See https://stackoverflow.com/questions/16266474/javascript-listen-for-postmessage-events-from-specific-iframe. + + let { data: res } = e; + + // validate return message + if (!data || `${data.type}_result` !== res.type) return; + + // only resolve when the result matching our callID is delivered + if (data.callID !== res.callID) return; + + window.removeEventListener("message", callback); + + // check for errors + if (isApiErrorResponse(res)) { + return reject(res.data); + } + + const finalizerFn = typeof foregroundModule === "string" ? null : foregroundModule.finalizer; + + // call the finalizer function if it exists + if (finalizerFn) { + try { + const finalizerResult = await finalizerFn(res.data, functionParams, params); + + // TODO: This is a bad check because the result could be falsy: + // if the finalizer transforms data + // update the result + if (finalizerResult) { + res.data = finalizerResult; + } + } catch (err) { + reject(err); + + return; + } + } + + resolve(res.data); + } + }); + } + + // @ts-expect-error + window.arweaveWallet = walletAPI; + + /** Handle events */ + window.addEventListener( + "message", + ( + e: MessageEvent<{ + type: "wander_event"; + event: Event; + }>, + ) => { + if (!e.data || !e.data.event || e.data.type !== "wander_event") return; + + events.emit(e.data.event.name, e.data.event.value); + }, + ); + + // at the end of the injected script, + // we dispatch the wallet loaded event + + async function dispatchArweaveWalletLoaded() { + if (!window.arweaveWallet || window.arweaveWallet.walletName !== "ArConnect") return; + + const permissions = await window.arweaveWallet.getPermissions().catch(() => []); + + // Note that for Wander Connect we just need to dispatch this once, no need to subscribe to the window load event to re-dispatch it: + dispatchEvent( + new CustomEvent("arweaveWalletLoaded", { + detail: { + permissions, + }, + }), + ); + + if (permissions.length > 0) { + events.emit("connect", null); + + const [activeAddress, addresses] = await Promise.all([ + window.arweaveWallet.getActiveAddress().catch(() => ""), + window.arweaveWallet.getAllAddresses().catch(() => []), + ]); + + events.emit("activeAddress", activeAddress); + events.emit("addresses", addresses); + } + } + + // Not sure there's a point in this, as the dApp will never be able to listen for it, but I'm maintaining the same structure we had before: + dispatchArweaveWalletLoaded(); + + // This doesn't work on page reload unless it's a hard load, as the event is dispatched earlier on reload, before the dApp can start listening, so... + // window.addEventListener("load", dispatchArweaveWalletLoaded); + + // ...we can instead monkey patch `window.addEventListener` and re-dispatch the "arweaveWalletLoaded" event as soon as a new listener is added: + + let addEventListener_ = Window.prototype.addEventListener; + + Window.prototype.addEventListener = function (eventName: string, ...args) { + if (eventName === "arweaveWalletLoaded") { + dispatchArweaveWalletLoaded(); + } + + addEventListener_.call(this, eventName, ...args); + }; +} diff --git a/vite-env.d.ts b/.old/vite-env.d.ts similarity index 100% rename from vite-env.d.ts rename to .old/vite-env.d.ts diff --git a/vite.config.js b/.old/vite.config.js similarity index 100% rename from vite.config.js rename to .old/vite.config.js diff --git a/vite.wallet-config.js b/.old/vite.wallet-config.js similarity index 100% rename from vite.wallet-config.js rename to .old/vite.wallet-config.js diff --git a/vitest.config.ts b/.old/vitest.config.ts similarity index 100% rename from vitest.config.ts rename to .old/vitest.config.ts diff --git a/.prettierignore b/.prettierignore index 98b51c44e..2b84b6884 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,12 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage +/.nx/cache +/.nx/workspace-data + +# From the old repo .prettierignore file: node_modules build .plasmo assets -.husky \ No newline at end of file +.husky diff --git a/.verdaccio/config.yml b/.verdaccio/config.yml new file mode 100644 index 000000000..f74420f2b --- /dev/null +++ b/.verdaccio/config.yml @@ -0,0 +1,28 @@ +# path to a directory with all packages +storage: ../tmp/local-registry/storage + +# a list of other known repositories we can talk to +uplinks: + npmjs: + url: https://registry.npmjs.org/ + maxage: 60m + +packages: + '**': + # give all users (including non-authenticated users) full access + # because it is a local registry + access: $all + publish: $all + unpublish: $all + + # if package is not available locally, proxy requests to npm registry + proxy: npmjs + +# log settings +log: + type: stdout + format: pretty + level: warn + +publish: + allow_offline: true # set offline to true to allow publish offline diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..a90dde472 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "nrwl.angular-console", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "ms-playwright.playwright" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 0c0d134bc..02e4ae1b0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "cSpell.words": ["tooltiptext"] + "nxConsole.nxWorkspacePath": "" } diff --git a/README.md b/README.md new file mode 100644 index 000000000..a31ae6ee7 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# Org + + + +✨ Your new, shiny [Nx workspace](https://nx.dev) is almost ready ✨. + +[Learn more about this workspace setup and its capabilities](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed! + +## Finish your CI setup + +[Click here to finish setting up your workspace!](https://cloud.nx.app/connect/hoNtgjU3kv) + + +## Run tasks + +To run the dev server for your app, use: + +```sh +npx nx serve wander-connect +``` + +To create a production bundle: + +```sh +npx nx build wander-connect +``` + +To see all available targets to run for a project, run: + +```sh +npx nx show project wander-connect +``` + +These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files. + +[More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) + +## Add new projects + +While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature. + +Use the plugin's generator to create new projects. + +To generate a new application, use: + +```sh +npx nx g @nx/react:app demo +``` + +To generate a new library, use: + +```sh +npx nx g @nx/react:lib mylib +``` + +You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list ` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE. + +[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) + + +[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) + +## Install Nx Console + +Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ. + +[Install Nx Console »](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) + +## Useful links + +Learn more: + +- [Learn more about this workspace setup](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) +- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) +- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) +- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) + +And join the Nx community: +- [Discord](https://go.nx.dev/community) +- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl) +- [Our Youtube channel](https://www.youtube.com/@nxdevtools) +- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) + + + + +TODO: +===== + +- [ ] SDK `package.json` has some paths from the previous version that should be addressed (`sdk-dist`). +- [ ] The original README is missing. +- [ ] tsconfig files for `wander-connect-sdk` as well as the root one have not been merged into the new `tsconfig.base.json` one nor into the app/lib-specific one. diff --git a/apps/wander-be-e2e/eslint.config.mjs b/apps/wander-be-e2e/eslint.config.mjs new file mode 100644 index 000000000..5ceb5d3fb --- /dev/null +++ b/apps/wander-be-e2e/eslint.config.mjs @@ -0,0 +1,13 @@ +import playwright from 'eslint-plugin-playwright'; +import baseConfig from '../../eslint.config.mjs'; + +export default [ + playwright.configs['flat/recommended'], + ...baseConfig, + + // { + // files: ['**/*.ts', '**/*.js'], + // // Override or add rules here + // rules: {}, + // }, +]; diff --git a/apps/wander-be-e2e/package.json b/apps/wander-be-e2e/package.json new file mode 100644 index 000000000..838b0fcce --- /dev/null +++ b/apps/wander-be-e2e/package.json @@ -0,0 +1,10 @@ +{ + "name": "@org/wander-be-e2e", + "version": "0.0.1", + "private": true, + "nx": { + "implicitDependencies": [ + "@org/wander-be" + ] + } +} diff --git a/apps/wander-be-e2e/playwright.config.ts b/apps/wander-be-e2e/playwright.config.ts new file mode 100644 index 000000000..59c4e9876 --- /dev/null +++ b/apps/wander-be-e2e/playwright.config.ts @@ -0,0 +1,68 @@ +import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; +import { workspaceRoot } from '@nx/devkit'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4200'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'pnpm exec nx run @org/wander-be:preview', + url: 'http://localhost:4200', + reuseExistingServer: true, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); diff --git a/apps/wander-be-e2e/src/example.spec.ts b/apps/wander-be-e2e/src/example.spec.ts new file mode 100644 index 000000000..fa8f1f335 --- /dev/null +++ b/apps/wander-be-e2e/src/example.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('/'); + + // Expect h1 to contain a substring. + expect(await page.locator('h1').innerText()).toContain('Welcome'); +}); diff --git a/apps/wander-be-e2e/tsconfig.json b/apps/wander-be-e2e/tsconfig.json new file mode 100644 index 000000000..1df867f3a --- /dev/null +++ b/apps/wander-be-e2e/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "allowJs": true, + "outDir": "out-tsc/playwright", + "sourceMap": false + }, + "include": [ + "**/*.ts", + "**/*.js", + "playwright.config.ts", + "src/**/*.spec.ts", + "src/**/*.spec.js", + "src/**/*.test.ts", + "src/**/*.test.js", + "src/**/*.d.ts" + ], + "exclude": [ + "out-tsc", + "test-output", + "eslint.config.js", + "eslint.config.mjs", + "eslint.config.cjs" + ] +} diff --git a/apps/wander-be/eslint.config.mjs b/apps/wander-be/eslint.config.mjs new file mode 100644 index 000000000..d8d771705 --- /dev/null +++ b/apps/wander-be/eslint.config.mjs @@ -0,0 +1,13 @@ +import nx from '@nx/eslint-plugin'; +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...nx.configs['flat/react'], + ...baseConfig, + + // { + // files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // // Override or add rules here + // rules: {}, + // }, +]; diff --git a/apps/wander-be/index.html b/apps/wander-be/index.html new file mode 100644 index 000000000..c2ef2dda4 --- /dev/null +++ b/apps/wander-be/index.html @@ -0,0 +1,16 @@ + + + + + WanderBe + + + + + + + +
+ + + diff --git a/apps/wander-be/package.json b/apps/wander-be/package.json new file mode 100644 index 000000000..9839fd43a --- /dev/null +++ b/apps/wander-be/package.json @@ -0,0 +1,5 @@ +{ + "name": "@org/wander-be", + "version": "0.0.1", + "private": true +} diff --git a/apps/wander-be/public/favicon.ico b/apps/wander-be/public/favicon.ico new file mode 100644 index 000000000..317ebcb23 Binary files /dev/null and b/apps/wander-be/public/favicon.ico differ diff --git a/apps/wander-be/src/app/app.module.scss b/apps/wander-be/src/app/app.module.scss new file mode 100644 index 000000000..7b88fbabf --- /dev/null +++ b/apps/wander-be/src/app/app.module.scss @@ -0,0 +1 @@ +/* Your styles goes here. */ diff --git a/apps/wander-be/src/app/app.tsx b/apps/wander-be/src/app/app.tsx new file mode 100644 index 000000000..7151913b0 --- /dev/null +++ b/apps/wander-be/src/app/app.tsx @@ -0,0 +1,13 @@ +// Uncomment this line to use CSS modules +// import styles from './app.module.scss'; +import NxWelcome from './nx-welcome'; + +export function App() { + return ( +
+ +
+ ); +} + +export default App; diff --git a/apps/wander-be/src/app/nx-welcome.tsx b/apps/wander-be/src/app/nx-welcome.tsx new file mode 100644 index 000000000..874aafb91 --- /dev/null +++ b/apps/wander-be/src/app/nx-welcome.tsx @@ -0,0 +1,848 @@ +/* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + This is a starter component and can be deleted. + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + Delete this file and get started with your project! + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ +export function NxWelcome({ title }: { title: string }) { + return ( + <> +

Invalid Wander Embedded configuration

${errorMessage}

`; + + // Stop any ongoing resource loading + window.stop(); + + // Replace the entire document content + location.replace(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`); + + throw new Error("Application validation failed"); + } +} + +// Validate immediately on load +// insecurelyValidateApplication(); + +if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1" && process.env.NODE_ENV === "development") { + (window as any).signOut = signOut; + (window as any).logOut = signOut; +} diff --git a/apps/wander-connect/src/utils/iframe.utils.ts b/apps/wander-connect/src/utils/iframe.utils.ts new file mode 100644 index 000000000..3d4b5a5ef --- /dev/null +++ b/apps/wander-connect/src/utils/iframe.utils.ts @@ -0,0 +1,66 @@ +import { ThemeMode } from "@wanderapp/ui"; + +const { search = "", ancestorOrigins = [] } = + import.meta.env?.VITE_IS_EMBEDDED_APP === "1" && typeof document !== "undefined" ? document.location : {}; + +export const searchParams = new URLSearchParams(search); +export const ancestorOrigin = ancestorOrigins[ancestorOrigins.length - 1]; + +export function isInsideIframe(): boolean { + try { + return typeof window !== "undefined" && (window.self !== window.top || !!ancestorOrigin); + } catch (e) { + // If we can't access window.top due to cross-origin restrictions, + // we're definitely in an iframe + return true; + } +} + +const NODE_ENV = process.env.NODE_ENV || "development"; + +const EMBEDDED_ENV_VARS_BY_ENV = { + development: { + DEFAULT_EMBEDDED_CLIENT_ID: import.meta.env?.VITE_DEV_DEFAULT_EMBEDDED_CLIENT_ID, + DEFAULT_EMBEDDED_SERVER_BASE_URL: import.meta.env?.VITE_DEV_DEFAULT_EMBEDDED_SERVER_BASE_URL, + }, + production: { + DEFAULT_EMBEDDED_CLIENT_ID: import.meta.env?.VITE_PROD_DEFAULT_EMBEDDED_CLIENT_ID, + DEFAULT_EMBEDDED_SERVER_BASE_URL: import.meta.env?.VITE_PROD_EMBEDDED_SERVER_BASE_URL, + }, + test: { + DEFAULT_EMBEDDED_CLIENT_ID: import.meta.env?.VITE_TEST_DEFAULT_EMBEDDED_CLIENT_ID, + DEFAULT_EMBEDDED_SERVER_BASE_URL: import.meta.env?.VITE_TEST_DEFAULT_EMBEDDED_SERVER_BASE_URL, + }, +} as const; + +const EMBEDDED_ENV_VARS = EMBEDDED_ENV_VARS_BY_ENV[NODE_ENV]; + +if (!EMBEDDED_ENV_VARS) throw new Error(`Missing ENV vars for NODE_ENV = "${NODE_ENV}"`); + +// Duplicated in `wander-connect-sdk/src/utils/url/url.utils.ts`: +const PARAM_CLIENT_ID = "client-id"; +const PARAM_THEME = "theme"; +const PARAM_ANCESTOR_ORIGIN = "ancestor-origin"; +const PARAM_HIDE_BE = "hide-be"; +const PARAM_INJECTED_BE = "injected-be"; +const PARAM_SERVER_BASE_URL = "server-base-url"; + +export const EMBEDDED_CLIENT_ID = searchParams.get(PARAM_CLIENT_ID) || EMBEDDED_ENV_VARS.DEFAULT_EMBEDDED_CLIENT_ID; + +export const EMBEDDED_THEME = (searchParams.get(PARAM_THEME) as ThemeMode) || "system"; + +export const EMBEDDED_ANCESTOR_ORIGIN = ancestorOrigin || searchParams.get(PARAM_ANCESTOR_ORIGIN); + +export const EMBEDDED_HIDE_BE = searchParams.get(PARAM_HIDE_BE) === "1" || false; + +export const EMBEDDED_INJECTED_BE = searchParams.get(PARAM_INJECTED_BE) === "1" || false; + +export const EMBEDDED_SERVER_BASE_URL = + searchParams.get(PARAM_SERVER_BASE_URL) || EMBEDDED_ENV_VARS.DEFAULT_EMBEDDED_SERVER_BASE_URL; + +// Note: DO NOT use document.referrer here as that will return the "incorrect" value when the user is redirected from +// an auth provider domain to back to Wander Embedded. + +export function getEmbeddedAncestorOrigin() { + return EMBEDDED_ANCESTOR_ORIGIN; +} diff --git a/apps/wander-connect/src/utils/storage/plasmo-storage/plasmo-storage.mock.ts b/apps/wander-connect/src/utils/storage/plasmo-storage/plasmo-storage.mock.ts new file mode 100644 index 000000000..184ae58b9 --- /dev/null +++ b/apps/wander-connect/src/utils/storage/plasmo-storage/plasmo-storage.mock.ts @@ -0,0 +1,304 @@ +import { Storage as PlasmoStorage, type StorageCallbackMap, type StorageWatchCallback } from "@plasmohq/storage"; +import { EnhancedStorage } from "../unpartitioned-storage/unpartitioned-storage"; +import { ItemStorageOptions } from "../storage-manager/storage-manager.utils"; +import { StorageManager } from "../storage-manager/storage-manager"; +import { LocalStorage } from "../unpartitioned-storage/local-storage"; + +export interface StorageMockInterface extends PlasmoStorage { + setItem(key: string, value: T, options?: ItemStorageOptions): Promise; + getItem(key: string): Promise; + removeItem(key: string): Promise; + getItems(keys: string[]): Promise>; + removeItems(keys: string[]): Promise; + setItems(items: Record, options?: ItemStorageOptions): Promise; + watch(callbackMap: StorageCallbackMap): boolean; + unwatch(callbackMap: StorageCallbackMap): null; + unwatchAll(): void; + setCache(key: string, value: T, maxAge?: number): Promise; + setPreference(key: string, value: T): Promise; + setTemporary(key: string, value: T, maxAge?: number): Promise; + setCritical(key: string, value: T): Promise; + evictIfNeeded(bytesNeeded?: number, aggressive?: boolean): Promise; + getUsageInfo(forceRefresh?: boolean): { + bytes: number; + percentage: number; + items: number; + available: number; + }; + ensureSpace(bytesNeeded: number, options?: { skipCheck?: boolean; forceEviction?: boolean }): Promise; + setPrioritizedItem( + key: string, + value: T, + priority: keyof typeof StorageManager.PRIORITY_LEVELS, + expiresIn?: number, + ): Promise; + requestStorageAccess(): Promise; + hasAvailableSpace(bytesNeeded: number): Promise; + getRaw(key: string): Promise; + setRaw(key: string, value: string): Promise; + keys(): Promise; +} + +export class StorageMock extends PlasmoStorage implements StorageMockInterface { + private storage: EnhancedStorage; + private _area: "session" | "local" = "session"; + + constructor(area: "session" | "local" = "session") { + super({ area }); + + this._area = area; + + this.storage = area === "local" ? LocalStorage.getInstanceWithoutWaiting() : new EnhancedStorage({ area }); + } + + get primaryClient(): chrome.storage.StorageArea { + throw new Error("Method not implemented."); + } + + get secondaryClient(): Storage { + throw new Error("Method not implemented."); + } + + get area(): "session" | "sync" | "local" | "managed" { + return this._area; + } + + get hasWebApi(): boolean { + return true; + } + + get hasExtensionApi(): boolean { + return false; + } + + get copiedKeySet(): Set { + throw new Error("Method not implemented."); + } + + setCopiedKeySet(keyList: string[]): void { + // Do nothing... + } + + get allCopied(): boolean { + throw new Error("Method not implemented."); + } + + // GET: + + getItem(key: string): Promise { + return Promise.resolve(this.storage.getItem(key, undefined)); + } + + getItems(keys: string[]): Promise> { + return Promise.resolve(this.storage.getItems(keys, undefined)); + } + + get: (key: string) => Promise = this.getItem; + getMany: (keys: string[]) => Promise> = this.getItems; + + // SET: + + setItem(key: string, rawValue: any, options?: ItemStorageOptions): Promise { + return new Promise(async (resolve) => { + await this.storePrevValue(key); + this.storage.setItem(key, rawValue, options); + this.callWatchers(key, rawValue); + resolve(); + }); + } + + setItems(items: Record, options?: ItemStorageOptions): Promise { + return new Promise((resolve) => { + this.storage.setItems(items, options); + resolve(); + }); + } + + set: (key: string, rawValue: any) => Promise = this.setItem as any; + setMany: (items: Record) => Promise = this.setItems as any; + + // REMOVE CLEAR + + removeItem(key: string): Promise { + return new Promise((resolve) => { + this.storePrevValue(key); + this.storage.removeItem(key); + this.callWatchers(key, undefined); + resolve(); + }); + } + + removeItems(keys: string[]): Promise { + return new Promise((resolve) => { + this.storage.removeItems(keys); + resolve(); + }); + } + + remove: (key: string) => Promise = this.removeItem; + removeMany: (keys: string[]) => Promise = this.removeItems; + + removeAll: () => Promise = () => { + return new Promise((resolve) => { + this.storage.clear(); + resolve(); + }); + }; + + // WATCH: + + watchers: Record = {}; + oldValues: Record = {}; + + async storePrevValue(key: string) { + this.oldValues[key] = await this.get(key); + } + + callWatchers(key: string, newValue: any) { + const oldValue = this.oldValues[key]; + + this.watchers[key]?.forEach((callback) => { + try { + callback( + { + newValue, + oldValue, + }, + this.area, + ); + } catch (err) { + console.warn("Error calling watcher:", err); + } + }); + } + + watch = (callbackMap: StorageCallbackMap) => { + Object.entries(callbackMap).forEach(([key, callback]) => { + this.watchers[key] ??= []; + this.watchers[key].push(callback); + }); + + return true; + }; + + unwatch = (callbackMap: StorageCallbackMap) => { + Object.entries(callbackMap).forEach(([key, callback]) => { + const watchers = this.watchers[key] || []; + const indexToDelete = watchers.indexOf(callback); + + if (indexToDelete !== -1) { + watchers.splice(indexToDelete, 1); + } + }); + + return null; + }; + + unwatchAll = () => { + this.watchers = {}; + }; + + // unpartitioned storage: + async requestStorageAccess() { + await this.storage.requestStorageAccess(); + } + + // Additional methods: + + async hasAvailableSpace(bytesNeeded: number): Promise { + return this.storage.hasAvailableSpace(bytesNeeded); + } + + async getRaw(key: string): Promise { + return this.storage.getRaw(key); + } + + async setRaw(key: string, value: string): Promise { + return this.storage.setRaw(key, value); + } + + async keys(): Promise { + return this.storage.keys(); + } + + /** + * Store cache data with automatic expiration and lower priority + */ + setCache(key: string, value: T, maxAge = 3600000): Promise { + return new Promise((resolve) => { + this.storage.setCache(key, value, maxAge); + resolve(); + }); + } + + /** + * Store user preferences with high priority and no expiration + */ + setPreference(key: string, value: T): Promise { + return new Promise((resolve) => { + this.storage.setPreference(key, value); + resolve(); + }); + } + + /** + * Store temporary data that can be easily evicted + */ + setTemporary(key: string, value: T, maxAge = 300000): Promise { + return new Promise((resolve) => { + this.storage.setTemporary(key, value, maxAge); + resolve(); + }); + } + + /** + * Store critical data that should not be evicted + */ + setCritical(key: string, value: T): Promise { + return new Promise((resolve) => { + this.storage.setCritical(key, value); + resolve(); + }); + } + + /** + * Check if storage is near capacity and evict items if needed + */ + async evictIfNeeded(bytesNeeded = 0): Promise { + return this.storage.evictIfNeeded(bytesNeeded); + } + + /** + * Get the current storage usage information + */ + getUsageInfo(): { + bytes: number; + percentage: number; + items: number; + available: number; + } { + return this.storage.getUsageInfo(); + } + + /** + * Check if there's enough space for an item of the given size + */ + async ensureSpace(bytesNeeded: number): Promise { + return this.storage.ensureSpace(bytesNeeded); + } + + /** + * Set an item with predefined priority levels + */ + setPrioritizedItem( + key: string, + value: T, + priority: keyof typeof StorageManager.PRIORITY_LEVELS, + expiresIn?: number, + ): Promise { + return new Promise((resolve) => { + this.storage.setPrioritizedItem(key, value, priority, expiresIn); + resolve(); + }); + } +} diff --git a/apps/wander-connect/src/utils/storage/storage-manager/storage-manager.ts b/apps/wander-connect/src/utils/storage/storage-manager/storage-manager.ts new file mode 100644 index 000000000..0b7f7a29c --- /dev/null +++ b/apps/wander-connect/src/utils/storage/storage-manager/storage-manager.ts @@ -0,0 +1,308 @@ +import { isComplexStorageItem } from "./storage-manager.utils"; + +/** + * Storage Manager for handling localStorage and sessionStorage eviction policies + */ +export class StorageManager { + public static readonly MAX_STORAGE_SIZE = 5 * 1024 * 1024; // 5MB default limit + public static readonly DEFAULT_PRIORITY = 10; // Critical priority (scale of 1-10) + public static readonly PRIORITY_LEVELS = { + CRITICAL: 10, // System-critical data that should never be evicted + HIGH: 8, // Important user preferences/settings + MEDIUM: 5, // Regular application data + LOW: 3, // Cache data that can be regenerated + TEMPORARY: 1, // Short-lived or disposable data + }; + + // Add a last cleanup timestamp to avoid too frequent cleanups + private static lastCleanupTime = 0; + private static readonly CLEANUP_INTERVAL = 60000; // 1 minute + + /** + * Evict items from storage based on priority and expiration + * @param storage The storage object to manage + * @param bytesNeeded Estimated bytes needed + * @param options Eviction options + * @returns True if eviction was successful or not needed + */ + static evictItems( + storage: Storage, + bytesNeeded = 0, + options: { bufferSize?: number; aggressive?: boolean } = {}, + ): boolean { + const initialLength = storage.length; + // Setup options with defaults + const bufferSize = options.bufferSize ?? 1024; + const aggressive = options.aggressive ?? false; + + // First, clear any expired items + this.clearExpiredItems(storage, aggressive); + + // Calculate current usage + const usage = this.calculateStorageUsage(storage); + + // No eviction needed if we have enough space + if (usage.currentSize + bytesNeeded + bufferSize <= this.MAX_STORAGE_SIZE) { + return true; + } + + // Sort items by priority and expiration + const sortedItems = this.getSortedStorageItems(storage); + + // Early exit if no items are available for eviction + if (sortedItems.length === 0) { + return false; + } + + // Calculate target size - how much space we want to have after eviction + const targetSize = this.MAX_STORAGE_SIZE - bytesNeeded - bufferSize; + + // Track current size instead of using initial usage + let currentSize = usage.currentSize; + let evictedCount = 0; + + // Determine the eviction threshold based on aggressiveness + const evictionThreshold = aggressive ? this.PRIORITY_LEVELS.HIGH : this.PRIORITY_LEVELS.CRITICAL; + + // Evict items until we have enough space + for (const item of sortedItems) { + if (item.priority >= evictionThreshold) { + continue; + } + + try { + storage.removeItem(item.key); + currentSize -= item.size; + evictedCount++; + } catch (error) { + // Log error but continue with other items + console.error(`Failed to evict item ${item.key}:`, error); + continue; + } + + // Check progress periodically for performance + if (evictedCount % 10 === 0) { + if (currentSize <= targetSize) { + return true; + } + } + } + + // Verify storage wasn't modified during eviction + if (storage.length !== initialLength - evictedCount) { + // Storage was modified during eviction, recalculate + const finalUsage = this.calculateStorageUsage(storage); + return finalUsage.currentSize + bytesNeeded <= this.MAX_STORAGE_SIZE; + } + + return currentSize + bytesNeeded <= this.MAX_STORAGE_SIZE; + } + + /** + * Clear all expired items from storage + */ + static clearExpiredItems(storage: Storage, force = false): number { + const now = Date.now(); + // Skip if we recently cleaned up and force isn't true + if (!force && now - this.lastCleanupTime < this.CLEANUP_INTERVAL) { + return 0; + } + + // Skip if storage is empty + if (storage.length === 0) { + this.lastCleanupTime = now; + return 0; + } + + let clearedCount = 0; + + for (const key of Object.keys(storage)) { + const rawValue = storage.getItem(key); + if (!rawValue) continue; + + try { + const parsed = JSON.parse(rawValue); + + if (isComplexStorageItem(parsed, { expiresAt: true }) && parsed.expiresAt < now) { + storage.removeItem(key); + clearedCount++; + } + } catch (e) { + // Skip items that can't be parsed + } + } + + this.lastCleanupTime = now; + return clearedCount; + } + + /** + * Efficiently calculates storage usage (in bytes) and item count for the given Storage object. + * Uses TextEncoder to correctly handle multi-byte characters. + * + * @param storage The Storage object (localStorage or sessionStorage) + * @returns An object with the total size in bytes and the item count. + */ + static calculateStorageUsage(storage: Storage): { + currentSize: number; + itemCount: number; + } { + // Create encoder once and reuse + const encoder = new TextEncoder(); + let totalBytes = 0; + const count = storage.length; + + // Use a single array to store concatenated strings + const buffer = new Array(2); + + for (let i = 0; i < count; i++) { + const key = storage.key(i); + if (key !== null) { + // Avoid string concatenation, use array positions instead + buffer[0] = key; + buffer[1] = storage.getItem(key) ?? ""; + // Join only when encoding to reduce string operations + totalBytes += encoder.encode(buffer.join("")).length; + } + } + + return { currentSize: totalBytes, itemCount: count }; + } + + static calculateStorageUsageByKey(storage: Storage, key: string): number { + const encoder = new TextEncoder(); + const buffer = new Array(2); + buffer[0] = key; + buffer[1] = storage.getItem(key) ?? ""; + return encoder.encode(buffer.join("")).length; + } + + static calculateStorageUsageByKeys(storage: Storage, keys: string[]): number { + return keys.reduce((acc, key) => acc + this.calculateStorageUsageByKey(storage, key), 0); + } + + /** + * Get all storage items sorted by eviction criteria + */ + static getSortedStorageItems(storage: Storage): Array<{ + key: string; + size: number; + priority: number; + expiresAt: number; + }> { + const encoder = new TextEncoder(); + const now = Date.now(); + const items: Array<{ + key: string; + size: number; + priority: number; + expiresAt: number; + }> = []; + + // Extract all items with metadata + Object.keys(storage).forEach((key) => { + const rawValue = storage.getItem(key); + if (!rawValue) return; + + const size = encoder.encode(key + rawValue).length; + + try { + const parsed = JSON.parse(rawValue); + + // Check if it's a complex item with metadata + if (isComplexStorageItem(parsed)) { + items.push({ + key, + size, + priority: parsed.priority ?? this.DEFAULT_PRIORITY, + expiresAt: parsed.expiresAt ?? Number.MAX_SAFE_INTEGER, + }); + } else { + // Simple items get default priority + items.push({ + key, + size, + priority: this.DEFAULT_PRIORITY, + expiresAt: Number.MAX_SAFE_INTEGER, + }); + } + } catch (e) { + // Non-JSON items get lowest priority + items.push({ + key, + size, + priority: this.DEFAULT_PRIORITY, + expiresAt: Number.MAX_SAFE_INTEGER, + }); + } + }); + + // Multi-level sorting: + // 1. Expired items first + // 2. Then by priority (lowest first) + // 3. Then by size (largest first to free up more space quickly) + // 4. Finally by expiration (soonest first) + return items.sort((a, b) => { + // Expired items first (can be combined with priority if both are expired) + const aExpired = a.expiresAt < now; + const bExpired = b.expiresAt < now; + + if (aExpired && !bExpired) return -1; + if (!aExpired && bExpired) return 1; + + // Then by priority (lowest first) + if (a.priority !== b.priority) { + return a.priority - b.priority; + } + + // Then by size (largest first) + if (a.size !== b.size) { + return b.size - a.size; + } + + // Finally by expiration date + return a.expiresAt - b.expiresAt; + }); + } + + /** + * Get priority levels for use in application code + */ + static get PRIORITY(): typeof StorageManager.PRIORITY_LEVELS { + return this.PRIORITY_LEVELS; + } + + /** + * Estimate size of an item before storing + * @param key The key to store + * @param value The value to store + */ + static estimateItemSize(key: string, value: T): number { + const serialized = JSON.stringify(value); + // 2 bytes per character for UTF-16 encoding + key length + return (key.length + serialized.length) * 2; + } + + /** + * Get the current storage limit + */ + static getStorageLimit(): number { + return this.MAX_STORAGE_SIZE; + } + + /** + * Get the current storage usage percentage + */ + static getStorageUsage(storage: Storage): { + bytes: number; + percentage: number; + items: number; + } { + const usage = this.calculateStorageUsage(storage); + return { + bytes: usage.currentSize, + percentage: (usage.currentSize / this.MAX_STORAGE_SIZE) * 100, + items: usage.itemCount, + }; + } +} diff --git a/apps/wander-connect/src/utils/storage/storage-manager/storage-manager.utils.ts b/apps/wander-connect/src/utils/storage/storage-manager/storage-manager.utils.ts new file mode 100644 index 000000000..96d7e0601 --- /dev/null +++ b/apps/wander-connect/src/utils/storage/storage-manager/storage-manager.utils.ts @@ -0,0 +1,63 @@ +export type StorageItem = + | T + | { + /** + * The value to store. + */ + value: T; + /** + * The expiration unix timestamp of the item. + * Optional, but either this or priority should be present. + */ + expiresAt?: number; + /** + * Higher number = higher priority (less likely to be evicted). + * Optional, but either this or expiresAt should be present. + */ + priority?: number; + }; + +export interface ItemStorageOptions { + expiresIn?: number; + priority?: number; +} + +/** + * Helper method to determine if a parsed item is a complex StorageItem with metadata + */ +export function isComplexStorageItem( + item: any, + requiredMetadata: { + expiresAt?: boolean; + priority?: boolean; + } = {}, +): item is { value: T; expiresAt?: number; priority?: number } { + // Early bailout checks for non-objects + if (!item || typeof item !== "object" || item === null) { + return false; + } + + // Check for required value property + if (!("value" in item)) { + return false; + } + + // Check for at least one metadata property + const hasExpiresAt = "expiresAt" in item; + const hasPriority = "priority" in item; + + if (!hasExpiresAt && !hasPriority) { + return false; + } + + // Check specific required options if provided + if (requiredMetadata.expiresAt && !hasExpiresAt) { + return false; + } + + if (requiredMetadata.priority && !hasPriority) { + return false; + } + + return true; +} diff --git a/apps/wander-connect/src/utils/storage/unpartitioned-storage/local-storage.ts b/apps/wander-connect/src/utils/storage/unpartitioned-storage/local-storage.ts new file mode 100644 index 000000000..7e385b7c8 --- /dev/null +++ b/apps/wander-connect/src/utils/storage/unpartitioned-storage/local-storage.ts @@ -0,0 +1,543 @@ +import { EnhancedStorage } from "./unpartitioned-storage"; +import JsCookie from "js-cookie"; +import { log, LOG_GROUP, browserInfo } from "@wanderapp/core"; +import { isInsideIframe } from "../../iframe.utils"; +import { getUnpartitionedStateStatus } from "./unpartitioned-storage.utils"; + +type CookieAttributes = typeof JsCookie["attributes"]; + +export class LocalStorage { + private static instance: LocalStorage | null = null; + + // Maximum size for a single cookie chunk (about 4KB - some overhead) + private static readonly MAX_COOKIE_CHUNK_SIZE = 3000; + + private static readonly NO_COOKIE_STORE_ERR = "CookieStore not supported"; + + // Suffix for chunked cookies + private static readonly CHUNK_SUFFIX = "_chunk_"; + private static readonly CHUNK_META_SUFFIX = "_chunk_meta"; + + private cookieStoreAvailable = false; + private isHttps: boolean; + private storage: EnhancedStorage; + + private get JS_COOKIE_OPTIONS(): CookieAttributes { + return { + path: "/", + sameSite: !this.isHttps ? "Lax" : "None", + expires: 365, + secure: this.isHttps, + }; + } + + private get COOKIE_STORE_OPTIONS(): Omit { + return { + path: "/", + sameSite: !this.isHttps ? "lax" : "none", + expires: Date.now() + 365 * 24 * 60 * 60 * 1000, + partitioned: false, + }; + } + + private constructor() { + this.storage = new EnhancedStorage({ area: "local" }); + + this.isHttps = typeof location !== "undefined" && location.protocol === "https:"; + + // Safari doesn't support the partitioned option in cookieStorage. See https://developer.mozilla.org/en-US/docs/Web/API/CookieStore#browser_compatibility + // But it could be available soon (Safari Technology Preview 212). See https://web.swipeinsight.app/posts/safari-technology-preview-212-introduces-partitioned-cookie-storage-support-14295 + + this.cookieStoreAvailable = typeof window !== "undefined" && !!window.cookieStore && !browserInfo.isSafari; + + if (getUnpartitionedStateStatus() === "limited") { + if (this.cookieStoreAvailable) { + console.log(`window.cookieStore will be used, with options =`, this.COOKIE_STORE_OPTIONS); + } else { + console.log(`jsCookie will be used, with options =`, this.JS_COOKIE_OPTIONS); + } + } + } + + get unpartitionedState() { + return { + status: this.storage.status, + error: this.storage.error, + }; + } + + static async getInstance(): Promise { + this.instance = this.instance ?? new LocalStorage(); + + await this.instance.storage.requestStorageAccess(); + + if (this.instance.storage === localStorage && getUnpartitionedStateStatus() === "supported" && isInsideIframe()) { + if (process.env.NODE_ENV === "development") { + throw new Error("Using partitioned state in a browser with unpartitioned state support."); + } else { + console.warn("Using partitioned state in a browser with unpartitioned state support."); + } + } + + return this.instance; + } + + // TODO: This is not the most elegant solution for the use case in `StorageMock` (`plasmo-storage.mock.ts`). Refactor this later: + + static getInstanceWithoutWaiting(): LocalStorage { + this.instance = this.instance ?? new LocalStorage(); + + this.instance.storage.requestStorageAccess().then(() => { + if (this.instance.storage === localStorage && getUnpartitionedStateStatus() === "supported" && isInsideIframe()) { + if (process.env.NODE_ENV === "development") { + throw new Error("Using partitioned state in a browser with unpartitioned state support."); + } else { + console.warn("Using partitioned state in a browser with unpartitioned state support."); + } + } + }); + + return this.instance; + } + + /** + * Returns `true` if the given key should be stored using cookies instead of localStorage. This only happens for the + * device nonce and the Supabsae auth token if and only if the unpartitioned state status is "limited", meaning, + * unpartitioned state is supported but only for cookies. + * + * In any other case, like not having unpartitioned state support at all or if unpartitioned state for localStorage + * is supported, we do not use cookies. + */ + private shouldStoreInCookies(key: string): boolean { + return false; + + // if (!["limited", "rejected"].includes(getUnpartitionedStateStatus())) return false; + + // This should work for "rejected" too, so that we use cookies for devices that support unpartitioned state but did't get access. However, that will fail + // if the third-party cookie access is not accepted. There seems to be an issue in Chromium-based browsers where the requested permission is inconsistent + // (sometimes it is embed content and sometimes it's third-party cookies). So, for now, this option will just be disabled and not having unpartitioned state + // access on browsers that support it will require users to re-authenticate on each tab. + + /* + if (!["limited"].includes(getUnpartitionedStateStatus())) return false; + + const shouldStoreInCookies = key === DEVICE_NONCE_KEY || SUPABASE_AUTH_TOKEN_KEY_REGEXP.test(key); + + if (window.location.hostname === "localhost" && shouldStoreInCookies) { + console.warn( + `${key} should be read/stored in cookies, but that won't work in localhost, so localStorage will be used instead. Please, retest in a preview environment. unpartitionedStateStatus = "${getUnpartitionedStateStatus()}."`, + ); + + return false; + } + + return shouldStoreInCookies; + */ + } + + /** + * Get access to the CookieStore API safely + */ + private getCookieStore(): CookieStore | undefined { + if (!this.cookieStoreAvailable) throw new Error(LocalStorage.NO_COOKIE_STORE_ERR); + + return window.cookieStore; + } + + /** + * Set a cookie using CookieStore API or js-cookie fallback + */ + private async setCookie(key: string, value: string): Promise { + try { + const cookieStore = this.getCookieStore(); + const cookieOptions = { + name: key, + value, + ...this.COOKIE_STORE_OPTIONS, + }; + + await cookieStore.set(cookieOptions); + const result = await this.getCookieValue(key); + + if (result !== value) { + throw new Error(`Failed to set cookie "${key}" = "${value}" ("${result}" read).`); + } + + return true; + } catch (err) { + if (err.message !== LocalStorage.NO_COOKIE_STORE_ERR) console.warn(`Error setting cookie "${key}":`, err); + + try { + // Fallback to js-cookie + JsCookie.set(key, value, this.JS_COOKIE_OPTIONS); + const result = JsCookie.get(key); + + if (result !== value) { + throw new Error(`Failed to set jsCookie "${key}" = "${value}" ("${result}" read).`); + } + + return true; + } catch (jsCookieError) { + console.warn(`Error setting cookie "${key}" with jsCookie:`, jsCookieError); + } + } + + return false; + } + + /** + * Get a cookie value using CookieStore API or js-cookie fallback + */ + private async getCookieValue(key: string): Promise { + try { + const cookieStore = this.getCookieStore(); + const cookie = await cookieStore.get(key); + + return cookie?.value || null; + } catch (err) { + if (err.message !== LocalStorage.NO_COOKIE_STORE_ERR) console.warn(`Error reading cookie "${key}":`, err); + + console.warn(`Getting jsCookie "${key}" =`, JsCookie.get(key)); + + try { + return JsCookie.get(key) || null; + } catch (jsCookieError) { + console.warn(`Error reading jsCookie "${key}":`, jsCookieError); + } + } + + return null; + } + + /** + * Get all cookies using CookieStore API or js-cookie fallback + */ + private async getAllCookies(): Promise> { + try { + const cookieStore = this.getCookieStore(); + const cookies = await cookieStore.getAll(); + + return cookies.reduce( + (acc, cookie) => { + acc[cookie.name] = cookie.value; + return acc; + }, + {} as Record, + ); + } catch (err) { + if (err.message !== LocalStorage.NO_COOKIE_STORE_ERR) console.warn(`Error reading all cookies:`, err); + + try { + return Cookies.get() || {}; + } catch (jsCookieError) { + console.warn(`Error reading all jsCookies:`, jsCookieError); + } + } + + return {}; + } + + private async getAllStorageCookieKeys(): Promise { + const allCookies = await this.getAllCookies(); + + return Object.keys(allCookies).filter( + (key) => this.shouldStoreInCookies(key) || key.includes(LocalStorage.CHUNK_SUFFIX), + ); + } + + /** + * Get a value from storage with appropriate method + */ + private getStorageValue(key: string, useRaw = false): T | null { + try { + return useRaw ? (this.storage.getRaw(key) as T) : (this.storage.getItem(key) as T); + } catch (err) { + console.warn(`Error reading "${key}" from storage:`, err); + } + + return null; + } + + /** + * Set a value to storage with appropriate method + */ + private setStorageValue(key: string, value: T, useRaw = false): boolean { + try { + if (useRaw) { + this.storage.setRaw(key, value as string); + } else { + this.storage.setItem(key, value); + } + + return true; + } catch (err) { + console.warn(`Error setting "${key}" in storage:`, err); + } + + return false; + } + + /** + * Set a cookie with chunking if needed + */ + private async setCookieWithChunkingIfNeeded(key: string, value: string): Promise { + if (!value) { + throw new Error(`Attempted to set empty value for key '${key}'`); + } + + // Check if value is too large for a single cookie + if (value.length > LocalStorage.MAX_COOKIE_CHUNK_SIZE) { + log(LOG_GROUP.STORAGE, `Value for key '${key}' is large (${value.length} chars), chunking...`); + + return this.setChunkedCookies(key, value); + } + + const success = await this.setCookie(key, value); + + if (success) { + log(LOG_GROUP.STORAGE, `Cookie set successfully for: ${key}`); + } + + return success; + } + + /** + * Set large values as multiple chunked cookies + */ + private async setChunkedCookies(key: string, value: string): Promise { + try { + // Clean up any existing chunks + await this.removeChunkedCookies(key); + + // Calculate chunks needed + const chunkCount = Math.ceil(value.length / LocalStorage.MAX_COOKIE_CHUNK_SIZE); + const metaKey = `${key}${LocalStorage.CHUNK_META_SUFFIX}`; + + // Set metadata first + const metaSuccess = await this.setCookie(metaKey, String(chunkCount)); + + if (!metaSuccess) { + throw new Error(`Failed to set metadata for chunked value ${key}`); + } + + // Store each chunk in parallel for better performance + const chunkPromises = []; + + for (let i = 0; i < chunkCount; i++) { + const chunkKey = `${key}${LocalStorage.CHUNK_SUFFIX}${i}`; + const start = i * LocalStorage.MAX_COOKIE_CHUNK_SIZE; + const end = Math.min(start + LocalStorage.MAX_COOKIE_CHUNK_SIZE, value.length); + const chunk = value.substring(start, end); + + chunkPromises.push(this.setCookie(chunkKey, chunk)); + } + + // Wait for all chunks to be set + const results = await Promise.all(chunkPromises); + const allChunksSuccess = results.every(Boolean); + + if (allChunksSuccess) { + log(LOG_GROUP.STORAGE, `Successfully set ${chunkCount} chunks for key ${key}`); + } + + return allChunksSuccess; + } catch (e) { + console.warn("Error in setChunkedCookies:", e); + + return false; + } + } + + /** + * Get a value potentially stored as multiple chunked cookies + */ + private async getChunkedCookies(key: string): Promise { + try { + const metaKey = `${key}${LocalStorage.CHUNK_META_SUFFIX}`; + const metaValue = await this.getCookieValue(metaKey); + + if (!metaValue) return null; + + const chunkCount = parseInt(metaValue, 10); + + if (isNaN(chunkCount) || chunkCount <= 0) { + throw new Error(`Invalid chunk count for key ${key}: ${metaValue}`); + } + + // Fetch all chunks in parallel for better performance + const chunkPromises = []; + + for (let i = 0; i < chunkCount; i++) { + const chunkKey = `${key}${LocalStorage.CHUNK_SUFFIX}${i}`; + chunkPromises.push(this.getCookieValue(chunkKey)); + } + + const chunks = await Promise.all(chunkPromises); + + // Check if any chunks are missing + if (chunks.some((chunk) => chunk === null)) { + throw new Error(`Incomplete chunked value for key ${key}, fallback to localStorage`); + } + + log(LOG_GROUP.STORAGE, `Retrieved ${chunkCount} chunks for key ${key}`); + + return chunks.join(""); + } catch (e) { + console.warn("Error in getChunkedCookies:", e); + + return null; + } + } + + /** + * Remove a cookie using CookieStore API or js-cookie fallback + */ + private async removeCookie(key: string): Promise { + try { + const cookieStore = this.getCookieStore(); + + await cookieStore.delete({ + name: key, + path: this.COOKIE_STORE_OPTIONS.path, + partitioned: this.COOKIE_STORE_OPTIONS.partitioned, + }); + } catch (err) { + if (err.message !== LocalStorage.NO_COOKIE_STORE_ERR) console.warn(`Error removing cookie "${key}":`, err); + + try { + JsCookie.remove(key, this.JS_COOKIE_OPTIONS); + } catch (jsCookieError) { + console.warn(`Error removing jsCookie "${key}":`, jsCookieError); + } + } + } + + /** + * Remove all chunks for a given key + */ + private async removeChunkedCookies(key: string): Promise { + try { + const metaKey = `${key}${LocalStorage.CHUNK_META_SUFFIX}`; + const metaValue = await this.getCookieValue(metaKey); + + if (metaValue) { + const chunkCount = parseInt(metaValue, 10); + if (!isNaN(chunkCount) && chunkCount > 0) { + // Remove all chunk cookies in parallel + const removePromises = []; + for (let i = 0; i < chunkCount; i++) { + removePromises.push(this.removeCookie(`${key}${LocalStorage.CHUNK_SUFFIX}${i}`)); + } + await Promise.all(removePromises); + } + + // Remove the metadata cookie + await this.removeCookie(metaKey); + } + } catch (e) { + console.warn("Error in removeChunkedCookies:", e); + } + } + + /** + * Set an item in storage, with appropriate backup strategies + */ + async setItem(key: string, value: T, useRaw = false): Promise { + if (!key) throw new Error("Missing key."); + + const shouldStoreInCookies = this.shouldStoreInCookies(key); + + log(LOG_GROUP.STORAGE, `Setting ${key} in ${shouldStoreInCookies ? "cookies" : "localStorage"}`); + + if (shouldStoreInCookies) { + await this.setCookieWithChunkingIfNeeded(key, value as string); + } else { + this.setStorageValue(key, value, useRaw); + } + } + + /** + * Get an item from storage, with appropriate fallback strategies + */ + async getItem(key: string, useRaw = false): Promise { + if (!key) throw new Error("Missing key."); + + const shouldStoreInCookies = this.shouldStoreInCookies(key); + + log(LOG_GROUP.STORAGE, `Getting ${key} from ${shouldStoreInCookies ? "cookies" : "localStorage"}`); + + if (shouldStoreInCookies) { + try { + let cookieValue = await this.getChunkedCookies(key); + + // If not found as chunks, try regular cookie + if (cookieValue === null) { + cookieValue = await this.getCookieValue(key); + } + + return cookieValue as unknown as T; + } catch (err) { + console.warn("Failed to get cookie:", err); + } + } else { + return this.getStorageValue(key, useRaw); + } + } + + /** + * Remove an item from all storage locations + */ + async removeItem(key: string): Promise { + if (!key) throw new Error("Missing key."); + + const shouldStoreInCookies = this.shouldStoreInCookies(key); + + log(LOG_GROUP.STORAGE, `Removing ${key} from localStorage${shouldStoreInCookies ? " and cookies" : ""}`); + + // Always remove from localStorage: + this.storage.removeItem(key); + + // If we're using cookies for this key, remove from there too: + if (shouldStoreInCookies) { + try { + const removeCookiePromises = [this.removeChunkedCookies(key), this.removeCookie(key)]; + + await Promise.all(removeCookiePromises); + } catch (error) { + console.warn("Failed to remove cookie:", error); + } + } + } + + /** + * Clear all storage + */ + async clear(): Promise { + // Clear localStorage + this.storage.clear(); + + try { + const storageCookieKeys = await this.getAllStorageCookieKeys(); + const removePromises = storageCookieKeys.map((key) => this.removeCookie(key)); + + await Promise.all(removePromises); + } catch (error) { + console.warn("Failed to clear cookies:", error); + } + } + + /** + * Get all keys from localStorage + */ + async keys(): Promise { + try { + const storageCookieKeys = await this.getAllStorageCookieKeys(); + + return [...new Set([...this.storage.keys(), ...storageCookieKeys])]; + } catch (err) { + console.warn("Failed to get keys from storage", err); + } + + return []; + } +} diff --git a/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartition-storage.test.ts b/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartition-storage.test.ts new file mode 100644 index 000000000..56b35920f --- /dev/null +++ b/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartition-storage.test.ts @@ -0,0 +1,593 @@ +import { describe, test, expect, beforeAll, beforeEach, vi, afterEach } from "vitest"; +import { EnhancedStorage } from "./unpartitioned-storage.js"; +import { StorageManager } from "../storage-manager/storage-manager.js"; +import { StorageMock } from "../plasmo-storage/plasmo-storage.mock.js"; + +// Set global test timeout +vi.setConfig({ testTimeout: 20000 }); + +// Mock the modules directly instead of using path aliases +vi.mock("~utils/embedded/iframe.utils", () => ({ + isInsideIframe: vi.fn().mockReturnValue(true), + ancestorOrigin: vi.fn().mockReturnValue("https://test.com"), + searchParams: new URLSearchParams({ + PARAM_CLIENT_ID: "test-client-id", + }), +})); + +vi.mock("~utils/log/log.utils", () => ({ + log: vi.fn(), + LOG_GROUP: { + STORAGE: "storage", + }, +})); + +// Setup global browser objects that might not be in JSDOM +beforeAll(() => { + // Ensure document.hasStorageAccess exists + if (!document.hasStorageAccess) { + document.hasStorageAccess = vi.fn().mockResolvedValue(false); + } + + // Ensure document.requestStorageAccess exists + if (!document.requestStorageAccess) { + document.requestStorageAccess = vi.fn().mockResolvedValue(undefined); + } + + // Ensure navigator.permissions exists + if (!navigator.permissions) { + Object.defineProperty(navigator, "permissions", { + value: { + query: vi.fn().mockResolvedValue({ state: "prompt" }), + }, + configurable: true, + }); + } else if (!navigator.permissions.query) { + navigator.permissions.query = vi.fn().mockResolvedValue({ state: "prompt" }); + } +}); + +describe("EnhancedStorage", () => { + beforeEach(() => { + localStorage.clear(); + sessionStorage.clear(); + }); + + afterEach(() => { + localStorage.clear(); + sessionStorage.clear(); + }); + + test("should initialize with localStorage by default", () => { + const storage = new EnhancedStorage(); + expect(storage["storageType"]).toBe("localStorage"); + }); + + test("should initialize with sessionStorage when specified", () => { + const storage = new EnhancedStorage({ area: "session" }); + expect(storage["storageType"]).toBe("sessionStorage"); + }); + + test("should store and retrieve simple items", () => { + const storage = new EnhancedStorage(); + const key = "testKey"; + const value = "testValue"; + + storage.setItem(key, value); + const retrieved = storage.getItem(key); + + expect(retrieved).toBe(value); + expect(JSON.parse(localStorage.getItem(key))).toBe(value); + }); + + test("should store and retrieve complex items with expiration", () => { + const storage = new EnhancedStorage(); + const key = "testKey"; + const value = { name: "test" }; + const options = { expiresIn: 3600000 }; // 1 hour + + storage.setItem(key, value, options); + + // Get the stored value directly from localStorage + const storedValue = JSON.parse(localStorage.getItem(key)); + expect(storedValue.value).toEqual(value); + expect(storedValue.expiresAt).toBeDefined(); + + const retrieved = storage.getItem(key); + expect(retrieved).toEqual(value); + }); + + test("should handle expired items", () => { + const storage = new EnhancedStorage(); + const key = "testKey"; + + // Store an expired item directly + localStorage.setItem( + key, + JSON.stringify({ + value: "expired value", + expiresAt: Date.now() - 1000, // Expired 1 second ago + }), + ); + + const retrieved = storage.getItem(key); + expect(retrieved).toBeNull(); + expect(localStorage.getItem(key)).toBeNull(); + }); + + test("should handle items with priority", () => { + const storage = new EnhancedStorage(); + const key = "priorityKey"; + const value = "important value"; + + storage.setPrioritizedItem(key, value, "HIGH"); + + const storedValue = JSON.parse(localStorage.getItem(key)); + expect(storedValue.priority).toBe(8); + expect(storedValue.value).toBe(value); + }); + + test("should get raw value without parsing", () => { + const storage = new EnhancedStorage(); + const key = "rawKey"; + const rawValue = '{"not": "valid json'; + + localStorage.setItem(key, rawValue); + const result = storage.getRaw(key); + expect(result).toBe(rawValue); + }); + + test("should set raw value without processing", () => { + const storage = new EnhancedStorage(); + const key = "rawKey"; + const rawValue = '{"not": "valid json'; + + storage.setRaw(key, rawValue); + expect(localStorage.getItem(key)).toBe(rawValue); + }); +}); + +describe("StorageManager", () => { + beforeEach(() => { + localStorage.clear(); + sessionStorage.clear(); + + StorageManager["lastCleanupTime"] = 0; + }); + + describe("Storage usage calculation", () => { + test("should calculate storage usage correctly", () => { + // Set up test data directly in sessionStorage + const testData = { + key1: "value1", + key2: '{"complex":"value"}', + }; + + Object.entries(testData).forEach(([key, value]) => { + sessionStorage.setItem(key, value); + }); + + const usage = StorageManager.calculateStorageUsage(sessionStorage); + + expect(usage.currentSize).toBe(StorageManager.calculateStorageUsageByKeys(sessionStorage, ["key1", "key2"])); + expect(usage.itemCount).toBe(2); + }); + }); + + describe("Item eviction", () => { + test("should clear expired items", () => { + // Set up test data with expired and non-expired items + sessionStorage.setItem( + "item1", + JSON.stringify({ + value: "test1", + expiresAt: Date.now() - 1000, // expired + }), + ); + sessionStorage.setItem( + "item2", + JSON.stringify({ + value: "test2", + expiresAt: Date.now() + 10000, // not expired + }), + ); + sessionStorage.setItem( + "item3", + JSON.stringify({ simple: "value" }), // no expiration + ); + + const clearedCount = StorageManager.clearExpiredItems(sessionStorage, true); + + expect(clearedCount).toBe(1); + expect(sessionStorage.getItem("item1")).toBeNull(); + expect(sessionStorage.getItem("item2")).not.toBeNull(); + expect(sessionStorage.getItem("item3")).not.toBeNull(); + }); + + test("should assign CRITICAL priority to simple items by default", () => { + // Store a simple item + sessionStorage.setItem("simpleItem", JSON.stringify("simple value")); + + // Store a non-JSON item + sessionStorage.setItem("nonJsonItem", "invalid-json"); + + // Get sorted items + const items = StorageManager.getSortedStorageItems(sessionStorage); + + // Find our test items + const simpleItem = items.find((item) => item.key === "simpleItem"); + const nonJsonItem = items.find((item) => item.key === "nonJsonItem"); + + // Verify priorities + expect(simpleItem.priority).toBe(StorageManager.PRIORITY_LEVELS.CRITICAL); + expect(nonJsonItem.priority).toBe(StorageManager.PRIORITY_LEVELS.CRITICAL); + }); + }); +}); + +describe("StorageMock", () => { + let storageMock: StorageMock; + + beforeEach(() => { + sessionStorage.clear(); + storageMock = new StorageMock(); + }); + + afterEach(() => { + sessionStorage.clear(); + }); + + test("should initialize with sessionStorage", () => { + expect(storageMock["storage"]["storageType"]).toBe("sessionStorage"); + }); + + test("should set and get items properly", async () => { + await storageMock.setItem("testKey", "testValue"); + + // Verify the raw storage state + const rawStored = sessionStorage.getItem("testKey"); + expect(rawStored).toBe(JSON.stringify("testValue")); + + // Verify through the API + const result = await storageMock.getItem("testKey"); + expect(result).toBe("testValue"); + }); + + test("should handle complex objects", async () => { + const complexObject = { test: "value", nested: { prop: 123 } }; + await storageMock.setItem("complex", complexObject); + + // Verify the raw storage state + const rawStored = JSON.parse(sessionStorage.getItem("complex")); + expect(rawStored).toEqual(complexObject); + + // Verify through the API + const result = await storageMock.getItem("complex"); + expect(result).toEqual(complexObject); + }); + + test("should remove items properly", async () => { + // First verify we can set an item + await storageMock.setItem("toRemove", "value"); + const initialValue = sessionStorage.getItem("toRemove"); + expect(initialValue).not.toBeNull(); + expect(JSON.parse(initialValue)).toBe("value"); + + // Then remove it + await storageMock.removeItem("toRemove"); + + // Verify it's gone from both the API and storage + const result = await storageMock.getItem("toRemove"); + expect(result).toBeUndefined(); + expect(sessionStorage.getItem("toRemove")).toBeNull(); + }); + + test("should handle batch operations", async () => { + const items = { + key1: "value1", + key2: "value2", + key3: { complex: true }, + }; + + await storageMock.setItems(items); + + // Verify all items were stored correctly + Object.entries(items).forEach(([key, value]) => { + const stored = JSON.parse(sessionStorage.getItem(key)); + expect(stored).toEqual(value); + }); + + const results = await storageMock.getItems(Object.keys(items)); + expect(results).toEqual(items); + + await storageMock.removeItems(["key1", "key2"]); + + // Verify removal + expect(sessionStorage.getItem("key1")).toBeNull(); + expect(sessionStorage.getItem("key2")).toBeNull(); + expect(sessionStorage.getItem("key3")).not.toBeNull(); + }); + + test("should prioritize items correctly", async () => { + await storageMock.setCritical("critical", "criticalValue"); + await storageMock.setPreference("preference", "prefValue"); + await storageMock.setCache("cache", "cacheValue"); + await storageMock.setTemporary("temp", "tempValue"); + + // Verify items are stored with correct priorities + const criticalItem = JSON.parse(sessionStorage.getItem("critical")); + const preferenceItem = JSON.parse(sessionStorage.getItem("preference")); + const cacheItem = JSON.parse(sessionStorage.getItem("cache")); + const tempItem = JSON.parse(sessionStorage.getItem("temp")); + + expect(criticalItem.priority).toBe(10); + expect(preferenceItem.priority).toBe(8); + expect(cacheItem.priority).toBe(3); + expect(tempItem.priority).toBe(1); + + const info = storageMock.getUsageInfo(); + expect(info.items).toBe(4); + }); +}); + +describe("Storage Eviction", () => { + let storage: EnhancedStorage; + + beforeEach(() => { + sessionStorage.clear(); + localStorage.clear(); + storage = new EnhancedStorage(); + }); + + afterEach(() => { + sessionStorage.clear(); + localStorage.clear(); + }); + + test("should respect browser storage limits", async () => { + const smallData = "x".repeat(1024); // 1KB + let storageError = false; + + try { + // Try to store data until we hit the browser limit + for (let i = 0; i < 10000 && !storageError; i++) { + try { + storage.setItem(`test${i}`, smallData); + } catch (e) { + if (e.name === "QuotaExceededError") { + storageError = true; + } + } + } + } catch (e) { + // Catch any quota exceeded errors + storageError = true; + } + + const usage = storage.getUsageInfo(); + expect(usage.bytes).toBeLessThanOrEqual(StorageManager.MAX_STORAGE_SIZE); + }); + + test("should handle eviction with realistic data sizes", async () => { + // Use smaller chunks for testing + const chunk = "x".repeat(1024); // 1KB chunks + const items = [ + { key: "critical", size: 2, priority: "CRITICAL" }, // 2KB + { key: "high", size: 3, priority: "HIGH" }, // 3KB + { key: "medium", size: 5, priority: "MEDIUM" }, // 5KB + { key: "low", size: 10, priority: "LOW" }, // 10KB + { key: "temp", size: 20, priority: "TEMPORARY" }, // 20KB + ]; + + // Store items + for (const item of items) { + storage.setPrioritizedItem( + item.key, + chunk.repeat(item.size), + item.priority as keyof typeof StorageManager.PRIORITY_LEVELS, + ); + } + + // Request space for new data + storage.ensureSpace(15 * 1024); // Try to ensure 15KB space + + // Verify eviction behavior + const remaining = items.filter((item) => storage.getItem(item.key) !== null); + + // Higher priority items should remain + expect(storage.getItem("critical")).not.toBeNull(); + expect(storage.getItem("high")).not.toBeNull(); + + // Lower priority items might be evicted + const usage = storage.getUsageInfo(); + expect(usage.bytes).toBeLessThanOrEqual(StorageManager.MAX_STORAGE_SIZE); + }); + + test("should handle quota exceeded errors gracefully", async () => { + const largeData = "x".repeat(10 * 1024); // 10KB chunks + let errorThrown = false; + + try { + // Try to store more than our test limit + for (let i = 0; i < 15; i++) { + storage.ensureSpace(10 * 1024); + storage.setItem(`large${i}`, largeData); + } + } catch (e) { + errorThrown = e.name === "QuotaExceededError"; + } + + const usage = storage.getUsageInfo(); + expect(usage.bytes).toBeLessThanOrEqual(StorageManager.MAX_STORAGE_SIZE); + expect(usage.percentage).toBeLessThanOrEqual(100); + }); + + test("should evict items to maintain buffer space", async () => { + // Fill storage with enough data to trigger eviction + const chunk = "x".repeat(1024); // 1KB + + // Add some initial data + for (let i = 0; i < 10; i++) { + storage.setPrioritizedItem(`data${i}`, chunk.repeat(100), "LOW"); // 100KB each + } + + // Force eviction by requesting space + storage.ensureSpace(2048, { forceEviction: true }); + + // Add new item + storage.setPrioritizedItem("newItem", chunk.repeat(2), "MEDIUM"); + + const usage = storage.getUsageInfo(); + expect(usage.available).toBeGreaterThan(0); + }); + + test("should evict expired items first", async () => { + const now = Date.now(); + + // Set items with explicit expiration timestamps + storage.setItem("expired1", "value1", { + expiresIn: -1000, // Expired 1 second ago + }); + storage.setItem("expired2", "value2", { + expiresIn: -2000, // Expired 2 seconds ago + }); + storage.setItem("valid", "value3", { + expiresIn: 10000, // Valid for 10 more seconds + }); + + // Force eviction + storage.evictIfNeeded(1000, true); + + // Check through storage API instead of direct localStorage access + expect(storage.getItem("expired1")).toBeNull(); + expect(storage.getItem("expired2")).toBeNull(); + expect(storage.getItem("valid")).not.toBeNull(); + }); + + test("should evict items based on priority", async () => { + // Fill storage with medium priority data (1MB chunks) + const chunk = "x".repeat(1024 * 1024); // 1MB + + // Add 3MB of medium priority data + for (let i = 0; i < 3; i++) { + storage.setPrioritizedItem(`medium${i}`, chunk, "MEDIUM"); + } + + // Add test items with different priorities + storage.setPrioritizedItem("critical", "criticalValue", "CRITICAL"); + storage.setPrioritizedItem("high", "highValue", "HIGH"); + storage.setPrioritizedItem("low", "lowValue", "LOW"); + storage.setPrioritizedItem("temporary", "tempValue", "TEMPORARY"); + + // Force eviction by requesting 2MB + storage.evictIfNeeded(2 * 1024 * 1024, true); + + // Verify priorities were respected + expect(storage.getItem("temporary")).toBeNull(); + expect(storage.getItem("low")).toBeNull(); + expect(storage.getItem("critical")).toBe("criticalValue"); + expect(storage.getItem("high")).toBe("highValue"); + }); + + test("should handle ensureSpace with different strategies", async () => { + // Fill storage with items + storage.setPrioritizedItem("temp1", "x".repeat(1000), "TEMPORARY"); + storage.setPrioritizedItem("temp2", "x".repeat(1000), "TEMPORARY"); + storage.setPrioritizedItem("critical", "important", "CRITICAL"); + + // Try to ensure space with default strategy + const result1 = storage.ensureSpace(500); + expect(result1).toBe(true); + + // Try with force eviction + const result2 = storage.ensureSpace(500, { forceEviction: true }); + expect(result2).toBe(true); + + // Critical data should still be present + expect(JSON.parse(localStorage.getItem("critical"))).toBeDefined(); + }); + + test("should respect storage limits", async () => { + const mediumData = "x".repeat(10 * 1024); // 10KB chunks + let stored = 0; + + // Try to store data until we hit a limit + try { + for (let i = 0; stored < StorageManager.MAX_STORAGE_SIZE; i++) { + storage.ensureSpace(10 * 1024); + storage.setPrioritizedItem(`data${i}`, mediumData, "LOW"); + stored += 10 * 1024; + } + } catch (e) { + // Expected to eventually throw + } + + const usage = storage.getUsageInfo(); + expect(usage.bytes).toBeLessThanOrEqual(StorageManager.MAX_STORAGE_SIZE); + }); + + test("should maintain priority order during eviction", async () => { + // Set up items with various priorities + const items = [ + { key: "critical1", priority: "CRITICAL", value: "value1" }, + { key: "high1", priority: "HIGH", value: "value2" }, + { key: "medium1", priority: "MEDIUM", value: "value3" }, + { key: "low1", priority: "LOW", value: "value4" }, + { key: "temp1", priority: "TEMPORARY", value: "value5" }, + ]; + + // Store all items + items.forEach((item) => { + storage.setPrioritizedItem(item.key, item.value, item.priority as any); + }); + + // Force aggressive eviction + storage.evictIfNeeded(2000, true); + + // Verify eviction order + const remaining = items.filter((item) => localStorage.getItem(item.key) !== null); + + // Check that remaining items are in priority order + remaining.forEach((item, index) => { + if (index > 0) { + const prevPriority = StorageManager.PRIORITY[items[index - 1].priority]; + const currentPriority = StorageManager.PRIORITY[item.priority]; + expect(currentPriority).toBeLessThanOrEqual(prevPriority); + } + }); + }); + + test("should handle concurrent eviction requests", async () => { + // Set up initial data + storage.setPrioritizedItem("item1", "x".repeat(1000), "LOW"); + storage.setPrioritizedItem("item2", "x".repeat(1000), "LOW"); + + // Trigger multiple eviction requests concurrently + const results = await Promise.all([ + storage.evictIfNeeded(500), + storage.evictIfNeeded(500), + storage.evictIfNeeded(500), + ]); + + // All requests should complete successfully + expect(results.every((result) => result === true)).toBe(true); + }); + + test("should calculate correct storage usage after eviction", async () => { + // Store enough data to trigger eviction + const testData = "x".repeat(10000); + for (let i = 0; i < 450; i++) { + storage.setItem(`test${i}`, testData, { priority: 1 }); + } + + const beforeUsage = storage.getUsageInfo(); + + // Force aggressive eviction 1MB + storage.evictIfNeeded(1024 * 1024, true); + + const afterUsage = storage.getUsageInfo(); + + expect(afterUsage.bytes).toBeLessThan(beforeUsage.bytes); + expect(afterUsage.items).toBeLessThan(beforeUsage.items); + }); +}); diff --git a/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartitioned-storage.ts b/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartitioned-storage.ts new file mode 100644 index 000000000..37219b182 --- /dev/null +++ b/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartitioned-storage.ts @@ -0,0 +1,564 @@ +import { log, LOG_GROUP, isError } from "@wanderapp/core"; +import { + getUnpartitionedStateStatus, + HAS_SIMPLE_STORAGE_API, + setUnpartitionedStateStatus, + type UnpartitionedStateStatus, +} from "./unpartitioned-storage.utils"; +import { + isComplexStorageItem, + type ItemStorageOptions, + type StorageItem, +} from "../storage-manager/storage-manager.utils"; +import { StorageManager } from "../storage-manager/storage-manager"; +import { isInsideIframe } from "../../iframe.utils"; + +type StorageType = "localStorage" | "sessionStorage"; + +interface EnhancedStorageOptions { + area?: "local" | "session"; +} + +export const COULD_NOT_ACCESS_UNPARTITIONED_STATE_ERR_MESSAGE = "Could not get access to unpartitioned state"; + +const timesInstantiated: Record = { + localStorage: 0, + sessionStorage: 0, +}; + +export class EnhancedStorage implements Storage { + public storage: Storage; + + public storageType: StorageType; + + public status: UnpartitionedStateStatus | null = null; + + public error: Error | null = null; + + private requestStorageAccessPromise: Promise | null = null; + + private requestStorageAccessResolve: (status: UnpartitionedStateStatus) => void = () => {}; + + private permissionStatusPromise: Promise | null = null; + + private isWaitingForUserAction = false; + + constructor({ area = "local" }: EnhancedStorageOptions = {}) { + this.storageType = area === "local" ? "localStorage" : "sessionStorage"; + this.storage = globalThis[this.storageType]; + + const timesStorageTypeInstantiated = ++timesInstantiated[this.storageType]; + + if (timesStorageTypeInstantiated > 1) { + if (process.env.NODE_ENV === "development") { + throw new Error( + `${this.storageType} instantiated ${timesStorageTypeInstantiated} times. Was this intentional?`, + ); + } else { + console.warn(`${this.storageType} instantiated ${timesStorageTypeInstantiated} times. Was this intentional?`); + } + } + + if (area === "session") { + // We want to start fresh each time the app loads: + this.storage.clear(); + } + } + + protected async requestStorageAccessAndInitializeStorage(): Promise { + if (!isInsideIframe()) { + console.warn( + "UnpartitionedStorage.requestStorageAccessAndInitializeStorage() should only be called from within an iframe. Regular, partitioned state will be used.", + ); + + return; + } + + try { + log(LOG_GROUP.STORAGE, `Requesting ${this.storageType} access with typed API`); + + // @ts-expect-error - Newer API with types may not be recognized by TypeScript + const handle = await document.requestStorageAccess({ + [this.storageType]: true, + cookies: true, + }); + + // @ts-expect-error - Newer API may not be recognized by TypeScript + if (handle && handle[this.storageType]) { + this.storage = handle[this.storageType]; + this.dispatchUnpartitionedStateStatusChange("supported"); + } else { + this.dispatchUnpartitionedStateStatusChange("limited"); + } + } catch (error) { + console.warn("document.requestStorageAccess() failed:", error); + + this.dispatchUnpartitionedStateStatusChange(error || "error"); + } + + return this.status; + } + + async requestStorageAccess(): Promise { + if (!isInsideIframe()) { + console.warn( + "UnpartitionedStorage.requestStorageAccess() should only be called from within an iframe. Regular, partitioned state will be used.", + ); + + return; + } + + // Unpartitioned state access already accepted, limited or unsupported: + if (["supported", "limited", "unsupported"].includes(this.status)) { + return this.status; + } + + // If the code below runs, this.status can only be null, "error" or "rejected": + + if (this.requestStorageAccessPromise && this.status === null) return this.requestStorageAccessPromise; + + return (this.requestStorageAccessPromise = new Promise(async (resolve) => { + // With this, calling dispatchUnpartitionedStateStatusChange() will automatically call resolve() too: + this.requestStorageAccessResolve = resolve; + + // Storage Access API not supported: + if (!HAS_SIMPLE_STORAGE_API) return this.dispatchUnpartitionedStateStatusChange("unsupported"); + + try { + // Get the current permissionState and start listening for permission changes: + const permissionState = await this.setupPermissionChangeHandlerAndGetCurrentValue(); + + await this.handleStorageAccessPermission(permissionState); + } catch (error) { + this.dispatchUnpartitionedStateStatusChange(error); + } + })); + } + + private async handleStorageAccessPermission(permissionState: PermissionState) { + // Note `dispatchUnpartitionedStateStatusChange()` is the function that calls `requestStorageAccessResolve()`, so we + // must be sure it's always invoked or the Promise created by `requestStorageAccess()` will never be invoked. + + if (permissionState === "granted") { + // Already granted, can request directly. `requestStorageAccessAndInitializeStorage()` will call + // `dispatchUnpartitionedStateStatusChange()`. + + await this.requestStorageAccessAndInitializeStorage(); + } else if (permissionState === "prompt") { + // Not granted, so we need to wait for user interaction to request. We dispatch a "rejected" event while we wait + // for permissions/access. + + let unpartitionedStateStatus = getUnpartitionedStateStatus(); + + if (unpartitionedStateStatus !== "limited" && unpartitionedStateStatus !== "unsupported") { + unpartitionedStateStatus = "rejected"; + } + + this.dispatchUnpartitionedStateStatusChange(unpartitionedStateStatus); + this.setupUserInteractionHandler(); + } else if (permissionState === "denied") { + // User has denied access, so nothing to do, just dispatch the "rejected" event. + this.dispatchUnpartitionedStateStatusChange("rejected"); + } + } + + /** + * Returns the current PermissionState and starts listening for permission changes, making sure only one listener is added. + */ + private async setupPermissionChangeHandlerAndGetCurrentValue(): Promise { + let permissionStatus = this.permissionStatusPromise ? await this.permissionStatusPromise : null; + let permissionState: PermissionState = permissionStatus?.state || "prompt"; + + if (navigator.permissions && !permissionStatus) { + try { + this.permissionStatusPromise = navigator.permissions.query({ + name: "storage-access" as PermissionName, + }); + + permissionStatus = await this.permissionStatusPromise; + permissionState = permissionStatus.state; + + permissionStatus.addEventListener("change", async () => { + let nextPermissionState = permissionStatus.state; + + if (nextPermissionState !== "granted" && (await document.hasStorageAccess())) { + console.warn( + `Storage access permission changed to ${nextPermissionState}, but document.hasStorageAccess() returned true`, + ); + + nextPermissionState = "granted"; + } else { + log(LOG_GROUP.STORAGE, `Storage access permission changed to ${nextPermissionState}`); + } + + this.handleStorageAccessPermission(nextPermissionState); + }); + } catch (error) { + log(LOG_GROUP.STORAGE, "Error checking permission:", error); + } + } + + if (permissionState !== "granted" && (await document.hasStorageAccess())) { + console.warn(`document.hasStorageAccess() returned true while permissionState was ${permissionState}`); + + permissionState = "granted"; + } + + return permissionState; + } + + /** + * Set up a handler to request storage access on user interaction + * This is required by the Storage Access API for security + */ + private setupUserInteractionHandler(): void { + if (this.isWaitingForUserAction) return; + + this.isWaitingForUserAction = true; + + log(LOG_GROUP.STORAGE, "Waiting for user interaction to request storage access"); + + function cleanupListeners() { + document.removeEventListener("click", handleUserInteraction); + document.removeEventListener("pointerdown", handleUserInteraction); + } + + async function handleUserInteraction() { + try { + const status = await this.requestStorageAccess(); + + log( + LOG_GROUP.STORAGE, + status === "supported" || status === "limited" + ? `Storage access granted after user interaction = ${status}` + : `Storage access denied after user interaction = ${status}`, + ); + } catch (error) { + log(LOG_GROUP.STORAGE, "Storage access error after user interaction:", error); + } + + cleanupListeners(); + } + + // Add event listeners with once:true to auto-remove after firing + document.addEventListener("click", handleUserInteraction, { once: true }); + document.addEventListener("pointerdown", handleUserInteraction, { + once: true, + }); + + // Also clean up after a timeout if user never interacts + setTimeout(cleanupListeners, 300000); // 5 minutes + } + + /** + * Handle the case when unpartitioned storage access is denied or unavailable + + */ + protected dispatchUnpartitionedStateStatusChange( + unpartitionedStateStatusOrError: UnpartitionedStateStatus | Error, + ): UnpartitionedStateStatus { + const unpartitionedStateStatus = isError(unpartitionedStateStatusOrError) + ? "error" + : unpartitionedStateStatusOrError; + + if (unpartitionedStateStatus === "error") { + this.error = isError(unpartitionedStateStatusOrError) + ? unpartitionedStateStatusOrError + : new Error("Unexpected unpartitioned state error"); + } else { + this.error = null; + } + + log(LOG_GROUP.STORAGE, `Unpartitioned state access for ${this.storageType} = ${unpartitionedStateStatus}`); + + setUnpartitionedStateStatus(unpartitionedStateStatus, this.error); + + this.requestStorageAccessResolve(unpartitionedStateStatus); + + return (this.status = unpartitionedStateStatus); + } + + /** + * Get raw value directly from storage without parsing or expiration checks + * + * @param key The key to retrieve + * @returns The raw value as stored + */ + getRaw(key: string): string | null { + return this.storage.getItem(key); + } + + /** + * Get an item from storage with proper typing and expiration handling + * @param key The key to retrieve + * @returns The value or null if not found or expired + */ + getItem(key: string, defaultValue?: T): T | null { + const rawValue = this.getRaw(key); + defaultValue = defaultValue === undefined && arguments.length < 2 ? null : defaultValue; + if (!rawValue) return defaultValue; + + try { + const parsed = JSON.parse(rawValue) as StorageItem; + + // Check if it's a complex storage item + if (isComplexStorageItem(parsed)) { + // Check for expiration + if (parsed.expiresAt && Date.now() > parsed.expiresAt) { + // Item has expired, remove it + this.removeItem(key); + return defaultValue; + } + return parsed.value; + } + + // If it's not a complex item, return as is + return parsed as T; + } catch (e) { + // If parsing fails, return the raw value (for backwards compatibility) + return rawValue as unknown as T; + } + } + + /** + * Set an item in storage with optional expiration and priority + * @param key The key to store + * @param value The value to store + * @param options Optional expiration and priority settings + */ + setItem(key: string, value: T, options?: ItemStorageOptions): void { + // If no options or no expiration/priority options, store the value directly + if (!options || (!options.expiresIn && options.priority === undefined)) { + this.setRaw(key, JSON.stringify(value)); + return; + } + + // Create a complex StorageItem with the provided properties + const item: { value: T; expiresAt?: number; priority?: number } = { value }; + + if (options.expiresIn !== undefined) { + item.expiresAt = Date.now() + options.expiresIn; + } + + if (options.priority !== undefined) { + item.priority = options.priority; + } + + this.setRaw(key, JSON.stringify(item)); + } + + /** + * Direct storage access for auth tokens and other data that + * should bypass enhanced features. + * + * @param key The key to store + * @param value The value to store exactly as provided + */ + setRaw(key: string, value: string): void { + // Store directly in the underlying storage without any processing + this.storage.setItem(key, value); + } + + /** + * Remove an item from storage + * @param key The key to remove + */ + removeItem(key: string): void { + this.storage.removeItem(key); + } + + /** + * Clear all items from storage + */ + clear(): void { + this.storage.clear(); + } + + /** + * Get the key at the specified index + * @param index The index of the key + * @returns The key at the specified index or null if not found + */ + key(index: number): string | null { + return this.storage.key(index); + } + + /** + * Get the number of items in storage + */ + get length(): number { + return this.storage.length; + } + + /** + * Get all keys in storage + * @returns An array of all keys in storage + */ + keys(): string[] { + return Object.keys(this.storage); + } + + /** + * Get multiple items from storage + * @param keys The keys to retrieve + * @returns An object with the keys and their values + */ + getItems(keys: string[], defaultValue?: T): Record { + const result: Record = {}; + defaultValue = defaultValue === undefined && arguments.length < 2 ? null : defaultValue; + + for (const key of keys) { + result[key] = this.getItem(key, defaultValue); + } + + return result; + } + + /** + * Set multiple items in storage + * @param items An object with keys and values to store + * @param options Optional expiration and priority settings + */ + setItems(items: Record, options?: ItemStorageOptions): void { + for (const [key, value] of Object.entries(items)) { + this.setItem(key, value, options); + } + } + + /** + * Remove multiple items from storage + * @param keys The keys to remove + */ + removeItems(keys: string[]): void { + for (const key of keys) { + this.removeItem(key); + } + } + + /** + * Set an item with predefined priority levels + * @param key The key to store + * @param value The value to store + * @param priority One of the predefined priority levels + * @param expiresIn Optional time in milliseconds until the item expires + */ + setPrioritizedItem( + key: string, + value: T, + priority: keyof typeof StorageManager.PRIORITY_LEVELS, + expiresIn?: number, + ): void { + this.setItem(key, value, { + priority: StorageManager.PRIORITY[priority], + expiresIn, + }); + } + + /** + * Store cache data with automatic expiration and lower priority + * @param key The key to store + * @param value The value to store + * @param maxAge Maximum age in milliseconds + */ + setCache(key: string, value: T, maxAge = 3600000): void { + this.setPrioritizedItem(key, value, "LOW", maxAge); + } + + /** + * Store user preferences with high priority and no expiration + * @param key The key to store + * @param value The value to store + */ + setPreference(key: string, value: T): void { + this.setPrioritizedItem(key, value, "HIGH"); + } + + /** + * Store temporary data that can be easily evicted + * @param key The key to store + * @param value The value to store + * @param maxAge Maximum age in milliseconds (default 5 minutes) + */ + setTemporary(key: string, value: T, maxAge = 300000): void { + this.setPrioritizedItem(key, value, "TEMPORARY", maxAge); + } + + /** + * Store critical data that should not be evicted + * @param key The key to store + * @param value The value to store + */ + setCritical(key: string, value: T): void { + this.setPrioritizedItem(key, value, "CRITICAL"); + } + + /** + * Checks if storage has enough space available, without aggressive eviction + * @param bytesNeeded Bytes needed for new items + * @returns True if sufficient space is available + */ + hasAvailableSpace(bytesNeeded: number): boolean { + // Clear expired items as this is always safe to do + StorageManager.clearExpiredItems(this.storage); + + // Check current usage after removing expired items + const usage = StorageManager.calculateStorageUsage(this.storage); + const available = StorageManager.getStorageLimit() - usage.currentSize; + + return bytesNeeded <= available; + } + + /** + * Ensures space is available for new data, using progressive strategies + * @param bytesNeeded Bytes needed for new items + * @param options Configuration options + * @returns True if space was ensured, false if impossible + */ + ensureSpace(bytesNeeded: number, options: { skipCheck?: boolean; forceEviction?: boolean } = {}): boolean { + // Skip availability check if requested + if (!options.skipCheck) { + const hasSpace = this.hasAvailableSpace(bytesNeeded); + if (hasSpace) return true; + } + + // Proceed to eviction if needed or forced + return this.evictIfNeeded(bytesNeeded, options.forceEviction); + } + + /** + * Evicts items from storage based on priority until enough space is available + * @param bytesNeeded Bytes needed to free up + * @param aggressive If true, uses more aggressive eviction strategy + * @returns True if eviction was successful, false if impossible + */ + evictIfNeeded(bytesNeeded: number, aggressive = false): boolean { + // Calculate target eviction size with different buffer strategies + const limit = StorageManager.getStorageLimit(); + const bufferSize = aggressive ? 1024 : Math.max(limit * 0.1, 5120); // 5KB or 10% + + return StorageManager.evictItems(this.storage, bytesNeeded, { + bufferSize, + aggressive, + }); + } + + /** + * Get current storage usage information + */ + getUsageInfo(): { + bytes: number; + percentage: number; + items: number; + available: number; + } { + const usage = StorageManager.calculateStorageUsage(this.storage); + const limit = StorageManager.getStorageLimit(); + + return { + bytes: usage.currentSize, + percentage: (usage.currentSize / limit) * 100, + items: usage.itemCount, + available: limit - usage.currentSize, + }; + } +} diff --git a/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartitioned-storage.utils.ts b/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartitioned-storage.utils.ts new file mode 100644 index 000000000..e11af055f --- /dev/null +++ b/apps/wander-connect/src/utils/storage/unpartitioned-storage/unpartitioned-storage.utils.ts @@ -0,0 +1,49 @@ +export const HAS_SIMPLE_STORAGE_API = + import.meta.env?.VITE_IS_EMBEDDED_APP === "1" && + typeof document !== "undefined" && + typeof document.hasStorageAccess === "function" && + typeof document.requestStorageAccess === "function"; + +export const HAS_ADVANCED_STORAGE_API = + HAS_SIMPLE_STORAGE_API && typeof (document as any).hasUnpartitionedCookieAccess === "function"; + +export type UnpartitionedStateStatus = "supported" | "unsupported" | "limited" | "rejected" | "error"; + +let _unpartitionedStateStatus: UnpartitionedStateStatus = HAS_SIMPLE_STORAGE_API + ? HAS_ADVANCED_STORAGE_API + ? "supported" + : "limited" + : "unsupported"; + +export interface UnpartitionedStateStatusChangeData { + unpartitionedStateStatus: UnpartitionedStateStatus; + prevUnpartitionedStateStatus: UnpartitionedStateStatus; + error?: Error; +} + +type UnpartitionedStateStatusChangeCallback = (data: UnpartitionedStateStatusChangeData) => void; + +const unpartitionedStateStatusChangeCallbacks = new Set(); + +export function addUnpartitionedStateStatusChangeListener(fn: UnpartitionedStateStatusChangeCallback) { + fn({ unpartitionedStateStatus: _unpartitionedStateStatus, prevUnpartitionedStateStatus: _unpartitionedStateStatus }); + unpartitionedStateStatusChangeCallbacks.add(fn); +} + +export function removeUnpartitionedStateStatusChangeListener(fn: UnpartitionedStateStatusChangeCallback) { + unpartitionedStateStatusChangeCallbacks.delete(fn); +} + +export function getUnpartitionedStateStatus() { + return _unpartitionedStateStatus; +} + +export function setUnpartitionedStateStatus(unpartitionedStateStatus: UnpartitionedStateStatus, error?: Error) { + const prevUnpartitionedStateStatus = _unpartitionedStateStatus; + + _unpartitionedStateStatus = unpartitionedStateStatus; + + unpartitionedStateStatusChangeCallbacks.forEach((cb) => { + cb({ unpartitionedStateStatus, prevUnpartitionedStateStatus, error }); + }); +} diff --git a/src/utils/embedded/utils/messages/embedded-messages.types.ts b/apps/wander-connect/src/utils/utils/messages/embedded-messages.types.ts similarity index 95% rename from src/utils/embedded/utils/messages/embedded-messages.types.ts rename to apps/wander-connect/src/utils/utils/messages/embedded-messages.types.ts index b973889b5..238a1e60a 100644 --- a/src/utils/embedded/utils/messages/embedded-messages.types.ts +++ b/apps/wander-connect/src/utils/utils/messages/embedded-messages.types.ts @@ -1,5 +1,5 @@ import type { AuthProviderType } from "embed-api"; -import type { EmbeddedLayout, RouteType } from "~utils/embedded/utils/routes/embedded-routes.utils"; +import type { EmbeddedLayout, RouteType } from "../routes/embedded-routes.utils"; export type EmbeddedMessageId = | "embedded_auth" diff --git a/apps/wander-connect/src/utils/utils/messages/embedded-messages.utils.ts b/apps/wander-connect/src/utils/utils/messages/embedded-messages.utils.ts new file mode 100644 index 000000000..ef604b6ab --- /dev/null +++ b/apps/wander-connect/src/utils/utils/messages/embedded-messages.utils.ts @@ -0,0 +1,126 @@ +import type { AuthProviderType, SupabaseUser } from "embed-api"; +import { nanoid } from "nanoid"; +import { getEmbeddedAncestorOrigin, isInsideIframe } from "../../iframe.utils"; +import { AUTH_PROVIDER_TYPE_BY_PROVIDER_STR } from "../../embedded.constants"; +import type { + EmbeddedAuthMessageData, + EmbeddedMessage, + EmbeddedMessageId, + EmbeddedMessageMap, + EmbeddedUserDetails, +} from "./embedded-messages.types"; + +const EMBEDDED_MESSAGE_IDS = [ + "embedded_auth", + "embedded_backup", + "embedded_open", + "embedded_close", + "embedded_resize", + "embedded_balance", + "embedded_request", +] as const satisfies EmbeddedMessageId[]; + +const messageKeyFnByType: { + [K in EmbeddedMessageId]: (data: EmbeddedMessageMap[K]) => string | null; +} = { + embedded_auth: (data) => { + if (!data.authStatus) return null; + + return [ + data.authType, + data.authStatus, + Object.entries(data.userDetails || {}) + .sort((a, b) => a[0].localeCompare(b[0])) + .map((v) => v[1]), + ].join("|"); + }, + embedded_backup: (data) => { + return [data.backupsNeeded, data.backupMessage].join("|"); + }, + embedded_open: () => null, + embedded_close: () => null, + embedded_resize: (data) => { + return [data.routeType, data.preferredLayoutType, data.width, data.height].join("|"); + }, + embedded_balance: (data) => { + return [data.amount, data.currency, data.formattedBalance].join("|"); + }, + embedded_request: (data) => { + return [data.pendingRequests, data.hasNewConnectRequest].join("|"); + }, +}; + +let lastMessageKeyByType: Partial> = {}; + +export interface PostEmbeddedMessageData { + type: K; + data: EmbeddedMessageMap[K]; +} + +export function postEmbeddedMessage({ type, data }: PostEmbeddedMessageData) { + if (!EMBEDDED_MESSAGE_IDS.includes(type)) + throw new Error(`Only the following message types are allowed: ${EMBEDDED_MESSAGE_IDS.join(", ")}.`); + + if (window.parent === null) { + throw new Error("Unexpected `null` parent Window."); + } + + if (type === "embedded_auth" && (data as EmbeddedAuthMessageData).authStatus === "not-authenticated") { + lastMessageKeyByType = {}; + } + + const messageKeyFn = messageKeyFnByType[type]; + const messageKey = messageKeyFn(data); + + if (messageKey !== null) { + const lastMessageKey = lastMessageKeyByType[type]; + + if (lastMessageKey === messageKey) { + // For message types that have a function defined in `messageKeyFnByType` (those that send data), do not send them + // again if the data hasn't changed since the last message of the same type: + return; + } + + lastMessageKeyByType[type] = messageKey; + } + + const message: EmbeddedMessage = { + id: nanoid(), + type, + data, + }; + + if (!isInsideIframe()) { + console.warn("Wander Embedded running as a standalone page. There's no parent Window to send this to =", message); + + return; + } + + window.parent.postMessage(message, getEmbeddedAncestorOrigin()); +} + +export function getUserDetailsFromSupabaseUser(user: SupabaseUser | null): EmbeddedUserDetails { + if (!user) return null; + + const userMetadata = user.user_metadata; + const emailConfirmed = !!user.email_confirmed_at || userMetadata.email_verified; + const phoneConfirmed = !!user.phone_confirmed_at || userMetadata.phone_verified; + + return { + id: user.id, + email: user.email || userMetadata.email || null, + phone: user.phone || null, + username: userMetadata.user_name || userMetadata.preferred_username || null, + name: userMetadata.name || null, + fullName: userMetadata.full_name || null, + picture: userMetadata.avatar_url || userMetadata.picture || null, + confirmed: !!user.confirmed_at || emailConfirmed || phoneConfirmed, + emailConfirmed, + phoneConfirmed, + createdAt: new Date(user.created_at), + }; +} + +export function getAuthProviderTypeFromSupabaseUser(user: SupabaseUser): AuthProviderType | null { + return AUTH_PROVIDER_TYPE_BY_PROVIDER_STR[user?.app_metadata?.provider] || null; +} diff --git a/apps/wander-connect/src/utils/utils/routes/embedded-routes.utils.ts b/apps/wander-connect/src/utils/utils/routes/embedded-routes.utils.ts new file mode 100644 index 000000000..7bbe88717 --- /dev/null +++ b/apps/wander-connect/src/utils/utils/routes/embedded-routes.utils.ts @@ -0,0 +1,37 @@ +import type { AuthStatus } from "../../embedded.types"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { WanderRoutePath } from "@wanderapp/core"; + +export type RouteType = "auth" | "account" | "settings" | "auth-request" | "default"; + +const routeTypeByLocationPrefix: Record = { + auth: "auth", + account: "account", + "quick-settings": "settings", + "auth-request": "auth-request", +}; + +export function locationToRouteType(path: WanderRoutePath, authStatus: AuthStatus): RouteType { + if (path === EmbeddedPaths.SupportUnpartitionedStateMissing) { + return authStatus === "unlocked" ? "default" : "auth"; + } + + const pathPrefix = path.split("/")[1] || ""; + + return routeTypeByLocationPrefix[pathPrefix] || "default"; +} + +// TODO: This should match SDK's options: +export type EmbeddedLayout = "modal" | "popup" | "sidebar" | "half"; + +const preferredLayoutByRouteType: Record = { + auth: "modal", + account: "modal", + settings: "popup", + "auth-request": "popup", + default: "popup", +}; + +export function routeTypeToPreferredLayout(routeType: RouteType): EmbeddedLayout { + return preferredLayoutByRouteType[routeType] || "popup"; +} diff --git a/src/utils/embedded/utils/trpc/trpc.utils.ts b/apps/wander-connect/src/utils/utils/trpc/trpc.utils.ts similarity index 92% rename from src/utils/embedded/utils/trpc/trpc.utils.ts rename to apps/wander-connect/src/utils/utils/trpc/trpc.utils.ts index 2cf7d120e..a121f391e 100644 --- a/src/utils/embedded/utils/trpc/trpc.utils.ts +++ b/apps/wander-connect/src/utils/utils/trpc/trpc.utils.ts @@ -1,5 +1,5 @@ import type { HttpStatusCode } from "axios"; -import type { trpcVanilla } from "~utils/embedded/embedded.utils"; +import { trpcVanilla } from "../../embedded.utils"; export interface TRPCErrorData { code: string; diff --git a/apps/wander-connect/src/utils/utils/useUnpartitionedStateCheck.ts b/apps/wander-connect/src/utils/utils/useUnpartitionedStateCheck.ts new file mode 100644 index 000000000..094b46343 --- /dev/null +++ b/apps/wander-connect/src/utils/utils/useUnpartitionedStateCheck.ts @@ -0,0 +1,47 @@ +import { useState, useEffect } from "react"; + +const checkUnpartitionedState = async (): Promise => { + try { + const testKey = "partition-test"; + const testValue = "123"; + + // 1️⃣ Test Cookie Partitioning + document.cookie = `${testKey}=${testValue}; SameSite=None; Secure`; + const cookiesAccessible = document.cookie.includes(testKey); + + // 2️⃣ Test LocalStorage Partitioning + localStorage.setItem(testKey, testValue); + const localStorageAccessible = localStorage.getItem(testKey) === testValue; + localStorage.removeItem(testKey); + + // 3️⃣ Test IndexedDB Partitioning + let indexedDBAccessible = false; + const dbRequest = indexedDB.open(testKey); + dbRequest.onsuccess = () => { + indexedDBAccessible = true; + indexedDB.deleteDatabase(testKey); + }; + + // 4️⃣ Test Cache Storage Partitioning + const cacheName = "partition-test-cache"; + const cache = await caches.open(cacheName); + await cache.put("/", new Response("test")); + const cacheAccessible = (await cache.match("/")) !== undefined; + await caches.delete(cacheName); + + // Return true if any method shows unpartitioned state + return cookiesAccessible || localStorageAccessible || indexedDBAccessible || cacheAccessible; + } catch (error) { + return false; + } +}; + +export const useUnpartitionedStateCheck = () => { + const [hasUnpartitionedState, setHasUnpartitionedState] = useState(null); + + useEffect(() => { + checkUnpartitionedState().then(setHasUnpartitionedState); + }, []); + + return hasUnpartitionedState; +}; diff --git a/src/utils/embedded/utils/wallets/embedded-wallets.utils.ts b/apps/wander-connect/src/utils/utils/wallets/embedded-wallets.utils.ts similarity index 72% rename from src/utils/embedded/utils/wallets/embedded-wallets.utils.ts rename to apps/wander-connect/src/utils/utils/wallets/embedded-wallets.utils.ts index 863a75148..54a669176 100644 --- a/src/utils/embedded/utils/wallets/embedded-wallets.utils.ts +++ b/apps/wander-connect/src/utils/utils/wallets/embedded-wallets.utils.ts @@ -1,4 +1,4 @@ -import type { TempWalletPromise } from "~utils/embedded/embedded.types"; +import { TempWalletPromise } from "../../embedded.types"; const FIVE_MINS_IN_MS = 5 * 60 * 1000; diff --git a/apps/wander-connect/src/views/account/add-wallet/account-add-wallet.view.tsx b/apps/wander-connect/src/views/account/add-wallet/account-add-wallet.view.tsx new file mode 100644 index 000000000..36004ae98 --- /dev/null +++ b/apps/wander-connect/src/views/account/add-wallet/account-add-wallet.view.tsx @@ -0,0 +1,87 @@ +import type { WalletSourceType } from "embed-api"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Box, Button, Card, KeyIcon, SeedIcon, WalletIcon, WanderFooter } from "@wanderapp/ui"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { useLocation } from "@wanderapp/core"; + +export function AccountAddWalletEmbeddedView() { + const { back } = useLocation(); + const { authProviderType, generateTempWallet, registerWallet } = useEmbedded(); + const [isLoading, setIsLoading] = useState({ + calledId: "", + status: false, + }); + + useEffect(() => { + // Pre-generation starts on app load, but this call will re-generate it again if it has expired, as we are trying to + // prevent a user accessing a site with Wander Embedded, not creating an account, and coming back way later after + // the pregenerated wallet has been sitting in memory for long: + generateTempWallet(); + }, []); + + const handleRegisterWallet = useCallback(async (source: WalletSourceType) => { + setIsLoading({ calledId: source, status: true }); + await registerWallet(source); + setIsLoading({ calledId: "", status: false }); + }, []); + + const isDisabled = useMemo(() => isLoading.status === true, [isLoading.status]); + + return ( + } + hasBackButton={true} + onBackButtonClick={back}> + + + + + {/* {authProviderType === "PASSKEYS" ? ( + + ) : ( + + )} */} + + + ); +} diff --git a/apps/wander-connect/src/views/account/backup-wallet/backup-full-wallet.view.tsx b/apps/wander-connect/src/views/account/backup-wallet/backup-full-wallet.view.tsx new file mode 100644 index 000000000..66ccdc8ea --- /dev/null +++ b/apps/wander-connect/src/views/account/backup-wallet/backup-full-wallet.view.tsx @@ -0,0 +1,69 @@ +import { Key01, PasscodeLock } from "@untitled-ui/icons-react"; +import copy from "copy-to-clipboard"; +import { useState, useEffect } from "react"; +import { Button, Copyable, Snackbar, CheckIcon, OnboardingCard, Link } from "@wanderapp/ui"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { WalletUtils, useLocation } from "@wanderapp/core"; + +export function AccountBackupFullWalletEmbeddedView() { + const { navigate } = useLocation(); + const { currentWallet, downloadKeyfile } = useEmbedded(); + + const [hasEncryptedSeedPhrase, setHasEncryptedSeedPhrase] = useState(false); + + useEffect(() => { + WalletUtils.hasEncryptedSeedPhrase(currentWallet.id).then((hasEncryptedSeedPhrase) => { + setHasEncryptedSeedPhrase(hasEncryptedSeedPhrase); + }); + }, [currentWallet.id]); + + return ( + navigate("/account/backup-wallet")}> + +

+ + This file can be used to import your wallet into other wallet apps. +

+ +

+ + This file can be used to recover your account if you lose access to your credentials. +

+
+ + +

Anyone with access to this file can access your wallet and funds, even without authenticating.

+ + Download a safer recovery file instead. + +
+ + { + copy(currentWallet.address); + }} + /> + + + + {hasEncryptedSeedPhrase && ( + + )} +
+ ); +} diff --git a/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx new file mode 100644 index 000000000..6e570447b --- /dev/null +++ b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-copy-seedphrase.view.tsx @@ -0,0 +1,36 @@ +import { useState } from "react"; +import { Button, Snackbar, SecretInput, OnboardingCard } from "@wanderapp/ui"; +import { useAsyncEffect, useLocation } from "@wanderapp/core"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../utils/embedded.hooks"; + +export function AccountBackupCopySeedphraseEmbeddedView() { + const { navigate } = useLocation(); + const { getSeedphrase } = useEmbedded(); + const [seedphrase, setSeedphrase] = useState(""); + + useAsyncEffect(async () => { + const recoveryPhrase = await getSeedphrase(() => Promise.resolve(true)); + + if (recoveryPhrase) { + setSeedphrase(recoveryPhrase); + } + }, []); + + // TODO: Add a cancel button to go straight to backup reminder or wallet dashboard + + return ( + navigate("/account/backup-wallet/full")}> + Do not share this with anyone. + + + + + + ); +} diff --git a/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-qrcode.tsx b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-qrcode.tsx new file mode 100644 index 000000000..9f197a741 --- /dev/null +++ b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-qrcode.tsx @@ -0,0 +1,94 @@ +import type { JWKInterface } from "@dha-team/arbundles"; +import { useState, useCallback, useEffect } from "react"; +import { toast } from "react-toastify"; +import { Flex, CopyToClipboard, Text, OnboardingCard, useTheme, QRCodeWrapper, QRCodeLoop } from "@wanderapp/ui"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { LocalWallet, useLocation, freeDecryptedWallet } from "@wanderapp/core"; +import browser from "webextension-polyfill"; +import { dataToFrames } from "qrloop"; + +export function AccountBackupWalletQrCodeEmbeddedView() { + const { isDarkMode } = useTheme(); + const { navigate } = useLocation(); + const { getDecryptedWallet } = useEmbedded(); + const [isLoading, setIsLoading] = useState(false); + const [copied, setCopied] = useState(false); + const [frames, setFrames] = useState([]); + const [decryptedWallet, setDecryptedWallet] = useState | null>(null); + + const fetchDecryptedWallet = useCallback(async () => { + try { + if (decryptedWallet) return; + setIsLoading(true); + const wallet = await getDecryptedWallet(); + setDecryptedWallet(wallet); + } catch (error) { + toast.error("Error getting wallet"); + } finally { + setIsLoading(false); + } + }, [getDecryptedWallet, decryptedWallet]); + + useEffect(() => { + fetchDecryptedWallet(); + }, [fetchDecryptedWallet]); + + useEffect(() => { + if (decryptedWallet?.keyfile) { + setFrames(dataToFrames(JSON.stringify(decryptedWallet.keyfile))); + freeDecryptedWallet(decryptedWallet.keyfile); + } + }, [decryptedWallet?.keyfile]); + + useEffect(() => { + return () => setFrames([]); + }, []); + + return ( + navigate("/account/backup-wallet")} + isLoading={isLoading}> + + +
+ {frames.length > 0 && } +
+
+ + + {decryptedWallet?.address} + + {children}} + text={decryptedWallet?.address} + /> + +
+
+ ); +} diff --git a/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-recovery-file.view.tsx b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-recovery-file.view.tsx new file mode 100644 index 000000000..0d33112e7 --- /dev/null +++ b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet-recovery-file.view.tsx @@ -0,0 +1,70 @@ +import { XClose } from "@untitled-ui/icons-react"; +import copy from "copy-to-clipboard"; +import { useState, useCallback } from "react"; +import { toast } from "react-toastify"; +import { Button, Copyable, Snackbar, OnboardingCard, Link } from "@wanderapp/ui"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { useLocation } from "@wanderapp/core"; + +export function AccountBackupWalletRecoveryFileEmbeddedView() { + const { navigate } = useLocation(); + const { currentWallet, generateRecoveryAndDownload } = useEmbedded(); + const [isLoading, setIsLoading] = useState(false); + + const handleGenerateRecoveryAndDownload = useCallback(async () => { + try { + setIsLoading(true); + await generateRecoveryAndDownload(); + } catch (error) { + toast.error("Error downloading recovery file"); + } finally { + setIsLoading(false); + } + }, [generateRecoveryAndDownload]); + + return ( + navigate("/account/backup-wallet")} + isLoading={isLoading}> + +

+ + This file cannot be used to import your wallet into other wallet apps. +

+ +

+ + Would you like to export your wallet instead? +

+ +

+ + This file cannot be used to recover your account if you lose access to your credentials. +

+
+ + +

+ If someone steals this file, they won't be able to access your wallet unless they also have access to your + credentials. +

+
+ + { + copy(currentWallet.address); + }} + /> + + +
+ ); +} diff --git a/apps/wander-connect/src/views/account/backup-wallet/backup-wallet.view.tsx b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet.view.tsx new file mode 100644 index 000000000..d7c7d0f8f --- /dev/null +++ b/apps/wander-connect/src/views/account/backup-wallet/backup-wallet.view.tsx @@ -0,0 +1,84 @@ +import { FolderShield, QrCode02, Wallet03 } from "@untitled-ui/icons-react"; +import { Button, OnboardingCard } from "@wanderapp/ui"; +import { useLocation } from "@wanderapp/core"; +import browser from "webextension-polyfill"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +export function AccountBackupWalletEmbeddedView() { + const { navigate } = useLocation(); + + // TODO: What if the user already has more than 3 backup shares? + + // TODO: Do we download one file for the whole account or a file per wallet? + + // TODO: Show confirmation message once backed up and keep the file in-memory + // in case the button is clicked again. + + // TODO: Add an option to encrypt with a password + + // TODO: Redirect user to backup confirmation next or show some kind of confirmation or just redirect home? + + return ( + navigate(EmbeddedPaths.WalletHomeEmbeddedView)}> + + + + + + + {/* + + + */} + + + + ); +} diff --git a/apps/wander-connect/src/views/account/change-password/account-change-password.view.tsx b/apps/wander-connect/src/views/account/change-password/account-change-password.view.tsx new file mode 100644 index 000000000..2b4ad71bf --- /dev/null +++ b/apps/wander-connect/src/views/account/change-password/account-change-password.view.tsx @@ -0,0 +1,288 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { toast } from "react-toastify"; +import { Text, Button, PasswordStrength, PasswordInput, OnboardingCard, CodeInput, type CodeInputHandle, Flex } from "@wanderapp/ui"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { getSupabaseClient, signOut } from "../../../utils/embedded.utils"; +import { StorageKeys, useLocation, useCooldownCallback, sleep, clearOtpAvailable, OTP_COOLDOWN_DURATION_SEC, OTP_LENGTH, setOtpAvailable } from "@wanderapp/core"; +import { useThrottledCallback } from "@swyg/corre"; +import { getFriendlyAuthErrorMessage, MIN_SUPABASE_PASSWORD_LENGTH } from "../../../domains/authentication/authentication.utils"; +import browser from "webextension-polyfill"; +import type { SupabaseUserMetadata } from "embed-api"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +export function AccountChangePasswordEmbeddedView() { + const { navigate } = useLocation(); + const { authStatus, user, requestPasswordChange, setRequestPasswordChange } = useEmbedded(); + const { email, user_metadata } = user; + + // Input refs: + + const passwordInputRef = useRef(); + const repeatPasswordInputRef = useRef(); + + // Loading state: + + const [isResending, setIsResending] = useState(false); + const [isUpdatingPassword, setIsUpdatingPassword] = useState(false); + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isUpdatingPassword; + const areButtonsDisabled = isViewLoading || isResending; + + // Code input: + + const codeInputRef = useRef(); + const [isComplete, setIsComplete] = useState(false); + + const handleCodeChange = useCallback((_, isComplete: boolean) => { + setIsComplete(isComplete); + }, []); + + // Code retrieval: + + const { fn: resendEmail, cooldownSeconds } = useCooldownCallback( + async (showConfirmationToast: boolean) => { + if (codeInputRef.current) codeInputRef.current.clear(); + + try { + setIsResending(true); + const supabase = await getSupabaseClient(); + + const { error } = await supabase.auth.reauthenticate(); + + if (error) { + toast.error(getFriendlyAuthErrorMessage(error, error.message)); + return; + } + + setOtpAvailable(); + + if (showConfirmationToast) toast.success("Password confirmation email resent successfully"); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error sending password confirmation email")); + } finally { + setIsResending(false); + } + }, + { + key: StorageKeys.CONNECT.AUTH.LAST_OTP_EMAIL, + cooldownDuration: OTP_COOLDOWN_DURATION_SEC, + }, + ); + + // Passwords match: + + const [confirmedPassword, setConfirmedPassword] = useState(""); + + const [{ password, passwordsMatch }, setPasswordsState] = useState({ + password: "", + passwordsMatch: false, + }); + + const isPasswordValid = passwordsMatch && password.length >= MIN_SUPABASE_PASSWORD_LENGTH; + + const handlePasswordChange = useThrottledCallback(() => { + const password = passwordInputRef.current.value; + const repeatPassword = repeatPasswordInputRef.current.value; + + setPasswordsState({ + password, + passwordsMatch: password === repeatPassword, + }); + }, 250); + + // Password change: + + const handleUpdatePassword = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + if (isUpdatingPassword) return; + + const passwordInput = passwordInputRef.current; + const repeatPasswordInput = repeatPasswordInputRef.current; + const otpCodeInput = codeInputRef.current; + + let password = ""; + let nonce: string | undefined; + + if (passwordInput && repeatPasswordInput) { + password = passwordInputRef.current.value || ""; + const repeatPassword = repeatPasswordInputRef.current.value || ""; + + if (password !== repeatPassword) { + toast.error(browser.i18n.getMessage("passwords_not_match")); + return; + } + + if (password.length < MIN_SUPABASE_PASSWORD_LENGTH) { + toast.error(`Password must be at least ${MIN_SUPABASE_PASSWORD_LENGTH} characters`); + return; + } + } else if (otpCodeInput) { + const otpCode = codeInputRef.current.getCode(); + + if (otpCode.length !== OTP_LENGTH) { + toast.error(`Please enter all ${OTP_LENGTH} digits of the verification code`); + return; + } + + password = confirmedPassword; + nonce = otpCode; + } else { + return; + } + + setIsUpdatingPassword(true); + + try { + const supabase = await getSupabaseClient(); + + // If the current session was created less than 24 hours ago, `updateUser()` will update the password when called + // without a `nonce`. If it's been more than 24 hours, it will throw an error, as it requires a `nonce`. In that + // case, we call `resendEmail()` and show the `CodeInput`. Once users enter the code, this function is called + // again, but now it will include `nonce`. + // See https://supabase.com/docs/reference/javascript/auth-reauthentication. + + const { error } = await supabase.auth.updateUser({ + password, + nonce, + data: user_metadata.hasPassword + ? undefined + : ({ + ...user_metadata, + hasPassword: true, + } satisfies SupabaseUserMetadata), + }); + + if (otpCodeInput) clearOtpAvailable(); + + if (error) { + const { message } = error; + + if (message === "Password update requires reauthentication") { + resendEmail(false); + setConfirmedPassword(password); + } else if (message === "Nonce has expired or is invalid") { + codeInputRef.current?.clear(); + toast.error("Invalid or expired code"); + } else { + // In this case, the error is most likely related to the password itself (e.g. password is the same as before, password is too short, etc.), so we + // just show the `PasswordInput` again: + + toast.error(getFriendlyAuthErrorMessage(error, message || "Error updating password")); + setConfirmedPassword(""); + } + + return; + } + + toast.success("Password updated successfully"); + + setRequestPasswordChange(false); + + await sleep(100); + + navigate(EmbeddedPaths.WalletHomeEmbeddedView); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error updating password")); + } finally { + setIsUpdatingPassword(false); + } + }, + [isUpdatingPassword, confirmedPassword, user_metadata, setRequestPasswordChange], + ); + + // Routing: + + useEffect(() => { + if (!email) { + if (process.env.NODE_ENV === "development") { + throw new Error("No email search param. The router should have taken care of this."); + } else { + navigate(EmbeddedPaths.Auth); + } + } + }, [email]); + + return confirmedPassword ? ( + + requestPasswordChange ? signOut(false) : navigate(EmbeddedPaths.WalletHomeEmbeddedView) + } + isLoading={isViewLoading} + onSubmit={handleUpdatePassword}> + + Enter the 6-digit verification code from that email to change your password. If you don't see the email, please + check your spam folder. + + + + + Verification Code + + + + + + + + + Didn't receive the email?{" "} + {cooldownSeconds === 0 ? ( + + ) : ( + <>Send again in {cooldownSeconds} seconds + )} + + + ) : ( + + requestPasswordChange ? signOut(false) : navigate(EmbeddedPaths.WalletHomeEmbeddedView) + } + isLoading={areButtonsDisabled} + onSubmit={handleUpdatePassword}> + + + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/account/export-wallet/account-export-wallet.view.tsx b/apps/wander-connect/src/views/account/export-wallet/account-export-wallet.view.tsx new file mode 100644 index 000000000..7415a2d8e --- /dev/null +++ b/apps/wander-connect/src/views/account/export-wallet/account-export-wallet.view.tsx @@ -0,0 +1,58 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { Box, Button, Card, Copyable, KeyIcon, SeedIcon, Snackbar, WanderFooter } from "@wanderapp/ui"; +import copy from "copy-to-clipboard"; +import { useEffect, useState } from "react"; +import { useLocation, WalletUtils } from "@wanderapp/core"; + +export function AccountExportWalletEmbeddedView() { + const { back } = useLocation(); + const { currentWallet, downloadKeyfile, copySeedphrase } = useEmbedded(); + const walletAddress = currentWallet.address; + + const [hasEncryptedSeedPhrase, setHasEncryptedSeedPhrase] = useState(false); + + useEffect(() => { + WalletUtils.hasEncryptedSeedPhrase(currentWallet.id).then((hasEncryptedSeedPhrase) => { + setHasEncryptedSeedPhrase(hasEncryptedSeedPhrase); + }); + }, [currentWallet.id]); + + // TODO: Add an option to encrypt with a password + + return ( + } + hasBackButton={true} + onBackButtonClick={back} + hasCloseButton={true} + onCloseButtonClick={() => { + window.history.back(); + }}> + + Do not share this with anyone. + { + return copy(walletAddress); + }} + value={walletAddress} + /> + + + + + ); +} diff --git a/apps/wander-connect/src/views/account/import-keyfile/account-import-keyfile.view.tsx b/apps/wander-connect/src/views/account/import-keyfile/account-import-keyfile.view.tsx new file mode 100644 index 000000000..bcc778bdd --- /dev/null +++ b/apps/wander-connect/src/views/account/import-keyfile/account-import-keyfile.view.tsx @@ -0,0 +1,96 @@ +import copy from "copy-to-clipboard"; +import { useCallback, useEffect, useState } from "react"; +import { Button, Card, Copyable, Row, Upload, WanderIcon, Text, WanderFooter } from "@wanderapp/ui"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { useLocation } from "@wanderapp/core"; + +export function AccountImportKeyfileEmbeddedView() { + const [loading, setLoading] = useState(false); + const { back } = useLocation(); + const [jsonData, setJsonData] = useState(null); + + const handleJsonParse = (parsedData: any) => { + setJsonData(parsedData); + }; + + const { importTempWallet, importedTempWalletAddress, deleteImportedTempWallet, registerWallet } = useEmbedded(); + + const handleImportWallet = useCallback(async () => { + try { + setLoading(true); + if (jsonData) { + const tempWallet = await importTempWallet(JSON.stringify(jsonData, null, 2)); + + if (!tempWallet) { + setLoading(false); + return alert(`Something isn't right`); + } + setLoading(false); + return tempWallet; + } + } catch (error) { + alert(error); + } finally { + setLoading(false); + } + }, [jsonData]); + + useEffect(() => { + return () => { + // Remove the imported keyfile from memory as soon as we leave this view. Note at this point it will already have + // been passed to `importTempWallet()`, if the user confirmed: + deleteImportedTempWallet(); + }; + }, []); + + return importedTempWalletAddress ? ( + + + {"Secured by"} + + + + } + hasBackButton={true} + onBackButtonClick={back}> + { + copy(importedTempWalletAddress); + }} + value={importedTempWalletAddress} + /> + + + + + + ) : ( + } + hasBackButton={true} + onBackButtonClick={back}> + + + + ); +} diff --git a/apps/wander-connect/src/views/account/import-seedphrase/account-import-seedphrase.view.tsx b/apps/wander-connect/src/views/account/import-seedphrase/account-import-seedphrase.view.tsx new file mode 100644 index 000000000..06c2e3ab0 --- /dev/null +++ b/apps/wander-connect/src/views/account/import-seedphrase/account-import-seedphrase.view.tsx @@ -0,0 +1,66 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Button, Card, SeedInput, WanderFooter } from "@wanderapp/ui"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { useLocation } from "@wanderapp/core"; +import { toast } from "react-toastify"; + +export function AccountImportSeedphraseEmbeddedView() { + const [loading, setLoading] = useState(false); + const { back } = useLocation(); + + const [seedPhrase, setSeedPhrase] = useState([]); + const { importTempWallet, deleteImportedTempWallet, registerWallet } = useEmbedded(); + + const handleInputChange = useCallback((index: number, value: string) => { + setSeedPhrase((prevSeedPhrase) => { + const newSeedPhrase = [...prevSeedPhrase]; + newSeedPhrase[index] = value; + return newSeedPhrase; + }); + }, []); + + const handleImportWallet = useCallback(async () => { + try { + setLoading(true); + if (!seedPhrase.length) return; + await importTempWallet(seedPhrase.join(" ")); + await registerWallet("IMPORTED"); + } catch (error) { + toast.error(error); + } finally { + setLoading(false); + } + }, [seedPhrase]); + + useEffect(() => { + return () => { + // Remove the imported keyfile from memory as soon as we leave this view. Note at this point it will already have + // been passed to `importTempWallet()`, if the user confirmed: + deleteImportedTempWallet(); + }; + }, []); + + const isSeedPhraseIncomplete = useMemo(() => { + if (seedPhrase.length !== 12) return true; + return seedPhrase.some((word) => word.trim() === ""); + }, [seedPhrase]); + + return ( + } + hasBackButton={true} + onBackButtonClick={back}> + + + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/connect/components/AppIcons.tsx b/apps/wander-connect/src/views/auth-request/connect/components/AppIcons.tsx new file mode 100644 index 000000000..f3cf76d81 --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/connect/components/AppIcons.tsx @@ -0,0 +1,29 @@ +import { AppInfo } from "@wanderapp/core"; +import { Row, Image } from "@wanderapp/ui"; +import WanderIcon from "url:assets/icon-embed.svg"; + +export default function AppIcons({ appInfo }: { appInfo: AppInfo }) { + return ( + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/connect/connect-custom.view.tsx b/apps/wander-connect/src/views/auth-request/connect/connect-custom.view.tsx new file mode 100644 index 000000000..e06f19a19 --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/connect/connect-custom.view.tsx @@ -0,0 +1,77 @@ +import { useCallback, useMemo } from "react"; +import browser from "webextension-polyfill"; +import { ExtensionStorage, permissionData, PermissionType, useCurrentAuthRequest, useLocation, useStorage } from "@wanderapp/core"; +import { AuthRequestCard, Box, Switch } from "@wanderapp/ui"; + +export function EmbeddedConnectCustomAuthRequestView() { + const { navigate } = useLocation(); + const { authRequest } = useCurrentAuthRequest("connect"); + const [requestedPermissions, setRequestedPermissions] = useStorage( + { + key: `requested_permissions`, + instance: ExtensionStorage, + }, + [], + ); + + const permissions = useMemo( + () => new Map(requestedPermissions.map((permission) => [permission, true])), + [requestedPermissions], + ); + + const handlePermissionChange = useCallback( + (permission: PermissionType) => { + const updated = new Map(permissions); + updated.set(permission, !permissions.get(permission)); + + const newPermissions = Array.from(updated.entries()) + .filter(([_, value]) => value) + .map(([key]) => key); + + setRequestedPermissions(newPermissions); + }, + [permissions, setRequestedPermissions], + ); + + const formatPermissionName = useCallback((permissionName: PermissionType) => { + if (permissionName === "SIGNATURE") return "Sign Data"; + + return permissionName + .split("_") + .map((word) => word.charAt(0) + word.slice(1).toLowerCase()) + .join(" "); + }, []); + + const handleClick = useCallback( + (e: React.MouseEvent, permission: PermissionType) => { + e.stopPropagation(); + e.preventDefault(); + handlePermissionChange(permission); + }, + [handlePermissionChange], + ); + + return ( + navigate(`/auth-request/connect/${authRequest.authID}/settings`)}> + {Object.keys(permissionData).map((permissionName: PermissionType) => ( + handleClick(e, permissionName)}> + {}} + isChecked={permissions.get(permissionName) ?? false} + /> + + ))} + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/connect/connect-settings.view.tsx b/apps/wander-connect/src/views/auth-request/connect/connect-settings.view.tsx new file mode 100644 index 000000000..6af1d91c7 --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/connect/connect-settings.view.tsx @@ -0,0 +1,154 @@ +import browser from "webextension-polyfill"; +import { Spacer } from "@arconnect/components-rebrand"; +import { useState, useMemo, useCallback } from "react"; +import { Edit02 } from "@untitled-ui/icons-react"; +import AppIcons from "./components/AppIcons"; +import { addApp, defaultGateway, ExtensionStorage, permissionData, PermissionType, SignPolicy, signPolicyOptions, useAsyncEffect, useCurrentAuthRequest, useLocation, useStorage, Application } from "@wanderapp/core"; +import { Box, Radio, Snackbar, Text, Row, ChevronRight, AuthRequestCard } from "@wanderapp/ui"; + +export function EmbeddedConnectSettingsAuthRequestView() { + const { navigate } = useLocation(); + const [signPolicy, setSignPolicy] = useStorage( + { + key: "sign_policy", + instance: ExtensionStorage, + }, + "ask_when_spending", + ); + + const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("connect"); + + const { url = "", permissions: authRequestPermissions = [], appInfo = {}, gateway } = authRequest; + + const [requestedPermissions, setRequestedPermissions] = useStorage({ + key: `requested_permissions`, + instance: ExtensionStorage, + }); + const [requestedPermCopy, setRequestedPermCopy] = useState([]); + + const isCustomPermissions = useMemo(() => { + if (!requestedPermissions) return false; + if (requestedPermissions?.length !== requestedPermCopy.length) return true; + + // Create sorted copies to ensure order doesn't matter + const sortedRequested = [...requestedPermissions].sort(); + const sortedInitial = [...requestedPermCopy].sort(); + + // Compare each element + return sortedRequested.some((permission, index) => permission !== sortedInitial[index]); + }, [requestedPermissions, requestedPermCopy]); + + const handlePermissionChange = (permission: SignPolicy) => { + setSignPolicy(permission); + }; + + const handleConfirm = useCallback(async () => { + if (!signPolicy || !url) return; + + // get existing permissions + const app = new Application(url); + const isAppPresent = await app.isAppPresent(); + + if (!isAppPresent) { + // add the app + await addApp({ + url, + permissions: requestedPermissions, + name: appInfo.name, + logo: appInfo.logo, + signPolicy, + // alwaysAsk, + allowance: { + enabled: false, + limit: "0", + spent: "0", // in winstons + }, + // TODO: wayfinder + gateway: gateway || defaultGateway, + }); + } else { + // update existing permissions, if the app + // has already been added + + await app.updateSettings({ + signPolicy, + permissions: requestedPermissions, + // alwaysAsk, + allowance: { + enabled: false, + limit: "0", + spent: "0", // in winstons + }, + }); + } + + acceptRequest(); + }, [url, requestedPermissions, appInfo, signPolicy, gateway, acceptRequest]); + + useAsyncEffect(async () => { + const requested: PermissionType[] = authRequestPermissions; + + // add existing permissions + if (url) { + const app = new Application(url); + const existing = await app.getPermissions(); + + for (const existingP of existing) { + if (requested.includes(existingP)) continue; + requested.push(existingP); + } + } + + const requestedPermissions = await ExtensionStorage.get(`requested_permissions`); + + if (!requestedPermissions) { + setRequestedPermissions(requested.filter((p) => Object.keys(permissionData).includes(p))); + } + + setRequestedPermCopy(requested.filter((p) => Object.keys(permissionData).includes(p))); + }, [url, authRequestPermissions]); + + return ( + navigate(`/auth-request/connect/${authRequest.authID}`)} + onCancel={() => rejectRequest()} + onConfirm={handleConfirm} + confirmLabel={browser.i18n.getMessage("connect")} + isDisabled={!signPolicy || !url}> + + + Confirm permissions + + + {signPolicyOptions.map((option) => ( + handlePermissionChange(option)} + /> + ))} + + navigate(`/auth-request/connect/${authRequest.authID}/custom`)}> + + {isCustomPermissions ? "Custom permissions set" : "Set custom permissions"} + + {isCustomPermissions ? ( + + ) : ( + + )} + + + + + {browser.i18n.getMessage(`${signPolicy}_description`)} + + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/connect/connect.view.tsx b/apps/wander-connect/src/views/auth-request/connect/connect.view.tsx new file mode 100644 index 000000000..f4d88af46 --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/connect/connect.view.tsx @@ -0,0 +1,162 @@ +import { useEffect, useState } from "react"; +import AppIcons from "./components/AppIcons"; +import browser from "webextension-polyfill"; +import { concatGatewayURL, ExtensionStorage, formatAddress, FULL_HISTORY, setActiveWallet, useActiveWallet, useAllWallets, useCurrentAuthRequest, useGateway, useLocation, useNameServiceProfile } from "@wanderapp/core"; +import { Button, Text, Row, Box, Avatar, WalletIcon, AuthRequestCard } from "@wanderapp/ui"; +import { Wallet } from "../../../utils/embedded.types"; + +export function EmbeddedConnectAuthRequestView() { + const { navigate } = useLocation(); + const activeWallet = useActiveWallet(); + const wallets = useAllWallets(); + const { authRequest, rejectRequest } = useCurrentAuthRequest("connect"); + + const [avatar, setAvatar] = useState(""); + + const nameServiceProfile = useNameServiceProfile(activeWallet?.address); + const nsGateway = useGateway(FULL_HISTORY); + + const { appInfo = {}, url = "" } = authRequest; + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + const handleWalletSelect = (wallet: Partial) => () => { + if (wallet.address) { + setIsDropdownOpen(false); + setActiveWallet(wallet.address); + } + }; + + useEffect(() => { + if (!activeWallet?.address) return; + + if (nameServiceProfile?.logo && nsGateway?.protocol && nsGateway?.host) { + setAvatar(concatGatewayURL(nsGateway) + "/" + nameServiceProfile.logo); + } + }, [activeWallet, nameServiceProfile, nsGateway]); + + const handleConfirm = () => { + ExtensionStorage.remove(`requested_permissions`); + ExtensionStorage.remove("sign_policy"); + navigate(`/auth-request/connect/${authRequest.authID}/settings`); + }; + + return ( + <> + rejectRequest()} + onConfirm={handleConfirm} + confirmLabel={browser.i18n.getMessage("next")}> + + + + {appInfo.name} would like to connect to your wallet + + + + Select an account to connect: + + + + + + + + + + + {activeWallet.nickname} + + {formatAddress(activeWallet.address, 4)} + + + + + + + + {/* Add overlay when dropdown is open */} + {isDropdownOpen && ( +
setIsDropdownOpen(false)} + /> + )} + + {/* Modal for wallet selection */} + {isDropdownOpen && ( + + {wallets.map((wallet, index) => ( +
{ + if (wallet.address !== activeWallet.address) { + e.currentTarget.style.backgroundColor = "#F9F9FC"; + } + }} + onMouseLeave={(e) => { + if (wallet.address !== activeWallet.address) { + e.currentTarget.style.backgroundColor = "transparent"; + } + }}> + + + + + + + {wallet.nickname} + + {wallet.address ? formatAddress(wallet.address, 4) : ""} + + +
+ ))} +
+ )} + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/decrypt/decrypt.view.tsx b/apps/wander-connect/src/views/auth-request/decrypt/decrypt.view.tsx new file mode 100644 index 000000000..3ec763990 --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/decrypt/decrypt.view.tsx @@ -0,0 +1,22 @@ +import { useCurrentAuthRequest } from "@wanderapp/core"; +import { Text, AuthRequestCard, Message } from "@wanderapp/ui"; +import browser from "webextension-polyfill"; + +export function EmbeddedDecryptAuthRequestView() { + const { authRequest, rejectRequest, acceptRequest } = useCurrentAuthRequest("decrypt"); + const { url, message } = authRequest; + + return ( + rejectRequest()} + onConfirm={() => acceptRequest()} + confirmLabel={browser.i18n.getMessage("decrypt_authorize")}> + + {browser.i18n.getMessage("decrypt_description", url)} + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/sign/batchSignDataItem.view.tsx b/apps/wander-connect/src/views/auth-request/sign/batchSignDataItem.view.tsx new file mode 100644 index 000000000..1d803c618 --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/sign/batchSignDataItem.view.tsx @@ -0,0 +1,115 @@ +import browser from "webextension-polyfill"; +import { useEffect, useState } from "react"; +import { Quantity } from "ao-tokens"; +import { fetchTokenByProcessId, RawDataItem, timeoutPromise, TokenInfo, useCurrentAuthRequest } from "@wanderapp/core"; +import { Box, Text, Row, AuthRequestCard, SignDataItemDetails } from "@wanderapp/ui"; + +export function EmbeddedBatchSignDataItemAuthRequestView() { + const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("batchSignDataItem"); + const { data, url } = authRequest; + const [loading, setLoading] = useState(false); + const [transaction, setTransaction] = useState(null); + const [transactionList, setTransactionList] = useState(null); + + useEffect(() => { + const fetchTransactionList = async () => { + setLoading(true); + + try { + if (Array.isArray(data)) { + const listItems = await Promise.all( + data.map(async (item, index) => { + let amount = ""; + let name = ""; + const quantity = item?.tags?.find((tag) => tag.name === "Quantity")?.value || "0"; + const transfer = item?.tags?.some((tag) => tag.name === "Action" && tag.value === "Transfer"); + + if (transfer && quantity) { + let tokenInfo: TokenInfo; + try { + // TODO: See if dataItem with no `target` property but a Target tag is valid, and update this code if needed. + tokenInfo = await timeoutPromise(fetchTokenByProcessId(item.target), 6000); + if (!tokenInfo) { + throw new Error("Token not found"); + } + const tokenAmount = new Quantity(BigInt(quantity), BigInt(tokenInfo.Denomination)); + amount = tokenAmount.toLocaleString(); + name = tokenInfo.Name; + } catch (error) { + console.error("Token fetch timed out or failed", error); + amount = quantity; + name = item.target; + } + } + + // TODO: Add the token logo or a "data" icon next to each item: + + return ( + setTransaction(item)} + isFullWidth> + + + Transaction {index + 1} + + {formatTransactionDescription(amount, name)} + + + ); + }), + ); + setTransactionList(listItems); + } + } finally { + setLoading(false); + } + }; + + fetchTransactionList(); + }, [data]); + + return transaction ? ( + setTransaction(null)} + onCancel={() => setTransaction(null)} + cancelLabel={browser.i18n.getMessage("back")}> + + + {browser.i18n.getMessage("batch_sign_data_description", url)} + + + + + + ) : ( + rejectRequest()} + onConfirm={() => acceptRequest()} + confirmLabel={browser.i18n.getMessage("sign_authorize_all")} + isDisabled={loading}> + + + {browser.i18n.getMessage("batch_sign_data_description", url)} + + + {transactionList} + + + ); +} + +function formatTransactionDescription(amount?: string, tokenName?: string): string { + if (amount && tokenName) { + return `Sending ${amount} of ${tokenName}`; + } + return "Unknown transaction"; +} diff --git a/apps/wander-connect/src/views/auth-request/sign/sign-details.view.tsx b/apps/wander-connect/src/views/auth-request/sign/sign-details.view.tsx new file mode 100644 index 000000000..0bed1aad7 --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/sign/sign-details.view.tsx @@ -0,0 +1,127 @@ +import Arweave from "arweave"; +import BigNumber from "bignumber.js"; +import { useMemo, useState } from "react"; +import prettyBytes from "pretty-bytes"; +import browser from "webextension-polyfill"; +import { defaultGateway, ExtensionStorage, formatAddress, humanizeTimestampTags, useCurrentAuthRequest, useLocation, useStorage } from "@wanderapp/core"; +import { Row, Text, Box, ChevronRight, AuthRequestCard, TransactionMessage, TransactionTag } from "@wanderapp/ui"; + +export function EmbeddedSignDetailsAuthRequestView() { + const { navigate } = useLocation(); + const { authRequest } = useCurrentAuthRequest("any"); + const transaction = (authRequest as any)?.transaction || (authRequest as any)?.data; + + const [activeAddress] = useStorage( + { + key: "active_address", + instance: ExtensionStorage, + }, + "", + ); + + // quantity + const quantity = useMemo(() => { + if (!transaction?.quantity) return BigNumber("0"); + + const arweave = new Arweave(defaultGateway); + const ar = arweave.ar.winstonToAr(transaction.quantity); + + return BigNumber(ar); + }, [transaction]); + + const [showTags, setShowTags] = useState(false); + + // transaction fee + const fee = useMemo(() => { + if (!transaction?.reward) { + return "0"; + } + + const arweave = new Arweave(defaultGateway); + + return arweave.ar.winstonToAr(transaction.reward); + }, [transaction]); + + // transaction size + const size = useMemo(() => { + if (!transaction) return 0; + + return transaction?.sizeInBytes ?? transaction?.data?.length ?? 0; + }, [transaction]); + + // tags + const tags = useMemo(() => { + if (!transaction) return []; + + if (!transaction?.get) { + return Array.isArray(transaction?.tags) ? transaction.tags : []; + } + + // @ts-expect-error + const tags = transaction.get("tags") as Tag[]; + const decodedTags = tags.map((tag) => ({ + name: tag.get("name", { decode: true, string: true }), + value: tag.get("value", { decode: true, string: true }), + })); + + return humanizeTimestampTags(decodedTags); + }, [transaction]); + + const recipient = useMemo(() => { + if (tags.length === 0) return transaction?.target || ""; + + // AO Token + const isAOTransferTx = + tags.some((tag) => tag.name === "Data-Protocol" && tag.value === "ao") && + tags.some((tag) => tag.name === "Action" && tag.value === "Transfer"); + if (isAOTransferTx) { + const recipientTag = tags.find((tag) => tag.name === "Recipient"); + if (recipientTag?.value) return recipientTag.value; + } + + return transaction?.target || ""; + }, [tags]); + + return ( + navigate(`/auth-request/${authRequest.type}/${authRequest.authID}`)} + onCancel={() => navigate(`/auth-request/${authRequest.type}/${authRequest.authID}`)} + cancelLabel={browser.i18n.getMessage("back")}> + + {transaction?.id && } + + {recipient && } + {!quantity.isZero() && } + + + setShowTags((prev) => !prev)}> + + Tags + +
+ +
+
+ {showTags && ( + + {tags.map((tag, index) => ( + + ))} + + )} +
+ + +
+ ); +} diff --git a/apps/wander-connect/src/views/auth-request/sign/sign.view.tsx b/apps/wander-connect/src/views/auth-request/sign/sign.view.tsx new file mode 100644 index 000000000..b22898a1b --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/sign/sign.view.tsx @@ -0,0 +1,131 @@ +import Arweave from "arweave"; +import BigNumber from "bignumber.js"; +import browser from "webextension-polyfill"; +import { AppInfo, Application, defaultGateway,formatBalance, Gateway, useAsyncEffect, useBalance, useCurrentAuthRequest } from "@wanderapp/core"; +import { Row, Text, Box, Divider, Snackbar, Image, AuthRequestCard, TransactionMessage } from "@wanderapp/ui"; +import { useState, useMemo } from "react"; + +export function EmbeddedSignAuthRequestView() { + const { authRequest, rejectRequest, acceptRequest } = useCurrentAuthRequest("sign"); + + const { url = "", transaction } = authRequest; + + const [appInfo, setAppInfo] = useState(); + + // quantity + const quantity = useMemo(() => { + if (!transaction?.quantity) { + return BigNumber("0"); + } + + const arweave = new Arweave(defaultGateway); + const ar = arweave.ar.winstonToAr(transaction.quantity); + + return BigNumber(ar); + }, [transaction]); + + const { data: arBalance = "0" } = useBalance(); + + const balance = useMemo(() => formatBalance(arBalance), [arBalance]); + + // transaction fee + const fee = useMemo(() => { + if (!transaction?.reward) return "0"; + + const arweave = new Arweave(defaultGateway); + return arweave.ar.winstonToAr(transaction.reward); + }, [transaction]); + + const isTransferTx = useMemo(() => BigNumber(transaction?.quantity || "0").gt(0), [transaction]); + + useAsyncEffect(async () => { + if (!url) return; + + const app = new Application(url); + const gateway = await app.getGatewayConfig(); + const appData = await app.getAppData(); + + setAppInfo({ ...appData, gateway }); + }, [url]); + + return ( + rejectRequest()} + onConfirm={() => acceptRequest()} + confirmLabel={browser.i18n.getMessage("sign_authorize")} + isDisabled={!transaction || authRequest.status !== "pending"}> + + + + + + {appInfo?.name} + + Gateway: {appInfo?.gateway?.host} + + + + + {isTransferTx ? ( + <> + + + Your account + + + + Balance + + + + {balance.displayBalance} AR + + + + + + Amount + + + {quantity.toString()} AR + + + + + Total fees + + + {fee} AR + + + + + + Total + + + {quantity.plus(fee).toString()} AR + + + + ) : ( + + Only confirm if you understand the content and trust the requesting site. This confirmation is used for + authentication purposes, funds are not being transferred. + + )} + + + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/sign/signDataItem.view.tsx b/apps/wander-connect/src/views/auth-request/sign/signDataItem.view.tsx new file mode 100644 index 000000000..d0e6d670e --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/sign/signDataItem.view.tsx @@ -0,0 +1,179 @@ +import { useMemo, useState } from "react"; +import { Quantity } from "ao-tokens"; +import { Loading } from "@arconnect/components-rebrand"; +import browser from "webextension-polyfill"; +import { AppInfo, Application, ExtensionStorage, fetchTokenByProcessId, formatBalance, Gateway, getTagValue, humanizeTimestampTags, PersistentStorage, TokenInfo, useAsyncEffect, useCurrentAuthRequest, useStorage, useTokenBalance } from "@wanderapp/core"; +import { Row, Text, Box, Divider, Snackbar, Image, AuthRequestCard, TransactionMessage } from "@wanderapp/ui"; + +export function EmbeddedSignDataAuthRequestView() { + const { authRequest, rejectRequest, acceptRequest } = useCurrentAuthRequest("signDataItem"); + const { url = "", data } = authRequest; + + const [appInfo, setAppInfo] = useState(); + const [loading, setLoading] = useState(false); + const [tokenInfo, setTokenInfo] = useState(null); + const [amount, setAmount] = useState(null); + + // active address + const [activeAddress] = useStorage( + { + key: "active_address", + instance: ExtensionStorage, + }, + "", + ); + + const tags = useMemo(() => humanizeTimestampTags(data?.tags || []), [data]); + const quantity = useMemo(() => getTagValue("Quantity", tags) || "0", [tags]); + const transfer = useMemo(() => tags.some((tag) => tag.name === "Action" && tag.value === "Transfer"), [tags]); + const process = data?.target; + const tokenName = tokenInfo?.Name; + + const { data: balance = "0", isLoading: balanceLoading } = useTokenBalance( + { + processId: process, + Denomination: tokenInfo?.Denomination, + }, + activeAddress, + ); + + const formattedBalance = useMemo(() => { + return formatBalance(balance).displayBalance; + }, [balance]); + + const formattedAmount = useMemo(() => (amount || 0).toLocaleString(), [amount]); + + useAsyncEffect(async () => { + if (!url) return; + + const app = new Application(url); + const gateway = await app.getGatewayConfig(); + const appData = await app.getAppData(); + + setAppInfo({ ...appData, gateway }); + }, [url]); + + // get ao token info + useAsyncEffect(async () => { + if (!process || !transfer) return; + + let tokenInfo: TokenInfo; + + try { + setLoading(true); + + tokenInfo = await fetchTokenByProcessId(data.target); + + if (!tokenInfo) throw new Error("Token not found"); + } catch (err) { + // fallback + console.log("err", err); + + try { + const [aoTokens = [], aoTokensCache = []] = await Promise.all([ + PersistentStorage.get("ao_tokens"), + PersistentStorage.get("ao_tokens_cache"), + ]); + const aoTokensCombined = [...aoTokens, ...aoTokensCache]; + const token = aoTokensCombined.find(({ processId }) => data.target === processId); + if (token) { + tokenInfo = token; + } + } catch {} + } finally { + if (tokenInfo) { + const tokenAmount = new Quantity(BigInt(quantity), BigInt(tokenInfo.Denomination)); + setAmount(tokenAmount); + setTokenInfo(tokenInfo); + } + + setLoading(false); + } + }, [data]); + + return ( + rejectRequest()} + onConfirm={() => acceptRequest()} + confirmLabel={browser.i18n.getMessage("signature_authorize")} + isDisabled={loading}> + + + + + + {appInfo?.name} + + Gateway: {appInfo?.gateway?.host} + + + + + {transfer ? ( + <> + + + Your account + + + + Balance + + + {balanceLoading ? ( + + ) : ( + + {formattedBalance} {tokenName} + + )} + + + + + Amount + + {loading ? ( + + ) : ( + + {formattedAmount} {tokenName} + + )} + + + + Total fees + + + 0 AR + + + + + + Total + + + {formattedAmount} {tokenName} + + + + ) : ( + + Only confirm if you understand the content and trust the requesting site. This confirmation is used for + authentication purposes, funds are not being transferred. + + )} + + + + ); +} diff --git a/apps/wander-connect/src/views/auth-request/signature/signature.view.tsx b/apps/wander-connect/src/views/auth-request/signature/signature.view.tsx new file mode 100644 index 000000000..151af50aa --- /dev/null +++ b/apps/wander-connect/src/views/auth-request/signature/signature.view.tsx @@ -0,0 +1,22 @@ +import browser from "webextension-polyfill"; +import { useCurrentAuthRequest } from "@wanderapp/core"; +import { Text, Message, AuthRequestCard } from "@wanderapp/ui"; + +export function EmbeddedSignatureAuthRequestView() { + const { authRequest, rejectRequest, acceptRequest } = useCurrentAuthRequest("signature"); + const { url, message } = authRequest; + + return ( + rejectRequest()} + onConfirm={() => acceptRequest()} + confirmLabel={browser.i18n.getMessage("signature_authorize")}> + + {browser.i18n.getMessage("signature_description", url)} + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/add-auth-provider/auth-add-auth-provider.view.tsx b/apps/wander-connect/src/views/auth/add-auth-provider/auth-add-auth-provider.view.tsx new file mode 100644 index 000000000..0f3b00ba9 --- /dev/null +++ b/apps/wander-connect/src/views/auth/add-auth-provider/auth-add-auth-provider.view.tsx @@ -0,0 +1,24 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { DevFigmaScreen } from "@wanderapp/ui"; + +export function AuthAddAuthProviderEmbeddedView() { + const { authProviderType } = useEmbedded(); + + return ( + alert("Not implemented."), + }, + { + label: "Back", + to: "/auth/add-wallet", + variant: "secondary", + }, + ]} + /> + ); +} diff --git a/apps/wander-connect/src/views/auth/add-device/auth-add-device.view.tsx b/apps/wander-connect/src/views/auth/add-device/auth-add-device.view.tsx new file mode 100644 index 000000000..80718a282 --- /dev/null +++ b/apps/wander-connect/src/views/auth/add-device/auth-add-device.view.tsx @@ -0,0 +1,21 @@ +import { DevFigmaScreen } from "@wanderapp/ui"; + +export function AuthAddDeviceEmbeddedView() { + return ( + alert("Not implemented."), + }, + { + label: "Back", + to: "/auth/add-wallet", + variant: "secondary", + }, + ]} + /> + ); +} diff --git a/apps/wander-connect/src/views/auth/add-qrcode/add-qrcode.view.tsx b/apps/wander-connect/src/views/auth/add-qrcode/add-qrcode.view.tsx new file mode 100644 index 000000000..e148a2596 --- /dev/null +++ b/apps/wander-connect/src/views/auth/add-qrcode/add-qrcode.view.tsx @@ -0,0 +1,39 @@ +import { useCallback } from "react"; +import { Box, Button, CameraIcon, Card, WanderFooter } from "@wanderapp/ui"; +import { useLocation } from "@wanderapp/core"; +import { toast } from "react-toastify"; +import { useWebcamPermission } from "../import-qrcode/hooks/useWebcamPermission"; + +export function AuthAddWithQRCodeEmbeddedView() { + const { navigate, back } = useLocation(); + const { isLoading, error, requestPermission } = useWebcamPermission(); + + const handleOpenWebcam = useCallback(async () => { + try { + const permissionGranted = await requestPermission(); + + if (permissionGranted) { + navigate("/auth/qrcode-scanner"); + } else { + toast.error("Permission denied"); + } + } catch (error) { + toast.error(error); + } + }, [navigate, requestPermission]); + + return ( + } + hasBackButton={true} + onBackButtonClick={back}> + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/add-wallet/auth-add-wallet.view.tsx b/apps/wander-connect/src/views/auth/add-wallet/auth-add-wallet.view.tsx new file mode 100644 index 000000000..2d4cfa54e --- /dev/null +++ b/apps/wander-connect/src/views/auth/add-wallet/auth-add-wallet.view.tsx @@ -0,0 +1,110 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { useCallback, useEffect, useState } from "react"; +import { Button, KeyIcon, SeedIcon, WalletIcon, OnboardingCard } from "@wanderapp/ui"; +import type { WalletSourceType } from "embed-api"; +import { QrCode02 } from "@untitled-ui/icons-react"; +import { navigate } from "wouter/use-hash-location"; +import { toast } from "react-toastify"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { signOut } from "../../../utils/embedded.utils"; + +export function AuthAddWalletEmbeddedView() { + const { generateTempWallet, registerWallet, authStatus } = useEmbedded(); + + // Loading state: + + const [isLoading, setIsLoading] = useState(false); + + const areButtonsDisabled = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isLoading; + + const isViewLoading = areButtonsDisabled; + + useEffect(() => { + // Pre-generation starts on app load, but this call will re-generate it again if it has expired, as we are trying to + // prevent a user accessing a site with Wander Embedded, not creating an account, and coming back way later after + // the pregenerated wallet has been sitting in memory for long: + generateTempWallet(); + }, []); + + const handleRegisterWallet = useCallback(async (source: WalletSourceType) => { + try { + setIsLoading(true); + await registerWallet(source); + } catch (error) { + console.error(error); + toast.error("An unexpected error happened."); + } finally { + setIsLoading(false); + } + }, []); + + return ( + + authStatus === "noWallets" ? signOut(false) : navigate(EmbeddedPaths.AuthRestoreShares) + } + isLoading={isViewLoading}> + + + + + + + + + {/* {authProviderType === "PASSKEYS" ? ( + + ) : ( + + )} */} + + ); +} diff --git a/apps/wander-connect/src/views/auth/auth-email-otp/auth-email-otp.view.tsx b/apps/wander-connect/src/views/auth/auth-email-otp/auth-email-otp.view.tsx new file mode 100644 index 000000000..f669da379 --- /dev/null +++ b/apps/wander-connect/src/views/auth/auth-email-otp/auth-email-otp.view.tsx @@ -0,0 +1,203 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { toast } from "react-toastify"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { Button, Text, Flex, CodeInput, type CodeInputHandle, OnboardingCard } from "@wanderapp/ui"; +import { + checkNeedsNewOtp, + clearOtpAvailable, + OTP_COOLDOWN_DURATION_SEC, + OTP_LENGTH, + setOtpAvailable, + useAsyncEffect, + PreferredEmailAuth, + PersistentStorage, + useStorage, + StorageKeys, + useCooldownCallback, + useLocation, + useSearchParams, +} from "@wanderapp/core"; +import { getFriendlyAuthErrorMessage } from "../../../domains/authentication/authentication.utils"; +import { getSupabaseClient } from "../../../utils/embedded.utils"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +export function AuthEmailOtpEmbeddedView() { + const { navigate, location } = useLocation(); + const { email, isAlreadyRegistered: isAlreadyRegisteredParam } = useSearchParams<{ + email: string; + isAlreadyRegistered: string; + }>(); + const { authStatus, authenticate } = useEmbedded(); + + const [_, setPreferredEmailAuth] = useStorage({ + key: StorageKeys.CONNECT.AUTH.PREFERRED_EMAIL_AUTH, + instance: PersistentStorage, + }); + + // Loading state: + + const [isResending, setIsResending] = useState(false); + const [isVerifying, setIsVerifying] = useState(false); + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isVerifying; + const areButtonsDisabled = isViewLoading || isResending; + + // Code input: + + const codeInputRef = useRef(); + const [isComplete, setIsComplete] = useState(false); + + const handleCodeChange = useCallback((_, isComplete: boolean) => { + setIsComplete(isComplete); + }, []); + + // Code retrieval: + + const { fn: signInWithOtp, cooldownSeconds } = useCooldownCallback( + async (showConfirmationToast: boolean) => { + try { + if (codeInputRef.current) codeInputRef.current.clear(); + + setIsResending(true); + + const supabase = await getSupabaseClient(); + + const { error } = await supabase.auth.signInWithOtp({ email }); + + if (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error sending email access code")); + return; + } + + setOtpAvailable(); + + if (showConfirmationToast) toast.success("Email access code resent successfully"); + + // Note we don't need to check if the email is verified or not. Once the user signs in using the OTP code, their + // email is verified if it wasn't already and the router redirects them to the right page (add wallet, backup reminder...). + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error sending email access code")); + } finally { + setIsResending(false); + } + }, + { + key: StorageKeys.CONNECT.AUTH.LAST_OTP_EMAIL, + cooldownDuration: OTP_COOLDOWN_DURATION_SEC, + }, + ); + + useAsyncEffect(async () => { + try { + if (await checkNeedsNewOtp()) signInWithOtp(false); + } catch { + // In case `LAST_OTP_EMAIL` is already be set in `localStorage` and the required time hasn't passed yet (so this call will throw an error). + } + }, [signInWithOtp]); + + // Handlers: + + const handleVerifyOtp = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + if (!email || isVerifying) return; + + const otpCode = codeInputRef.current.getCode(); + + if (otpCode.length !== OTP_LENGTH) { + toast.error(`Please enter all ${OTP_LENGTH} digits of the access code`); + return; + } + + setIsVerifying(true); + + try { + await authenticate({ + method: "verifyOtp", + email, + token: otpCode, + }); + + // This is done to clear the search params (email and isAlreadyRegistered): + navigate(location, { search: { email } }); + + setPreferredEmailAuth("otp"); + } catch (error) { + setIsVerifying(false); + toast.error(getFriendlyAuthErrorMessage(error, "Invalid or expired code")); + } finally { + clearOtpAvailable(); + } + + // We leave isVerifying = true intentionally as the user will simply be redirected after verifying the account. + }, + [email, isVerifying, authenticate], + ); + + useEffect(() => { + if (!email) { + if (process.env.NODE_ENV === "development") { + throw new Error("No email search param. The router should have taken care of this."); + } else { + navigate(EmbeddedPaths.Auth); + } + } + }, [email]); + + return ( + navigate(EmbeddedPaths.Auth)} + isLoading={isViewLoading} + onSubmit={handleVerifyOtp}> + + Enter the 6-digit access code from that email to sign in to your account. If you don't see the email, please + check your spam folder. + + + + + Access Code + + + + + + + + + Didn't receive the email?{" "} + {cooldownSeconds === 0 ? ( + + ) : ( + <>Send again in {cooldownSeconds} seconds + )} + + + {isAlreadyRegisteredParam === "0" ? null : ( + + + + )} + + ); +} diff --git a/apps/wander-connect/src/views/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx b/apps/wander-connect/src/views/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx new file mode 100644 index 000000000..6d8936259 --- /dev/null +++ b/apps/wander-connect/src/views/auth/auth-email-sign-in-password/auth-email-sign-in-password.view.tsx @@ -0,0 +1,137 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { toast } from "react-toastify"; +import { Button, Text, TextInput, PasswordInput, OnboardingCard, InputButton } from "@wanderapp/ui"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useLocation, useSearchParams, PersistentStorage, useStorage, PreferredEmailAuth, StorageKeys } from "@wanderapp/core"; +import { EditIcon } from "@iconicicons/react"; +import { getFriendlyAuthErrorMessage } from "../../../domains/authentication/authentication.utils"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +export function AuthEmailSignInPasswordEmbeddedView() { + const { navigate } = useLocation(); + const { email } = useSearchParams<{ email: string }>(); + const { authStatus, authenticate } = useEmbedded(); + + const [_, setPreferredEmailAuth] = useStorage({ + key: StorageKeys.CONNECT.AUTH.PREFERRED_EMAIL_AUTH, + instance: PersistentStorage, + }); + + // Input refs: + + const passwordInputRef = useRef(); + + // Loading state: + + const [isAuthenticating, setIsAuthenticating] = useState(false); + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isAuthenticating; + const areButtonsDisabled = isViewLoading; + + // Handlers: + + const handleEmailSignIn = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + try { + const password = passwordInputRef.current?.value || ""; + + if (!password) { + toast.error("Please enter an email and password"); + return; + } + + setIsAuthenticating(true); + + await authenticate({ + method: "signInWithPassword", + email, + password, + }); + + setPreferredEmailAuth("password"); + } catch (error) { + if (error.code === "email_not_confirmed") { + navigate(EmbeddedPaths.AuthEmailVerify); + } else { + toast.error(getFriendlyAuthErrorMessage(error, error.message || "Error signing in")); + } + } finally { + setIsAuthenticating(false); + } + }, + [email], + ); + + useEffect(() => { + if (!email) { + if (process.env.NODE_ENV === "development") { + throw new Error("No email search param. The router should have taken care of this."); + } else { + navigate(EmbeddedPaths.Auth); + } + } + }, [email]); + + const editIcon = ( + + ); + + const emailInputButton = ( + navigate("/auth", { search: { email } })} + /> + ); + + return ( + navigate(`/auth`)} + onSubmit={handleEmailSignIn}> + + + + + + + + Forgot your password?{" "} + + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/auth-email-sign-up/auth-email-sign-up.view.tsx b/apps/wander-connect/src/views/auth/auth-email-sign-up/auth-email-sign-up.view.tsx new file mode 100644 index 000000000..d16a24917 --- /dev/null +++ b/apps/wander-connect/src/views/auth/auth-email-sign-up/auth-email-sign-up.view.tsx @@ -0,0 +1,176 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { toast } from "react-toastify"; +import { Button, TextInput, PasswordStrength, PasswordInput, OnboardingCard, InputButton } from "@wanderapp/ui"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useLocation, useSearchParams, PersistentStorage, StorageKeys } from "@wanderapp/core"; +import { useThrottledCallback } from "@swyg/corre"; +import { EditIcon } from "@iconicicons/react"; +import browser from "webextension-polyfill"; +import type { SupabaseUserMetadata } from "embed-api"; +import { getFriendlyAuthErrorMessage, MIN_SUPABASE_PASSWORD_LENGTH } from "../../../domains/authentication/authentication.utils"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +export function AuthEmailSignUpEmbeddedView() { + const { navigate } = useLocation(); + const { email } = useSearchParams<{ email: string }>(); + const { authStatus } = useEmbedded(); + + // Input refs: + + const passwordInputRef = useRef(); + const repeatPasswordInputRef = useRef(); + + // Loading state: + + const [isAuthenticating, setIsAuthenticating] = useState(false); + + const areButtonsDisabled = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isAuthenticating; + + const isViewLoading = areButtonsDisabled; + + // Passwords match: + + const [{ password, passwordsMatch }, setPasswordsState] = useState({ + password: "", + passwordsMatch: false, + }); + + const isPasswordValid = passwordsMatch && password.length >= MIN_SUPABASE_PASSWORD_LENGTH; + + const handlePasswordChange = useThrottledCallback(() => { + const password = passwordInputRef.current.value; + const repeatPassword = repeatPasswordInputRef.current.value; + + setPasswordsState({ + password, + passwordsMatch: password === repeatPassword, + }); + }, 250); + + const handleEmailSignUp = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const password = passwordInputRef.current.value || ""; + const repeatPassword = repeatPasswordInputRef.current.value || ""; + + try { + setIsAuthenticating(true); + + const supabase = await getSupabaseClient(); + + if (!email || !password) { + toast.error("Please enter an email and password"); + return; + } + + if (password !== repeatPassword) { + toast.error(browser.i18n.getMessage("passwords_not_match")); + return; + } + + if (password.length < MIN_SUPABASE_PASSWORD_LENGTH) { + toast.error(`Password must be at least ${MIN_SUPABASE_PASSWORD_LENGTH} characters`); + return; + } + + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + data: { + hasPassword: true, + } satisfies SupabaseUserMetadata, + }, + }); + + if (error) { + toast.error(getFriendlyAuthErrorMessage(error, error.message || "Error signing up")); + return; + } + + await PersistentStorage.setItem(StorageKeys.CONNECT.AUTH.LAST_OTP_EMAIL, Date.now()); + + navigate(EmbeddedPaths.AuthEmailVerify, { + search: { email }, + }); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error signing up")); + } finally { + setIsAuthenticating(false); + } + }, + [email], + ); + + useEffect(() => { + if (!email) { + if (process.env.NODE_ENV === "development") { + throw new Error("No email search param. The router should have taken care of this."); + } else { + navigate(EmbeddedPaths.Auth); + } + } + }, [email]); + + const editIcon = ( + + ); + + const emailInputButton = ( + navigate("/auth", { search: { email } })} + /> + ); + + return ( + navigate(EmbeddedPaths.Auth)} + isLoading={isViewLoading} + onSubmit={handleEmailSignUp}> + + + + + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/auth-email-verify/auth-email-verify.view.tsx b/apps/wander-connect/src/views/auth/auth-email-verify/auth-email-verify.view.tsx new file mode 100644 index 000000000..9ddc1a14b --- /dev/null +++ b/apps/wander-connect/src/views/auth/auth-email-verify/auth-email-verify.view.tsx @@ -0,0 +1,181 @@ +import { toast } from "react-toastify"; +import { Text, Button, OnboardingCard, Flex, CodeInput, type CodeInputHandle } from "@wanderapp/ui"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useLocation, useSearchParams, StorageKeys, + checkNeedsNewOtp, + clearOtpAvailable, + OTP_COOLDOWN_DURATION_SEC, + OTP_LENGTH, + setOtpAvailable, + useAsyncEffect, + useCooldownCallback +} from "@wanderapp/core"; +import { getFriendlyAuthErrorMessage } from "../../../domains/authentication/authentication.utils"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { getSupabaseClient } from "../../../utils/embedded.utils"; + +export function AuthEmailVerifyEmbeddedView() { + const { navigate } = useLocation(); + const { authStatus, authenticate } = useEmbedded(); + const { email } = useSearchParams<{ email: string }>(); + + // Loading state: + + const [isResending, setIsResending] = useState(false); + const [isVerifying, setIsVerifying] = useState(false); + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isVerifying; + const areButtonsDisabled = isViewLoading || isResending; + + // Code input: + + const codeInputRef = useRef(); + const [isComplete, setIsComplete] = useState(false); + + const handleCodeChange = useCallback((_, isComplete: boolean) => { + setIsComplete(isComplete); + }, []); + + // Code retrieval: + + const { fn: resendEmail, cooldownSeconds } = useCooldownCallback( + async (showConfirmationToast: boolean) => { + if (!email) return; + + codeInputRef.current.clear(); + + try { + setIsResending(true); + const supabase = await getSupabaseClient(); + + const { error } = await supabase.auth.resend({ + type: "signup", + email, + }); + + if (error) { + toast.error(getFriendlyAuthErrorMessage(error, error.message)); + return; + } + + setOtpAvailable(); + + if (showConfirmationToast) toast.success("Verification email resent successfully"); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error sending verification email")); + } finally { + setIsResending(false); + } + }, + { + key: StorageKeys.CONNECT.AUTH.LAST_OTP_EMAIL, + cooldownDuration: OTP_COOLDOWN_DURATION_SEC, + }, + ); + + useAsyncEffect(async () => { + try { + if (await checkNeedsNewOtp()) resendEmail(false); + } catch (err) { + // When coming from `AuthEmailSignUpEmbeddedView` for the first time, `LAST_OTP_EMAIL` would already be set + // in `localStorage`, so this call will throw an error. If the user doesn't confirm the email now and comes back + // later, then if the required time has passed, a new code will be requested automatically. + } + }, [resendEmail]); + + // Code verification: + + const handleVerifyCode = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + if (!email || isVerifying) return; + + const otpCode = codeInputRef.current.getCode(); + + if (otpCode.length !== OTP_LENGTH) { + toast.error(`Please enter all ${OTP_LENGTH} digits of the verification code`); + return; + } + + setIsVerifying(true); + + try { + await authenticate({ + method: "verifyOtp", + email, + token: otpCode, + }); + + toast.success("Email verified successfully"); + } catch (error) { + setIsVerifying(false); + toast.error(getFriendlyAuthErrorMessage(error, "Invalid or expired code")); + } finally { + clearOtpAvailable(); + } + + // We leave isVerifying = true intentionally as the user will simply be redirected after verifying the account. + }, + [email, isVerifying, authenticate], + ); + + useEffect(() => { + if (!email) { + if (process.env.NODE_ENV === "development") { + throw new Error("No email search param. The router should have taken care of this."); + } else { + navigate(EmbeddedPaths.Auth); + } + } + }, [email]); + + return ( + navigate(EmbeddedPaths.Auth)} + isLoading={isViewLoading} + onSubmit={handleVerifyCode}> + + Enter the 6-digit verification code from that email to complete signup. If you don't see the email, please check + your spam folder. + + + + + Verification Code + + + + + + + + + Didn't receive the email?{" "} + {cooldownSeconds === 0 ? ( + + ) : ( + <>Send again in {cooldownSeconds} seconds + )} + + + ); +} diff --git a/apps/wander-connect/src/views/auth/auth-more-providers/auth-more-providers.view.tsx b/apps/wander-connect/src/views/auth/auth-more-providers/auth-more-providers.view.tsx new file mode 100644 index 000000000..717691539 --- /dev/null +++ b/apps/wander-connect/src/views/auth/auth-more-providers/auth-more-providers.view.tsx @@ -0,0 +1,69 @@ +import { AppleIcon, Button, FacebookIcon, TwitterIcon, OnboardingCard } from "@wanderapp/ui"; +import { useCallback, useState } from "react"; +import { useLocation } from "@wanderapp/core"; +import { toast } from "react-toastify"; +import { getFriendlyAuthErrorMessage } from "../../../domains/authentication/authentication.utils"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { OAutProviderType } from "../../../utils/embedded.types"; + +export function AuthMoreProvidersEmbeddedView() { + const { navigate } = useLocation(); + const { authenticate, authStatus, recoverableAccount } = useEmbedded(); + + // Loading state: + + const [isAuthenticating, setIsAuthenticating] = useState(false); + + const areButtonsDisabled = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isAuthenticating; + + const isViewLoading = areButtonsDisabled; + + // Handlers: + + const handleAuthenticate = useCallback(async (authProviderType: OAutProviderType) => { + try { + setIsAuthenticating(true); + await authenticate(authProviderType); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, `Error signing in with ${authProviderType}`)); + } finally { + setIsAuthenticating(false); + } + }, []); + + return ( + navigate(`/auth`)} + isLoading={isViewLoading}> + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/auth/auth.view.tsx b/apps/wander-connect/src/views/auth/auth/auth.view.tsx new file mode 100644 index 000000000..27d5036c1 --- /dev/null +++ b/apps/wander-connect/src/views/auth/auth/auth.view.tsx @@ -0,0 +1,257 @@ +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { toast } from "react-toastify"; +import { + Button, + Divider, + GoogleIcon, + TextInput, + Row, + SocialsIcon, + Text, + Wander2Icon, + RecoverHeaderIcon, + Snackbar, + KeyIcon, + InputButton, + OnboardingCard, +} from "@wanderapp/ui"; +import React, { useCallback, useRef, useState } from "react"; +import { EMBEDDED_HIDE_BE, EMBEDDED_INJECTED_BE, isInsideIframe } from "../../../utils/iframe.utils"; +import { useAsyncEffect, StorageKeys, PersistentStorage, useStorage, PreferredEmailAuth, sleep, isValidEmail, useLocation, useSearchParams } from "@wanderapp/core"; +import { getFriendlyAuthErrorMessage } from "../../../domains/authentication/authentication.utils"; +import { OAutProviderType } from "../../../utils/embedded.types"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { postEmbeddedMessage } from "../../../utils/utils/messages/embedded-messages.utils"; +import { getSupabaseClient } from "../../../utils/embedded.utils"; + +const IS_PASSKEYS_ENABLED = false; + +function isPasskeysSupportedByBrowser() { + return typeof window !== "undefined" && !!window.PublicKeyCredential && !isInsideIframe(); +} + +async function isPasskeysSupportedByContext() { + if (!isPasskeysSupportedByBrowser()) return false; + + // If we're in an iframe, we need to check if the browser supports WebAuthn in iframes (the right values for the + // sandbox attribute must have been set): + + try { + return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); + } catch (error) { + console.error("WebAuthn is not available in this context =", error); + } + + return false; +} + +export function AuthEmbeddedView() { + const { navigate } = useLocation(); + + const { email, isAlreadyRegistered: isAlreadyRegisteredParam } = useSearchParams<{ + email: string; + isAlreadyRegistered: string; + }>(); + + const { authStatus, authenticate, recoverableAccount } = useEmbedded(); + + const [isUsingNativeWallet, setIsUsingNativeWallet] = useStorage( + { + key: StorageKeys.CONNECT.AUTH.IS_USING_BE, + instance: PersistentStorage, + }, + (storedValue, isHydrated) => { + if (!isHydrated) return undefined; + + return storedValue === undefined ? EMBEDDED_INJECTED_BE : storedValue; + }, + ); + + const [preferredEmailAuth] = useStorage({ + key: StorageKeys.CONNECT.AUTH.PREFERRED_EMAIL_AUTH, + instance: PersistentStorage, + }); + + // Passkeys: + + const [isPasskeysSupported, setIsPasskeysSupported] = useState(() => isPasskeysSupportedByBrowser()); + + useAsyncEffect(async () => { + setIsPasskeysSupported(await isPasskeysSupportedByContext()); + }, []); + + // Input refs: + + const emailInputRef = useRef(); + + // Loading state: + + const [isAuthenticating, setIsAuthenticating] = useState(false); + const [isCheckingEmail, setIsCheckingEmail] = useState(false); + + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isAuthenticating; + + const areButtonsDisabled = isViewLoading || isCheckingEmail; + + // Handlers: + + const handleAuthenticate = useCallback(async (authProviderType: OAutProviderType) => { + try { + setIsAuthenticating(true); + await authenticate(authProviderType); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, `Error signing in with ${authProviderType}`)); + } finally { + setIsAuthenticating(false); + } + }, []); + + const handleNativeWallet = useCallback(async () => { + setIsAuthenticating(true); + + try { + postEmbeddedMessage({ + type: "embedded_auth", + data: { + authType: "NATIVE_WALLET", + authStatus: null, + userDetails: null, + }, + }); + + await sleep(500); + } finally { + // Reset this shortly after the modal is closed so that if the user opens + // it again, they can pick a different option: + setIsAuthenticating(false); + setIsUsingNativeWallet(true); + } + }, []); + + const handleCheckEmail = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + try { + setIsCheckingEmail(true); + + const supabase = await getSupabaseClient(); + + const email = emailInputRef.current?.value || ""; + + if (!email || !isValidEmail(email)) { + toast.error("Please enter a valid email address"); + return; + } + + const { data, error } = await supabase.rpc("user_exists_by_email", { + p_email: email, + }); + + const isAlreadyRegistered = + isAlreadyRegisteredParam === "0" + ? false + : !!data || error?.message === "An account with this email already exists, but it is not using a password."; + + if (error && error.message !== "An account with this email already exists, but it is not using a password.") { + toast.error(getFriendlyAuthErrorMessage(error, error.message || "Error checking email")); + return; + } + + // In order to make sure we hide the "Use password instead" link to both new and unverified users, the RPC call above would need to check whether the + // specific email is verified too. Passing around the `isAlreadyRegistered` URL param as we are doing is less reliable. + + navigate( + !isAlreadyRegistered || preferredEmailAuth !== "password" + ? EmbeddedPaths.AuthEmailOtp + : EmbeddedPaths.AuthEmailSignInPassword, + { + search: { email, isAlreadyRegistered: isAlreadyRegistered ? "1" : "0" }, + }, + ); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error checking email")); + } finally { + setIsCheckingEmail(false); + } + }, + [preferredEmailAuth, isAlreadyRegisteredParam], + ); + + const emailInputButton = ; + const showWanderExtensionButton = !EMBEDDED_HIDE_BE && window.arweaveWallet?.walletName === "ArConnect"; + const showWanderExtensionMessage = showWanderExtensionButton && isUsingNativeWallet; + + // TODO: Remember last selection and highlight that one / show it in the main screen (not in "More") + + return ( + : null} + headerText={recoverableAccount ? "Select New Sign In Method" : "Sign Up or Sign In"} + hasBackButton={false} + isLoading={isViewLoading} + onSubmit={handleCheckEmail}> + {showWanderExtensionMessage ? ( + + ) : null} + + + + + + {IS_PASSKEYS_ENABLED && isPasskeysSupported && ( + + )} + + {showWanderExtensionButton ? ( + + + + + + ) : ( + + )} + + + + {!recoverableAccount ? ( + + Can't sign in?{" "} + + + ) : null} + + ); +} diff --git a/apps/wander-connect/src/views/auth/import-keyfile/auth-import-keyfile.view.tsx b/apps/wander-connect/src/views/auth/import-keyfile/auth-import-keyfile.view.tsx new file mode 100644 index 000000000..9090042bf --- /dev/null +++ b/apps/wander-connect/src/views/auth/import-keyfile/auth-import-keyfile.view.tsx @@ -0,0 +1,118 @@ +import { useEffect, useState } from "react"; +import { Row, Upload, Copyable, Button, OnboardingCard, Snackbar } from "@wanderapp/ui"; +import copy from "copy-to-clipboard"; +import { useLocation, useWalletUpload, WalletUtils } from "@wanderapp/core"; +import { toast } from "react-toastify"; +import { useEmbedded } from "../../../utils/embedded.hooks"; + +export function AuthImportKeyfileEmbeddedView() { + const { navigate } = useLocation(); + const { importTempWallet, deleteImportedTempWallet, registerWallet, wallets, authStatus } = useEmbedded(); + + // Upload: + + const { + data: uploadData, + isLoading: isUploading, + error: uploadError, + importedWalletAddress, + parse: parseUpload, + reset: resetUpload, + } = useWalletUpload({ + wallets, + importTempWallet, + allowRecoveryFile: false, + mustWalletExist: false, + }); + + // Loading state: + + const [isAdding, setIsAdding] = useState(false); + + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isAdding; + + const areButtonsDisabled = isViewLoading || isUploading; + + const handleAddWallet = async () => { + try { + if (areButtonsDisabled) return; + + if (uploadError) { + toast.error(uploadError); + + return; + } + + if (!WalletUtils.isJWK(uploadData)) { + toast.error("Invalid file."); + + return; + } + + setIsAdding(true); + + await registerWallet("IMPORTED"); + } catch (error) { + console.error(error); + toast.error("Unexpected error while importing wallet."); + } finally { + setIsAdding(false); + } + }; + + const handleTryAgain = () => { + resetUpload(); + deleteImportedTempWallet(); + }; + + useEffect(() => { + return () => { + // Remove the imported keyfile from memory as soon as we leave this view. Note at this point it will already have + // been passed to `importTempWallet()`, if the user confirmed: + deleteImportedTempWallet(); + }; + }, []); + + return importedWalletAddress ? ( + navigate(`/auth/add-wallet`)} + isLoading={isViewLoading}> + { + copy(importedWalletAddress); + }} + value={importedWalletAddress} + /> + + + + + + ) : ( + navigate(`/auth/add-wallet`)} + isLoading={isViewLoading}> + + + {uploadError} + + ); +} diff --git a/src/routes/embedded/auth/import-qrcode/auth-import-qrcode.tsx b/apps/wander-connect/src/views/auth/import-qrcode/auth-import-qrcode.tsx similarity index 87% rename from src/routes/embedded/auth/import-qrcode/auth-import-qrcode.tsx rename to apps/wander-connect/src/views/auth/import-qrcode/auth-import-qrcode.tsx index acaa6e8c0..06cb210a7 100644 --- a/src/routes/embedded/auth/import-qrcode/auth-import-qrcode.tsx +++ b/apps/wander-connect/src/views/auth/import-qrcode/auth-import-qrcode.tsx @@ -1,4 +1,4 @@ -import { useEmbedded } from "~utils/embedded/embedded.hooks"; +import { useEmbedded } from "../../../utils/embedded.hooks"; import { QrCodeScanEmbeddedView } from "./components/QrCodeEmbeddedView"; export function AuthImportQrCodeEmbeddedView() { diff --git a/apps/wander-connect/src/views/auth/import-qrcode/components/QrCodeEmbeddedView.tsx b/apps/wander-connect/src/views/auth/import-qrcode/components/QrCodeEmbeddedView.tsx new file mode 100644 index 000000000..8fb814353 --- /dev/null +++ b/apps/wander-connect/src/views/auth/import-qrcode/components/QrCodeEmbeddedView.tsx @@ -0,0 +1,178 @@ +import { useCallback, useEffect, useState } from "react"; +import { Row, Copyable, Button, CameraIcon, Box, QRLoopScanner, OnboardingCard, Link } from "@wanderapp/ui"; +import copy from "copy-to-clipboard"; +import { useLocation, WalletUtils, WanderRoutePath } from "@wanderapp/core"; +import { toast } from "react-toastify"; +import { JWKInterface } from "@dha-team/arbundles/node"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; +import { useWebcamPermission } from "../hooks/useWebcamPermission"; + +export interface QrCodeScanEmbeddedViewProps { + headerText: string; + subtitle: string; + backButtonClickHref: WanderRoutePath; + type: "importWallet" | "restoreWallet" | "recoverAccount"; +} + +export function QrCodeScanEmbeddedView({ + headerText, + subtitle, + backButtonClickHref, + type, +}: QrCodeScanEmbeddedViewProps) { + const { navigate } = useLocation(); + const [isLoading, setIsLoading] = useState(false); + const [jwkData, setJwkData] = useState(null); + + const { + importTempWallet, + deleteImportedTempWallet, + registerWallet, + recoverWallet, + importedTempWalletAddress, + fetchRecoverableAccounts, + fetchRecoverableAccountWallets, + clearRecoverableAccounts, + } = useEmbedded(); + const { isLoading: isWebcamLoading, requestPermission, permissionStatus } = useWebcamPermission(); + + const areButtonsDisabled = isLoading; + const isViewLoading = isLoading; + + const handleOpenWebcam = useCallback(async () => { + try { + const permissionGranted = await requestPermission(); + + if (!permissionGranted) { + toast.error("Permission denied"); + } + } catch (error) { + toast.error(error); + } + }, [navigate, requestPermission]); + + const handleConfirm = async () => { + try { + if (areButtonsDisabled) return; + + if (!WalletUtils.isJWK(jwkData)) { + toast.error("Invalid QR code."); + + return; + } + + setIsLoading(true); + + if (type === "importWallet") { + await registerWallet("IMPORTED"); + } else if (type === "restoreWallet") { + await recoverWallet(jwkData); + } else { + const recoverableAccounts = await fetchRecoverableAccounts(); + + if (recoverableAccounts.length === 1) { + await fetchRecoverableAccountWallets(recoverableAccounts[0]); + navigate(EmbeddedPaths.Auth); + } else if (recoverableAccounts.length > 1) { + navigate("/auth/recover-account/select"); + } else { + toast.error("No recoverable accounts found"); + } + } + } catch (error) { + console.error(error); + const errorMessage = `Unexpected error while ${type === "importWallet" ? "importing" : type === "restoreWallet" ? "restoring" : "recovering"} ${type !== "recoverAccount" ? "wallet" : "account"}.`; + toast.error(errorMessage); + } finally { + setIsLoading(false); + } + }; + + const handleScanResult = async (result: JWKInterface) => { + try { + if (!WalletUtils.isJWK(result)) { + throw new Error("Invalid QR code."); + } + + setJwkData(result); + await importTempWallet(result); + } catch (error) { + toast.error(error); + } + }; + + const handleTryAgain = () => { + deleteImportedTempWallet(); + }; + + useEffect(() => { + if (type === "recoverAccount") { + deleteImportedTempWallet(); + clearRecoverableAccounts(); + } + + return () => { + // Remove the imported keyfile from memory as soon as we leave this view. Note at this point it will already have + // been passed to `importTempWallet()`, if the user confirmed: + if (type !== "recoverAccount") { + deleteImportedTempWallet(); + } + }; + }, [type]); + + return importedTempWalletAddress ? ( + navigate(backButtonClickHref)} + isLoading={isViewLoading}> + { + copy(importedTempWalletAddress); + }} + value={importedTempWalletAddress} + /> + + + + + + ) : ( + navigate(backButtonClickHref)} + isLoading={isViewLoading}> + {permissionStatus === "granted" ? ( + <> + + + Where do I find my QR code? + + + ) : ( + + + + )} + + ); +} diff --git a/apps/wander-connect/src/views/auth/import-qrcode/hooks/useWebcamPermission.tsx b/apps/wander-connect/src/views/auth/import-qrcode/hooks/useWebcamPermission.tsx new file mode 100644 index 000000000..952e78a73 --- /dev/null +++ b/apps/wander-connect/src/views/auth/import-qrcode/hooks/useWebcamPermission.tsx @@ -0,0 +1,104 @@ +import { useState, useEffect, useCallback } from "react"; + +/** + * Permission statuses for webcam access + */ +type PermissionStatus = "prompt" | "granted" | "denied" | "not-supported"; + +/** + * Custom hook for managing webcam permission + * @returns Object with permission status, loading state, and request function + */ +export function useWebcamPermission() { + const [permissionStatus, setPermissionStatus] = useState("prompt"); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // Check if the browser supports mediaDevices API + const isSupported = + typeof navigator !== "undefined" && navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia; + + // Initialize permission status based on browser support + useEffect(() => { + if (!isSupported) { + setPermissionStatus("not-supported"); + } + }, [isSupported]); + + // Request webcam permission + const requestPermission = useCallback(async () => { + if (!isSupported) { + setError("Camera API is not supported in this browser"); + return false; + } + + setIsLoading(true); + setError(null); + + try { + // Request video stream (this triggers permission dialog) + const stream = await navigator.mediaDevices.getUserMedia({ + video: true, + audio: false, + }); + + // If we get here, permission was granted + setPermissionStatus("granted"); + + // Stop all tracks to release the camera + stream.getTracks().forEach((track) => track.stop()); + + return true; + } catch (err) { + // Handle different error types + if (err instanceof Error) { + if (err.name === "NotAllowedError" || err.name === "PermissionDeniedError") { + setPermissionStatus("denied"); + setError("Camera access was denied"); + } else { + setError(`Error accessing camera: ${err.message}`); + } + } else { + setError("Unknown error occurred when accessing camera"); + } + + return false; + } finally { + setIsLoading(false); + } + }, [isSupported]); + + // Check for existing permissions + useEffect(() => { + // If the Permissions API is available, check current permission status + if (navigator.permissions && navigator.permissions.query) { + navigator.permissions + .query({ name: "camera" as PermissionName }) + .then((permissionStatus) => { + if (permissionStatus.state === "granted") { + setPermissionStatus("granted"); + } else if (permissionStatus.state === "denied") { + setPermissionStatus("denied"); + } else { + setPermissionStatus("prompt"); + } + + // Listen for changes to permission state + permissionStatus.onchange = () => { + setPermissionStatus(permissionStatus.state as PermissionStatus); + }; + }) + .catch(() => { + // If permissions API fails, we'll rely on the getUserMedia request + console.log("Permissions API not fully supported, will use getUserMedia for permission check"); + }); + } + }, []); + + return { + permissionStatus, + isLoading, + error, + requestPermission, + }; +} diff --git a/apps/wander-connect/src/views/auth/import-seedphrase/auth-import-seedphrase.view.tsx b/apps/wander-connect/src/views/auth/import-seedphrase/auth-import-seedphrase.view.tsx new file mode 100644 index 000000000..a8d91a8c1 --- /dev/null +++ b/apps/wander-connect/src/views/auth/import-seedphrase/auth-import-seedphrase.view.tsx @@ -0,0 +1,131 @@ +import copy from "copy-to-clipboard"; +import { useCallback, useEffect, useState } from "react"; +import { toast } from "react-toastify"; +import { Button, Row, SeedInput, Copyable, OnboardingCard } from "@wanderapp/ui"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { useLocation } from "@wanderapp/core"; + +export function AuthImportSeedphraseEmbeddedView() { + const { navigate } = useLocation(); + const { importTempWallet, importedTempWalletAddress, deleteImportedTempWallet, registerWallet, wallets, authStatus } = + useEmbedded(); + + // Loading state: + + const [isLoading, setIsLoading] = useState(false); + + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isLoading; + + const areButtonsDisabled = isViewLoading; + + // Seed phrase: + + const [seedPhrase, setSeedPhrase] = useState([]); + + const validateSeedPhrase = useCallback(() => { + const parsedSeedPhrase = seedPhrase.filter((word) => !!word.trim()); + + if (![12, 18, 24].includes(parsedSeedPhrase.length)) { + toast.error("Incomplete seedphrase."); + + return false; + } + + return true; + }, [seedPhrase]); + + const handleImportWallet = useCallback(async () => { + try { + const isSeedPhraseValid = validateSeedPhrase(); + + if (!isSeedPhraseValid) return; + + setIsLoading(true); + + await importTempWallet(seedPhrase.join(" ")); + } catch (error) { + toast.error(error); + } finally { + setIsLoading(false); + } + }, [seedPhrase]); + + const handleAddWallet = useCallback(async () => { + try { + const isSeedPhraseValid = validateSeedPhrase(); + + if (!isSeedPhraseValid) return; + + const isWalletPresent = wallets.some(({ address }) => address === importedTempWalletAddress); + + if (isWalletPresent) { + toast.error("This wallet was already added to your account."); + + return; + } + + setIsLoading(true); + await registerWallet("IMPORTED"); + } catch (error) { + console.error(error); + toast.error("An unexpected error happened."); + } finally { + setIsLoading(false); + } + }, [registerWallet, seedPhrase, wallets, importedTempWalletAddress]); + + const handleInputChange = useCallback((index: number, value: string) => { + setSeedPhrase((prevSeedPhrase) => { + const newSeedPhrase = [...prevSeedPhrase]; + newSeedPhrase[index] = value; + return newSeedPhrase; + }); + }, []); + + useEffect(() => { + return () => { + // Remove the imported keyfile from memory as soon as we leave this view. Note at this point it will already have + // been passed to `importTempWallet()`, if the user confirmed: + deleteImportedTempWallet(); + }; + }, []); + + return importedTempWalletAddress ? ( + navigate(`/auth/add-wallet`)} + isLoading={isViewLoading}> + { + copy(importedTempWalletAddress); + }} + value={importedTempWalletAddress} + /> + + + + + + ) : ( + navigate(`/auth/add-wallet`)} + isLoading={isViewLoading}> + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/qrcode-scanner/auth-qrcode-scanner.view.tsx b/apps/wander-connect/src/views/auth/qrcode-scanner/auth-qrcode-scanner.view.tsx new file mode 100644 index 000000000..e92cef34a --- /dev/null +++ b/apps/wander-connect/src/views/auth/qrcode-scanner/auth-qrcode-scanner.view.tsx @@ -0,0 +1,18 @@ +import { Card, WanderFooter, QrReader } from "@wanderapp/ui"; +import { useLocation } from "@wanderapp/core"; +import { QrReader } from "~components/embed/ui/organisms"; + +export function AuthQRCodeScannerEmbeddedView() { + const { back } = useLocation(); + + return ( + } + hasBackButton={true} + onBackButtonClick={back}> + + + ); +} diff --git a/apps/wander-connect/src/views/auth/recover-account/auth-recover-account.view.tsx b/apps/wander-connect/src/views/auth/recover-account/auth-recover-account.view.tsx new file mode 100644 index 000000000..27887f37b --- /dev/null +++ b/apps/wander-connect/src/views/auth/recover-account/auth-recover-account.view.tsx @@ -0,0 +1,108 @@ +import { useCallback, useRef, useState } from "react"; +import { toast } from "react-toastify"; +import { Button, Divider, InputButton, KeyIcon, OnboardingCard, RecoverHeaderIcon, SeedIcon, TextInput } from "@wanderapp/ui"; +import { isValidEmail, useLocation, useSearchParams } from "@wanderapp/core"; +import { QrCode02 } from "@untitled-ui/icons-react"; +import { getFriendlyAuthErrorMessage } from "../../../domains/authentication/authentication.utils"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { getSupabaseClient } from "../../../utils/embedded.utils"; + +export function AuthRecoverAccountEmbeddedView() { + const { navigate } = useLocation(); + const { email } = useSearchParams<{ email: string }>(); + const { authStatus } = useEmbedded(); + + // Input refs: + + const emailInputRef = useRef(); + + // Loading state: + + const [isCheckingEmail, setIsCheckingEmail] = useState(false); + const isViewLoading = authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading"; + const areButtonsDisabled = isViewLoading || isCheckingEmail; + + // Handlers: + + const handleCheckEmail = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + + try { + setIsCheckingEmail(true); + + const supabase = await getSupabaseClient(); + + const email = emailInputRef.current?.value || ""; + + if (!email || !isValidEmail(email)) { + toast.error("Please enter a valid email address"); + return; + } + + const { data, error } = await supabase.rpc("user_exists_by_email", { + p_email: email, + }); + + const isAlreadyRegistered = + !!data || error?.message === "An account with this email already exists, but it is not using a password."; + + if (error && error.message !== "An account with this email already exists, but it is not using a password.") { + toast.error(getFriendlyAuthErrorMessage(error, error.message || "Error checking email")); + return; + } + + if (!isAlreadyRegistered) { + toast.error(`No account found for ${email}`); + return; + } + + navigate(EmbeddedPaths.AuthRecoverAccountOtp, { + search: { email }, + }); + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error checking email")); + } finally { + setIsCheckingEmail(false); + } + }, []); + + const emailInputButton = ; + + return ( + } + headerText="Recover your account" + subtitle="Select a method for recovering your account." + onBackButtonClick={() => navigate(`/auth`)} + isLoading={isViewLoading} + onSubmit={handleCheckEmail}> + + + + + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/recover-account/confirm/auth-recover-confirm.view.tsx b/apps/wander-connect/src/views/auth/recover-account/confirm/auth-recover-confirm.view.tsx new file mode 100644 index 000000000..2c8b4fc4a --- /dev/null +++ b/apps/wander-connect/src/views/auth/recover-account/confirm/auth-recover-confirm.view.tsx @@ -0,0 +1,151 @@ +import { useState, useCallback, useMemo, useRef, useEffect } from "react"; +import { toast } from "react-toastify"; +import { Button, Checkbox, OnboardingCard, RecoverHeaderIcon, Snackbar } from "@wanderapp/ui"; +import { formatAddress, useLocation, withRetry } from "@wanderapp/core"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; + +export function AuthRecoverAccountConfirmEmbeddedView() { + const { navigate } = useLocation(); + const { + recoverAccount, + recoverWallet, + recoverableAccount, + deleteImportedTempWallet, + setRecoverableAccount, + recoverableAccountWallets, + authProviderType, + wallets, + } = useEmbedded(); + const [shouldRecoverWallet, setShouldRecoverWallet] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isChecked, setIsChecked] = useState(false); + const isAccountRecovered = useRef(false); + const isWalletRecovered = useRef(false); + const walletsRef = useRef(wallets); + + const lostWallets = useMemo( + () => recoverableAccountWallets?.filter((wallet) => wallet.canBeRecovered === false) || [], + [recoverableAccountWallets], + ); + + const recoverableWallets = useMemo( + () => recoverableAccountWallets?.filter((wallet) => wallet.canBeRecovered === true) || [], + [recoverableAccountWallets], + ); + + const handleRecoverWallet = useCallback(async () => { + try { + if (!isWalletRecovered.current) { + setIsLoading(true); + await new Promise((resolve) => { + const interval = setInterval(() => { + if (walletsRef.current.length > 0) { + clearInterval(interval); + resolve(true); + } + }, 1000); + }); + await withRetry(recoverWallet, 3, 1000); + await deleteImportedTempWallet(); + isWalletRecovered.current = true; + } + } catch { + } finally { + setRecoverableAccount(null); + navigate(EmbeddedPaths.WalletHomeEmbeddedView); + setIsLoading(false); + } + }, [recoverWallet, deleteImportedTempWallet, navigate]); + + const handleRecoverAccount = useCallback(async () => { + try { + setIsLoading(true); + setShouldRecoverWallet(false); + if (walletsRef.current.length > 0) { + isAccountRecovered.current = true; + setShouldRecoverWallet(true); + return; + } + + if (!isAccountRecovered.current) { + await recoverAccount(authProviderType, recoverableAccount.userId); + isAccountRecovered.current = true; + setShouldRecoverWallet(true); + } + } catch (error) { + toast.error(error?.message || "Error recovering account"); + } finally { + setIsLoading(false); + } + }, [recoverAccount, recoverableAccount, authProviderType]); + + useEffect(() => { + if (shouldRecoverWallet && !isWalletRecovered.current) { + handleRecoverWallet(); + setShouldRecoverWallet(false); + } + }, [shouldRecoverWallet, handleRecoverWallet]); + + useEffect(() => { + walletsRef.current = wallets; + }, [wallets]); + + return ( + } + headerText="Recover account" + onBackButtonClick={() => navigate(EmbeddedPaths.AuthRecoverAccount)} + isLoading={isLoading}> + + + {lostWallets.length > 0 ? ( + +

+ These wallets have never been backed up. After recovery, you will permanently lose access to these wallets. +

+
    + {lostWallets.map((wallet, index) => ( +
  • • {formatAddress(wallet.address, 16)}
  • + ))} +
+
+ ) : null} + + {recoverableWallets.length > 0 ? ( + +

+ These wallets can be recovered. You will need to follow the recovery process for each wallet. Make sure you + have your recovery information (recovery file, seed phrase or private key) ready. +

+
    + {recoverableWallets.map((wallet) => ( +
  • • {formatAddress(wallet.address, 16)}
  • + ))} +
+
+ ) : null} + + 0 + ? "I understand some wallets will be permanently lost." + : "I have my recovery information ready for each wallet." + } + description="Note: you can set this up on the settings page" + isDisabled={isLoading} + handleChange={() => setIsChecked(!isChecked)} + isChecked={isChecked} + /> + + +
+ ); +} diff --git a/apps/wander-connect/src/views/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx b/apps/wander-connect/src/views/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx new file mode 100644 index 000000000..ddba6dafb --- /dev/null +++ b/apps/wander-connect/src/views/auth/recover-account/keyfile/auth-recover-account-keyfile.view.tsx @@ -0,0 +1,128 @@ +import { useEffect, useState } from "react"; +import { useLocation, useWalletUpload, WalletUtils } from "@wanderapp/core"; +import { Row, Button, Copyable, Upload, Text, Snackbar, RecoverHeaderIcon, OnboardingCard } from "@wanderapp/ui"; +import copy from "copy-to-clipboard"; +import { toast } from "react-toastify"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; + +export function AuthRecoverAccountKeyfileEmbeddedView() { + const { navigate } = useLocation(); + const [isRecovering, setIsRecovering] = useState(false); + + const { + importTempWallet, + deleteImportedTempWallet, + fetchRecoverableAccounts, + clearRecoverableAccounts, + fetchRecoverableAccountWallets, + } = useEmbedded(); + + const { + data: uploadData, + isLoading: isUploading, + error: uploadError, + importedWalletAddress, + parse: parseUpload, + reset: resetUpload, + } = useWalletUpload({ + wallets: [], + importTempWallet, + allowRecoveryFile: false, + mustWalletExist: false, + }); + + const areButtonsDisabled = isRecovering || isUploading; + const isViewLoading = isRecovering; + + const handleRecover = async () => { + try { + if (areButtonsDisabled) return; + + if (uploadError) { + toast.error(uploadError); + + return; + } + + if (!WalletUtils.isJWK(uploadData)) { + toast.error("Invalid file."); + + return; + } + + setIsRecovering(true); + + const recoverableAccounts = await fetchRecoverableAccounts(); + + if (recoverableAccounts.length === 1) { + await fetchRecoverableAccountWallets(recoverableAccounts[0]); + navigate(EmbeddedPaths.Auth); + } else if (recoverableAccounts.length > 1) { + navigate("/auth/recover-account/select"); + } else { + toast.error("No recoverable accounts found"); + setIsRecovering(false); + } + } catch (error) { + console.error(error); + toast.error("Unexpected error while recovering account."); + setIsRecovering(false); + } + }; + + const handleTryAgain = () => { + resetUpload(); + deleteImportedTempWallet(); + }; + + useEffect(() => { + deleteImportedTempWallet(); + clearRecoverableAccounts(); + }, []); + + return importedWalletAddress ? ( + } + headerText="Import Keyfile" + subtitle="Upload your private key to recover your account." + onBackButtonClick={() => navigate(`/auth/recover-account`)} + isLoading={isViewLoading}> + Would you like to recover this account? + { + copy(importedWalletAddress); + }} + value={importedWalletAddress} + /> + + + + + + ) : ( + } + headerText="Import Keyfile" + subtitle="Upload your private key to recover your account." + onBackButtonClick={() => navigate(EmbeddedPaths.AuthRecoverAccount)} + isLoading={isViewLoading}> + + + {uploadError} + + ); +} diff --git a/apps/wander-connect/src/views/auth/recover-account/otp/auth-recover-account-otp.view.tsx b/apps/wander-connect/src/views/auth/recover-account/otp/auth-recover-account-otp.view.tsx new file mode 100644 index 000000000..8ab48270b --- /dev/null +++ b/apps/wander-connect/src/views/auth/recover-account/otp/auth-recover-account-otp.view.tsx @@ -0,0 +1,178 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { toast } from "react-toastify"; +import { Button, Text, RecoverHeaderIcon, CodeInput, CodeInputHandle, Flex, OnboardingCard } from "@wanderapp/ui"; +import { checkNeedsNewOtp, clearOtpAvailable, OTP_COOLDOWN_DURATION_SEC, OTP_LENGTH, setOtpAvailable, StorageKeys, useAsyncEffect, useCooldownCallback, useLocation, useSearchParams } from "@wanderapp/core"; +import { getFriendlyAuthErrorMessage } from "../../../../domains/authentication/authentication.utils"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; +import { getSupabaseClient } from "../../../../utils/embedded.utils"; + +export function AuthRecoverAccountOtpEmbeddedView() { + const { navigate } = useLocation(); + const { email } = useSearchParams<{ email: string }>(); + const { authStatus, authenticate, setRequestPasswordChange } = useEmbedded(); + + // Loading state: + + const [isResending, setIsResending] = useState(false); + const [isVerifying, setIsVerifying] = useState(false); + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isVerifying; + const areButtonsDisabled = isViewLoading || isResending; + + // Code input: + + const codeInputRef = useRef(); + const [isComplete, setIsComplete] = useState(false); + + const handleCodeChange = useCallback((_, isComplete: boolean) => { + setIsComplete(isComplete); + }, []); + + // Code retrieval: + + const { fn: signInWithOtp, cooldownSeconds } = useCooldownCallback( + async (showConfirmationToast: boolean) => { + try { + if (codeInputRef.current) codeInputRef.current.clear(); + + setIsResending(true); + + const supabase = await getSupabaseClient(); + + const { error } = await supabase.auth.signInWithOtp({ + email, + options: { + shouldCreateUser: false, + }, + }); + + if (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error sending account recovery email")); + return; + } + + setOtpAvailable(); + + if (showConfirmationToast) toast.success("Account recovery email resent successfully"); + + // Note we don't need to check if the email is verified or not. Once the user signs in using the OTP code, their + // email is verified if it wasn't already and the router redirects them to the right page (add wallet, backup reminder...). + } catch (error) { + toast.error(getFriendlyAuthErrorMessage(error, "Error sending account recovery email")); + } finally { + setIsResending(false); + } + }, + { + key: StorageKeys.CONNECT.AUTH.LAST_OTP_EMAIL, + cooldownDuration: OTP_COOLDOWN_DURATION_SEC, + }, + ); + + useAsyncEffect(async () => { + try { + if (await checkNeedsNewOtp()) signInWithOtp(false); + } catch { + // In case `LAST_OTP_EMAIL` is already be set in `localStorage` and the required time hasn't passed yet (so this call will throw an error). + } + }, [signInWithOtp]); + + // Handlers: + + const handleVerifyOtp = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + if (!email || isVerifying) return; + + const otpCode = codeInputRef.current.getCode(); + + if (otpCode.length !== OTP_LENGTH) { + toast.error(`Please enter all ${OTP_LENGTH} digits of the verification code`); + return; + } + + setIsVerifying(true); + + try { + await authenticate({ + method: "verifyOtp", + email, + token: otpCode, + }); + + toast.success("Account recovered successfully"); + + setRequestPasswordChange(true); + } catch (error) { + setIsVerifying(false); + toast.error(getFriendlyAuthErrorMessage(error, "Invalid or expired code")); + } finally { + clearOtpAvailable(); + } + + // We leave isVerifying = true intentionally as the user will simply be redirected after verifying the account. + }, + [email, isVerifying, authenticate], + ); + + useEffect(() => { + if (!email) { + if (process.env.NODE_ENV === "development") { + throw new Error("No email search param. The router should have taken care of this."); + } else { + navigate(EmbeddedPaths.Auth); + } + } + }, [email]); + + return ( + } + headerText="Recover your account" + subtitle={`We've sent an email to ${email}`} + onBackButtonClick={() => navigate(EmbeddedPaths.AuthRecoverAccount)} + isLoading={isViewLoading} + onSubmit={handleVerifyOtp}> + + Enter the 6-digit verification code from that email to recover your account. If you don't see the email, please + check your spam folder. + + + + + Verification Code + + + + + + + + + Didn't receive the email?{" "} + {cooldownSeconds === 0 ? ( + + ) : ( + <>Send again in {cooldownSeconds} seconds + )} + + + ); +} diff --git a/apps/wander-connect/src/views/auth/recover-account/qrcode/auth-recover-account-qrcode.view.tsx b/apps/wander-connect/src/views/auth/recover-account/qrcode/auth-recover-account-qrcode.view.tsx new file mode 100644 index 000000000..ac80a581f --- /dev/null +++ b/apps/wander-connect/src/views/auth/recover-account/qrcode/auth-recover-account-qrcode.view.tsx @@ -0,0 +1,12 @@ +import { QrCodeScanEmbeddedView } from "../../import-qrcode/components/QrCodeEmbeddedView"; + +export function AuthRecoverAccountQrCodeEmbeddedView() { + return ( + + ); +} diff --git a/apps/wander-connect/src/views/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx b/apps/wander-connect/src/views/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx new file mode 100644 index 000000000..a7d185908 --- /dev/null +++ b/apps/wander-connect/src/views/auth/recover-account/seedphrase/auth-recover-account-seedphrase.view.tsx @@ -0,0 +1,109 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useLocation } from "@wanderapp/core"; +import { Copyable, Row, Button, SeedInput, RecoverHeaderIcon, OnboardingCard } from "@wanderapp/ui"; +import copy from "copy-to-clipboard"; +import { toast } from "react-toastify"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; + +export function AuthRecoverAccountSeedphraseEmbeddedView() { + const [loading, setLoading] = useState(false); + const [seedPhrase, setSeedPhrase] = useState(Array(12).fill("")); + const { navigate, back } = useLocation(); + const { + importTempWallet, + importedTempWalletAddress, + deleteImportedTempWallet, + fetchRecoverableAccounts, + clearRecoverableAccounts, + fetchRecoverableAccountWallets, + } = useEmbedded(); + + const handleImportWallet = useCallback(async () => { + try { + setLoading(true); + if (!seedPhrase.length) return; + await importTempWallet(seedPhrase.join(" ")); + } catch (error) { + toast.error(error); + } finally { + setLoading(false); + } + }, [seedPhrase]); + + const handleInputChange = useCallback((index: number, value: string) => { + setSeedPhrase((prevSeedPhrase) => { + const newSeedPhrase = [...prevSeedPhrase]; + newSeedPhrase[index] = value; + return newSeedPhrase; + }); + }, []); + + const handleRecover = async () => { + try { + setLoading(true); + const recoverableAccounts = await fetchRecoverableAccounts(); + if (recoverableAccounts.length === 1) { + await fetchRecoverableAccountWallets(recoverableAccounts[0]); + navigate(EmbeddedPaths.Auth); + } else if (recoverableAccounts.length > 1) { + navigate(EmbeddedPaths.AuthRecoverAccountSelect); + } else { + toast.error("No recoverable accounts found"); + } + setLoading(false); + } catch (error) { + toast.error(error?.message || "Error recovering account"); + setLoading(false); + } + }; + + useEffect(() => { + deleteImportedTempWallet(); + clearRecoverableAccounts(); + }, []); + + const isSeedPhraseIncomplete = useMemo(() => { + if (seedPhrase.length !== 12) return true; + return seedPhrase.some((word) => word.trim() === ""); + }, [seedPhrase]); + + return importedTempWalletAddress ? ( + } + headerText="Enter Seedphrase" + subtitle="Would you like to add this wallet to your account?" + onBackButtonClick={() => navigate(EmbeddedPaths.AuthRecoverAccount)} + isLoading={loading} + style={{ gap: 24 }}> + { + copy(importedTempWalletAddress); + }} + value={importedTempWalletAddress} + /> + + + + + + ) : ( + navigate(`/auth/recover-account`)} + isLoading={loading}> + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/recover-account/select-account/auth-recover-account-select.view.tsx b/apps/wander-connect/src/views/auth/recover-account/select-account/auth-recover-account-select.view.tsx new file mode 100644 index 000000000..84b1fbc32 --- /dev/null +++ b/apps/wander-connect/src/views/auth/recover-account/select-account/auth-recover-account-select.view.tsx @@ -0,0 +1,55 @@ +import { useCallback, useState } from "react"; +import { Box, Text, Button, RecoverHeaderIcon, Flex, OnboardingCard } from "@wanderapp/ui"; +import type { RecoverableAccount } from "embed-api"; +import { truncateMiddle, useLocation } from "@wanderapp/core"; +import { toast } from "react-toastify"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; + +export function AuthRecoverAccountSelectEmbeddedView() { + const { navigate } = useLocation(); + const [loading, setLoading] = useState>({}); + const { recoverableAccounts, setRecoverableAccount, fetchRecoverableAccountWallets } = useEmbedded(); + + const handleSelectAccount = useCallback( + async (account: RecoverableAccount) => { + try { + setLoading((prev) => ({ ...prev, [account.userId]: true })); + setRecoverableAccount(account); + await fetchRecoverableAccountWallets(account); + navigate(EmbeddedPaths.Auth); + } catch (error) { + toast.error("Error fetching recoverable account wallets"); + } finally { + setLoading((prev) => ({ ...prev, [account.userId]: false })); + } + }, + [setRecoverableAccount, navigate, fetchRecoverableAccountWallets], + ); + + return ( + } + headerText="Select account to recover" + onBackButtonClick={() => navigate(EmbeddedPaths.AuthRecoverAccount)}> + + + {recoverableAccounts?.map((account) => ( + + ))} + + + + ); +} diff --git a/apps/wander-connect/src/views/auth/restore-shares/auth-restore-shares.view.tsx b/apps/wander-connect/src/views/auth/restore-shares/auth-restore-shares.view.tsx new file mode 100644 index 000000000..349e613a6 --- /dev/null +++ b/apps/wander-connect/src/views/auth/restore-shares/auth-restore-shares.view.tsx @@ -0,0 +1,156 @@ +import { Divider, QrCode02 } from "@untitled-ui/icons-react"; +import { useMemo } from "react"; +import { Button, WalletIcon, Snackbar, SnackbarVariant, KeyIcon, OnboardingCard, SeedIcon } from "@wanderapp/ui"; +import browser from "webextension-polyfill"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { signOut } from "../../../utils/embedded.utils"; + +export function AuthRestoreSharesEmbeddedView() { + const { wallets, unpartitionedStateStatus } = useEmbedded(); + const hasMultipleWallets = wallets.length > 1; + + // TODO: Let them know what type of backup/export they made and where? Let them check all available options? + + const { lastRecovery, hasRecoverableWallets } = useMemo(() => { + let lastRecovery = 0; + let hasRecoverableWallets = false; + + wallets.forEach(({ canBeRecovered, lastBackedUpAt, lastExportedAt }) => { + lastRecovery = canBeRecovered + ? Math.max(lastRecovery, lastBackedUpAt?.getTime() || 0, lastExportedAt?.getTime() || 0) + : lastRecovery; + hasRecoverableWallets = hasRecoverableWallets || canBeRecovered; + }); + + return { + lastRecovery, + hasRecoverableWallets, + }; + }, []); + + let createFirst = false; + let snackbarVariant: SnackbarVariant = "warning"; + let title = "No wallets found on this device"; + let message = "New device or browser? Did you clear your browser data? Select a method for restoring your wallet."; + let unpartitionedStorageMessage = ""; + + // Keep in mind users could have seen the QR or seedphrase backup but then they might not scan or save it, respectively. Also, users might have exported + // a keyfile or recovery file, but they might have lost it. + + if (!hasRecoverableWallets) { + createFirst = true; + snackbarVariant = "error"; + title = "No backups detected"; + message = hasMultipleWallets + ? `It looks like your wallets are not recoverable. Unless you backed any of them up, they are lost forever.` + : `It looks like your wallet is not recoverable. Unless you backed it up, it's lost forever.`; + + if (unpartitionedStateStatus !== "supported") { + // TODO: Let them know where they've previously signed in? + unpartitionedStorageMessage = + "If you've previously used Wander on a different site, your might still be able to backup your wallet from it."; + } + } + + return ( + signOut(false)}> + +

{message}

+ {unpartitionedStorageMessage ?

{unpartitionedStorageMessage}

: null} + {lastRecovery ?

You last backed up your wallet on {formatDate(new Date(lastRecovery), "")}

: null} +

+ Need help?{" "} + +

+
+ + {createFirst ? ( + <> + + + + + ) : null} + + {/* + */} + + {/* */} + + + + + + + + + + {createFirst ? null : ( + <> + + + + + )} +
+ ); +} diff --git a/apps/wander-connect/src/views/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view.tsx b/apps/wander-connect/src/views/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view.tsx new file mode 100644 index 000000000..54124e03e --- /dev/null +++ b/apps/wander-connect/src/views/auth/restore-shares/create-confirmation/auth-restore-shares-create-confirmation.view.tsx @@ -0,0 +1,39 @@ +import { Button, WalletIcon, Snackbar, OnboardingCard } from "@wanderapp/ui"; +import browser from "webextension-polyfill"; +import { useLocation } from "@wanderapp/core"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; + +export function AuthRestoreSharesCreateConfirmationEmbeddedView() { + const { navigate } = useLocation(); + + return ( + navigate(EmbeddedPaths.AuthRestoreShares)}> + +

You won't be able to access your old wallet again.

+

+ Need help?{" "} + +

+
+ + + + +
+ ); +} diff --git a/apps/wander-connect/src/views/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx b/apps/wander-connect/src/views/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx new file mode 100644 index 000000000..331e3c9a4 --- /dev/null +++ b/apps/wander-connect/src/views/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view.tsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from "react"; +import { toast } from "react-toastify"; +import copy from "copy-to-clipboard"; +import { CommonRouteProps, useLocation, useWalletUpload, WalletUtils } from "@wanderapp/core"; +import { Button, Copyable, OnboardingCard, Row, Snackbar, Upload } from "@wanderapp/ui"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; + +export type AuthRestoreFileViewVariant = "recovery-file" | "keyfile"; + +export interface AuthRestoreSharesKeyfileEmbeddedViewProps extends CommonRouteProps { + variant?: AuthRestoreFileViewVariant; +} + +export function AuthRestoreSharesKeyfileEmbeddedView({ + variant = "keyfile", +}: AuthRestoreSharesKeyfileEmbeddedViewProps) { + const { navigate } = useLocation(); + const [isRecovering, setIsRecovering] = useState(false); + + const { importTempWallet, deleteImportedTempWallet, recoverWallet, wallets } = useEmbedded(); + + const { + data: uploadData, + isLoading: isUploading, + error: uploadError, + importedWalletAddress, + parse: parseUpload, + reset: resetUpload, + } = useWalletUpload({ + wallets, + importTempWallet, + allowRecoveryFile: true, + mustWalletExist: true, + }); + + const areButtonsDisabled = isRecovering || isUploading; + const isViewLoading = isRecovering; + + const handleRecoverWallet = async () => { + try { + if (areButtonsDisabled) return; + + if (uploadError) { + toast.error(uploadError); + + return; + } + + if (!WalletUtils.isJWK(uploadData) && !WalletUtils.isRecoveryJSON(uploadData)) { + toast.error("Invalid file."); + + return; + } + + setIsRecovering(true); + + await recoverWallet(uploadData); + } catch (error) { + console.error(error); + toast.error("Unexpected error while recovering wallet."); + } finally { + setIsRecovering(false); + } + }; + + const handleTryAgain = () => { + resetUpload(); + deleteImportedTempWallet(); + }; + + useEffect(() => { + return () => { + // Remove the imported keyfile from memory as soon as we leave this view. Note at this point it will already have + // been passed to `importTempWallet()`, if the user confirmed: + deleteImportedTempWallet(); + }; + }, []); + + const fileTypeLabel = variant === "keyfile" ? "private key" : "recovery file"; + + return importedWalletAddress ? ( + navigate("/auth/restore-shares")} + isLoading={isViewLoading}> + { + copy(importedWalletAddress); + }} + value={importedWalletAddress} + /> + + + + + + ) : ( + navigate("/auth/restore-shares")} + isLoading={isViewLoading}> + + + {uploadError} + + ); +} diff --git a/apps/wander-connect/src/views/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view.tsx b/apps/wander-connect/src/views/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view.tsx new file mode 100644 index 000000000..868c5b1bb --- /dev/null +++ b/apps/wander-connect/src/views/auth/restore-shares/qrcode/auth-restore-shares-qrcode.view.tsx @@ -0,0 +1,12 @@ +import { QrCodeScanEmbeddedView } from "../../import-qrcode/components/QrCodeEmbeddedView"; + +export function AuthRestoreSharesQrCodeEmbeddedView() { + return ( + + ); +} diff --git a/src/routes/embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx b/apps/wander-connect/src/views/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx similarity index 53% rename from src/routes/embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx rename to apps/wander-connect/src/views/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx index 9537ff633..a3bce1ef6 100644 --- a/src/routes/embedded/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx +++ b/apps/wander-connect/src/views/auth/restore-shares/recovery-file/auth-restore-shares-recovery-file.view.tsx @@ -1,4 +1,4 @@ -import { AuthRestoreSharesKeyfileEmbeddedView } from "~routes/embedded/auth/restore-shares/keyfile/auth-restore-shares-keyfile.view"; +import { AuthRestoreSharesKeyfileEmbeddedView } from "../keyfile/auth-restore-shares-keyfile.view"; export function AuthRestoreSharesRecoveryFileEmbeddedView() { return ; diff --git a/apps/wander-connect/src/views/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx b/apps/wander-connect/src/views/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx new file mode 100644 index 000000000..c26427aa1 --- /dev/null +++ b/apps/wander-connect/src/views/auth/restore-shares/seedphrase/auth-restore-shares-seedphrase.view.tsx @@ -0,0 +1,121 @@ +import { useCallback, useEffect, useState } from "react"; +import { toast } from "react-toastify"; +import { useLocation } from "@wanderapp/core"; +import { Button, Copyable, OnboardingCard, Row, SeedInput } from "@wanderapp/ui"; +import copy from "copy-to-clipboard"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; + +export function AuthRestoreSharesSeedPhraseEmbeddedView() { + const [loading, setLoading] = useState(false); + const { navigate } = useLocation(); + const [seedPhrase, setSeedPhrase] = useState([]); + const { importTempWallet, importedTempWalletAddress, deleteImportedTempWallet, wallets, recoverWallet } = + useEmbedded(); + + const validateSeedPhrase = useCallback(() => { + const parsedSeedPhrase = seedPhrase.filter((word) => !!word.trim()); + + if (![12, 18, 24].includes(parsedSeedPhrase.length)) { + toast.error("Incomplete seedphrase."); + + return false; + } + + return true; + }, [seedPhrase]); + + const handleImportWallet = useCallback(async () => { + try { + const isSeedPhraseValid = validateSeedPhrase(); + + if (!isSeedPhraseValid) return; + + setLoading(true); + + await importTempWallet(seedPhrase.join(" ")); + } catch (error) { + toast.error(error); + } finally { + setLoading(false); + } + }, [seedPhrase]); + + const handleRecoverWallet = useCallback(async () => { + try { + const isSeedPhraseValid = validateSeedPhrase(); + + if (!isSeedPhraseValid) return; + + const isWalletPresent = wallets.some(({ address }) => address === importedTempWalletAddress); + + if (!isWalletPresent) { + toast.error("This wallet is not part of your account."); + + return; + } + + setLoading(true); + + await recoverWallet(seedPhrase.join(" ")); + } catch (error) { + console.error(error); + toast.error("An unexpected error happened."); + } finally { + setLoading(false); + } + }, [seedPhrase, wallets, importedTempWalletAddress]); + + const handleInputChange = useCallback((index: number, value: string) => { + setSeedPhrase((prevSeedPhrase) => { + const newSeedPhrase = [...prevSeedPhrase]; + newSeedPhrase[index] = value; + return newSeedPhrase; + }); + }, []); + + useEffect(() => { + return () => { + // Remove the imported keyfile from memory as soon as we leave this view. Note at this point it will already have + // been passed to `importTempWallet()`, if the user confirmed: + deleteImportedTempWallet(); + }; + }, []); + + return importedTempWalletAddress ? ( + navigate("/auth/restore-shares")} + isLoading={loading}> + { + copy(importedTempWalletAddress); + }} + value={importedTempWalletAddress} + /> + + + + + + ) : ( + navigate("/auth/restore-shares")} + isLoading={loading}> + + + + + ); +} diff --git a/apps/wander-connect/src/views/support/unpartitioned-state/unpartitioned-state.module.scss b/apps/wander-connect/src/views/support/unpartitioned-state/unpartitioned-state.module.scss new file mode 100644 index 000000000..65362135a --- /dev/null +++ b/apps/wander-connect/src/views/support/unpartitioned-state/unpartitioned-state.module.scss @@ -0,0 +1,45 @@ +.supportList { + display: block; + width: 100%; + font-size: var(--font-size-base); +} + +.supportGroup:not(:last-child) { + padding-bottom: var(--spacing-6); + margin-bottom: var(--spacing-6); + border-bottom: 1px solid var(--color-divider-default); +} + +.browserList { + display: grid; + grid-template-columns: 1fr 1fr; +} + +.browserItem { + display: flex; + gap: var(--spacing-2); + align-items: center; + margin-top: var(--spacing-4); +} + +.fullySupportedLabel, +.limitedSupportLabel { + display: inline-block; + border-radius: 128px; + padding: var(--spacing-1) var(--spacing-2); + font-size: var(--font-size-sm); +} + +.fullySupportedLabel { + background: var(--snackbar-background-success); + color: var(--snackbar-color-success); +} + +.limitedSupportLabel { + background: var(--snackbar-background-warning); + color: var(--snackbar-color-warning); +} + +.couldProbablyGetAccessDisclaimer { + margin-top: var(--spacing-6); +} diff --git a/apps/wander-connect/src/views/support/unpartitioned-state/unpartitioned-state.view.tsx b/apps/wander-connect/src/views/support/unpartitioned-state/unpartitioned-state.view.tsx new file mode 100644 index 000000000..914074c30 --- /dev/null +++ b/apps/wander-connect/src/views/support/unpartitioned-state/unpartitioned-state.view.tsx @@ -0,0 +1,623 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { browserInfo, useLocation } from "@wanderapp/core"; +import { Image, FormattedText, OnboardingCard, Button, Checkbox, Snackbar } from "@wanderapp/ui"; +import { toast } from "react-toastify"; +import { useInterval } from "@swyg/corre"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { signOut } from "../../../utils/embedded.utils"; +import { LocalStorage } from "../../../utils/storage/unpartitioned-storage/local-storage"; +import { COULD_NOT_ACCESS_UNPARTITIONED_STATE_ERR_MESSAGE } from "../../../utils/storage/unpartitioned-storage/unpartitioned-storage"; +import { HAS_ADVANCED_STORAGE_API } from "../../../utils/storage/unpartitioned-storage/unpartitioned-storage.utils"; + +// Logos downloaded from https://github.com/alrra/browser-logos +import chromeLogoSrc from "url:assets/icons/browsers/chrome-logo.png"; +import edgeLogoSrc from "url:assets/icons/browsers/edge-logo.png"; +import operaLogoSrc from "url:assets/icons/browsers/opera-logo.png"; +import braveLogoSrc from "url:assets/icons/browsers/brave-logo.png"; +import safariLogoSrc from "url:assets/icons/browsers/safari-logo.png"; +import firefoxLogoSrc from "url:assets/icons/browsers/firefox-logo.png"; + +import brave1ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/brave-1-light.png"; +import brave2ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/brave-2-light.png"; +import brave1ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/brave-1-dark.png"; +import brave2ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/brave-2-dark.png"; + +import chrome1ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-1-light.png"; +import chrome2ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-2-light.png"; +import chrome3ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-3-light.png"; +import chrome4ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-4-light.png"; +import chrome5ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-5-light.png"; +import chrome1ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-1-dark.png"; +import chrome2ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-2-dark.png"; +import chrome3ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-3-dark.png"; +import chrome4ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-4-dark.png"; +import chrome5ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-5-dark.png"; + +import chromeAndroid1ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-android-1-light.png"; +import chromeAndroid2ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-android-2-light.png"; +import chromeAndroid3ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/chrome-android-3-light.png"; +import chromeAndroid1ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-android-1-dark.png"; +import chromeAndroid2ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-android-2-dark.png"; +import chromeAndroid3ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/chrome-android-3-dark.png"; + +import edge1ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/edge-1-light.png"; +import edge2ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/edge-2-light.png"; +import edge3ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/edge-3-light.png"; +import edge4ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/edge-4-light.png"; +import edge5ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/edge-5-light.png"; +import edge1ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/edge-1-dark.png"; +import edge2ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/edge-2-dark.png"; +import edge3ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/edge-3-dark.png"; +import edge4ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/edge-4-dark.png"; +import edge5ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/edge-5-dark.png"; + +import inApp1ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/in-app-1-light.png"; +import inApp2ScreenshotSrc from "url:assets/screenshots/unpartitioned-state/in-app-2-light.png"; +import inApp1ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/in-app-1-dark.png"; +import inApp2ScreenshotDarkSrc from "url:assets/screenshots/unpartitioned-state/in-app-2-dark.png"; + +import styles from "./unpartitioned-state.module.scss"; + +export function UnpartitionedStateMissingEmbeddedView() { + const { navigate } = useLocation(); + const { + authStatus, + unpartitionedStateStatus, + unpartitionedStateConfirmed, + confirmUnpartitionedState, + backupsNeeded, + } = useEmbedded(); + + // Requesting access logic: + + const [tryAgain, setTryAgain] = useState(false); + const [isOptionMissing, setIsOptionMissing] = useState(false); + + const couldProbablyGetAccess = + HAS_ADVANCED_STORAGE_API && (unpartitionedStateStatus === "rejected" || unpartitionedStateStatus === "error"); + + const shouldTryToGetAccess = authStatus === "noAuth" && couldProbablyGetAccess; + + // Loading state: + + const [isConfirming, setIsConfirming] = useState(false); + const [isRequestingPermission, setIsRequestingPermission] = useState(false); + + const isViewLoading = + authStatus === "unknown" || authStatus === "loading" || authStatus === "authLoading" || isConfirming; + const areButtonsDisabled = isViewLoading || isRequestingPermission; + + // Checkbox and error handling: + + const [isChecked, setIsChecked] = useState(false); + const [errorsWhileRequestingAccess, setErrorsWhileRequestingAccess] = useState(0); + + // Handlers: + + const handleContinueAnyway = useCallback(async () => { + try { + setIsConfirming(true); + await confirmUnpartitionedState(isChecked); + + if (!shouldTryToGetAccess || unpartitionedStateConfirmed) navigate(EmbeddedPaths.Auth); + else setIsConfirming(false); + } catch (error) { + toast.error("Unexpected error. Please, try again."); + setIsConfirming(false); + } + }, [unpartitionedStateConfirmed, confirmUnpartitionedState, isChecked, navigate]); + + const handleRequestPermission = useCallback(async () => { + try { + setIsRequestingPermission(true); + + const localStorage = await LocalStorage.getInstance(); + + if (["rejected", "error"].includes(localStorage.unpartitionedState.status)) { + throw localStorage.unpartitionedState.error || new Error(COULD_NOT_ACCESS_UNPARTITIONED_STATE_ERR_MESSAGE); + } + + navigate(EmbeddedPaths.Auth); + } catch (error) { + // Brave throws a "NotAllowedError" error while trying to request access, even after a user interaction... + + toast.error( + error instanceof Error && + (error.name === "NotAllowedError" || error.message === COULD_NOT_ACCESS_UNPARTITIONED_STATE_ERR_MESSAGE) + ? "Could not get access. Did you enable it?" + : "Unexpected error. Please, try again.", + ); + + setErrorsWhileRequestingAccess((prevErrorsWhileRequestingAccess) => prevErrorsWhileRequestingAccess + 1); + setIsRequestingPermission(false); + } + }, [navigate]); + + useInterval( + async () => { + if (isRequestingPermission) return; + + const hasAccess = await document.hasStorageAccess(); + + if (hasAccess) { + const localStorage = await LocalStorage.getInstance(); + + if (!["rejected", "error"].includes(localStorage.unpartitionedState.status)) { + setIsRequestingPermission(true); + navigate(EmbeddedPaths.Auth); + } + } + }, + shouldTryToGetAccess ? 1000 : null, + ); + + useEffect(() => { + if (unpartitionedStateStatus === "supported") { + if (process.env.NODE_ENV === "development") { + throw new Error("This view should not be accessible if unpartitioned state is supported and accepted."); + } else { + navigate(EmbeddedPaths.Auth); + } + } + }, [unpartitionedStateStatus]); + + const needsConfirmation = !unpartitionedStateConfirmed && authStatus === "noAuth"; + const requestAccessButtonText = errorsWhileRequestingAccess === 0 ? "Check access" : "Check access again"; + const accessTo = browserInfo.isBrave ? "embedded content" : "third-party cookies"; + + let headerText = "Limited browser support"; + let subtitle = couldProbablyGetAccess + ? `Wander could not get access to ${accessTo}. You'll need to manually import your wallet on each new site.` + : "Your browser doesn't support cross-site wallet syncing. You'll need to manually import your wallet on each new site."; + + let children = ( + <> +
+
Fully supported
+
+
    +
  • + + Chrome +
  • +
  • + + Edge +
  • +
  • + + Opera +
  • +
  • + + Brave +
  • +
+
+
Limited support
+
+
    +
  • + + Safari +
  • +
  • + + Firefox +
  • +
+
+
+ + {couldProbablyGetAccess ? ( + + Your browser should be supported, but Wander could not get access to {accessTo}. + {authStatus === "noAuth" + ? "" + : backupsNeeded > 0 + ? " Please, back up your wallet and sign out to try again." + : " Please, sign out to try again."} +

, + authStatus === "noAuth" ? ( + + ) : ( + + ), + ]} + className={styles.couldProbablyGetAccessDisclaimer} + /> + ) : null} + + {needsConfirmation ? ( + setIsChecked(!isChecked)} + isChecked={isChecked} + /> + ) : null} + + {authStatus === "noAuth" ? ( + + ) : null} + + ); + + const allowRequestAfterConfirmation = tryAgain && authStatus === "noAuth"; + + if (shouldTryToGetAccess && (!unpartitionedStateConfirmed || allowRequestAfterConfirmation)) { + headerText = `Enable ${accessTo}`; + subtitle = `Before you continue, Wander Connect needs access to ${accessTo} to enable cross-site authentication and wallet synching.`; + + const optionMissingButton = isOptionMissing ? null : ( + + ); + + let browserSpecificInstructions: React.ReactNode = null; + + if (errorsWhileRequestingAccess > 0) { + // TODO: On iOS Brave (which uses Safari's engine most likely) the only option seems to be turn down Shields, so we + // might want to add a specific case for that. See https://community-labs-group.slack.com/archives/C05U48QHY0G/p1755097816534059?thread_ts=1754912705.245929&cid=C05U48QHY0G + + if (browserInfo.isBrave) { + browserSpecificInstructions = ( + + You can enable this from the Embedded content option in the + navigation bar: +

, +

+ +

, +

+ +

, + optionMissingButton, + isOptionMissing ?

Alternatively, turning Shields down should also work.

: null, + ]} + /> + ); + } else if (browserInfo.isChromeAndroid || browserInfo.isChromeIOS) { + browserSpecificInstructions = ( + + You can enable this from the Cookies and site data option in the + navigation bar: +

, +

+ +

, +

+ +

, +

+ +

, + ]} + /> + ); + } else if (browserInfo.isInAppAndroidBrowser) { + browserSpecificInstructions = ( + You'll have to switch to your browser app to enable this:

, +

+ +

, +

+ +

, + ]} + /> + ); + } else if (browserInfo.isEdge) { + browserSpecificInstructions = ( + + You can enable this from the Third-party cookies option in the + navigation bar: +

, +

+ +

, +

+ +

, + optionMissingButton, + isOptionMissing ? ( +

+ In some Edge versions, this setting appears under the About{" "} + popup: +

+ ) : null, + isOptionMissing ? ( +

+ +

+ ) : null, + isOptionMissing ? ( +

+ +

+ ) : null, + isOptionMissing ? ( +

+ +

+ ) : null, + isOptionMissing ? ( +

+ You might also find this option in{" "} + + Settings › Privacy, search, and services › Block third-party cookies + + . +

+ ) : null, + ]} + /> + ); + } else { + // Chrome or, most likely, other Chromium-based browser with more or less the same UI (e.g. Opera): + + browserSpecificInstructions = ( + + You can enable this from the Third-party cookies option in the + navigation bar: +

, +

+ +

, +

+ +

, + optionMissingButton, + isOptionMissing ? ( +

+ In some {browserInfo.isChrome ? "Chrome versions" : "browsers"}, this setting appears under the{" "} + Site information popup: +

+ ) : null, + isOptionMissing ? ( +

+ +

+ ) : null, + isOptionMissing ? ( +

+ +

+ ) : null, + isOptionMissing ? ( +

+ +

+ ) : null, + isOptionMissing ? ( +

+ You might also find this option in{" "} + Settings › Privacy and security › Third-party cookies. +

+ ) : null, + ]} + /> + ); + } + } + + children = ( + <> + {browserSpecificInstructions} + + + + {errorsWhileRequestingAccess >= 2 || !needsConfirmation ? ( + + ) : null} + + ); + } + + return ( + + {children} + + ); +} diff --git a/apps/wander-connect/src/views/wallet/buy/buy.cash.view.tsx b/apps/wander-connect/src/views/wallet/buy/buy.cash.view.tsx new file mode 100644 index 000000000..d22e4f316 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/buy.cash.view.tsx @@ -0,0 +1,148 @@ +import browser from "webextension-polyfill"; +import AutosizeInput from "react-input-autosize"; +import { useLocation, useTransak } from "@wanderapp/core"; +import { Button, Text, Row, ChevronRight, DefaultCard } from "@wanderapp/ui"; +import { CurrencySelector } from "./components/selector/CurrencySelector"; +import { PaymentSelector } from "./components/selector/PaymentSelector"; + +// TODO: It would be better to use React component SVG that uses `currentColor` +import arLogo from "url:/assets/ecosystem/ar-logo.svg"; + +import styles from "./buy.module.scss"; + +const TRANSAK_API_KEY = import.meta.env?.VITE_TRANSAK_API_KEY; + +export function WalletBuyCashEmbeddedView() { + const { navigate } = useLocation(); + + const { + purchaseAmount, + arConversion, + loading, + invalidFiatAmount, + selectedCurrency, + paymentMethod, + quote, + error, + currencies, + showCurrencySelector, + showPaymentSelector, + setPaymentMethod, + handleAmountChange, + handleUpdateCurrency, + openCurrencySelector, + openPaymentSelector, + closeCurrencySelector, + closePaymentSelector, + openTransak, + getDisplayAmount, + } = useTransak(TRANSAK_API_KEY, true); + + const renderMainView = () => ( + navigate("/wallet/receive/options")}> +
+ handleAmountChange(e.target.value)} + placeholder="0" + inputStyle={{ + fontSize: "40px", + fontWeight: "500", + color: "var(--input-color)", + border: "none", + background: "transparent", + }} + /> + + {arConversion ? "AR" : selectedCurrency?.symbol || "USD"} + + AR +
+ + + {getDisplayAmount()} + + + + + + + {error && ( + + {error} + + )} + + +
+ ); + + return ( + <> + {renderMainView()} + {showCurrencySelector && ( + { + handleUpdateCurrency(currency); + closeCurrencySelector(e); + }} + onClose={closeCurrencySelector} + /> + )} + {showPaymentSelector && selectedCurrency && ( + { + setPaymentMethod(payment); + closePaymentSelector(); + }} + onClose={closePaymentSelector} + /> + )} + + ); +} diff --git a/apps/wander-connect/src/views/wallet/buy/buy.container.view.tsx b/apps/wander-connect/src/views/wallet/buy/buy.container.view.tsx new file mode 100644 index 000000000..361cb2dfd --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/buy.container.view.tsx @@ -0,0 +1,41 @@ +import { useLocation } from "@wanderapp/core"; +import { Card, Button, Text, BuyWithCashIcon, BuyWithCryptoIcon, Box } from "@wanderapp/ui"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +export function WalletBuyEmbeddedView() { + const { navigate } = useLocation(); + + return ( + navigate(EmbeddedPaths.WalletHomeEmbeddedView)} + style={{ padding: "32px" }}> + + + + + + + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/wallet/buy/buy.input.view.tsx b/apps/wander-connect/src/views/wallet/buy/buy.input.view.tsx new file mode 100644 index 000000000..d63c21bce --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/buy.input.view.tsx @@ -0,0 +1,130 @@ +import { useState } from "react"; +import { useLocation } from "@wanderapp/core"; +import { Card, Button, Text, Input, Row, Box, ChevronRight } from "@wanderapp/ui"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +export function WalletBuyInputEmbeddedView() { + const { navigate } = useLocation(); + const [purchaseAmount, setPurchaseAmount] = useState(""); + const [payAmount, setPayAmount] = useState(""); + + const handlePurchaseChange = (value: string) => { + if (/^(\d*\.?\d*)$/.test(value) || value === "") { + setPurchaseAmount(value); + } + }; + + const handlePayChange = (value: string) => { + if (/^(\d*\.?\d*)$/.test(value) || value === "") { + setPayAmount(value); + } + }; + + const handleConnectWallet = () => { + // Navigate to wallet connection screen + // navigate("/wallet/connect"); + }; + + const handleEnterAmount = () => { + // Navigate to next step in purchase flow + // navigate("/wallet/buy/confirm"); + }; + + return ( + navigate("/wallet/buy")} + onCloseButtonClick={() => navigate(EmbeddedPaths.WalletHomeEmbeddedView)} + style={{ padding: "24px" }}> + + + Purchase + + + handlePurchaseChange(e.target.value)} + placeholder="0.00" + style={{ + fontSize: "32px", + fontWeight: "500", + color: "#121212", + border: "none", + padding: "0", + width: "70%", + }} + /> + + + PL + + + + + + ${purchaseAmount ? Number(purchaseAmount).toFixed(2) : "0.00"} USD + + + + + + Pay with + + + handlePayChange(e.target.value)} + placeholder="0.00" + style={{ + fontSize: "32px", + fontWeight: "500", + color: "#121212", + border: "none", + padding: "0", + width: "70%", + }} + /> + + USDA + + + + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/wallet/buy/buy.module.scss b/apps/wander-connect/src/views/wallet/buy/buy.module.scss new file mode 100644 index 000000000..806d8a8bd --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/buy.module.scss @@ -0,0 +1,23 @@ +.buttonDropdown { + border-radius: var(--border-radius-xl); + border: 1px solid var(--color-border-default, var(--brand-color-gray-200)); + text-decoration: none; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + padding: var(--spacing-2) var(--spacing-3); + gap: var(--spacing-3); + color: var(--input-color); + font-family: var(--font-family-serif); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-relaxed); + min-height: var(--input-height); + height: auto; + + &:hover { + background: rgba(var(--color-overlay-rgb), 0.0625); + } +} diff --git a/apps/wander-connect/src/views/wallet/buy/buy.success.view.tsx b/apps/wander-connect/src/views/wallet/buy/buy.success.view.tsx new file mode 100644 index 000000000..d7931c3a7 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/buy.success.view.tsx @@ -0,0 +1,28 @@ +import { useLocation } from "@wanderapp/core"; +import { Card, Text, Box, SuccessCheckIcon } from "@wanderapp/ui"; + +export function WalletBuySuccessEmbeddedView() { + const { navigate, back } = useLocation(); + + return ( + navigate("/wallet/buy/cash")} + hasCloseButton={true} + onCloseButtonClick={back} + style={{ padding: "32px" }}> + + + + + Congrats! + +
+ + Your purchase is in progress. This may take up to 30-60 minutes to complete. + +
+
+ ); +} diff --git a/apps/wander-connect/src/views/wallet/buy/components/selector/CurrencySelector.tsx b/apps/wander-connect/src/views/wallet/buy/components/selector/CurrencySelector.tsx new file mode 100644 index 000000000..2e4975836 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/components/selector/CurrencySelector.tsx @@ -0,0 +1,90 @@ +import { useMemo } from "react"; +import React from "react"; +import { Coins03 } from "@untitled-ui/icons-react"; +import { useInput } from "@arconnect/components-rebrand"; +import { SelectorContainer } from "./SelectorContainer"; +import { SelectorItem } from "./SelectorItem"; +import { TextInput } from "@wanderapp/ui"; + +interface CurrencySelectorProps { + currencies: any[]; + selectedCurrency: any; + handleUpdateCurrency: (currency: any, e?: React.MouseEvent) => void; + onClose: (e?: React.MouseEvent) => void; +} + +export const CurrencySelector = ({ + currencies, + selectedCurrency, + handleUpdateCurrency, + onClose, +}: CurrencySelectorProps) => { + const searchInput = useInput(); + + const filteredCurrencies = useMemo(() => { + if (!searchInput.state) { + return currencies; + } + return currencies.filter((currency) => { + const name = currency.name?.toLowerCase() || ""; + const symbol = currency.symbol?.toLowerCase() || ""; + const searchLower = searchInput.state.toLowerCase(); + return name.includes(searchLower) || symbol.includes(searchLower); + }); + }, [currencies, searchInput.state]); + + return ( + + + +
    + {filteredCurrencies.map((currency) => { + const currencyIcon = currency.logo ? ( + {currency.name} + ) : ( + + ); + + return ( + handleUpdateCurrency(currency, e)} + /> + ); + })} +
+
+ ); +}; diff --git a/apps/wander-connect/src/views/wallet/buy/components/selector/PaymentSelector.tsx b/apps/wander-connect/src/views/wallet/buy/components/selector/PaymentSelector.tsx new file mode 100644 index 000000000..0a57f183c --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/components/selector/PaymentSelector.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import { Bank, BankNote01, CreditCard01 } from "@untitled-ui/icons-react"; +import { SelectorContainer } from "./SelectorContainer"; +import { SelectorItem } from "./SelectorItem"; +import { paymentMethods } from "@wanderapp/core"; + +interface PaymentSelectorProps { + selectedCurrency: any; + paymentMethod: any; + setPaymentMethod: (payment: any) => void; + onClose: (e?: React.MouseEvent) => void; +} + +export const PaymentSelector = ({ + selectedCurrency, + paymentMethod, + setPaymentMethod, + onClose, +}: PaymentSelectorProps) => { + return ( + +
    + {(selectedCurrency?.paymentOptions || []) + .filter((payment: any) => payment.isActive) + .map((payment: any) => { + const isWireTransfer = payment.id === "pm_us_wire_bank_transfer"; + const isCashApp = payment.id === "pm_cash_app"; + + let paymentIcon; + if (isWireTransfer) { + paymentIcon = ; + } else if (isCashApp) { + paymentIcon = ; + } else if (payment.icon) { + paymentIcon = ( + {payment.name} + ); + } else { + paymentIcon = ; + } + + return ( + { + e.preventDefault(); + setPaymentMethod(payment); + onClose(e); + }} + /> + ); + })} +
+
+ ); +}; diff --git a/apps/wander-connect/src/views/wallet/buy/components/selector/SelectorContainer.tsx b/apps/wander-connect/src/views/wallet/buy/components/selector/SelectorContainer.tsx new file mode 100644 index 000000000..813bc0248 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/components/selector/SelectorContainer.tsx @@ -0,0 +1,26 @@ +import { DefaultCard } from "@wanderapp/ui"; +import React, { type PropsWithChildren } from "react"; + +interface SelectorContainerProps extends PropsWithChildren { + title: string; + onClose: (e?: React.MouseEvent) => void; +} + +export const SelectorContainer = ({ title, onClose, children }: SelectorContainerProps) => ( + + {children} + +); diff --git a/apps/wander-connect/src/views/wallet/buy/components/selector/SelectorItem.tsx b/apps/wander-connect/src/views/wallet/buy/components/selector/SelectorItem.tsx new file mode 100644 index 000000000..1795a2589 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/buy/components/selector/SelectorItem.tsx @@ -0,0 +1,72 @@ +import { Text} from "@wanderapp/ui"; +import React from "react"; + +import styles from "../../buy.module.scss"; + +interface SelectorItemProps { + icon: React.ReactNode; + title: string; + subtitle: string; + isSelected: boolean; + onClick: (e: React.MouseEvent) => void; +} + +export const SelectorItem = ({ icon, title, subtitle, isSelected, onClick }: SelectorItemProps) => ( +
  • + +
  • +); diff --git a/apps/wander-connect/src/views/wallet/deposit/deposit.container.view.tsx b/apps/wander-connect/src/views/wallet/deposit/deposit.container.view.tsx new file mode 100644 index 000000000..ecabce6e6 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/deposit/deposit.container.view.tsx @@ -0,0 +1,34 @@ +import copy from "copy-to-clipboard"; +import { useActiveWallet, useLocation } from "@wanderapp/core"; +import { Button, Copyable, DefaultCard } from "@wanderapp/ui"; + +export function WalletDepositTokensEmbeddedView() { + const wallet = useActiveWallet(); + const { navigate } = useLocation(); + const walletAddress = wallet.address; + + return ( + navigate("/wallet/receive/options")}> + { + copy(walletAddress); + }} + /> + + + + ); +} diff --git a/apps/wander-connect/src/views/wallet/home/actions/action-item.module.scss b/apps/wander-connect/src/views/wallet/home/actions/action-item.module.scss new file mode 100644 index 000000000..91ab1f0e9 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/actions/action-item.module.scss @@ -0,0 +1,5 @@ +.root { + display: flex; + align-items: center; + justify-content: flex-start; +} diff --git a/apps/wander-connect/src/views/wallet/home/actions/action-item.tsx b/apps/wander-connect/src/views/wallet/home/actions/action-item.tsx new file mode 100644 index 000000000..afb982329 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/actions/action-item.tsx @@ -0,0 +1,20 @@ +import { WanderRoutePath } from "@wanderapp/core"; +import { Text, Button } from "@wanderapp/ui"; + +import styles from "./action-item.module.scss"; + +export interface ActionItemProps { + label: string; + icon: (props: React.SVGProps) => JSX.Element; + to?: WanderRoutePath; + onClick?: () => void; +} + +export function ActionItem({ label, icon: Icon, to, onClick }: ActionItemProps) { + return ( + + ); +} diff --git a/apps/wander-connect/src/views/wallet/home/actions/actions.container.tsx b/apps/wander-connect/src/views/wallet/home/actions/actions.container.tsx new file mode 100644 index 000000000..9f81a1774 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/actions/actions.container.tsx @@ -0,0 +1,45 @@ +import browser from "webextension-polyfill"; +import { Lock01 } from "@untitled-ui/icons-react"; +import { CoinsIcon, ReceiptIcon, XClose, Box } from "@wanderapp/ui"; +import { useEmbedded } from "../../../../utils/embedded.hooks"; +import { signOut } from "../../../../utils/embedded.utils"; +import { ActionItem } from "./action-item"; + +export function WalletHomeActions() { + const { user } = useEmbedded(); + + return ( + + + + + + + + signOut()} /> + + {/* + + + + View wallet dashboard + + + */} + + ); +} diff --git a/apps/wander-connect/src/views/wallet/home/assets/asset-item.module.scss b/apps/wander-connect/src/views/wallet/home/assets/asset-item.module.scss new file mode 100644 index 000000000..b4f0e2738 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/assets/asset-item.module.scss @@ -0,0 +1,107 @@ +@keyframes marqueeAnimation { + 0% { transform: translate(0deg); } + 100% { transform: translate(var(--marqueeOffset, 0)); } +} + +.root { + position: relative; + width: 100%; + height: 64px; + display: flex; + align-items: stretch; + justify-content: flex-start; + gap: var(--spacing-2); + padding: 0; + transition: align-items linear 150ms; +} + +.info { + position: relative; + display: flex; + flex: 1 1 auto; + min-width: 0; + align-items: center; + overflow: hidden; +} + +.name, +.balance, +.fiat { + height: 26px; + line-height: 26px; +} + +.name { + white-space: nowrap; +} + +.balanceAndPrice { + position: absolute; + right: 0; + top: 50%; + max-width: var(--balanceMaxWidth, 50%); + padding-left: var(--spacing-4); + transition: max-width linear 150ms, transform linear 150ms; + transform: translate(var(--balanceOffset, 0px), -50%); + display: flex; + flex-direction: column; + align-items: flex-end; + margin-left: auto; + background: var(--color-background-default); + flex: 1; + min-width: 0; + + &::before { + content: ""; + position: absolute; + top: 0; + left: calc(-1 * var(--spacing-4)); + width: var(--spacing-8); + height: 100%; + background: linear-gradient(to left, var(--color-background-default) 50%, transparent); + pointer-events: none; + } +} + +.fiat { +} + +.balance { + position: relative; + display: flex; + justify-content: flex-end; + max-width: 100%; +} + +.balanceAmountWrapper { + flex: 1 1 auto; + display: flex; + justify-content: flex-start; + overflow: hidden; + min-width: 26px; +} + +.balanceAmount { + padding: 0 8px; +} + +.balanceTicker { + position: relative; + + &::before { + content: ""; + position: absolute; + top: 0; + right: 100%; + height: 100%; + pointer-events: none; + width: var(--spacing-2); + background: var(--color-background-default); + } + + .ellipseBalance &::before { + width: var(--spacing-8); + background: linear-gradient(to left, var(--color-background-default) 50%, transparent); + } +} + diff --git a/apps/wander-connect/src/views/wallet/home/assets/asset-item.tsx b/apps/wander-connect/src/views/wallet/home/assets/asset-item.tsx new file mode 100644 index 000000000..02316d21e --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/assets/asset-item.tsx @@ -0,0 +1,220 @@ +import BigNumber from "bignumber.js"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import clsx from "clsx"; +import { useThrottledCallback } from "@swyg/corre"; +import { ExtensionStorage, formatBalance, formatFiatBalance, useStorage, useTokenBalance, useSetting } from "@wanderapp/core"; +import { Text } from "@wanderapp/ui"; + +import styles from "./asset-item.module.scss"; + +interface AssetItemProps { + id: string; + defaultLogo: string; + tokenName: string; + ticker: string; + amount: string; + fiatPrice: number; + divisibility: number; +} + +export function AssetItem({ id, defaultLogo, tokenName, ticker, amount, fiatPrice, divisibility }: AssetItemProps) { + const [totalBalance, setTotalBalance] = useState(""); + const [currency] = useSetting("currency"); + const [activeAddress] = useStorage({ + key: "active_address", + instance: ExtensionStorage, + }); + + const tokenInfo = useMemo(() => { + return { + id, + processId: id, + Ticker: ticker, + Name: tokenName, + Denomination: divisibility, + Logo: defaultLogo, + }; + }, [id, ticker, tokenName, divisibility, defaultLogo]); + + const { data: fractBalance = "0", isError, error, isLoading } = useTokenBalance(tokenInfo, activeAddress); + + const balance = useMemo(() => { + if (isError) return "0"; + const formattedBalance = formatBalance(BigNumber(fractBalance)); + setTotalBalance(formattedBalance.tooltipBalance); + return formattedBalance.displayBalance; + }, [fractBalance, isError]); + + const formattedFiatPrice = useMemo(() => { + if (!fiatPrice) return undefined; + return formatFiatBalance(fiatPrice, currency); + }, [fiatPrice, currency]); + + const infoRef = useRef(null); + const nameRef = useRef(null); + const balanceAndPriceRef = useRef(null); + const balanceRef = useRef(null); + const tickerRef = useRef(null); + + const [isBalanceExpanded, setIsBalanceExpanded] = useState(false); + const [balanceOffset, setBalanceOffset] = useState(undefined); + const [balanceMaxWidth, setBalanceMaxWidth] = useState(undefined); + const [nameMarqueeOffset, setNameMarqueeOffset] = useState(undefined); + const [balanceMarqueeOffset, setBalanceMarqueeOffset] = useState(undefined); + + const handleExpandName = () => { + const nameElement = nameRef.current; + const infoElement = infoRef.current; + const balanceAndPriceElement = balanceAndPriceRef.current; + + if (!nameElement || !infoElement || !balanceAndPriceElement) return; + + const nameWidth = nameElement.offsetWidth; + const totalWidth = infoElement.offsetWidth; + const visibleWidth = totalWidth - balanceAndPriceElement.offsetWidth - 16; + + if (nameWidth <= visibleWidth) return; + + // TODO: Set max + const extraWidthNeeded = Math.min(nameWidth - visibleWidth, totalWidth / 2 + 16); + + setBalanceOffset(extraWidthNeeded); + + if (nameWidth > totalWidth) { + const nameMarqueeOffset = nameWidth - totalWidth; + + setNameMarqueeOffset(nameMarqueeOffset); + } + }; + + const [ellipseBalance, setEllipseBalance] = useState(false); + + const getEllipseBalance = useCallback(() => { + const nameElement = nameRef.current; + const balanceElement = balanceRef.current; + const infoElement = infoRef.current; + const tickerElement = tickerRef.current; + + if (!nameElement || !balanceElement || !infoElement || !tickerElement) return false; + + const width = infoElement.offsetWidth - nameElement.offsetWidth - 16; + const tickerWidth = tickerElement.offsetWidth; + const balanceWidth = balanceElement.offsetWidth; + const totalWidth = infoElement.offsetWidth - tickerWidth; + const visibleWidth = Math.max(width - tickerWidth, totalWidth / 2 - 16 - tickerWidth); + + return balanceWidth > visibleWidth; + }, []); + + const getRealBalanceMaxWidth = useCallback(() => { + const nameElement = nameRef.current; + const infoElement = infoRef.current; + + if (!nameElement || !infoElement) return false; + + const width = infoElement.offsetWidth - nameElement.offsetWidth - 16; + const halfTotalWidth = infoElement.offsetWidth / 2; + + return width > halfTotalWidth ? width : undefined; + }, []); + + const throttledUpdateEllipseBalance = useThrottledCallback(() => { + setBalanceMaxWidth(getRealBalanceMaxWidth()); + setEllipseBalance(getEllipseBalance()); + }, 500, []); + + useEffect(() => { + setBalanceMaxWidth(getRealBalanceMaxWidth()); + setEllipseBalance(getEllipseBalance()); + + window.addEventListener("resize", throttledUpdateEllipseBalance); + + return () => { + window.removeEventListener("resize", throttledUpdateEllipseBalance); + } + }, [tokenInfo, balance, getEllipseBalance, throttledUpdateEllipseBalance]); + + const handleExpandBalance = () => { + const balanceElement = balanceRef.current; + const infoElement = infoRef.current; + const tickerElement = tickerRef.current; + + if (!balanceElement || !infoElement || !tickerElement) return; + + const tickerWidth = tickerElement.offsetWidth; + const balanceWidth = balanceElement.offsetWidth; + const totalWidth = infoElement.offsetWidth - tickerWidth; + const visibleWidth = totalWidth / 2 - 16 - tickerWidth; + + if (balanceWidth <= visibleWidth) return; + + setIsBalanceExpanded(true); + + setBalanceMaxWidth(infoElement.offsetWidth + 16); + + if (balanceWidth > totalWidth) { + const balanceMarqueeOffset = balanceWidth - totalWidth; + + setBalanceMarqueeOffset(balanceMarqueeOffset); + } + } + + const handleCollapse = () => { + setIsBalanceExpanded(false); + setBalanceOffset(undefined); + setBalanceMaxWidth(getRealBalanceMaxWidth()) + + setNameMarqueeOffset(undefined); + setBalanceMarqueeOffset(undefined) + }; + + // const href = id === "AO" + // ? `https://viewblock.io/arweave/address/${ activeWalletAddress }` as const + // : `https://www.ao.link/#/token/${id}` as const; + + const style: React.CSSProperties = {} as any; + + if (balanceOffset !== undefined) style["--balanceOffset"] = `${ balanceOffset }px`; + if (balanceMaxWidth !== undefined) style["--balanceMaxWidth"] = `${ balanceMaxWidth }px`; + + const nameStyle: React.CSSProperties = {} as any; + + if (nameMarqueeOffset) { + nameStyle.animation = `${ nameMarqueeOffset / 15 }s linear infinite alternate ${ styles.marqueeAnimation }`; + nameStyle["--marqueeOffset"] = `${ -nameMarqueeOffset }px`; + } + + const balanceStyle: React.CSSProperties = {} as any; + + if (balanceMarqueeOffset) { + balanceStyle.animation = `${ balanceMarqueeOffset / 15 }s linear infinite alternate ${ styles.marqueeAnimation }`; + balanceStyle["--marqueeOffset"] = `${ -balanceMarqueeOffset }px`; + } + + return ( +
    + + +
    + + {tokenName} + + +
    + + + { balance } + + { ticker } + + + {formattedFiatPrice || "N/A"} + +
    +
    +
    + ); +} diff --git a/apps/wander-connect/src/views/wallet/home/assets/assets.container.tsx b/apps/wander-connect/src/views/wallet/home/assets/assets.container.tsx new file mode 100644 index 000000000..310dc12a5 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/assets/assets.container.tsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import { Button } from "@wanderapp/ui"; +import { AssetItem } from "./asset-item"; +import type { TokenInfoWithBalance } from "@wanderapp/core"; + +export function WalletHomeAssets({ + activeWalletAddress, + tokens, + prices, +}: { + activeWalletAddress: string; + tokens: TokenInfoWithBalance[]; + prices: Record; +}) { + const [showAllTokens, setShowAllTokens] = useState(false); + + const hasMoreTokens = tokens.length > 3; + + const displayedTokens = showAllTokens ? tokens : tokens.slice(0, 3); + + const handleLoadMore = () => { + setShowAllTokens(true); + }; + + const handleShowLess = () => { + setShowAllTokens(false); + }; + + return ( + <> + {displayedTokens.map((token) => ( + + ))} + + {hasMoreTokens && ( + + )} + + ); +} diff --git a/apps/wander-connect/src/views/wallet/home/balance.container.tsx b/apps/wander-connect/src/views/wallet/home/balance.container.tsx new file mode 100644 index 000000000..c452ea8af --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/balance.container.tsx @@ -0,0 +1,12 @@ +import { Text, Box, Balance } from "@wanderapp/ui"; + +export function WalletHomeBalance() { + return ( + + + Your Balance + + + + ); +} diff --git a/apps/wander-connect/src/views/wallet/home/wallet.view.tsx b/apps/wander-connect/src/views/wallet/home/wallet.view.tsx new file mode 100644 index 000000000..cea972709 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/home/wallet.view.tsx @@ -0,0 +1,119 @@ +import React, { useEffect, useMemo } from "react"; +import { WalletHomeActions } from "./actions/actions.container"; +import { WalletHomeAssets } from "./assets/assets.container"; +import { WalletHomeBalance } from "./balance.container"; +import browser from "webextension-polyfill"; +import { Button, Card } from "@arconnect/components-rebrand"; +import { Divider } from "@untitled-ui/icons-react"; +import { useStorage, ExtensionStorage, useBalanceSortedTokens, useActiveWallet, StoredWallet, scheduleImportAoTokens } from "@wanderapp/core"; +import { SnackbarVariant, AccountSelector, Snackbar, TabBar } from "@wanderapp/ui"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { useEmbedded } from "../../../utils/embedded.hooks"; + +export function WalletHomeEmbeddedView() { + const { walletCount, lastRegisteredWallet, clearLastRegisteredWallet, backupMessage, unpartitionedStateStatus } = + useEmbedded(); + + const [activeTab, setActiveTab] = useStorage( + { + key: "wallet_home_active_tab", + instance: ExtensionStorage, + }, + 0, + ); + const [announcement, _] = useStorage({ + key: "show_announcement", + instance: ExtensionStorage, + }); + + const { tokens, prices } = useBalanceSortedTokens({ + type: "asset", + hidden: false, + }); + + // TODO: Use wallets from `EmbeddedProvider` once the nickname/alias is also there. + + const wallet = useActiveWallet(); + + const [wallets] = useStorage( + { + key: "wallets", + instance: ExtensionStorage, + }, + [], + ); + + // Banners: + + const { variant, title, children } = useMemo(() => { + let variant: SnackbarVariant = unpartitionedStateStatus === "supported" ? "warning" : "error"; + let title: string | undefined = undefined; + let children: React.ReactNode = undefined; + + if (lastRegisteredWallet) { + variant = "success"; + title = + walletCount === 1 + ? `Your account has been created!` + : `Your wallet has been ${lastRegisteredWallet.source.type === "IMPORTED" ? "imported" : "created"}!`; + + children = ( + + ); + } else if (backupMessage) { + title = backupMessage; + + children = ( +

    + + +

    + ); + } + + return { variant, title, children }; + }, [walletCount, lastRegisteredWallet, clearLastRegisteredWallet, backupMessage, unpartitionedStateStatus]); + + useEffect(() => { + const trackEventAndPage = async () => { + await trackEvent(EventType.LOGIN, {}); + await trackPage(PageType.HOME); + }; + trackEventAndPage(); + + scheduleImportAoTokens(); + }, []); + + return ( + + + + + + {children} + + + {activeTab === 1 ? ( + + ) : ( + + )} + + ); +} diff --git a/apps/wander-connect/src/views/wallet/receive/options/receive.module.scss b/apps/wander-connect/src/views/wallet/receive/options/receive.module.scss new file mode 100644 index 000000000..7e48d8575 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/receive/options/receive.module.scss @@ -0,0 +1,21 @@ +.linkOption { + border-radius: var(--border-radius-xl); + border: 1px solid var(--color-border-default, var(--brand-color-gray-200)); + text-decoration: none; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + padding: var(--spacing-3); + gap: var(--spacing-3); + color: var(--input-color); + font-family: var(--font-family-serif); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-relaxed); + + &:hover { + background: rgba(var(--color-overlay-rgb), 0.0625); + } +} diff --git a/apps/wander-connect/src/views/wallet/receive/options/receive.options.view.tsx b/apps/wander-connect/src/views/wallet/receive/options/receive.options.view.tsx new file mode 100644 index 000000000..78d010a10 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/receive/options/receive.options.view.tsx @@ -0,0 +1,28 @@ +import { DefaultCard, BuyWithCashIcon, Link } from "@wanderapp/ui"; +import { useLocation } from "@wanderapp/core"; +import { EmbeddedPaths } from "../../../../router/dashboard/iframe.routes"; + +import styles from "./receive.module.scss"; + +export function WalletReceiveOptionsEmbeddedView() { + const { navigate } = useLocation(); + + return ( + navigate(EmbeddedPaths.WalletHomeEmbeddedView)}> + + Cash + + + + + + + Receive from another wallet + + + ); +} diff --git a/apps/wander-connect/src/views/wallet/receive/receive.view.css b/apps/wander-connect/src/views/wallet/receive/receive.view.css new file mode 100644 index 000000000..5c8c65f16 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/receive/receive.view.css @@ -0,0 +1,12 @@ +@import "../../../../components/embed/tokens/index.css"; + +.wrapper { + display: flex; + justify-content: center; + align-items: center; + background-color: #0d6ce9; + border-radius: 24px; + padding: 16px; + width: auto; + height: auto; +} diff --git a/apps/wander-connect/src/views/wallet/receive/receive.view.tsx b/apps/wander-connect/src/views/wallet/receive/receive.view.tsx new file mode 100644 index 000000000..157720d2f --- /dev/null +++ b/apps/wander-connect/src/views/wallet/receive/receive.view.tsx @@ -0,0 +1,53 @@ +import { QRCodeSVG } from "qrcode.react"; +import { useMemo } from "react"; +import { Card, Copyable, Box } from "@wanderapp/ui"; +import { useLocation } from "@wanderapp/core"; +import copy from "copy-to-clipboard"; +import { useEmbedded } from "../../../utils/embedded.hooks"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; + +import "./receive.view.css"; + +export function WalletReceiveEmbeddedView() { + const { currentWallet } = useEmbedded(); + const { navigate } = useLocation(); + + const effectiveAddress = useMemo(() => currentWallet.address, [currentWallet]); + + return ( + navigate(EmbeddedPaths.WalletHomeEmbeddedView)} + style={{ padding: "32px" }}> + + {/* + {effectiveWalletName} + */} +
    + +
    + { + copy(effectiveAddress ?? ""); + }} + /> +
    +
    + ); +} diff --git a/apps/wander-connect/src/views/wallet/transactions-history/transactions-history.view.tsx b/apps/wander-connect/src/views/wallet/transactions-history/transactions-history.view.tsx new file mode 100644 index 000000000..c51ea9df6 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/transactions-history/transactions-history.view.tsx @@ -0,0 +1,37 @@ +import { Loading } from "@arconnect/components-rebrand"; +import { Text, Box, Button, AuthRequestCard } from "@wanderapp/ui"; +import browser from "webextension-polyfill"; +import { useActiveWallet, useLocation, useTransactions } from "@wanderapp/core"; +import { TransactionGroup } from "../transactions/components/TransactionGroup"; + +export function WalletTransactionsHistoryEmbeddedView() { + const { address } = useActiveWallet(); + const { navigate } = useLocation(); + const { transactions, loading, hasNextPage, fetchTransactions, count } = useTransactions(address); + + return ( + navigate("/wallet/transactions")}> + {count.actual > 0 ? ( + <> + {Object.entries(transactions).map(([monthYear, transactions]) => ( + + ))} + + {hasNextPage && ( + + )} + + ) : ( + + {loading ? ( + + ) : ( + {browser.i18n.getMessage("no_transactions")} + )} + + )} + + ); +} diff --git a/apps/wander-connect/src/views/wallet/transactions/components/TransactionGroup.tsx b/apps/wander-connect/src/views/wallet/transactions/components/TransactionGroup.tsx new file mode 100644 index 000000000..4c7eea8b5 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/transactions/components/TransactionGroup.tsx @@ -0,0 +1,30 @@ +import { Box, Text } from "@wanderapp/ui"; +import { TransactionItem } from "./TransactionItem"; +import { ExtendedTransaction, getFullMonthNameWithYear } from "@wanderapp/core"; + +interface TransactionGroupProps { + monthYear: string; + transactions: ExtendedTransaction[]; +} + +export const TransactionGroup = ({ monthYear, transactions }: TransactionGroupProps) => { + return ( + + + {getFullMonthNameWithYear(monthYear)} + + + {transactions.map((transaction) => ( + + ))} + + + ); +}; diff --git a/apps/wander-connect/src/views/wallet/transactions/components/TransactionItem.tsx b/apps/wander-connect/src/views/wallet/transactions/components/TransactionItem.tsx new file mode 100644 index 000000000..23838b83d --- /dev/null +++ b/apps/wander-connect/src/views/wallet/transactions/components/TransactionItem.tsx @@ -0,0 +1,87 @@ +import { Row, Text, Box, TokenLogo } from "@wanderapp/ui"; +import browser from "webextension-polyfill"; +import { useCallback, useMemo } from "react"; +import { ExtendedTransaction, TokenInfo, getMonthName, getTransactionDescription, getFormattedAmount } from "@wanderapp/core"; + +interface TransactionItemProps { + transaction: ExtendedTransaction; +} + +export const TransactionItem = ({ transaction }: TransactionItemProps) => { + const tokenInfo = useMemo(() => { + const { aoInfo } = transaction; + + return aoInfo + ? ({ + processId: "", + Denomination: aoInfo.denomination, + Logo: aoInfo.logo, + Ticker: aoInfo.tickerName, + } satisfies TokenInfo) + : "AR"; + }, [transaction]); + + const handleTransactionClick = useCallback(() => { + const id = transaction.node.id; + const url = transaction?.aoInfo ? `https://www.ao.link/#/message/${id}` : `https://viewblock.io/arweave/tx/${id}`; + + window.open(url, "_blank"); + }, [transaction]); + + const formattedDate = useMemo(() => { + return transaction.date + ? `${getMonthName(`${transaction.month}-${transaction.year}`)} ${transaction.day}` + : browser.i18n.getMessage("pending"); + }, [transaction]); + + return ( + + + + + +
    + + {getTransactionDescription(transaction)} + + {formattedDate} +
    + + {getFormattedAmount(transaction)} + +
    +
    +
    +
    + ); +}; diff --git a/apps/wander-connect/src/views/wallet/transactions/transaction-complete.view.tsx b/apps/wander-connect/src/views/wallet/transactions/transaction-complete.view.tsx new file mode 100644 index 000000000..54a111152 --- /dev/null +++ b/apps/wander-connect/src/views/wallet/transactions/transaction-complete.view.tsx @@ -0,0 +1,72 @@ +import { CommonRouteProps, useLocation, useSearchParams, WanderRoutePath } from "@wanderapp/core"; +import { Box, Card, XClose, Text, Button } from "@wanderapp/ui"; +import browser from "webextension-polyfill"; +import Lottie from "react-lottie"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { postEmbeddedMessage } from "../../../utils/utils/messages/embedded-messages.utils"; + +import checkmarkAnimationData from "assets/lotties/checkmark.json"; + +export interface TransactionCompletedParams { + id: string; +} + +export type TransactionCompletedViewProps = CommonRouteProps; + +export function WalletTransactionCompleteEmbeddedView({ params: { id } }: TransactionCompletedViewProps) { + const { navigate } = useLocation(); + const { back: backPath, isAo } = useSearchParams<{ + back?: string; + isAo: boolean; + }>(); + + const handleCancel = () => { + postEmbeddedMessage({ + type: "embedded_close", + data: null, + }); + navigate(EmbeddedPaths.WalletHomeEmbeddedView); + }; + + if (!id) return null; + + return ( + } + onCloseButtonClick={handleCancel} + style={{ padding: "2rem" }}> + + + + + {browser.i18n.getMessage("transaction_complete")} + + + + + + + ); +} diff --git a/apps/wander-connect/src/views/wallet/transactions/transactions.view.tsx b/apps/wander-connect/src/views/wallet/transactions/transactions.view.tsx new file mode 100644 index 000000000..340a72dae --- /dev/null +++ b/apps/wander-connect/src/views/wallet/transactions/transactions.view.tsx @@ -0,0 +1,38 @@ +import { Text, Box, Button, AuthRequestCard } from "@wanderapp/ui"; +import { useActiveWallet, useLocation, useTransactions } from "@wanderapp/core"; +import browser from "webextension-polyfill"; +import { Loading } from "@arconnect/components-rebrand"; +import { EmbeddedPaths } from "../../../router/dashboard/iframe.routes"; +import { TransactionGroup } from "./components/TransactionGroup"; + +export function WalletTransactionsEmbeddedView() { + const { address } = useActiveWallet(); + const { navigate } = useLocation(); + const { transactions, loading, hasNextPage, count } = useTransactions(address, 3); + + return ( + navigate(EmbeddedPaths.WalletHomeEmbeddedView)}> + {count.current > 0 ? ( + Object.entries(transactions).map(([monthYear, transactions]) => ( + + )) + ) : ( + + {loading ? ( + + ) : ( + {browser.i18n.getMessage("no_transactions")} + )} + + )} + + {!loading && (count.actual > 3 || hasNextPage) && ( + + )} + + ); +} diff --git a/apps/wander-connect/tsconfig.app.json b/apps/wander-connect/tsconfig.app.json new file mode 100644 index 000000000..ed9014b2a --- /dev/null +++ b/apps/wander-connect/tsconfig.app.json @@ -0,0 +1,47 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo", + "jsx": "react-jsx", + "lib": ["dom"], + "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts", "vite/client"], + "rootDir": "src", + + // Added: + + "paths": { + "@wanderapp/isomorphic-messaging": ["./libs/isomorphic-messaging/src/index.ts"], + "@wanderapp/core": ["./libs/core/src/index.ts"], + "@wanderapp/ui": ["./libs/ui/src/index.ts"] + } + }, + "exclude": [ + "out-tsc", + "dist", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"], + "references": [ + { + "path": "../../libs/ui/tsconfig.lib.json" + }, + { + "path": "../../libs/core/tsconfig.lib.json" + } + ] +} diff --git a/apps/wander-connect/tsconfig.json b/apps/wander-connect/tsconfig.json new file mode 100644 index 000000000..2a81a300b --- /dev/null +++ b/apps/wander-connect/tsconfig.json @@ -0,0 +1,19 @@ +{ + "files": [], + "include": [], + "references": [ + { + "path": "../../libs/ui" + }, + { + "path": "../../libs/core" + }, + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../tsconfig.base.json" +} diff --git a/apps/wander-connect/tsconfig.spec.json b/apps/wander-connect/tsconfig.spec.json new file mode 100644 index 000000000..eb6ba4312 --- /dev/null +++ b/apps/wander-connect/tsconfig.spec.json @@ -0,0 +1,36 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out-tsc/vitest", + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ], + "jsx": "react-jsx" + }, + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "references": [ + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/apps/wander-connect/vite.config.ts b/apps/wander-connect/vite.config.ts new file mode 100644 index 000000000..8bf3a7efb --- /dev/null +++ b/apps/wander-connect/vite.config.ts @@ -0,0 +1,91 @@ +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/apps/wander-connect', + + resolve: { + alias: { + // "~": path.resolve(__dirname, "./src"), + // "~domains": path.resolve(__dirname, "./src/domains"), + // "~constants": path.resolve(__dirname, "./src/constants"), + // "~api": path.resolve(__dirname, "./src/api"), + // "~applications": path.resolve(__dirname, "./src/applications"), + // "~components": path.resolve(__dirname, "./src/components"), + // "~contacts": path.resolve(__dirname, "./src/contacts"), + // "~gateways": path.resolve(__dirname, "./src/gateways"), + // "~iframe": path.resolve(__dirname, "./src/iframe"), + // "~lib": path.resolve(__dirname, "./src/lib"), + // "~notifications": path.resolve(__dirname, "./src/notifications"), + // "~routes": path.resolve(__dirname, "./src/routes"), + // "~settings": path.resolve(__dirname, "./src/settings"), + // "~subscriptions": path.resolve(__dirname, "./src/subscriptions"), + // "~tokens": path.resolve(__dirname, "./src/tokens"), + // "~utils": path.resolve(__dirname, "./src/utils"), + // "~wallets": path.resolve(__dirname, "./src/wallets"), + + // BE or Embed (iframe) strategies for messaging and chunking: + "~isomorphic-messaging": path.resolve( + __dirname, + "./src/utils/messaging/strategies/iframe/iframe-messaging.strategy.ts", + ), + "~isomorphic-chunking": path.resolve( + __dirname, + "./src/utils/messaging/strategies/iframe/iframe-chunking.strategy.ts", + ), + + // Prisma Enum Fix: + // See https://github.com/prisma/prisma/issues/12504#issuecomment-1136126199 + // See https://github.com/sveltejs/kit/issues/4444 + ".prisma/client/index-browser": "./node_modules/.prisma/client/index-browser.js", + + // Assets: + "~assets": path.resolve(__dirname, "./assets"), + "assets/lotties": path.resolve(__dirname, "./assets/lotties"), + "url:/assets": path.resolve(__dirname, "./assets"), + "url:assets": path.resolve(__dirname, "./assets"), + "url:/assets-beta": path.resolve(__dirname, "./assets-beta"), + "url:assets-beta": path.resolve(__dirname, "./assets-beta"), + + // Polyfill `webextension-polyfill` for embedded, as that's not a BE but a regular SPA: + "webextension-polyfill": path.resolve(__dirname, "./src/iframe/browser"), + } + }, + + server: { + port: 4200, + host: 'localhost', + }, + preview: { + port: 4300, + host: 'localhost', + }, + plugins: [react()], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: './dist', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + test: { + name: '@org/wander-connect', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: './test-output/vitest/coverage', + provider: 'v8' as const, + }, + }, +})); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..a2c384d55 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,47 @@ +import nx from "@nx/eslint-plugin"; + +export default [ + ...nx.configs["flat/base"], + ...nx.configs["flat/typescript"], + ...nx.configs["flat/javascript"], + { + ignores: ["**/dist", "**/vite.config.*.timestamp*", "**/vitest.config.*.timestamp*", "**/test-output"], + }, + { + files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], + rules: { + "@nx/enforce-module-boundaries": [ + "error", + { + enforceBuildableLibDependency: true, + allow: ["^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$"], + depConstraints: [ + { + sourceTag: "*", + onlyDependOnLibsWithTags: ["*"], + }, + ], + }, + ], + }, + }, + { + files: ["**/*.ts", "**/*.tsx", "**/*.cts", "**/*.mts", "**/*.js", "**/*.jsx", "**/*.cjs", "**/*.mjs"], + // Override or add rules here + rules: { + "@typescript-eslint/ban-ts-comment": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-unused-vars": 0, + "no-async-promise-executor": 0, + "no-empty": 0, + "no-loss-of-precision": 0, + "no-prototype-builtins": 0, + "no-restricted-globals": 0, + "no-unsafe-optional-chaining": 0, + "no-useless-escape": 0, + "react-hooks/exhaustive-deps": 0, + "react/jsx-no-undef": 0, + }, + }, +]; diff --git a/libs/core/.babelrc b/libs/core/.babelrc new file mode 100644 index 000000000..1ea870ead --- /dev/null +++ b/libs/core/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/core/README.md b/libs/core/README.md new file mode 100644 index 000000000..91b6e86e5 --- /dev/null +++ b/libs/core/README.md @@ -0,0 +1,7 @@ +# core + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test core` to execute the unit tests via [Vitest](https://vitest.dev/). diff --git a/libs/core/eslint.config.mjs b/libs/core/eslint.config.mjs new file mode 100644 index 000000000..3e04c76e7 --- /dev/null +++ b/libs/core/eslint.config.mjs @@ -0,0 +1,13 @@ +import nx from "@nx/eslint-plugin"; +import baseConfig from "../../eslint.config.mjs"; + +export default [ + ...nx.configs["flat/react"], + ...baseConfig, + + // { + // files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], + // // Override or add rules here + // rules: {}, + // }, +]; diff --git a/libs/core/package.json b/libs/core/package.json new file mode 100644 index 000000000..45646fe70 --- /dev/null +++ b/libs/core/package.json @@ -0,0 +1,19 @@ +{ + "name": "@wanderapp/core", + "version": "0.0.1", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "nx": { + "name": "core" + } +} diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts new file mode 100644 index 000000000..68436a7a8 --- /dev/null +++ b/libs/core/src/index.ts @@ -0,0 +1,150 @@ +export * from "./lib/agents/constants"; +export * from "./lib/agents/deploy"; +export * from "./lib/agents/hooks"; +export * from "./lib/agents/queries"; +export * from "./lib/agents/swap"; +export * from "./lib/agents/sync"; +export * from "./lib/agents/types"; +export * from "./lib/agents/utils/date.utils"; +export * from "./lib/agents/utils/index"; + +export * from "./lib/applications/allowance"; +export * from "./lib/applications/application.class"; +export * from "./lib/applications/application.utils"; +export * from "./lib/applications/gateway"; +export * from "./lib/applications/permissions"; +export * from "./lib/applications/tab"; +export * from "./lib/applications/useActiveTab"; + +export * from "./lib/auth/auth.constants"; +export * from "./lib/auth/auth.hooks"; +export * from "./lib/auth/auth.provider"; +export * from "./lib/auth/auth.types"; +export * from "./lib/auth/auth.utils"; + +export * from "./lib/constants/api"; + +export * from "./lib/gateways/api"; +export * from "./lib/gateways/ar_protocol"; +export * from "./lib/gateways/cache"; +export * from "./lib/gateways/gateway"; +export * from "./lib/gateways/types"; +export * from "./lib/gateways/utils"; +export * from "./lib/gateways/wayfinder"; + +export * from "./lib/nameservice/nameservice"; + +export * from "./lib/injected-api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; +export * from "./lib/injected-api/background/background-setup"; +export * from "./lib/injected-api/foreground/foreground-setup-wallet-sdk"; +export * from "./lib/injected-api/foreground/foreground-setup-embedded-wallet-sdk"; +export * from "./lib/injected-api/modules/sign_data_item/types"; +export * from "./lib/injected-api/modules/sign/tags"; + +export * from "./lib/notifications/utils"; + +export * from "./lib/react-query/query-client.constants"; + +export * from "./lib/router/auth/auth-router.hook"; +export * from "./lib/router/extension/extension-router.hook"; +export * from "./lib/router/extension/extension.routes"; +export * from "./lib/router/router.types"; +export * from "./lib/router/router.utils"; + +export * from "./lib/subscriptions/index"; +export * from "./lib/subscriptions/payments"; +export * from "./lib/subscriptions/subscription"; + +export * from "./lib/tokens/aoTokens/ao.constants"; +export * from "./lib/tokens/aoTokens/ao"; +export * from "./lib/tokens/aoTokens/config"; +export * from "./lib/tokens/aoTokens/router"; +export * from "./lib/tokens/aoTokens/sync"; + +export * from "./lib/tokens/hooks/index"; +export * from "./lib/tokens/hooks/useFormattedTokenBalance"; +export * from "./lib/tokens/hooks/useMarketStats"; +export * from "./lib/tokens/hooks/useTokenMarketData"; +export * from "./lib/tokens/hooks/useTokenPriceChange"; + +export * from "./lib/tokens/currency"; +export * from "./lib/tokens/index"; +export * from "./lib/tokens/knownTokens"; +export * from "./lib/tokens/token"; +export * from "./lib/tokens/useRemoveCover"; + +export * from "./lib/transactions/transactions"; + +export * from "./lib/transak/transak.alarms"; +export * from "./lib/transak/transak.constants"; +export * from "./lib/transak/transak.hooks"; +export * from "./lib/transak/transak.queries"; +export * from "./lib/transak/transak.utils"; + +export * from "./lib/utils/analytics/analytics"; +export * from "./lib/utils/array/array.utils"; +export * from "./lib/utils/assertions/assertions"; +export * from "./lib/utils/browser-extension/context-menus"; +export * from "./lib/utils/browser-extension/icon"; +export * from "./lib/utils/browser-extension/runtime"; +export * from "./lib/utils/browser-info/browser-info.utils"; +export * from "./lib/utils/coingecko/coingecko"; +export * from "./lib/utils/email/email.utils"; +export * from "./lib/utils/error/ErrorBoundary/errorBoundary"; +export * from "./lib/utils/error/error.utils"; +export * from "./lib/utils/events/events"; +export * from "./lib/utils/fair-launch/fair-launch.alarms"; +export * from "./lib/utils/fair-launch/fair-launch.constants"; +export * from "./lib/utils/fair-launch/fair-launch.hooks"; +export * from "./lib/utils/fair-launch/fair-launch.types"; +export * from "./lib/utils/fair-launch/fair-launch.utils"; +export * from "./lib/utils/file/file.utils"; +export * from "./lib/utils/format/format"; +export * from "./lib/utils/inactivity/inactivity.constants"; +export * from "./lib/utils/inactivity/inactivity.hooks"; +export * from "./lib/utils/inactivity/inactivity.manager"; +export * from "./lib/utils/inactivity/inactivity.types"; +export * from "./lib/utils/inactivity/inactivity.utils"; +export * from "./lib/utils/log/log.utils"; +export * from "./lib/utils/logo/logo.utils"; +export * from "./lib/utils/mutex/mutex"; +export * from "./lib/utils/otp/otp.utils"; +export * from "./lib/utils/promises/isPromise"; +export * from "./lib/utils/promises/resolvable"; +export * from "./lib/utils/promises/retry"; +export * from "./lib/utils/promises/sleep"; +export * from "./lib/utils/promises/timeout"; +export * from "./lib/utils/ramps/ramps"; +export * from "./lib/utils/react/useAsyncEffect"; +export * from "./lib/utils/react/useCooldownCallback"; +export * from "./lib/utils/settings/index"; +export * from "./lib/utils/settings/hook"; +export * from "./lib/utils/settings/setting"; +export * from "./lib/utils/signer/signer.utils"; +export * from "./lib/utils/storage/storage"; +export * from "./lib/utils/storage/storage.constants"; +export * from "./lib/utils/tier/alarms"; +export * from "./lib/utils/tier/carousel"; +export * from "./lib/utils/tier/constants"; +export * from "./lib/utils/tier/hooks"; +export * from "./lib/utils/tier/types"; +export * from "./lib/utils/tier/utils"; +export * from "./lib/utils/timestamp/timestamp.utils"; +export * from "./lib/utils/url/url.utils"; +export * from "./lib/utils/wayfinder/wayfinder"; + +export * from "./lib/wallets/connect/wallets.constants"; +export * from "./lib/wallets/connect/wallets.hooks"; +export * from "./lib/wallets/connect/wallets.provider"; +export * from "./lib/wallets/connect/wallets.service"; +export * from "./lib/wallets/connect/wallets.utils"; +export * from "./lib/wallets/hardware/index"; +export * from "./lib/wallets/hardware/keystone"; +export * from "./lib/wallets/upload/use-wallet-upload.hook"; +export * from "./lib/wallets/auth"; +export * from "./lib/wallets/encryption"; +export * from "./lib/wallets/generator"; +export * from "./lib/wallets/hooks"; +export * from "./lib/wallets/index"; +export * from "./lib/wallets/wallets.types"; +export * from "./lib/wallets/wallets.utils"; diff --git a/libs/core/src/lib/agents/constants.ts b/libs/core/src/lib/agents/constants.ts new file mode 100644 index 000000000..fe1c3fd2f --- /dev/null +++ b/libs/core/src/lib/agents/constants.ts @@ -0,0 +1,27 @@ +export const APP_NAME = "Wander"; + +export const defaultServices = { + gatewayUrl: "https://arweave.net", + cuUrl: "https://cu.ao-testnet.xyz", + muUrl: "https://mu.ao-testnet.xyz", +}; + +export const aoExplorerUrl = "https://www.ao.link"; + +export const AO_MINTER_PROCESS_ID = "NTE-RcHEeO15MYMUbXwWytRxn_IUJmXPKPOFVc5qZcg"; +export const WANDER_FEE_PROCESS_ID = "OZas4eERzCPCHUB0bTIpsmjzdR0P3Xq8bZoSWdLk8Uw"; + +export const ONE_HOUR_MS = 3600000; +export const SHOW_CREATE_WANDER_AGENT_CTA = "show_create_wander_agent_cta"; +export const HAS_SHOWN_AGENTS_EXPLAINER_POPUP = "has_shown_agents_explainer_popup"; +export const AO_YIELD_AGENT_RECENT_TXS = "ao_yield_agent_recent_txs"; +export const AO_YIELD_AGENT_RECENT_TXS_CHECK_IN_PROGRESS_KEY = "ao_yield_agent_recent_txs_check_in_progress"; +export const AO_YIELD_AGENT_LAST_SWAP_DATE_KEY = "ao_yield_agent_last_swap_date"; +export const AO_YIELD_AGENT_SWAP_IN_PROGRESS_KEY = "ao_yield_agent_swap_in_progress"; +export const AO_YIELD_AGENT_COOLDOWN_KEY = "ao_yield_agent_cooldown_until"; +export const AO_YIELD_AGENT_ALARM_NAME = "ao-yield-agent-alarm"; +export const AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME = "ao-yield-agent-recent-txs-check-alarm"; +export const AO_YIELD_AGENT_SYNC_ALARM_NAME_PREFIX = "ao-yield-agent-sync-alarm-"; +export const AO_YIELD_AGENT_SYNC_STATUS_PREFIX_KEY = "ao-yield-agent-sync-status-"; + +export const AGENT_VERSION = "1.0.0"; diff --git a/libs/core/src/lib/agents/deploy.ts b/libs/core/src/lib/agents/deploy.ts new file mode 100644 index 000000000..807788f7f --- /dev/null +++ b/libs/core/src/lib/agents/deploy.ts @@ -0,0 +1,262 @@ +import { connect } from "@permaweb/aoconnect"; +import type { AosConfig, DeployConfig, DeployResult, Services } from "./types"; +import { APP_NAME, defaultServices } from "./constants"; +import { getArweave, isArweaveAddress, isCronPattern, parseToInt, pollForProcessSpawn } from "./utils"; +import { ExtensionStorage } from "~utils/storage"; +import { getActiveKeyfile } from "~wallets"; +import { createDataItemSigner } from "~tokens/aoTokens/ao"; +import { retryWithDelay } from "~utils/promises/retry"; +import { AOS_QUERY } from "./queries"; +import { log, LOG_GROUP } from "~utils/log/log.utils"; +import { isURL } from "~utils/urls/isURL"; + +/** + * Manages deployments of contracts to AO. + */ +export class DeploymentsManager { + #cachedAosConfig: AosConfig | null = null; + + #validateServices(services?: Services) { + // Validate and use provided URLs or fall back to defaults + const { gatewayUrl, cuUrl, muUrl } = services ?? {}; + + services = { + gatewayUrl: isURL(gatewayUrl) ? gatewayUrl : defaultServices.gatewayUrl, + cuUrl: isURL(cuUrl) ? cuUrl : defaultServices.cuUrl, + muUrl: isURL(muUrl) ? muUrl : defaultServices.muUrl, + }; + + return services; + } + + #getAoInstance(services: Services) { + if ( + (!services.cuUrl || services.cuUrl === defaultServices.cuUrl) && + (!services.gatewayUrl || services.gatewayUrl === defaultServices.gatewayUrl) && + (!services.muUrl || services.muUrl === defaultServices.muUrl) + ) { + return connect({ + GATEWAY_URL: defaultServices.gatewayUrl, + MU_URL: defaultServices.muUrl, + CU_URL: defaultServices.cuUrl, + }); + } + + return connect({ + GATEWAY_URL: services.gatewayUrl, + MU_URL: services.muUrl, + CU_URL: services.cuUrl, + }); + } + + async #getAosConfig() { + if (this.#cachedAosConfig) { + return this.#cachedAosConfig; + } + + const defaultDetails = { + module: "JArYBF-D8q2OmZ4Mok00sD2Y_6SYEQ7Hjx-6VZ_jl3g", + sqliteModule: "33d-3X8mpv6xYBlVB-eXMrPfH5Kzf6Hiwhcv0UA10sw", + scheduler: "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA", + authority: "fcoN_xJeisVsPXA-trzVAuIiqO3ydLQxM-L4XbrQKzY", + }; + + try { + const response = await fetch("https://raw.githubusercontent.com/pawanpaudel93/ao-deploy-config/main/config.json"); + const config = (await response.json()) as AosConfig; + this.#cachedAosConfig = { + module: config?.module || defaultDetails.module, + sqliteModule: config?.sqliteModule || defaultDetails.sqliteModule, + scheduler: config?.scheduler || defaultDetails.scheduler, + authority: defaultDetails.authority, + }; + return this.#cachedAosConfig; + } catch { + return defaultDetails; + } + } + + async #findProcess(name: string, owner: string, retry: DeployConfig["retry"], gateway: string) { + const processId = await retryWithDelay( + async () => { + const res = await getArweave(gateway).api.post("/graphql", { + query: AOS_QUERY, + variables: { owners: [owner], names: [name] }, + }); + if (!res.ok || res?.data?.data === null) { + throw new Error(`(${res.status}) ${res.statusText} - GraphQL ERROR`); + } + return res?.data?.data?.transactions?.edges?.[0]?.node?.id; + }, + retry?.count, + retry?.delay, + ); + + return processId; + } + + #validateCron(cron: string) { + const isCronValid = isCronPattern(cron); + if (!isCronValid) { + throw new Error("Invalid cron flag!"); + } + } + + /** + * Deploys or updates a contract on AO. + * @param {DeployConfig} deployConfig - Configuration options for the deployment. + * @returns {Promise} The result of the deployment. + */ + async deployContract({ + name, + contractPath, + tags, + cron, + module, + scheduler, + retry, + configName, + processId, + services, + onBoot, + silent = false, + forceSpawn = false, + }: DeployConfig): Promise { + name = name || "ao-yield-agent"; + configName = configName || name; + retry = { + count: parseToInt(retry?.count, 10), + delay: parseToInt(retry?.delay, 3000), + }; + + const aosConfig = await this.#getAosConfig(); + module = isArweaveAddress(module) ? module! : aosConfig.module; + scheduler = isArweaveAddress(scheduler) ? scheduler! : aosConfig.scheduler; + + const owner = await ExtensionStorage.get("active_address"); + const wallet = await getActiveKeyfile(); + + if (wallet.type === "hardware") { + throw new Error("AO Yield Agents are not supported on hardware wallets."); + } + + const signer = createDataItemSigner(wallet.keyfile); + services = this.#validateServices(services); + + // Initialize the AO instance with validated URLs + const aoInstance = this.#getAoInstance(services); + + let isNewProcess = forceSpawn; + + if (!forceSpawn && (!processId || (processId && !isArweaveAddress(processId)))) { + processId = await this.#findProcess(name, owner, retry, services.gatewayUrl!); + isNewProcess = !processId; + } + + if (!contractPath) { + throw new Error("Please provide either a valid contract path."); + } + + const contractSrc = await (await fetch(contractPath)).text(); + + if (isNewProcess) { + log(LOG_GROUP.AGENTS, "Spawning new process..."); + tags = Array.isArray(tags) ? tags : []; + tags = [ + { name: "App-Name", value: APP_NAME }, + { name: "Name", value: name }, + { name: "aos-Version", value: "2.0.4" }, + { name: "Authority", value: aosConfig.authority }, + ...tags, + ]; + + if (onBoot) { + tags = [...tags, { name: "On-Boot", value: "Data" }]; + } + + if (cron) { + this.#validateCron(cron); + tags = [...tags, { name: "Cron-Interval", value: cron }, { name: "Cron-Tag-Action", value: "Cron" }]; + } + + const data = onBoot ? contractSrc : "1984"; + processId = await retryWithDelay( + () => aoInstance.spawn({ module, signer, tags, data, scheduler }), + retry.count, + retry.delay, + ); + + await pollForProcessSpawn({ + processId, + gatewayUrl: services.gatewayUrl, + }); + + if (onBoot) { + return { name, processId, isNewProcess, configName }; + } + } + + let messageId: string; + if (!onBoot || !isNewProcess) { + if (!isNewProcess) { + log(LOG_GROUP.AGENTS, "Updating existing process..."); + } + // Load contract to process + messageId = await retryWithDelay( + async () => + aoInstance.message({ + process: processId!, + tags: [{ name: "Action", value: "Eval" }], + data: contractSrc, + signer, + }), + retry.count, + retry.delay, + ); + + const { Output, Error: error } = await retryWithDelay( + async () => + aoInstance.result({ + process: processId!, + message: messageId, + }), + retry.count, + retry.delay, + ); + + let errorMessage = null; + + if (Output?.data?.output) { + errorMessage = Output.data.output; + } else if (error) { + if (typeof error === "object" && Object.keys(error).length > 0) { + errorMessage = JSON.stringify(error); + } else { + errorMessage = String(error); + } + } + + if (errorMessage) { + throw new Error(errorMessage); + } + } + + return { + name, + processId: processId!, + messageId: messageId!, + isNewProcess, + configName, + }; + } +} + +/** + * Deploys or updates a contract on AO. + * @param {DeployConfig} deployConfig - Configuration options for the deployment. + * @returns {Promise} The result of the deployment. + */ +export async function deployContract(deployConfig: DeployConfig): Promise { + const manager = new DeploymentsManager(); + return manager.deployContract(deployConfig); +} diff --git a/libs/core/src/lib/agents/hooks.ts b/libs/core/src/lib/agents/hooks.ts new file mode 100644 index 000000000..f46816a02 --- /dev/null +++ b/libs/core/src/lib/agents/hooks.ts @@ -0,0 +1,278 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { getAOYieldAgentInfo, getAOYieldAgents, getWanderFee, processTransactions, tokenIdInfoMap } from "./utils"; +import type { + AOYieldAgent, + AOYieldAgentCreate, + AOYieldAgentInfo, + AOYieldAgentStatus, + MintingStatus, + SwapSuccessTransaction, +} from "./types"; +import { ExtensionStorage, useStorage } from "../../../../../libs/core-2/src/lib/utils/storage/storage"; +import type GQLResultInterface from "ar-gql/dist/faces"; +import { gql } from "~gateways/api"; +import { retryWithDelay } from "~utils/promises/retry"; +import { SWAP_SUCCESS_QUERY_WITH_CURSOR } from "./queries"; +import { useQuery } from "@tanstack/react-query"; +import { defaultOptions } from "~tokens/hooks"; +import { checkIfMintingIsPaused, checkIfAgentHasRecentSwaps } from "./swap"; +import dayjs from "dayjs"; +import browser from "webextension-polyfill"; +import type { DefiFeeDetails } from "~utils/tier/types"; +import { useDelegationInfo } from "~utils/fair_launch/fair_launch.hooks"; +import { useActiveAddress } from "~wallets/hooks"; + +interface UseAOYieldAgentsProps { + status?: AOYieldAgentStatus; + showNewAtTop?: boolean; +} + +export function useAOYieldAgents({ status, showNewAtTop = false }: UseAOYieldAgentsProps = {}) { + const [activeAddress] = useStorage({ key: "active_address", instance: ExtensionStorage }); + const [agents] = useStorage( + { + key: `ao_yield_agents_${activeAddress}`, + instance: ExtensionStorage, + }, + [], + ); + + return useMemo(() => { + if (!agents.length) return []; + + const filteredAgents = status ? agents.filter((agent) => agent.status === status) : agents; + return showNewAtTop ? [...filteredAgents].reverse() : filteredAgents; + }, [agents, status, showNewAtTop]); +} + +export function useAOYieldLatestAgent() { + const [activeAddress] = useStorage({ key: "active_address", instance: ExtensionStorage }); + const [agents] = useStorage( + { + key: `ao_yield_agents_${activeAddress}`, + instance: ExtensionStorage, + }, + [], + ); + + return useMemo(() => { + if (!agents.length) return undefined; + + return agents.find((agent) => agent.status === "Active") || agents[agents.length - 1]; + }, [agents]); +} + +export function useAOYieldAgent(agentId: string, status?: AOYieldAgentStatus) { + const [activeAddress] = useStorage({ key: "active_address", instance: ExtensionStorage }); + const [agents] = useStorage( + { + key: `ao_yield_agents_${activeAddress}`, + instance: ExtensionStorage, + }, + [], + ); + + return useMemo(() => { + if (!agents.length) return undefined; + + if (!status) { + return agents.find((agent) => agent.id === agentId); + } + return agents.find((agent) => agent.id === agentId && agent.status === status); + }, [agents, agentId, status]); +} + +export function useAOYieldAgentInfo(agentId: string) { + return useQuery({ + queryKey: ["ao-yield-agent-info", agentId], + queryFn: () => getAOYieldAgentInfo(agentId), + enabled: !!agentId, + refetchInterval: 60_000, + staleTime: 60_000, + gcTime: 60_000, + retry: 3, + retryDelay: (attemptIndex: number) => Math.min(1000 * 2 ** attemptIndex, 30000), + refetchOnWindowFocus: true, + }); +} + +export const useTransactions = (agentId: string, limit?: number) => { + const defaultCursor = ""; + const defaultHasNextPage = true; + + const [count, setCount] = useState({ current: 0, actual: 0 }); + const [cursor, setCursor] = useState(defaultCursor); + const [hasNextPage, setHasNextPage] = useState(defaultHasNextPage); + const [transactions, setTransactions] = useState([]); + const [loading, setLoading] = useState(false); + + const fetchTransactions = useCallback(async () => { + try { + if (!agentId || !hasNextPage) return; + + setLoading(true); + + const rawSwapSuccess = hasNextPage + ? await retryWithDelay(async (attempt: number) => { + const data = await gql(SWAP_SUCCESS_QUERY_WITH_CURSOR, { agentId, after: cursor }); + if (data?.data === null && (data as any)?.errors?.length > 0) { + throw new Error((data as any)?.errors?.[0]?.message || "GraphQL Error"); + } + return data; + }, 2) + : ({ + data: { + transactions: { + pageInfo: { hasNextPage: false }, + edges: [], + }, + }, + } as GQLResultInterface); + + let processedTransactions = await processTransactions(rawSwapSuccess); + + setCursor((prev) => processedTransactions[processedTransactions.length - 1]?.cursor ?? prev); + + setHasNextPage(rawSwapSuccess?.data?.transactions?.pageInfo?.hasNextPage ?? true); + + const actualCount = processedTransactions.length; + + if (limit) { + processedTransactions = processedTransactions.slice(0, limit); + } + + setCount((prev) => ({ + current: prev.current + processedTransactions.length, + actual: prev.actual + actualCount, + })); + + const filteredTransactions = processedTransactions.reduce( + (unique, transaction) => { + const existingIndex = unique.findIndex((tx) => tx.id === transaction.id); + if (existingIndex === -1) { + unique.push(transaction); + } + return unique; + }, + [...transactions], + ); + + // sort by timestamp in descending order (newest first) + filteredTransactions.sort((a, b) => b.timestamp - a.timestamp); + + setTransactions(filteredTransactions); + } catch (error) { + console.error("Error fetching transactions", error); + } finally { + setLoading(false); + } + }, [agentId, hasNextPage, transactions, limit]); + + useEffect(() => { + setCursor(defaultCursor); + setHasNextPage(defaultHasNextPage); + setCount({ current: 0, actual: 0 }); + setTransactions([]); + fetchTransactions(); + }, [agentId]); + + return { + transactions, + loading, + hasNextPage, + count, + fetchTransactions, + }; +}; + +export function useAOMintingStatus() { + const [activeAddress] = useStorage({ key: "active_address", instance: ExtensionStorage }); + + return useQuery({ + queryKey: ["ao-minting-status"], + queryFn: async () => { + const [isPaused, wasStoredAsPaused] = await Promise.all([ + checkIfMintingIsPaused(), + ExtensionStorage.get("ao_minting_paused"), + ]); + + // If state changed, update storage and handle notifications + if (wasStoredAsPaused !== isPaused) { + await ExtensionStorage.set("ao_minting_paused", isPaused); + + // If minting just resumed (was paused, now active), check agent activity too + if (wasStoredAsPaused === true && isPaused === false) { + // Get active agent to check its swap activity + const agents = await getAOYieldAgents(activeAddress); + const activeAgent = agents.find((agent) => agent.status === "Active"); + + if (activeAgent) { + // Check if agent has recent swaps to confirm full functionality + const hasRecentSwaps = await checkIfAgentHasRecentSwaps(activeAgent.id); + + // Only show resume notification if agent is also making swaps + if (hasRecentSwaps) { + await ExtensionStorage.set("show_mint_resumed", true); + } + } + } + } + + return isPaused ? "Paused" : "Active"; + }, + enabled: !!activeAddress, + ...defaultOptions, + }); +} + +export function useAODelegationInfo() { + const activeAddress = useActiveAddress(); + const { data: delegationInfo = {}, isLoading } = useDelegationInfo(); + + return useMemo(() => { + if (isLoading || !activeAddress) return true; + return !!delegationInfo?.[activeAddress]; + }, [activeAddress, delegationInfo, isLoading]); +} + +export function useAOYieldAgentProperties(agent: AOYieldAgentCreate | AOYieldAgent, feeDetails?: DefiFeeDetails) { + const properties = useMemo(() => { + if (!agent) return []; + + const { conversionPercentage, startDate, endDate, runIndefinitely, slippage } = agent; + const asset = "asset" in agent ? agent.asset : tokenIdInfoMap[agent?.tokenOut]; + const days = dayjs(endDate).diff(dayjs(startDate), "day") + 1; + const runningTime = runIndefinitely ? "∞ days" : `${days} ${days === 1 ? "day" : "days"}`; + const endDateFormatted = runIndefinitely ? "∞" : dayjs(endDate).format("MMM D, YYYY"); + + const properties: Array<{ name: string; value: string; originalFeePercent?: string; feeHasChanged?: boolean }> = [ + { name: "daily_conversion", value: `${conversionPercentage}% of AO earnings` }, + { name: "buy_asset", value: asset?.ticker || "" }, + { name: "running_time", value: runningTime }, + { name: "start_date", value: dayjs(startDate).format("MMM D, YYYY") }, + { name: "end_date", value: endDateFormatted }, + { name: "slippage", value: `${slippage}%` }, + ]; + + if (feeDetails) { + properties.push({ + name: "fee", + value: browser.i18n.getMessage("percentage_of_each_conversion", [feeDetails.finalFeePercent]), + originalFeePercent: feeDetails.originalFeePercent, + feeHasChanged: feeDetails.feeHasChanged, + }); + } + + return properties; + }, [agent]); + + return properties; +} + +export function useWanderFee() { + return useQuery({ + queryKey: ["wander-fee"], + queryFn: () => getWanderFee(), + ...defaultOptions, + }); +} diff --git a/libs/core/src/lib/agents/queries.ts b/libs/core/src/lib/agents/queries.ts new file mode 100644 index 000000000..7bcd0ce8c --- /dev/null +++ b/libs/core/src/lib/agents/queries.ts @@ -0,0 +1,150 @@ +import { AO_MINTER_PROCESS_ID } from "./constants"; + +const AO_PROCESS_ID = "0syT13r0s0tgPmIed95bJnuSqaD29HQNN8D3ElLSrsc" as const; + +export const SWAP_SUCCESS_QUERY_WITH_CURSOR = ` +query ($agentId: String!, $after: String) { + transactions( + first: 10, + after: $after, + tags: [ + {name: "Data-Protocol", values: ["ao"]}, + {name: "Action", values: ["Swap-Success"]}, + {name: "From-Process", values: [$agentId]} + ] + ) { + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + block { timestamp, height } + tags { name, value } + } + } + } +} +`; + +export const AOS_QUERY = `query ($owners: [String!]!, $names: [String!]!) { + transactions( + first: 1, + owners: $owners, + tags: [ + { name: "Data-Protocol", values: ["ao"] }, + { name: "Type", values: ["Process"]}, + { name: "Name", values: $names} + ] + ) { + edges { + node { + id + } + } + } + }`; + +export const TRANSACTION_QUERY = `query ($ids: [ID!]!) { + transactions(ids: $ids) { + edges { + node { + id + } + } + } +}`; + +export const AO_PROCESS_MINT_QUERY = ` +query ($count: Int!) { + transactions( + first: $count, + recipients: ["${AO_PROCESS_ID}"], + tags: [ + {name: "Data-Protocol", values: ["ao"]}, + {name: "Action", values: ["Mint"]}, + {name: "From-Process", values: ["${AO_MINTER_PROCESS_ID}"]}, + {name: "Index", values: ["1"]} + ] + ) { + edges { + node { + id + block { timestamp, height } + tags { name, value } + } + } + } +} +`; + +export const AO_PROCESS_MINT_WITH_NONCE_QUERY = ` +query ($nonce: String!) { + transactions( + first: 30, + recipients: ["${AO_PROCESS_ID}"], + tags: [ + {name: "Data-Protocol", values: ["ao"]}, + {name: "Action", values: ["Mint"]}, + {name: "From-Process", values: ["${AO_MINTER_PROCESS_ID}"]}, + {name: "Nonce", values: [$nonce]} + ] + ) { + edges { + node { + id + block { timestamp, height } + } + } + } +} +`; + +export const AO_YIELD_AGENT_RECENT_TX_QUERY = ` +query ($parentTxIds: [String!]!) { + transactions( + first: 100, + tags: [ + {name: "Data-Protocol", values: ["ao"]}, + {name: "Action", values: ["Swap-Success"]}, + {name: "Pushed-For", values: $parentTxIds} + ] + ) { + edges { + node { + id + recipient + tags { name, value } + } + } + } +}`; + +export const AO_YIELD_AGENT_SYNC_QUERY = ` +query ($address: String!, $after: String) { + transactions( + first: 100, + after: $after, + owners: [$address], + tags: [ + {name: "Data-Protocol", values: ["ao"]}, + {name: "Type", values: ["Process"]}, + {name: "App-Name", values: ["Wander"]}, + {name: "Name", values: ["ao-yield-agent"]}, + ] + ) { + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + block { timestamp, height } + tags { name, value } + } + } + } +} +`; diff --git a/libs/core/src/lib/agents/swap.ts b/libs/core/src/lib/agents/swap.ts new file mode 100644 index 000000000..67227bda7 --- /dev/null +++ b/libs/core/src/lib/agents/swap.ts @@ -0,0 +1,744 @@ +import { gql } from "~gateways/api"; +import { + AO_PROCESS_MINT_QUERY, + AO_PROCESS_MINT_WITH_NONCE_QUERY, + AO_YIELD_AGENT_RECENT_TX_QUERY, + SWAP_SUCCESS_QUERY_WITH_CURSOR, +} from "./queries"; +import { + getAOYieldAgentInfo, + getArweave, + getRecentTxs, + setRecentTxs, + updateAOYieldAgent, + getAOYieldAgents, +} from "./utils"; +import { getActiveAddress } from "~wallets/wallets.utils"; +import { getWallets } from "~wallets"; +import BigNumber from "bignumber.js"; +import { fetchTokenBalance, getAOTokenPrice, getTagValue, sendAoTransferForWallet } from "~tokens/aoTokens/ao"; +import { connect } from "@permaweb/aoconnect"; +import { defaultConfig } from "~tokens/aoTokens/config"; +import { defaultOptions } from "~tokens/hooks"; +import { log, LOG_GROUP } from "~utils/log/log.utils"; +import { + AO_YIELD_AGENT_ALARM_NAME, + AO_YIELD_AGENT_LAST_SWAP_DATE_KEY, + AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME, + AO_YIELD_AGENT_RECENT_TXS_CHECK_IN_PROGRESS_KEY, + AO_YIELD_AGENT_SWAP_IN_PROGRESS_KEY, + ONE_HOUR_MS, + AO_YIELD_AGENT_COOLDOWN_KEY, +} from "./constants"; +import type { AOYieldAgent, AOYieldAgentInfo, MintQuantityResult, MintTransaction, ParsedMintData } from "./types"; +import type { DecodedTag } from "~api/modules/sign/tags"; +import { ExtensionStorage } from "~utils/storage"; +import browser from "webextension-polyfill"; +import { EventType, trackDirect } from "~utils/analytics"; +import { getActiveTier, saveWalletLifetimeSavings } from "~utils/tier/utils"; +import { queryClient } from "~utils/tanstack"; +import { balanceToFractioned } from "~tokens/currency"; +import { AO_PROCESS_ID, defaultTokens } from "~tokens/aoTokens/ao.constants"; + +let isSwapExecutionInProgress = false; +let isSchedulingInProgress = false; +let isRecentTxCheckInProgress = false; + +async function startSwapInProgress(lockTimestamp: number) { + await ExtensionStorage.set(AO_YIELD_AGENT_SWAP_IN_PROGRESS_KEY, lockTimestamp); +} + +async function clearSwapInProgress() { + await ExtensionStorage.remove(AO_YIELD_AGENT_SWAP_IN_PROGRESS_KEY); +} + +async function addCooldown() { + await ExtensionStorage.set(`${AO_YIELD_AGENT_COOLDOWN_KEY}`, Date.now() + 5 * 60 * 1000); +} + +async function isCooldownActive() { + const cooldownUntil = await ExtensionStorage.get(AO_YIELD_AGENT_COOLDOWN_KEY); + if (cooldownUntil && cooldownUntil > Date.now()) { + return true; + } + return false; +} + +function sortAscending(a: MintTransaction, b: MintTransaction): number { + return a.timestamp - b.timestamp; +} + +function convertQuantityToUsd(quantity: string, price: number): string { + const amount = balanceToFractioned(quantity, { decimals: 12 }); + return BigNumber(amount.multipliedBy(price).toPrecision(8, BigNumber.ROUND_HALF_UP)).toFixed(); +} + +/** + * Normalizes a timestamp to the start of the day. + * @param timestamp - The timestamp to normalize. + * @returns The normalized timestamp. + */ +function normalizeToStartOfDay(timestamp: number): number { + return new Date(timestamp).setHours(0, 0, 0, 0); +} + +/** + * Creates a transaction object from a GraphQL edge. + * @param edge - The GraphQL edge. + * @returns The transaction object. + */ +function createTransactionFromEdge(edge: any): MintTransaction { + const tags = edge.node.tags; + const total = getTagValue("Total", tags); + const nonce = getTagValue("Nonce", tags); + + return { + id: edge.node.id, + timestamp: edge.node.block?.timestamp ? edge.node.block.timestamp * 1000 : Date.now(), + total: Number(total), + nonce: Number(nonce), + }; +} + +/** + * Builds a transaction map from edges. + * @param edges - The edges to build the transaction map from. + * @returns The transaction map. + */ +function buildTransactionMap(edges: any[]): Map { + const transactionMap = new Map(); + + edges.forEach((edge) => { + const transaction = createTransactionFromEdge(edge); + + if (!transactionMap.has(edge.node.id)) { + transactionMap.set(edge.node.id, transaction); + } + }); + + return transactionMap; +} + +export async function fetchAllMintTransactions(count = 10): Promise { + count = Math.min(count, 100); + const response = await gql(AO_PROCESS_MINT_QUERY, { count }); + const edges = response?.data?.transactions?.edges || []; + + const transactionMap = buildTransactionMap(edges); + + return Array.from(transactionMap.values()).sort(sortAscending); +} + +export async function fetchMintTransactionsByNonce(nonce: number): Promise { + const response = await gql(AO_PROCESS_MINT_WITH_NONCE_QUERY, { nonce }); + const edges = response?.data?.transactions?.edges || []; + + const transactionMap = buildTransactionMap(edges); + + return Array.from(transactionMap.values()); +} + +export async function checkIfMintingIsPaused(): Promise { + const transactions = await fetchAllMintTransactions(); + const now = Date.now(); + const today = normalizeToStartOfDay(now); + const yesterday = normalizeToStartOfDay(now - 24 * 60 * 60 * 1000); + + const hasTransactionToday = transactions.some((tx) => normalizeToStartOfDay(tx.timestamp) === today); + const hasTransactionYesterday = transactions.some((tx) => normalizeToStartOfDay(tx.timestamp) === yesterday); + + // If there's a transaction today but none yesterday, minting is not paused + if (hasTransactionToday && !hasTransactionYesterday) { + return false; + } + + // If there are no transactions today or yesterday, minting is paused + return !hasTransactionToday && !hasTransactionYesterday; +} + +/** + * Checks if the agent has recent swap transactions (within last 24 hours) + * This ensures the agent is not only receiving AO tokens but also swapping them + */ +export async function checkIfAgentHasRecentSwaps(agentId: string): Promise { + if (!agentId) return false; + + try { + const response = await gql(SWAP_SUCCESS_QUERY_WITH_CURSOR, { agentId, after: "" }); + const edges = response?.data?.transactions?.edges || []; + + if (edges.length === 0) return false; + + const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000; + + // Check if there are any swap transactions within the last 24 hours + const hasRecentSwaps = edges.some((edge) => { + const timestamp = edge.node.block?.timestamp ? edge.node.block.timestamp * 1000 : Date.now(); + return timestamp > oneDayAgo; + }); + + return hasRecentSwaps; + } catch (error) { + log(LOG_GROUP.AGENTS, "Error checking agent recent swaps:", error); + return false; + } +} + +export async function fetchRawMintData(transactionId: string): Promise { + const arweave = getArweave(); + const response = await arweave.api.get(transactionId); + return response.data; +} + +export async function parseRawMintData(transactionId: string): Promise { + const rawData = await fetchRawMintData(transactionId); + const lines = rawData.split("\n"); + + return lines.map((line: string) => { + const [recipient, amount, user, token] = line.split(","); + return { recipient, amount, user, token }; + }); +} + +/** + * Processes a single mint transaction for mint quantity calculation. + * @param transaction - The transaction to process. + * @param address - The address to process the transaction for. + * @returns The mint quantity. + */ +async function processSingleMintTransaction(transaction: MintTransaction, address: string): Promise { + const data = await parseRawMintData(transaction.id); + const mint = data.find((mint) => mint.recipient === address); + return mint ? BigNumber(mint.amount) : BigNumber(0); +} + +/** + * Processes transactions with nonce for mint quantity calculation. + * @param transaction - The transaction to process. + * @param address - The address to process the transaction for. + * @returns The mint quantity. + */ +async function processNoncedMintTransactions(transaction: MintTransaction, address: string): Promise { + const mintTransactions = await fetchMintTransactionsByNonce(transaction.nonce!); + + for (const tx of mintTransactions) { + const data = await parseRawMintData(tx.id); + const mint = data.find((mint) => mint.recipient === address); + if (mint) { + return BigNumber(mint.amount); + } + } + + return BigNumber(0); +} + +export async function calculateMintQuantityForDateRange( + address: string, + startDate: number, + endDate: number, +): Promise { + const normalizedStartDate = normalizeToStartOfDay(startDate); + const normalizedEndDate = normalizeToStartOfDay(endDate); + + const days = Math.max(1, Math.round((normalizedEndDate - normalizedStartDate) / (24 * 60 * 60 * 1000))); + const transactions = await fetchAllMintTransactions(days); + const transactionsInRange = transactions.filter((tx) => { + const txDate = normalizeToStartOfDay(tx.timestamp); + return txDate >= normalizedStartDate && txDate <= normalizedEndDate; + }); + + if (!transactionsInRange.length) { + return { + quantity: "0", + swapDateFrom: normalizedStartDate, + swapDateTo: normalizedEndDate, + }; + } + + const actualStartDate = normalizeToStartOfDay(transactionsInRange[0].timestamp); + const actualEndDate = normalizeToStartOfDay(transactionsInRange[transactionsInRange.length - 1].timestamp); + + let mintedQuantity = BigNumber(0); + + for (const transaction of transactionsInRange) { + const transactionAmount = + transaction.total === 1 + ? await processSingleMintTransaction(transaction, address) + : await processNoncedMintTransactions(transaction, address); + + mintedQuantity = mintedQuantity.plus(transactionAmount); + } + + return { + quantity: mintedQuantity.toFixed(0, BigNumber.ROUND_FLOOR), + swapDateFrom: actualStartDate, + swapDateTo: actualEndDate, + }; +} + +/** + * Validates agent eligibility for swap. + * @param activeAgent - The active agent. + * @returns True if the agent is eligible for swap, false otherwise. + */ +async function validateAgentForSwap(activeAgent: AOYieldAgent): Promise { + if (normalizeToStartOfDay(activeAgent.startDate) > normalizeToStartOfDay(Date.now())) { + log(LOG_GROUP.AGENTS, "Agent start date is in the future"); + return false; + } + + if (normalizeToStartOfDay(activeAgent.endDate) < normalizeToStartOfDay(Date.now())) { + if (activeAgent.status === "Active") { + await updateAOYieldAgent(activeAgent.id, { status: "Completed" }); + } + log(LOG_GROUP.AGENTS, "Agent running time has ended"); + return false; + } + return true; +} + +/** + * Checks if swap is already in progress or completed. + * @param agentInfo - The agent info. + * @returns True if the swap is already in progress or completed, false otherwise. + */ +async function shouldSkipSwap(agentInfo: AOYieldAgentInfo, walletAddress: string): Promise { + const { processedUpToDate, swappedUpToDate } = agentInfo; + + const normalizedNow = normalizeToStartOfDay(Date.now()); + if (processedUpToDate && normalizeToStartOfDay(processedUpToDate) === normalizedNow) { + await setLastSwapDateForWallet(walletAddress, normalizedNow); + log(LOG_GROUP.AGENTS, "Swap already processed up to date today"); + return true; + } + + if (swappedUpToDate && normalizeToStartOfDay(swappedUpToDate) === normalizedNow) { + await setLastSwapDateForWallet(walletAddress, normalizedNow); + log(LOG_GROUP.AGENTS, "Agent has already swapped up to date today"); + return true; + } + + return false; +} + +/** + * Calculates swap dates. + * @param agentInfo - The agent info. + * @returns The swap dates. + */ +function calculateSwapDates(agentInfo: AOYieldAgentInfo): { from: number; to: number } { + let swapDateFrom = normalizeToStartOfDay(agentInfo.startDate); + const processedUpToDate = agentInfo.processedUpToDate || agentInfo.swappedUpToDate; + if (processedUpToDate) { + // If the agent has been processed up to a certain date, we need to start from the next day + swapDateFrom = normalizeToStartOfDay(processedUpToDate) + 24 * 60 * 60 * 1000; + } + + const swapDateTo = normalizeToStartOfDay(Date.now()); + + return { from: swapDateFrom, to: swapDateTo }; +} + +/** + * Validates token balance for swap. + * @param activeAddress - The active address. + * @param swapQuantity - The swap quantity. + * @returns True if the token balance is valid, false otherwise. + */ +async function validateTokenBalanceForSwap(activeAddress: string, swapQuantity: string): Promise { + const token = defaultTokens[1]; + const balance = await queryClient.fetchQuery({ + queryKey: ["tokenBalance", AO_PROCESS_ID, activeAddress], + queryFn: async () => { + try { + const balance = await fetchTokenBalance(token, activeAddress); + return balance || "0"; + } catch { + return "0"; + } + }, + ...defaultOptions, + }); + + log(LOG_GROUP.AGENTS, { balance }); + + if (balance && BigNumber(balance).shiftedBy(12).lt(swapQuantity)) { + log(LOG_GROUP.AGENTS, "Not enough AO tokens to swap"); + return false; + } + + return true; +} + +/** + * Gets the last swap date for a specific wallet address + * @param walletAddress - The wallet address + * @returns The last swap timestamp or undefined + */ +async function getLastSwapDateForWallet(walletAddress: string): Promise { + return await ExtensionStorage.get(`${AO_YIELD_AGENT_LAST_SWAP_DATE_KEY}_${walletAddress}`); +} + +/** + * Sets the last swap date for a specific wallet address + * @param walletAddress - The wallet address + * @param timestamp - The swap timestamp + */ +async function setLastSwapDateForWallet(walletAddress: string, timestamp: number): Promise { + await ExtensionStorage.set(`${AO_YIELD_AGENT_LAST_SWAP_DATE_KEY}_${walletAddress}`, timestamp); +} + +/** + * Checks if a wallet has already swapped today + * @param walletAddress - The wallet address to check + * @returns True if wallet has already swapped today + */ +async function hasWalletSwappedToday(walletAddress: string): Promise { + const lastSwapDate = await getLastSwapDateForWallet(walletAddress); + if (lastSwapDate) { + return normalizeToStartOfDay(lastSwapDate) === normalizeToStartOfDay(Date.now()); + } + return false; +} + +/** + * Executes the token swap. + * @param activeAgent - The active agent. + * @param swapQuantity - The swap quantity. + * @param swapDateFrom - The swap date from. + * @param swapDateTo - The swap date to. + * @param walletAddress - The wallet address to use for the swap. + */ +async function executeTokenSwap( + activeAgent: AOYieldAgent, + swapQuantity: string, + swapDateFrom: number, + swapDateTo: number, + walletAddress: string, +): Promise { + log(LOG_GROUP.AGENTS, "Transferring AO tokens to agent: ", activeAgent.id, "from wallet:", walletAddress); + + const ao = connect(defaultConfig); + const messageId = await sendAoTransferForWallet( + ao, + AO_PROCESS_ID, + activeAgent.id, + swapQuantity, + [ + { name: "X-Swap-Date-From", value: swapDateFrom.toString() }, + { name: "X-Swap-Date-To", value: swapDateTo.toString() }, + ], + walletAddress, + ); + + if (!messageId) { + log(LOG_GROUP.AGENTS, "Failed to transfer AO tokens to agent: ", activeAgent.id, "from wallet:", walletAddress); + return; + } + + try { + const { Error, Messages } = await ao.result({ message: messageId, process: AO_PROCESS_ID }); + const hasValidTag = Messages?.some((message) => + message?.Tags?.some( + (tag: DecodedTag) => tag.name === "Action" && (tag.value === "Debit-Notice" || tag.value === "Credit-Notice"), + ), + ); + + if (Error || !hasValidTag) { + log(LOG_GROUP.AGENTS, "Failed to transfer AO tokens to agent: ", activeAgent.id, "from wallet:", walletAddress); + return; + } + } catch {} + + log(LOG_GROUP.AGENTS, "Transferred AO tokens to agent: ", activeAgent.id, "from wallet:", walletAddress); + + // Set wallet-specific last swap date + await setLastSwapDateForWallet(walletAddress, swapDateTo); + + await queryClient.invalidateQueries({ queryKey: ["tokenBalance", AO_PROCESS_ID, walletAddress] }); + + return messageId; +} + +/** + * Processes automatic swap for a single agent + * @param agent - The agent to process + * @param walletAddress - The wallet address that owns the agent + */ +async function processAgentSwap(agent: AOYieldAgent, walletAddress: string): Promise { + try { + // Validate agent eligibility + if (!(await validateAgentForSwap(agent))) { + return; + } + + // Fetch fresh agent info + const agentInfo = await queryClient.fetchQuery({ + queryKey: ["ao-yield-agent-info", agent.id], + queryFn: () => getAOYieldAgentInfo(agent.id), + staleTime: 0, // Force fresh data + gcTime: 0, + retry: 3, + retryDelay: (attemptIndex: number) => Math.min(1000 * 2 ** attemptIndex, 30000), + }); + + if (!agentInfo) { + log(LOG_GROUP.AGENTS, "Agent info not found"); + return; + } + + // Check if swap should be skipped + if (await shouldSkipSwap(agentInfo, walletAddress)) { + return; + } + + // Calculate swap dates + const { from: swapDateFrom, to: swapDateTo } = calculateSwapDates(agentInfo); + + // Calculate mint quantity for the date range + const mintResult = await calculateMintQuantityForDateRange(walletAddress, swapDateFrom, swapDateTo); + const { quantity: mintedQuantity, swapDateFrom: actualDateFrom, swapDateTo: actualDateTo } = mintResult; + + // Calculate swap quantity + const swapQuantity = BigNumber(mintedQuantity) + .multipliedBy(agent.conversionPercentage) + .dividedBy(100) + .toFixed(0, BigNumber.ROUND_FLOOR); + + log(LOG_GROUP.AGENTS, { agent: agent.id, walletAddress, mintedQuantity, swapQuantity }); + + if (BigNumber(swapQuantity).eq("0")) { + log(LOG_GROUP.AGENTS, "No swap needed for agent:", agent.id); + return; + } + + // Validate token balance + if (!(await validateTokenBalanceForSwap(walletAddress, swapQuantity))) { + return; + } + + // Execute the swap + const messageId = await executeTokenSwap(agent, swapQuantity, actualDateFrom, actualDateTo, walletAddress); + if (!messageId) { + return; + } + + // Add the swap to the recent txs list + const recentTxs = await getRecentTxs(); + await setRecentTxs([...recentTxs, { id: messageId, timestamp: Date.now(), queryCount: 0 }]); + + // Schedule alarm to check if the swap was successful + await browser.alarms.clear(AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME); + browser.alarms.create(AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME, { + delayInMinutes: 1, + periodInMinutes: 2, + }); + + log(LOG_GROUP.AGENTS, "Successfully processed swap for agent:", agent.id, "wallet:", walletAddress); + } catch (error) { + log(LOG_GROUP.AGENTS, "Error processing agent swap:", agent.id, "wallet:", walletAddress, error); + } +} + +export async function executeAutomaticSwapIfNeeded(): Promise { + // TODO: decide keep or remove this cooldown ? + if (await isCooldownActive()) { + log(LOG_GROUP.AGENTS, "Cooldown is active, skipping swap"); + return; + } + + if (isSwapExecutionInProgress) return; + + isSwapExecutionInProgress = true; + + try { + // Atomic check-and-set for swap lock + const lockTimestamp = Date.now(); + const currentLock = await ExtensionStorage.get(AO_YIELD_AGENT_SWAP_IN_PROGRESS_KEY); + + if (currentLock) { + // If swap has been in progress for more than 1 hour, consider it failed and allow retry + if (lockTimestamp - currentLock < ONE_HOUR_MS) { + log(LOG_GROUP.AGENTS, "Swap already in progress"); + return; + } else { + log(LOG_GROUP.AGENTS, "Clearing stale swap in progress flag"); + } + } + + // Try to acquire the lock atomically + await startSwapInProgress(lockTimestamp); + + // Double-check that we actually got the lock (handle race conditions) + const verifyLock = await ExtensionStorage.get(AO_YIELD_AGENT_SWAP_IN_PROGRESS_KEY); + if (verifyLock !== lockTimestamp) { + log(LOG_GROUP.AGENTS, "Failed to acquire swap lock - another process is running"); + return; + } + + // Get all wallets + const wallets = await getWallets(); + if (!wallets || wallets.length === 0) { + log(LOG_GROUP.AGENTS, "No wallets found"); + return; + } + + log(LOG_GROUP.AGENTS, `Processing agents for ${wallets.length} wallets`); + + // Process agents for each wallet + for (const wallet of wallets) { + if (wallet.type === "hardware") continue; + + try { + // Check if this wallet has already swapped today + if (await hasWalletSwappedToday(wallet.address)) { + log(LOG_GROUP.AGENTS, `Wallet ${wallet.address} already swapped today`); + continue; + } + + // Get active agents for this wallet + const agents = await getAOYieldAgents(wallet.address, "Active"); + + if (agents.length === 0) { + log(LOG_GROUP.AGENTS, `No active agents found for wallet: ${wallet.address}`); + continue; + } + + log(LOG_GROUP.AGENTS, `Processing active agent for wallet: ${wallet.address}`); + + // Process each agent for this wallet + const activeAgent = agents[agents.length - 1]; + await processAgentSwap(activeAgent, wallet.address); + await addCooldown(); + } catch (error) { + log(LOG_GROUP.AGENTS, `Error processing agents for wallet ${wallet.address}:`, error); + // Continue processing other wallets even if one fails + continue; + } + } + } catch (error) { + log(LOG_GROUP.AGENTS, "Error performing swap: ", error); + } finally { + await clearSwapInProgress(); + isSwapExecutionInProgress = false; + } +} + +export async function checkIfRecentTxSwapSucceeded(): Promise { + try { + log(LOG_GROUP.AGENTS, "Checking if recent tx swap succeeded"); + + if (isRecentTxCheckInProgress) return; + + isRecentTxCheckInProgress = true; + + const activeAddress = await getActiveAddress(); + if (!activeAddress) { + log(LOG_GROUP.AGENTS, "No active address found"); + return; + } + + const checkInProgress = await ExtensionStorage.get(AO_YIELD_AGENT_RECENT_TXS_CHECK_IN_PROGRESS_KEY); + if (checkInProgress) { + log(LOG_GROUP.AGENTS, "Recent txs check already in progress"); + return; + } + + await ExtensionStorage.set(AO_YIELD_AGENT_RECENT_TXS_CHECK_IN_PROGRESS_KEY, true); + + let recentTxs = await getRecentTxs(); + if (recentTxs.length === 0) { + await browser.alarms.clear(AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME); + log(LOG_GROUP.AGENTS, "No recent txs found. Clearing recent txs check alarm."); + return; + } + + const parentTxIds = recentTxs.map((tx) => tx.id); + const response = await gql(AO_YIELD_AGENT_RECENT_TX_QUERY, { parentTxIds }); + const edges = response?.data?.transactions?.edges || []; + + log(LOG_GROUP.AGENTS, "Recent txs found: ", recentTxs.length); + + const foundTxIds = new Set(); + + const trackDirectPromises = edges.map(async (edge) => { + const tags = edge.node.tags; + const txId = getTagValue("Pushed-For", tags); + + if (foundTxIds.has(txId)) return; + foundTxIds.add(txId); + + const activeTier = await queryClient + .fetchQuery({ + queryKey: ["active-tier", activeAddress], + queryFn: () => getActiveTier(activeAddress), + ...defaultOptions, + }) + .catch(() => ({ tier: "" })); + + const swapFee = getTagValue("Swap-Fee", tags) || "0"; + const feeSavingsQuantity = getTagValue("Fee-Savings", tags) || "0"; + const agentOwner = getTagValue("Agent-Owner", tags); + const walletAddress = edge.node.recipient || agentOwner; + + const aoPrice = await getAOTokenPrice(); + const feeSavingsUsd = convertQuantityToUsd(feeSavingsQuantity, aoPrice); + const wanderFeeUsd = convertQuantityToUsd(swapFee, aoPrice); + + const transactionData = { + buyAsset: getTagValue("Token-Out", tags), + sellAmount: getTagValue("Amount-In", tags), + buyAmount: getTagValue("Amount-Out", tags), + dexUsed: getTagValue("Dex", tags), + wanderFee: swapFee, + wanderFeeUsd: wanderFeeUsd, + waivedFee: feeSavingsQuantity, + waivedFeeUsd: feeSavingsUsd, + tier: activeTier.tier || "", + }; + + // Save wallet savings + await saveWalletLifetimeSavings(walletAddress, feeSavingsUsd); + + try { + return await trackDirect(EventType.AO_YIELD_AGENT_TRANSACTION, transactionData); + } catch (err) { + log(LOG_GROUP.AGENTS, "Error tracking recent tx: ", err); + throw err; + } + }); + + await Promise.allSettled(trackDirectPromises); + + recentTxs = await getRecentTxs(); + // Filter transactions that haven't been found in the GraphQL response (not processed yet) + // and have been queried less than 11 times (to retry failed queries) + const remainingTxs = recentTxs.filter((tx) => !foundTxIds.has(tx.id) && tx.queryCount <= 10); + log(LOG_GROUP.AGENTS, "Remaining txs: ", remainingTxs.length); + if (remainingTxs.length === 0) { + log(LOG_GROUP.AGENTS, "No remaining txs found. Clearing recent txs check alarm."); + await browser.alarms.clear(AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME); + } + await setRecentTxs(remainingTxs.map((tx) => ({ ...tx, queryCount: tx.queryCount + 1 }))); + } catch { + log(LOG_GROUP.AGENTS, "Error checking recent txs"); + } finally { + await ExtensionStorage.remove(AO_YIELD_AGENT_RECENT_TXS_CHECK_IN_PROGRESS_KEY); + isRecentTxCheckInProgress = false; + } +} + +export async function scheduleSwapExecution() { + if (isSchedulingInProgress) return; + + isSchedulingInProgress = true; + + try { + const alarms = await browser.alarms.get(AO_YIELD_AGENT_ALARM_NAME); + if (alarms) return; + + browser.alarms.create(AO_YIELD_AGENT_ALARM_NAME, { when: Date.now() }); + } finally { + isSchedulingInProgress = false; + } +} diff --git a/libs/core/src/lib/agents/sync.ts b/libs/core/src/lib/agents/sync.ts new file mode 100644 index 000000000..7fec90489 --- /dev/null +++ b/libs/core/src/lib/agents/sync.ts @@ -0,0 +1,161 @@ +import { gql, gqlAll } from "~gateways/api"; +import { AO_YIELD_AGENT_SYNC_QUERY } from "./queries"; +import { getAOYieldAgentInfo, getAOYieldAgents, setAOYieldAgents, updateAOYieldAgent } from "./utils"; +import { log, LOG_GROUP } from "~utils/log/log.utils"; +import { + AO_YIELD_AGENT_SYNC_ALARM_NAME_PREFIX, + AO_YIELD_AGENT_SYNC_STATUS_PREFIX_KEY, + HAS_SHOWN_AGENTS_EXPLAINER_POPUP, + SHOW_CREATE_WANDER_AGENT_CTA, +} from "./constants"; +import browser from "webextension-polyfill"; +import type { AOYieldAgent } from "./types"; +import { IS_EMBEDDED_APP } from "~utils/_embedded/embedded.constants"; +import { pLimit } from "plimit-lit"; +import { ExtensionStorage } from "~utils/storage"; +import { queryClient } from "~utils/tanstack"; +import { isWalletUnlocked } from "~wallets/auth"; + +const limit = pLimit(10); + +export async function checkAndSyncAgents(address: string): Promise { + try { + await ExtensionStorage.set(AO_YIELD_AGENT_SYNC_STATUS_PREFIX_KEY + address, { + status: "in_progress", + timestamp: Date.now(), + }); + + if (!address) { + log(LOG_GROUP.AGENTS, "No address provided"); + return; + } + + log(LOG_GROUP.AGENTS, "Checking and syncing agents for: ", address); + + let agents = await getAOYieldAgents(address); + if (agents.length > 0) { + log(LOG_GROUP.AGENTS, "Agents already present, no need to sync"); + return; + } + + const edges = await gqlAll(AO_YIELD_AGENT_SYNC_QUERY, { address }); + + if (edges.length === 0) { + log(LOG_GROUP.AGENTS, "No agents found"); + return; + } + + // sort edges by timestamp in ascending order + const sortedEdges = edges.sort((a, b) => { + const aDate = new Date(a.node.block?.timestamp ? a.node.block.timestamp * 1000 : Date.now()); + const bDate = new Date(b.node.block?.timestamp ? b.node.block.timestamp * 1000 : Date.now()); + return aDate.getTime() - bDate.getTime(); + }); + + const agentIds = sortedEdges.map((edge) => edge.node.id); + + // Set extension storage values immediately since we know agents exist + await ExtensionStorage.set(HAS_SHOWN_AGENTS_EXPLAINER_POPUP, true); + await ExtensionStorage.set(SHOW_CREATE_WANDER_AGENT_CTA, false); + + // Read existing agents once and maintain ordered slots + const currentAgents = await getAOYieldAgents(address); + const agentSlots: (AOYieldAgent | null)[] = new Array(agentIds.length).fill(null); + let successCount = 0; + + const agentInfoPromises = agentIds.map((agentId, index) => + limit(async () => { + try { + log(LOG_GROUP.AGENTS, `Fetching agent info for ${agentId}`); + const agentInfo = await queryClient.fetchQuery({ + queryKey: ["ao-yield-agent-info", agentId], + queryFn: () => getAOYieldAgentInfo(agentId), + staleTime: 0, // Force fresh data + gcTime: 0, + retry: 1, + retryDelay: (attemptIndex: number) => Math.min(1000 * 2 ** attemptIndex, 30000), + }); + + if (!agentInfo) { + log(LOG_GROUP.AGENTS, `Agent info not found for ${agentId}`); + return null; + } + + log(LOG_GROUP.AGENTS, `Agent info fetched for ${agentId}`); + + if (!agentInfo.agentVersion) { + log(LOG_GROUP.AGENTS, `Agent version not found for ${agentId}`); + return null; + } + + const agent: AOYieldAgent = { + id: agentId, + status: agentInfo.status, + conversionPercentage: agentInfo.conversionPercentage, + tokenOut: agentInfo.tokenOut, + startDate: agentInfo.startDate, + endDate: agentInfo.endDate, + runIndefinitely: agentInfo.runIndefinitely, + slippage: agentInfo.slippage, + version: agentInfo.agentVersion, + }; + + // Store agent in correct position and update storage with ordered agents + agentSlots[index] = agent; + const orderedNewAgents = agentSlots.filter((agent): agent is AOYieldAgent => agent !== null); + await setAOYieldAgents(address, [...currentAgents, ...orderedNewAgents]); + successCount++; + log(LOG_GROUP.AGENTS, `Agent ${agentId} added at position ${index} (${successCount}/${agentIds.length})`); + + return agent; + } catch (error) { + log(LOG_GROUP.AGENTS, `Error fetching agent info for ${agentId}:`, error); + return null; + } + }), + ); + + await Promise.allSettled(agentInfoPromises); + + try { + log(LOG_GROUP.AGENTS, "Checking for expired agents"); + const aoAgents = await getAOYieldAgents(address); + const expiredAgents = aoAgents.filter((agent) => agent.status === "Active" && agent.endDate < Date.now()); + const expiredPromises = expiredAgents.map(async (agent) => { + const walletUnlocked = await isWalletUnlocked(); + if (walletUnlocked) { + await updateAOYieldAgent(agent.id, { status: "Completed" }); + } + }); + + log(LOG_GROUP.AGENTS, `Updating ${expiredAgents.length} expired agents status to completed`); + await Promise.allSettled(expiredPromises); + } catch (error) { + log(LOG_GROUP.AGENTS, "Error checking for expired agents: ", error); + } + + if (successCount > 0) { + log(LOG_GROUP.AGENTS, `Successfully synced ${successCount} agents progressively`); + } else { + log(LOG_GROUP.AGENTS, "No valid agents were fetched successfully"); + } + } catch (error) { + log(LOG_GROUP.AGENTS, "Error checking and syncing agents: ", error); + } finally { + await ExtensionStorage.remove(AO_YIELD_AGENT_SYNC_STATUS_PREFIX_KEY + address); + } +} + +export async function scheduleAgentsSync(address: string) { + if (IS_EMBEDDED_APP) return; + + try { + const alarmName = AO_YIELD_AGENT_SYNC_ALARM_NAME_PREFIX + address; + const alarms = await browser.alarms.get(alarmName); + if (alarms) return; + + browser.alarms.create(alarmName, { when: Date.now() }); + } catch (error) { + log(LOG_GROUP.AGENTS, "Error scheduling agents sync: ", error); + } +} diff --git a/libs/core/src/lib/agents/types.ts b/libs/core/src/lib/agents/types.ts new file mode 100644 index 000000000..1562b210c --- /dev/null +++ b/libs/core/src/lib/agents/types.ts @@ -0,0 +1,217 @@ +export type ConfigName = string; + +export interface Tag { + name: string; + value: string; +} + +export interface Services { + /** + * The URL of the desired Gateway. + * @default "https://arweave.net" + */ + gatewayUrl?: string; + + /** + * The URL of the desired AO Compute Unit. + * @default "https://cu.ao-testnet.xyz" + */ + cuUrl?: string; + + /** + * The URL of the desired AO Messenger Unit. + * @default "https://mu.ao-testnet.xyz" + */ + muUrl?: string; +} + +export type DeployConfig = { + /** + * Process name to spawn + * @default "default" + */ + name?: string; + + /** + * Path to contract main file + */ + contractPath: string; + + /** + * Config name used for logging + */ + configName?: string; + + /** + * The module source to use to spin up Process + * @default "Fetches from `https://raw.githubusercontent.com/pawanpaudel93/ao-deploy-config/main/config.json`" + */ + module?: string; + + /** + * Scheduler to use for Process + * @default "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA" + */ + scheduler?: string; + + /** + * Additional tags to use for spawning Process + */ + tags?: Tag[]; + + /** + * Cron interval to use for Process i.e (1-minute, 5-minutes) + */ + cron?: string; + + /** + * Retry options + */ + retry?: { + /** + * Retry count + * @default 10 + */ + count?: number; + /** + * Retry delay in milliseconds + * @default 3000 + */ + delay?: number; + }; + + /** + * Process Id of an existing process + */ + processId?: string; + + /** + * Configuration for various AO services + */ + services?: Services; + + /** + * Enable AOS On-Boot loading to load contract when process is spawned. + * Sets "On-Boot=Data" tag during deployment. + * CLI: --on-boot + * @see https://github.com/permaweb/aos?tab=readme-ov-file#boot-loading + * @default false + */ + onBoot?: boolean; + + /** + * Disable logging to console + * @default false + */ + silent?: boolean; + + /** + * Force spawning a new process without checking for existing ones. + * @default false + */ + forceSpawn?: boolean; +}; + +export interface DeployResult { + name: string; + configName: string; + messageId?: string; + processId: string; + isNewProcess: boolean; +} + +export interface AosConfig { + module: string; + sqliteModule: string; + scheduler: string; + authority: string; +} + +export type AOYieldAgentStatus = "Active" | "Cancelled" | "Completed" | "Paused"; + +export type AOYieldAgentDex = "BOTEGA" | "PERMASWAP" | "AUTO"; + +export interface AOYieldAgent { + id: string; + status: AOYieldAgentStatus; + conversionPercentage: number; + tokenOut: string; + startDate: number; + endDate: number; + runIndefinitely: boolean; + slippage: number; + totalTransactions?: number; + version: string; +} + +export interface AOYieldAgentInfo extends AOYieldAgent { + dex: AOYieldAgentDex; + totalAOSold: string; + totalBought: Record; + totalTransactions: number; + totalWanderFee: string; + swapInProgress: boolean; + processedUpToDate?: number; + swappedUpToDate?: number; + agentVersion: string; +} + +export interface AOYieldAgentCreate { + conversionPercentage: number; + asset: Asset; + slippage: number; + runIndefinitely: boolean; + startDate: number; + endDate: number; +} + +export interface Asset { + denomination: number; + ticker: string; + logo: string; + id: string; +} + +export interface SwapSuccessTransaction { + amountIn: string; + amountOut: string; + tokenIn: string; + tokenOut: string; + wanderFee: string; + timestamp: number; + id: string; + cursor: string; +} + +export type MintingStatus = "Active" | "Paused"; + +export type RecentTx = { + id: string; + timestamp: number; + queryCount: number; +}; + +export interface MintTransaction { + id: string; + timestamp: number; + total?: number; + nonce?: number; +} + +export interface ParsedMintData { + recipient: string; + amount: string; + user: string; + token: string; +} + +export interface MintQuantityResult { + quantity: string; + swapDateFrom: number; + swapDateTo: number; +} + +export interface AOYieldAgentSyncStatus { + status: "in_progress" | "completed"; + timestamp: number; +} diff --git a/libs/core/src/lib/agents/utils/date.utils.ts b/libs/core/src/lib/agents/utils/date.utils.ts new file mode 100644 index 000000000..b70e1c0c2 --- /dev/null +++ b/libs/core/src/lib/agents/utils/date.utils.ts @@ -0,0 +1,236 @@ +export interface DateCheckOptions { + day: number; + currentDate: Date; + startDate: Date | null; + endDate: Date | null; +} + +export interface OtherMonthDateCheckOptions extends DateCheckOptions { + monthOffset: number; +} + +export function getDaysInMonth(date: Date): number { + return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); +} + +export function getFirstDayOfMonth(date: Date): number { + return new Date(date.getFullYear(), date.getMonth(), 1).getDay(); +} + +export function isDateSelected({ day, currentDate, startDate, endDate }: DateCheckOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + return (startDate && date.getTime() === startDate.getTime()) || (endDate && date.getTime() === endDate.getTime()); +} + +export function isStartDate({ day, currentDate, startDate }: DateCheckOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + return startDate && date.getTime() === startDate.getTime(); +} + +export function isEndDate({ day, currentDate, endDate }: DateCheckOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + return endDate && date.getTime() === endDate.getTime(); +} + +export function isDateInRange({ day, currentDate, startDate, endDate }: DateCheckOptions): boolean { + if (!startDate || !endDate) return false; + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + return date >= startDate && date <= endDate; +} + +export function isStartOfWeekInRange({ day, currentDate, startDate, endDate }: DateCheckOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + if (!startDate || !endDate || date < startDate || date > endDate) return false; + + const dayOfWeek = date.getDay(); + + for (let i = 0; i < dayOfWeek; i++) { + const prevDate = new Date(date); + prevDate.setDate(date.getDate() - (dayOfWeek - i)); + if (prevDate >= startDate && prevDate <= endDate) { + return false; + } + } + return true; +} + +export function isEndOfWeekInRange({ day, currentDate, startDate, endDate }: DateCheckOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + if (!startDate || !endDate || date < startDate || date > endDate) return false; + + const dayOfWeek = date.getDay(); + + for (let i = dayOfWeek + 1; i < 7; i++) { + const nextDate = new Date(date); + nextDate.setDate(date.getDate() + (i - dayOfWeek)); + if (nextDate >= startDate && nextDate <= endDate) { + return false; + } + } + return true; +} + +export function isDateSelectedOtherMonth({ + day, + currentDate, + startDate, + endDate, + monthOffset, +}: OtherMonthDateCheckOptions): boolean { + const targetMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + monthOffset, day); + return ( + (startDate && targetMonth.getTime() === startDate.getTime()) || + (endDate && targetMonth.getTime() === endDate.getTime()) + ); +} + +export function isStartDateOtherMonth({ + day, + currentDate, + startDate, + monthOffset, +}: OtherMonthDateCheckOptions): boolean { + const targetMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + monthOffset, day); + return startDate && targetMonth.getTime() === startDate.getTime(); +} + +export function isEndDateOtherMonth({ day, currentDate, endDate, monthOffset }: OtherMonthDateCheckOptions): boolean { + const targetMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + monthOffset, day); + return endDate && targetMonth.getTime() === endDate.getTime(); +} + +export function isStartOfWeekInRangeOtherMonth({ + day, + currentDate, + startDate, + endDate, + monthOffset, +}: OtherMonthDateCheckOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth() + monthOffset, day); + if (!startDate || !endDate || date < startDate || date > endDate) return false; + + const dayOfWeek = date.getDay(); + + for (let i = 0; i < dayOfWeek; i++) { + const prevDate = new Date(date); + prevDate.setDate(date.getDate() - (dayOfWeek - i)); + if (prevDate >= startDate && prevDate <= endDate) { + return false; + } + } + return true; +} + +export function isEndOfWeekInRangeOtherMonth({ + day, + currentDate, + startDate, + endDate, + monthOffset, +}: OtherMonthDateCheckOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth() + monthOffset, day); + if (!startDate || !endDate || date < startDate || date > endDate) return false; + + const dayOfWeek = date.getDay(); + + for (let i = dayOfWeek + 1; i < 7; i++) { + const nextDate = new Date(date); + nextDate.setDate(date.getDate() + (i - dayOfWeek)); + if (nextDate >= startDate && nextDate <= endDate) { + return false; + } + } + return true; +} + +export function shouldConnectToPrevMonth({ day, currentDate, startDate, endDate }: DateCheckOptions): boolean { + if (!startDate || !endDate) return false; + const currentMonthDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + const lastDayPrevMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 0); + + return ( + day === 1 && + lastDayPrevMonth >= startDate && + lastDayPrevMonth <= endDate && + currentMonthDate >= startDate && + currentMonthDate <= endDate && + lastDayPrevMonth.getDay() !== 6 + ); +} + +export function shouldConnectToNextMonth({ day, currentDate, startDate, endDate }: DateCheckOptions): boolean { + if (!startDate || !endDate) return false; + const daysInCurrentMonth = getDaysInMonth(currentDate); + const currentMonthDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + const firstDayNextMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1); + + return ( + day === daysInCurrentMonth && + firstDayNextMonth >= startDate && + firstDayNextMonth <= endDate && + currentMonthDate >= startDate && + currentMonthDate <= endDate && + firstDayNextMonth.getDay() !== 0 + ); +} + +export interface DateDisabledOptions { + day: number; + currentDate: Date; + startDate: Date | null; + isSelectingStart: boolean; +} + +export function isDateDisabled({ day, currentDate, startDate, isSelectingStart }: DateDisabledOptions): boolean { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + // Always disable past dates + if (date < today) { + return true; + } + + // When selecting start date, only past dates are disabled + if (isSelectingStart || !startDate) { + return false; + } + + // When selecting end date, dates before start date are disabled (but same date is allowed) + if (startDate && date < startDate) { + return true; + } + + return false; +} + +const LONG_MONTHS = new Set([8, 10, 11]); // Sep, Nov, Dec (0-indexed) + +/** + * Determines if a date should use short format based on month name length + * @param date The date to check + * @returns true if short format should be used + */ +export const shouldUseShortFormat = (date: Date | null): boolean => { + if (!date) return false; + + const month = date.getMonth(); + + // Use short format for long month names + return LONG_MONTHS.has(month); +}; + +/** + * Formats a date with automatic short/long format detection + * @param date The date to format + * @param shortMonth If true, use short month format + * @returns Formatted date string + */ +export const formatDate = (date: Date | null, shortMonth?: boolean) => { + if (!date) return ""; + return date.toLocaleDateString("en-US", { + month: shortMonth ? "short" : "long", + day: "numeric", + year: "numeric", + }); +}; diff --git a/libs/core/src/lib/agents/utils/index.ts b/libs/core/src/lib/agents/utils/index.ts new file mode 100644 index 000000000..a22aa7092 --- /dev/null +++ b/libs/core/src/lib/agents/utils/index.ts @@ -0,0 +1,467 @@ +import Arweave from "arweave"; +import { defaultGateway } from "~gateways/gateway"; +import { ExtensionStorage } from "~utils/storage"; +import type { AOYieldAgent, AOYieldAgentInfo, AOYieldAgentStatus, MintingStatus, RecentTx, Tag } from "../types"; +import { connect } from "@permaweb/aoconnect"; +import { defaultConfig } from "~tokens/aoTokens/config"; +import { createDataItemSigner, getTagValue } from "~tokens/aoTokens/ao"; +import { getActiveAddress, getActiveKeyfile } from "~wallets"; +import { isLocalWallet } from "~utils/assertions"; +import { retryWithDelay } from "~utils/promises/retry"; +import { TRANSACTION_QUERY } from "../queries"; +import type GQLResultInterface from "ar-gql/dist/faces"; +import { formatBalance } from "~utils/format"; +import { balanceToFractioned } from "~tokens/currency"; +import { freeDecryptedWallet } from "~wallets/encryption"; +import WarIcon from "url:/assets/ecosystem/war.png"; +import wUSDCIcon from "url:/assets/ecosystem/wusdc.svg"; +import type { Asset } from "~utils/agents/types"; +import { AO_YIELD_AGENT_RECENT_TXS, WANDER_FEE_PROCESS_ID } from "../constants"; +import dayjs from "dayjs"; +import { isURL } from "~utils/urls/isURL"; +import { queryClient } from "~utils/tanstack"; +import { Mutex } from "../../mutex/mutex"; +import { Id, Owner, WAR_PROCESS_ID, WUSDC_PROCESS_ID } from "~tokens/aoTokens/ao.constants"; + +const agentStorageMutex = new Mutex(); + +/** + * Initializes a default Arweave instance. + */ +export const arweave = Arweave.init(defaultGateway); + +/** + * Parses a gateway URL and returns an object containing the host, port, and protocol. + * + * @param url - The gateway URL to be parsed. + * @returns An object with the host, port, and protocol of the URL. + */ +function parseGatewayUrl(url: string): { + host: string; + port: number; + protocol: string; +} { + const parsedUrl = new URL(url); + return { + host: parsedUrl.hostname, + port: parsedUrl.port ? Number.parseInt(parsedUrl.port, 10) : 443, + protocol: parsedUrl.protocol.replace(":", ""), + }; +} + +/** + * Initializes an Arweave instance with a custom gateway. + * + * @param gateway - The gateway URL to connect to. + * @returns An Arweave instance configured with the provided gateway. + */ +export function getArweave(gateway?: string) { + try { + if (!gateway) return arweave; + const { host, port, protocol } = parseGatewayUrl(gateway); + return Arweave.init({ host, port, protocol }); + } catch { + return arweave; + } +} + +export function isArweaveAddress(address: any): boolean { + return typeof address === "string" && /^[\w-]{43}$/.test(address); +} + +/** + * Parses a string to an integer. + * If parsing fails (i.e., the value is NaN), it returns the specified default value. + * + * @param value - The string to be parsed. + * @param defaultValue - The default value to return if parsing fails. + * @returns The parsed integer or the default value if parsing fails. + */ +export function parseToInt(value: string | number | undefined, defaultValue: number): number { + if (value === undefined) { + return defaultValue; + } + const parsedValue = Number.parseInt(value.toString()); + if (Number.isNaN(parsedValue)) { + return defaultValue; + } + return parsedValue; +} + +/** + * Validates a URL string. + * If the URL is not valid, it returns the specified default value. + * + * @param value - The URL string to be validated. + * @param defaultValue - The default value to return if the URL is not valid. + * @returns The URL if valid, or the default value if the URL is not valid. + */ +export function parseUrl(value: string | undefined, defaultValue: string): string { + if (value === undefined) { + return defaultValue; + } + const urlValid = isURL(value); + if (!urlValid) { + return defaultValue; + } + return value; +} + +export function jsonStringify(value?: any): string { + try { + return JSON.stringify(value, null, 2); + } catch { + return value; + } +} + +export function isCronPattern(cron: string): boolean { + if (!cron) { + return false; + } + const cronRegex = /^\d+-(?:Second|second|Minute|minute|Hour|hour|Day|day|Month|month|Year|year|Block|block)s?$/; + return cronRegex.test(cron); +} + +interface PollingOptions { + maxAttempts?: number; + initialDelayMs?: number; + backoffFactor?: number; +} + +export async function pollForProcessSpawn({ + processId, + gatewayUrl, + options = {}, +}: { + processId: string; + gatewayUrl?: string; + options?: PollingOptions; +}): Promise { + const { maxAttempts = 10, initialDelayMs = 3000, backoffFactor = 1.5 } = options; + + const arweave = getArweave(gatewayUrl); + + const queryTransaction = async () => { + const response = await arweave.api.post("/graphql", { + query: TRANSACTION_QUERY, + variables: { ids: [processId] }, + }); + + const transaction = response?.data?.data?.transactions?.edges?.[0]?.node; + if (!transaction) { + throw new Error("Transaction not found"); + } + return transaction; + }; + + try { + await retryWithDelay( + queryTransaction, + maxAttempts, + initialDelayMs, + (attempt) => initialDelayMs * Math.pow(backoffFactor, attempt - 1), + ); + } catch { + throw new Error( + `Failed to find process ${processId} after ${maxAttempts} attempts. The process may still be spawning.`, + ); + } +} + +export async function getAOYieldAgents(activeAddress: string, status?: AOYieldAgentStatus) { + const agents = ((await ExtensionStorage.get(`ao_yield_agents_${activeAddress}`)) || []) as AOYieldAgent[]; + if (status) { + return agents.filter((agent) => agent.status === status); + } + + return agents; +} + +export async function setAOYieldAgents(activeAddress: string, agents: AOYieldAgent[]) { + const unlock = await agentStorageMutex.lock(); + try { + await ExtensionStorage.set(`ao_yield_agents_${activeAddress}`, agents); + } finally { + unlock(); + } +} + +/** + * Updates a local AO Yield Agent with the provided data + * @param agentId - The ID of the agent to update + * @param updateData - Partial agent data to update + * @param activeAddress - Optional active address, will fetch if not provided + * @returns Promise - Returns true if agent was found and updated, false otherwise + */ +export async function updateLocalAOYieldAgent( + agentId: string, + updateData: Partial, + activeAddress?: string, +): Promise { + try { + const address = activeAddress || (await getActiveAddress()); + const agents = await getAOYieldAgents(address); + const foundAgentIndex = agents.findIndex((agent) => agent.id === agentId); + + if (foundAgentIndex !== -1) { + agents[foundAgentIndex] = updateAgentProperties(agents[foundAgentIndex], updateData); + await setAOYieldAgents(address, agents); + return true; + } + + return false; + } catch (error) { + console.error("Error updating local AO Yield Agent", error); + return false; + } +} + +export async function getRecentTxs(): Promise { + const recentTxs = await ExtensionStorage.get(AO_YIELD_AGENT_RECENT_TXS); + return recentTxs || []; +} + +export async function setRecentTxs(recentTxs: RecentTx[]) { + await ExtensionStorage.set(AO_YIELD_AGENT_RECENT_TXS, recentTxs); +} + +export async function getAOYieldActiveAgent() { + const activeAddress = await getActiveAddress(); + const agents = await getAOYieldAgents(activeAddress, "Active"); + return agents[agents.length - 1]; +} + +export async function getAOYieldAgentInfo(agentId: string) { + const aoInstance = connect(defaultConfig); + + const dryrunRes = await aoInstance.dryrun({ + Id, + Owner, + process: agentId, + tags: [{ name: "Action", value: "Info" }], + }); + + const message = dryrunRes.Messages?.[0]; + const tags = message?.Tags; + + const dex = getTagValue("Dex", tags); + const status = getTagValue("Status", tags); + const tokenOut = getTagValue("Token-Out", tags); + const conversionPercentage = getTagValue("Conversion-Percentage", tags); + const startDate = getTagValue("Start-Date", tags); + const endDate = getTagValue("End-Date", tags); + const runIndefinitely = getTagValue("Run-Indefinitely", tags); + const slippage = getTagValue("Slippage", tags); + const totalAOSold = getTagValue("Total-AO-Sold", tags); + const totalBought = getTagValue("Total-Bought", tags); + const totalTransactions = getTagValue("Total-Transactions", tags); + const totalWanderFee = getTagValue("Total-Wander-Fee", tags); + const swapInProgress = getTagValue("Swap-In-Progress", tags); + const processedUpToDate = getTagValue("Processed-Up-To-Date", tags); + const swappedUpToDate = getTagValue("Swapped-Up-To-Date", tags); + const agentVersion = getTagValue("Agent-Version", tags); + + return { + id: agentId, + status, + dex, + tokenOut, + conversionPercentage: Number(conversionPercentage), + startDate: Number(startDate), + endDate: Number(endDate), + runIndefinitely: runIndefinitely === "true", + slippage: Number(slippage), + totalAOSold, + totalBought: JSON.parse(totalBought), + totalTransactions: Number(totalTransactions), + totalWanderFee, + swapInProgress: swapInProgress === "true", + processedUpToDate: processedUpToDate !== "nil" ? Number(processedUpToDate) : undefined, + swappedUpToDate: swappedUpToDate !== "nil" ? Number(swappedUpToDate) : undefined, + agentVersion, + } as AOYieldAgentInfo; +} + +/** + * Builds tags array for agent update based on the update data + */ +function buildUpdateTags(updateData: Partial): Tag[] { + const tags: Tag[] = [{ name: "Action", value: "Update-Agent" }]; + + const tagMappings: Array<{ + key: keyof Partial; + tagName: string; + transform?: (value: any) => string; + }> = [ + { key: "slippage", tagName: "Slippage", transform: (v) => v.toString() }, + { key: "tokenOut", tagName: "Token-Out" }, + { key: "startDate", tagName: "Start-Date", transform: (v) => v.toString() }, + { key: "endDate", tagName: "End-Date", transform: (v) => v.toString() }, + { key: "runIndefinitely", tagName: "Run-Indefinitely", transform: (v) => v.toString() }, + { key: "status", tagName: "Status" }, + ]; + + for (const mapping of tagMappings) { + const value = updateData[mapping.key]; + if (value !== undefined) { + const tagValue = mapping.transform ? mapping.transform(value) : String(value); + tags.push({ name: mapping.tagName, value: tagValue }); + } + } + + return tags; +} + +/** + * Updates local agent data with the provided update data + */ +function updateAgentProperties(agent: AOYieldAgent, updateData: Partial): AOYieldAgent { + const updatableFields: Array = [ + "totalTransactions", + "conversionPercentage", + "version", + "slippage", + "tokenOut", + "startDate", + "endDate", + "runIndefinitely", + "status", + ]; + + for (const field of updatableFields) { + if (updateData[field] !== undefined) { + (agent as any)[field] = updateData[field]; + } + } + + return agent; +} + +export async function updateAOYieldAgent(agentId: string, updateData: Partial) { + try { + const aoInstance = connect(defaultConfig); + + const decryptedWallet = await getActiveKeyfile(); + isLocalWallet(decryptedWallet); + const keyfile = decryptedWallet.keyfile; + + const signer = createDataItemSigner(keyfile); + const tags = buildUpdateTags(updateData); + + const messageId = await aoInstance.message({ + process: agentId, + tags, + signer, + }); + + // Free the keyfile from memory + freeDecryptedWallet(decryptedWallet.keyfile); + + const result = await aoInstance + .result({ + process: agentId, + message: messageId, + }) + .catch(() => ({ Error: undefined })); + + if (result.Error) { + throw new Error(`Failed to update agent: ${result.Error}`); + } + + await updateLocalAOYieldAgent(agentId, updateData); + await queryClient.invalidateQueries({ queryKey: ["ao-yield-agent-info", agentId] }); + } catch (error) { + throw new Error(`Failed to update AO Yield Agent: ${error instanceof Error ? error.message : String(error)}`); + } +} + +export async function processTransactions(rawData: GQLResultInterface) { + const edges = rawData?.data?.transactions?.edges || []; + const processedTransactions = edges.map((edge) => { + const tags = edge.node.tags as Tag[]; + return { + amountIn: getTagValue("Amount-In", tags), + amountOut: getTagValue("Amount-Out", tags), + tokenIn: getTagValue("Token-In", tags), + tokenOut: getTagValue("Token-Out", tags), + wanderFee: getTagValue("Swap-Fee", tags), + timestamp: edge.node.block?.timestamp ? edge.node.block.timestamp * 1000 : Date.now(), + id: edge.node.id, + cursor: edge.cursor, + }; + }); + return processedTransactions.filter((tx) => tx.amountIn && tx.amountOut && tx.tokenIn && tx.tokenOut); +} + +export function getStatusColor(status: AOYieldAgentStatus, mintingStatus?: MintingStatus) { + const statusText = getStatusText(status, mintingStatus); + + switch (statusText) { + case "Active": + return "#56C980"; + case "Cancelled": + return "#EE5A4F"; + case "Paused": + return "#FFE342"; + default: + return "#6B57F9"; + } +} + +export function getStatusText(status: AOYieldAgentStatus, mintingStatus?: MintingStatus) { + if (mintingStatus && mintingStatus === "Paused" && status === "Active") { + return "Paused"; + } + return status; +} + +export const assets: Asset[] = [ + { + ticker: "wUSDC", + logo: wUSDCIcon, + id: WUSDC_PROCESS_ID, + denomination: 6, + }, + { + ticker: "wAR", + logo: WarIcon, + id: WAR_PROCESS_ID, + denomination: 12, + }, +]; + +export const tokenIdInfoMap = { + [WUSDC_PROCESS_ID]: assets[0], + [WAR_PROCESS_ID]: assets[1], +}; + +export function formatTokenQuantity(value: string, decimals: number) { + return formatBalance(balanceToFractioned(String(value), { decimals })).displayBalance; +} + +export function formatDate(date: Date | null, fallbackLabel: string) { + return date ? dayjs(date).format("MMM D, YYYY") : fallbackLabel; +} + +export async function getWanderFee() { + const defaultFee = "0.25"; + try { + const aoInstance = connect(defaultConfig); + + const dryrunRes = await aoInstance.dryrun({ + Id, + Owner, + process: WANDER_FEE_PROCESS_ID, + tags: [{ name: "Action", value: "Info" }], + }); + + const tags = dryrunRes.Messages?.[0]?.Tags || []; + const fee = getTagValue("Conversion-Fee", tags) || defaultFee; + + return fee; + } catch (error) { + console.error("Error getting wander fee", error); + return defaultFee; + } +} diff --git a/libs/core/src/lib/applications/allowance.ts b/libs/core/src/lib/applications/allowance.ts new file mode 100644 index 000000000..5af08f976 --- /dev/null +++ b/libs/core/src/lib/applications/allowance.ts @@ -0,0 +1,19 @@ +import type BigNumber from "bignumber.js"; + +export interface Allowance { + enabled: boolean; + limit: string; // in winstons + spent: string; // in winstons +} + +export interface AllowanceBigNumber { + enabled: boolean; + limit: BigNumber; // in winstons + spent: BigNumber; // in winstons +} + +export const defaultAllowance: Allowance = { + enabled: true, + limit: "1000000000000", + spent: "0", +}; diff --git a/libs/core/src/lib/applications/application.class.ts b/libs/core/src/lib/applications/application.class.ts new file mode 100644 index 000000000..98ec64088 --- /dev/null +++ b/libs/core/src/lib/applications/application.class.ts @@ -0,0 +1,237 @@ +import { getMissingPermissions, type PermissionType } from "./permissions"; +import { type Allowance, type AllowanceBigNumber, defaultAllowance } from "./allowance"; +import type { Storage } from "@plasmohq/storage"; +import BigNumber from "bignumber.js"; +import { PersistentStorage, useStorage } from "../utils/storage/storage"; +import { defaultGateway, Gateway } from "../gateways/gateway"; + +export const PREFIX = "app_"; +export const defaultBundler = "https://turbo.ardrive.io"; +export const pricingEndpoint = "https://payment.ardrive.io"; + +export class Application { + /** Root URL of the app */ + public url: string; + + #storage: Storage; + + constructor(url: string) { + this.url = url; + this.#storage = PersistentStorage; + } + + /** + * Private method used to retrieve all settings for an app + * @returns settings objects or an empty object + */ + async #getSettings() { + const settings = await this.#storage.get>(`${PREFIX}${this.url}`); + + return settings || {}; + } + + /** + * Update settings for the app + * + * @param val Object of settings to update to + */ + async updateSettings( + val: + | Partial + | ((current: Record) => Partial | Promise>), + ) { + const settings = await this.#getSettings(); + + if (typeof val === "function") { + val = await val(settings); + } + + // update keys + for (const key in val) { + settings[key] = val[key]; + } + + // save settings + const res = await this.#storage.set(`${PREFIX}${this.url}`, settings); + + return res; + } + + /** + * App name and logo + */ + async getAppData(): Promise { + const settings = await this.#getSettings(); + + return { + name: settings.name, + logo: settings.logo, + }; + } + + /** + * Permissions granted to this app + */ + async getPermissions(): Promise { + const settings = await this.#getSettings(); + + return settings.permissions || []; + } + + /** + * Check if the app has the provided permissions + * + * @param permissions Permissions to check for + */ + async hasPermissions(permissions: PermissionType[]): Promise<{ + /** App has permissions or not */ + result: boolean; + /** Existing permissions */ + has: PermissionType[]; + /** Missing permissions */ + missing: PermissionType[]; + }> { + const existingPermissions = await this.getPermissions(); + const missing = getMissingPermissions(existingPermissions, permissions); + + return { + result: missing.length === 0, + has: existingPermissions, + missing, + }; + } + + /** + * Get if the app is connected to Wander + */ + async isConnected() { + const permissions = await this.getPermissions(); + + return permissions.length > 0; + } + + /** + * Check if the app is present in the storage + */ + async isAppPresent() { + const settings = await this.#getSettings(); + + return Object.keys(settings).length > 0; + } + + /** + * Gateway config for each individual app + */ + async getGatewayConfig(): Promise { + const settings = await this.#getSettings(); + + // TODO: wayfinder + return settings.gateway || defaultGateway; + } + + /** + * Get the URL of the service for submitting data + * to Arweave instead of using a gateway + */ + async getBundler(): Promise { + const settings = await this.#getSettings(); + + return settings.bundler || defaultBundler; + } + + /** + * Allowance limit and spent qty + */ + async getAllowance(): Promise { + const settings = await this.#getSettings(); + + const allowance = settings.allowance || defaultAllowance; + return { + enabled: allowance.enabled, + limit: BigNumber(allowance.limit), + spent: BigNumber(allowance.spent), + }; + } + + /** + * Sign policy for the app + */ + async getSignPolicy(): Promise<"always_ask" | "ask_when_spending" | "auto_confirm"> { + const settings = await this.#getSettings(); + + return settings.signPolicy || "always_ask"; + } + + /** + * Blocked from interacting with Wander + */ + async isBlocked(): Promise { + const settings = await this.#getSettings(); + + return !!settings.blocked; + } + + hook() { + return useStorage( + { + key: `${PREFIX}${this.url}`, + instance: PersistentStorage, + }, + (val) => { + if (typeof val === "undefined") return val; + + // assign with default values + const values = { + allowance: defaultAllowance, + // TODO: wayfinder + gateway: defaultGateway, + bundler: defaultBundler, + ...val, + }; + + return values; + }, + ); + } +} + +/** + * App info submitted by the dApp + */ +export interface AppInfo { + name?: string; + logo?: string; +} + +/** + * App Logo info to be used to derive the proper placeholder + * extends AppInfo's name and logo + * adding an optional type: "default" | "gateway" and placeholder + */ +export interface AppLogoInfo extends AppInfo { + type?: "default" | "gateway"; + placeholder?: string; +} + +/** + * Sign policy for the app + * - **always_ask**: always ask the user to sign + * - **ask_when_spending**: ask the user to sign when user assets are being spent or has network fees to be paid else auto sign + * - **auto_confirm**: automatically sign every transactions + * + * @default "always_ask" + */ +export type SignPolicy = "always_ask" | "ask_when_spending" | "auto_confirm"; + +/** + * Params to add an app with + */ +export interface InitAppParams extends AppInfo { + url: string; + permissions: PermissionType[]; + gateway?: Gateway; + allowance?: Allowance; + blocked?: boolean; + bundler?: string; + signPolicy?: SignPolicy; +} diff --git a/libs/core/src/lib/applications/application.utils.ts b/libs/core/src/lib/applications/application.utils.ts new file mode 100644 index 000000000..83b941c03 --- /dev/null +++ b/libs/core/src/lib/applications/application.utils.ts @@ -0,0 +1,76 @@ +import { PersistentStorage } from "../utils/storage/storage"; +import { Application, type InitAppParams, PREFIX } from "./application.class"; +import browser from "webextension-polyfill"; + +/** + * Get all connected app keys + */ +export async function getStoredApps(): Promise { + return (await PersistentStorage.get("apps")) || []; +} + +/** + * Get all applications connected + */ +export async function getApps() { + // fetch app urls + const appUrls = await getStoredApps(); + const apps: Application[] = []; + + // init all apps + for (const url of appUrls) { + apps.push(new Application(url)); + } + + return apps; +} + +/** + * Add an application + */ +export async function addApp({ url, ...rest }: InitAppParams) { + if (url === "") return; + + const storedApps = await getStoredApps(); + + // check if app is already added + if (storedApps.includes(url)) return; + + // add app url + await PersistentStorage.set("apps", [...storedApps, url]); + + // save app settings + await PersistentStorage.set(`${PREFIX}${url}`, { + url, + ...rest, + }); +} + +/** + * Remove an application (disconnect) + * + * @param url URL of the tab to remove + */ +export async function removeApp(url: string) { + const storedApps = await getStoredApps(); + + // remove app key + await PersistentStorage.set( + "apps", + storedApps.filter((val) => val !== url), + ); + + // remove app settings + await PersistentStorage.remove(`${PREFIX}${url}`); +} + +/** + * Get the active tab object + */ +export const getActiveTab = async () => + ( + await browser.tabs.query({ + active: true, + currentWindow: true, + }) + )[0]; diff --git a/libs/core/src/lib/applications/gateway.ts b/libs/core/src/lib/applications/gateway.ts new file mode 100644 index 000000000..f7eff328a --- /dev/null +++ b/libs/core/src/lib/applications/gateway.ts @@ -0,0 +1,17 @@ +import { type Application } from "./application.class"; +import { concatGatewayURL } from "../gateways/utils"; + +/** + * Get the full gateway URL string, from the + * extension storage. This should only be used + * for background scripts, not for the UX. + * The UX should use the redux gateway config + * reducer and concat the gateway URL from there. + * + * @returns Gateway URL + */ +export async function gatewayURL(app: Application) { + const gatewayConfig = await app.getGatewayConfig(); + + return concatGatewayURL(gatewayConfig); +} diff --git a/libs/core/src/lib/applications/permissions.ts b/libs/core/src/lib/applications/permissions.ts new file mode 100644 index 000000000..b69229a09 --- /dev/null +++ b/libs/core/src/lib/applications/permissions.ts @@ -0,0 +1,46 @@ +/** + * Wander permissions + */ +export type PermissionType = + | "ACCESS_ADDRESS" + | "ACCESS_PUBLIC_KEY" + | "ACCESS_ALL_ADDRESSES" + | "SIGN_TRANSACTION" + | "ENCRYPT" + | "DECRYPT" + | "SIGNATURE" + | "ACCESS_ARWEAVE_CONFIG" + | "DISPATCH" + | "ACCESS_TOKENS"; + +/** + * All permissions with their descriptions + */ +export const permissionData: Record = { + ACCESS_ADDRESS: "permissionDescriptionAccessAddress", + ACCESS_PUBLIC_KEY: "permissionDescriptionAccessPublicKey", + ACCESS_ALL_ADDRESSES: "permissionDescriptionAccessAllAddresses", + SIGN_TRANSACTION: "permissionDescriptionSign", + ENCRYPT: "permissionDescriptionEncrypt", + DECRYPT: "permissionDescriptionDecrypt", + SIGNATURE: "permissionDescriptionSignature", + ACCESS_ARWEAVE_CONFIG: "permissionDescriptionArweaveConfig", + DISPATCH: "permissionDescriptionDispatch", + ACCESS_TOKENS: "permissionAccessTokens", +}; + +/** + * Get permissions that are missing from the + * allowed permissions list + * + * @param existing The permissions the app already has + * @param required The permissions the app is required to have + * @returns The missing permissions + */ +export function getMissingPermissions(existing: PermissionType[], required: PermissionType[]) { + const missing = required.filter((permission) => !existing.includes(permission)); + + return missing; +} + +export const signPolicyOptions = ["always_ask", "ask_when_spending", "auto_confirm"] as const; diff --git a/libs/core/src/lib/applications/tab.ts b/libs/core/src/lib/applications/tab.ts new file mode 100644 index 000000000..900e5577f --- /dev/null +++ b/libs/core/src/lib/applications/tab.ts @@ -0,0 +1,28 @@ +import browser from "webextension-polyfill"; + +/** + * Get a browser tab by id + * + * @param id ID of the tab to get + */ +export async function getTab(id: number) { + // get all tabs + const tabs = await browser.tabs.query({}); + + return tabs.find((tab) => tab.id === id); +} + +/** + * Run code for each tab + */ +export async function forEachTab(fn: (tab: browser.Tabs.Tab) => void | Promise) { + // get all tabs + const tabs = await browser.tabs.query({}); + + // go through all tabs + for (const tab of tabs) { + if (!tab.id || !tab.url) continue; + + await fn(tab); + } +} diff --git a/libs/core/src/lib/applications/useActiveTab.ts b/libs/core/src/lib/applications/useActiveTab.ts new file mode 100644 index 000000000..657571cdb --- /dev/null +++ b/libs/core/src/lib/applications/useActiveTab.ts @@ -0,0 +1,28 @@ +import browser, { type Tabs } from "webextension-polyfill"; +import { useState } from "react"; +import { useAsyncEffect } from "../utils/react/useAsyncEffect"; +import { getActiveTab } from "./application.utils"; + + +export default function useActiveTab() { + const [activeTab, setActiveTab] = useState(); + + useAsyncEffect(async () => { + // listener for active tab + const fetchActiveTab = async () => { + const tab = await getActiveTab(); + + setActiveTab(tab); + }; + + // load current tab + await fetchActiveTab(); + + // listen for changes + browser.tabs.onUpdated.addListener(fetchActiveTab); + + return () => browser.tabs.onUpdated.removeListener(fetchActiveTab); + }, []); + + return activeTab; +} diff --git a/libs/core/src/lib/auth/auth.constants.ts b/libs/core/src/lib/auth/auth.constants.ts new file mode 100644 index 000000000..28ae9d742 --- /dev/null +++ b/libs/core/src/lib/auth/auth.constants.ts @@ -0,0 +1,19 @@ +import { ModuleAppData } from "../injected-api/background/background-modules"; + +export const AUTH_POPUP_REQUEST_WAIT_MS = 1000 as const; +export const AUTH_POPUP_REQUEST_ARRIVAL_WINDOW_MS = 1000 as const; +export const AUTH_POPUP_CLOSING_DELAY_MS = process.env.NODE_ENV === "development" ? (5000 as const) : (0 as const); +export const AUTH_POPUP_UNLOCK_REQUEST_TTL_MS = 900000 as const; // 15 min. + +export const DEFAULT_MODULE_APP_DATA = { + tabID: -1, + url: "", +} as const satisfies ModuleAppData; + +// Errors: + +export const ERR_MSG_USER_CANCELLED_AUTH = "User cancelled the AuthRequest"; +export const ERR_MSG_NO_WALLETS_ADDED = "No wallets added"; +export const ERR_MSG_NO_ACTIVE_WALLET = "No active wallet"; +export const ERR_MSG_UNLOCK_TIMEOUT = "Unlock request timed out"; +export const ERR_MSG_NO_KEY = "No key"; diff --git a/libs/core/src/lib/auth/auth.hooks.ts b/libs/core/src/lib/auth/auth.hooks.ts new file mode 100644 index 000000000..884f1ed70 --- /dev/null +++ b/libs/core/src/lib/auth/auth.hooks.ts @@ -0,0 +1,51 @@ +import { useContext } from "react"; +import { AuthRequestsContext } from "./auth.provider"; +import { AuthRequestByType, AuthType } from "./auth.types"; +import { ERR_MSG_USER_CANCELLED_AUTH } from "./auth.constants"; + +export function useAuthRequests() { + return useContext(AuthRequestsContext); +} + +/** + * Get the current AuthRequest and validate it has the expected type. + * + * @param type Expected type of the AuthRequest. + */ +export function useCurrentAuthRequest(expectedAuthType: T | "any") { + const { authRequests, currentAuthRequestIndex, lastCompletedAuthRequest, completeAuthRequest, closeAuthPopup } = + useContext(AuthRequestsContext); + + const authRequest = authRequests[currentAuthRequestIndex] as AuthRequestByType[T]; + const authRequestType = authRequest?.type; + + if (expectedAuthType !== "any") { + if (!authRequest) { + throw new Error(`Missing "${expectedAuthType}" AuthRequest.`); + } else if (expectedAuthType !== authRequestType) { + throw new Error(`Unexpected "${authRequestType}" AuthRequest. ${expectedAuthType} expected.`); + } + } + + const { type, authID, status } = authRequest || {}; + + function acceptRequest(data?: any) { + if (status !== "pending") throw new Error(`AuthRequest ${type}(${authID}) already ${status}`); + + return completeAuthRequest(authID, data); + } + + function rejectRequest(errorMessage?: string) { + if (status !== "pending") throw new Error(`AuthRequest ${type}(${authID}) already ${status}`); + + return completeAuthRequest(authID, new Error(errorMessage || ERR_MSG_USER_CANCELLED_AUTH)); + } + + return { + authRequest, + lastCompletedAuthRequest, + acceptRequest, + rejectRequest, + closeAuthPopup, + }; +} diff --git a/libs/core/src/lib/auth/auth.provider.tsx b/libs/core/src/lib/auth/auth.provider.tsx new file mode 100644 index 000000000..19f6b9499 --- /dev/null +++ b/libs/core/src/lib/auth/auth.provider.tsx @@ -0,0 +1,478 @@ +import { createContext, useCallback, useEffect, useRef, useState, type PropsWithChildren } from "react"; +import { + AUTH_POPUP_CLOSING_DELAY_MS, + AUTH_POPUP_REQUEST_WAIT_MS, + AUTH_POPUP_REQUEST_ARRIVAL_WINDOW_MS, + AUTH_POPUP_UNLOCK_REQUEST_TTL_MS, + ERR_MSG_USER_CANCELLED_AUTH, +} from "./auth.constants"; +import type { AuthRequest, AuthRequestStatus, SignAuthRequest, SignKeystoneAuthRequest } from "./auth.types"; +import { compareConnectAuthRequests, replyToAuthRequest, stopKeepAlive } from "./auth.utils"; +import Arweave from "arweave"; +import { bytesFromChunks, constructTransaction, type SplitTransaction } from "../injected-api/modules/sign/transaction_builder"; +import { isomorphicOnMessage } from "@wanderapp/isomorphic-messaging"; +import type { IBridgeMessage } from "@arconnect/webext-bridge"; +import { defaultGateway } from "../gateways/gateway"; +import { log, LOG_GROUP } from "../utils/log/log.utils"; +import type { Chunk } from "../injected-api/modules/sign/chunks"; +import { isError } from "../utils/error/error.utils"; +import { getDecryptionKey } from "../wallets/auth"; + +export interface AuthRequestsContextState { + authRequests: AuthRequest[]; + currentAuthRequestIndex: number; + lastCompletedAuthRequest: null | AuthRequest; +} + +export interface AuthRequestsContextData extends AuthRequestsContextState { + setCurrentAuthRequestIndex: (currentAuthRequestIndex: number) => void; + completeAuthRequest: (authID: string, data: any) => Promise; + closeAuthPopup: (delay?: number) => () => void; +} + +const AUTH_REQUESTS_CONTEXT_INITIAL_STATE: AuthRequestsContextState = { + authRequests: [], + currentAuthRequestIndex: 0, + lastCompletedAuthRequest: null, +}; + +export const AuthRequestsContext = createContext({ + ...AUTH_REQUESTS_CONTEXT_INITIAL_STATE, + setCurrentAuthRequestIndex: () => {}, + completeAuthRequest: async () => {}, + closeAuthPopup: (delay?: number) => () => {}, +}); + +// TODO: Add props for onRequestChange, addSignOutListener... + +export function AuthRequestsProvider({ children }: PropsWithChildren) { + const [{ authRequests, currentAuthRequestIndex, lastCompletedAuthRequest }, setAuthRequestContextState] = + useState(AUTH_REQUESTS_CONTEXT_INITIAL_STATE); + + // Track when the component mounted + const mountTimeRef = useRef(Date.now()); + + const setCurrentAuthRequestIndex = useCallback((currentAuthRequestIndex: number) => { + setAuthRequestContextState((prevAuthRequestContextState) => { + return { + ...prevAuthRequestContextState, + currentAuthRequestIndex, + }; + }); + }, []); + + const closeAuthPopup = useCallback((delay = 0) => { + function closeOrClear() { + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") { + setAuthRequestContextState(AUTH_REQUESTS_CONTEXT_INITIAL_STATE); + + // Note the wait-before-closing functionality won't work for Embed with this implementation. One + // possible fix would be to add a shouldClose = true property to the "embedded_request" message being sent from + // here. + } else { + window.top.close(); + } + } + + if (delay > 0) { + const timeoutID = setTimeout(closeOrClear, delay); + + return () => clearTimeout(timeoutID); + } + + closeOrClear(); + + return () => {}; + }, []); + + const completeAuthRequest = useCallback( + async (authID: string, data: any) => { + const completedAuthRequest = authRequests.find((authRequest) => authRequest.authID === authID); + + const completedAuthRequests = [completedAuthRequest]; + const completedAuthRequestType = completedAuthRequest.type; + + if (completedAuthRequestType === "connect") { + // Find equivalent ConnectAuthRequest to also accept/reject those: + authRequests.forEach((authRequest) => { + if ( + authRequest.type === "connect" && + authRequest !== completedAuthRequest && + compareConnectAuthRequests(authRequest, completedAuthRequest) + ) { + completedAuthRequests.push(authRequest); + } + }); + } + + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") { + const pendingRequests = + authRequests.filter((authRequest) => authRequest.status === "pending").length - completedAuthRequests.length; + + postEmbeddedMessage({ + type: "embedded_request", + data: { + pendingRequests, + hasNewConnectRequest: false, + }, + }); + } + + // TODO: Consider automatically rejecting (expiring) AuthRequest if there are more than 100. + + const status: AuthRequestStatus = isError(data) ? "rejected" : "accepted"; + + const authRequestRepliesPromises: Promise[] = completedAuthRequests.map( + (completedAuthRequest) => { + return replyToAuthRequest(completedAuthRequest.type, completedAuthRequest.authID, data) + .then(() => { + return status; + }) + .catch((err) => { + console.warn(`replyToAuthRequest(${authID}) failed:`, err); + + return "error"; + }); + }, + ); + + const completedAuthRequestsStatus = await Promise.all(authRequestRepliesPromises); + + log( + LOG_GROUP.AUTH, + `completeAuthRequest(authID = "${authID}", status = "${status}") => ${completedAuthRequestsStatus.join(", ")}`, + ); + + // If it is any other type of `AuthRequest`, we mark it as accepted/cancelled and move on to the next one: + setAuthRequestContextState((prevAuthRequestContextState) => { + const { authRequests, currentAuthRequestIndex } = prevAuthRequestContextState; + + if (authID !== authRequests[currentAuthRequestIndex]?.authID) { + console.warn(`Mismatch between authID="${authID}" and AuthRequest[${currentAuthRequestIndex}]?.authID`); + + return prevAuthRequestContextState; + } + + if (authID !== completedAuthRequests[0]?.authID) { + console.warn(`Mismatch between authID="${authID}" and completedAuthRequests[0]?.authID`); + + return prevAuthRequestContextState; + } + + const nextAuthRequests = authRequests; + + let nextLastCompletedAuthRequest: AuthRequest; + + completedAuthRequests.forEach((completedAuthRequest, i) => { + const completedAuthRequestIndex = nextAuthRequests.findIndex( + (authRequest) => authRequest.authID === completedAuthRequest.authID, + ); + + if (completedAuthRequestIndex === -1) { + console.warn(`Could not find AuthRequest with authID="${completedAuthRequestsStatus}"`); + + return; + } + + nextAuthRequests[completedAuthRequestIndex] = { + ...nextAuthRequests[completedAuthRequestIndex], + completedAt: Date.now(), + status: completedAuthRequestsStatus[i], + }; + + if (i === 0) nextLastCompletedAuthRequest = nextAuthRequests[completedAuthRequestIndex]; + }); + + // Find the index of the next "pending" `AuthRequest`, or keep it unchanged if there are none left: + let nextCurrentAuthRequestIndex = currentAuthRequestIndex; + + do { + nextCurrentAuthRequestIndex = (nextCurrentAuthRequestIndex + 1) % nextAuthRequests.length; + } while ( + nextCurrentAuthRequestIndex !== currentAuthRequestIndex && + nextAuthRequests[nextCurrentAuthRequestIndex].status !== "pending" + ); + + if (nextCurrentAuthRequestIndex === currentAuthRequestIndex) { + nextCurrentAuthRequestIndex = nextAuthRequests.length; + } + + return { + authRequests: nextAuthRequests, + currentAuthRequestIndex: nextCurrentAuthRequestIndex, + lastCompletedAuthRequest: nextLastCompletedAuthRequest, + }; + }); + }, + [authRequests], + ); + + useEffect(() => { + log(LOG_GROUP.AUTH, "Auth popup initialized. Waiting for AuthRequests"); + + isomorphicOnMessage("auth_request", (authRequestMessage) => { + log(LOG_GROUP.AUTH, "auth_request =", authRequestMessage); + + // UnlockAuthRequests are not enqueued as those are simply used to open the popup to prompt users to enter their + // password and wait for the wallet to unlock: + + if (!authRequestMessage?.data) { + console.warn("auth_request without data"); + return; + } + + const authRequest = authRequestMessage.data; + + setAuthRequestContextState((prevAuthRequestContextState) => { + const { authRequests, currentAuthRequestIndex, lastCompletedAuthRequest } = prevAuthRequestContextState; + + // TODO: Additional considerations when enqueueing new `AuthRequest`s: + // + // - `AuthRequest`s could be grouped by domain and/or tab. This means the auth popup could/should provide a + // domain/tab selector and automatically select the active tab when the user switches it. + // + // - Separate `signDataItem`requests could/should automatically be combined into a single `batchSignDataItem` + // (except maybe for the current `AuthRequest`, as otherwise the UI would constantly change as new requests + // are added to the batch). + + const nextAuthRequests = [...authRequests, { ...authRequest, status: "pending" }] satisfies AuthRequest[]; + + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") { + const pendingRequests = nextAuthRequests.filter((authRequest) => authRequest.status === "pending").length; + + postEmbeddedMessage({ + type: "embedded_request", + data: { + pendingRequests, + hasNewConnectRequest: authRequest.type === "connect", + }, + }); + } + + // TODO: Add setting to decide whether we automatically jump to a new pending request when they arrive or stay + // in the one currently selected. + + return { + authRequests: nextAuthRequests, + currentAuthRequestIndex, + lastCompletedAuthRequest, + }; + }); + }); + }, []); + + useEffect(() => { + function handleTabReloadedOrClosed(message: IBridgeMessage) { + const tabID = message?.data; + + if (!tabID) return; + + setAuthRequestContextState((prevAuthRequestContextState) => { + const { + authRequests: prevAuthRequests, + currentAuthRequestIndex, + lastCompletedAuthRequest, + } = prevAuthRequestContextState; + + let pendingRequestsCount = 0; + + const authRequests = prevAuthRequests.map((authRequest) => { + if (authRequest.tabID === tabID) { + stopKeepAlive(authRequest.authID); + + return { + ...authRequest, + completedAt: Date.now(), + status: "aborted", + } satisfies AuthRequest; + } + + if (authRequest.status === "pending") { + ++pendingRequestsCount; + } + + return authRequest; + }); + + if (pendingRequestsCount === 0 && authRequests.length > 0) { + // All tabs that sent AuthRequest also got closed/reloaded/disconnected, so close the popup immediately: + closeAuthPopup(); + } + + // TODO: Consider automatically selecting the next pending AuthRequest. + + return { + authRequests, + currentAuthRequestIndex, + lastCompletedAuthRequest, + }; + }); + } + + isomorphicOnMessage("auth_tab_reloaded", handleTabReloadedOrClosed); + isomorphicOnMessage("auth_tab_closed", handleTabReloadedOrClosed); + isomorphicOnMessage("auth_active_wallet_change", handleTabReloadedOrClosed); + isomorphicOnMessage("auth_app_disconnected", handleTabReloadedOrClosed); + }, [closeAuthPopup]); + + useEffect(() => { + const chunksByCollectionID: Record = {}; + + // Listen for chunks needed in `sign.tsx` and `signKeystone.tsx`: + + isomorphicOnMessage("auth_chunk", ({ sender, data }) => { + if (sender.context !== "background") return; + + const { type, collectionID } = data; + + log(LOG_GROUP.CHUNKS, `auth_chunk(type ="${type}", collectionID = "${collectionID})"`); + + if (type === "start") { + chunksByCollectionID[collectionID] = []; + } else if (type === "end") { + const arweave = new Arweave(defaultGateway); + + setAuthRequestContextState((prevAuthRequestContextState) => { + const { + authRequests: prevAuthRequests, + currentAuthRequestIndex, + lastCompletedAuthRequest, + } = prevAuthRequestContextState; + + const targetAuthRequest = prevAuthRequests.find((authRequest) => { + return ( + (authRequest.type === "sign" || authRequest.type === "signKeystone") && + authRequest.collectionID === collectionID + ); + }) as SignAuthRequest | SignKeystoneAuthRequest; + + if (!targetAuthRequest) return prevAuthRequestContextState; + + // Update SignAuthRequest with `transaction`: + + if (targetAuthRequest.type === "sign") { + const transaction = arweave.transactions.fromRaw( + constructTransaction( + targetAuthRequest.transaction as SplitTransaction, + chunksByCollectionID[collectionID], + ), + ); + + const authRequests = prevAuthRequests.map((authRequest) => { + if (authRequest.authID !== targetAuthRequest.authID) return authRequest; + + return { + ...authRequest, + transaction, + } as SignAuthRequest; + }); + + return { + authRequests, + currentAuthRequestIndex, + lastCompletedAuthRequest, + }; + } + + // Update SignKeystoneAuthRequest with `data`: + + const bytes = bytesFromChunks(chunksByCollectionID[collectionID]); + const data = Buffer.from(bytes); + + const authRequests = prevAuthRequests.map((authRequest) => { + if (authRequest.authID !== targetAuthRequest.authID) return authRequest; + + return { + ...authRequest, + data, + } as SignKeystoneAuthRequest; + }); + + return { + authRequests, + currentAuthRequestIndex, + lastCompletedAuthRequest, + }; + }); + } else { + if (!chunksByCollectionID[collectionID]) { + chunksByCollectionID[collectionID] = [data]; + } else { + chunksByCollectionID[collectionID].push(data); + } + } + }); + }, []); + + useEffect(() => { + let clearCloseAuthPopupTimeout = () => {}; + + async function setCloseTimers() { + const isEmbedded = import.meta.env?.VITE_IS_EMBEDDED_APP === "1"; + const hasAuthRequests = authRequests.length > 0; + const isDone = hasAuthRequests && authRequests.every((authRequest) => authRequest.status !== "pending"); + + if (isDone) { + // Close the window if the last request has been handled: + // TODO: Add setting to decide whether this closes automatically or stays open in a "done" state. + + clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_CLOSING_DELAY_MS); + } else if (!isEmbedded) { + const isLocked = !(await getDecryptionKey()); + + // Check if we're within the initial request arrival window (AUTH_POPUP_REQUEST_ARRIVAL_WINDOW_MS ms after mounting) + // This gives time for pending auth requests to arrive before automatically closing the popup + const isInRequestArrivalWindow = Date.now() - mountTimeRef.current < AUTH_POPUP_REQUEST_ARRIVAL_WINDOW_MS; + + if (isLocked) { + // If the wallet is locked, the user has `AUTH_POPUP_UNLOCK_REQUEST_TTL_MS` (15 minutes) to unlock it before + // we close it automatically. While that's happening, AuthRequest might or might not be in this provide + // already: + clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_UNLOCK_REQUEST_TTL_MS); + } else if (!hasAuthRequests && !isInRequestArrivalWindow) { + // Once the wallet is unlocked, we close the popup if an AuthRequest doesn't arrive in less than + // `AUTH_POPUP_REQUEST_WAIT_MS` (1 second) but only if the component is not in the initial request arrival window: + clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_REQUEST_WAIT_MS); + } + } + } + + setCloseTimers(); + + function rejectPendingAuthRequests() { + closeAuthPopup(); + + authRequests.forEach((authRequest) => { + if (authRequest.status !== "pending") return; + + // Send cancel event for all pending requests if the popup is closed by + // the user (BE) or if the user signs out (Connect): + replyToAuthRequest(authRequest.type, authRequest.authID, new Error(ERR_MSG_USER_CANCELLED_AUTH)); + }); + } + + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") addSignOutListener(rejectPendingAuthRequests); + else window.addEventListener("beforeunload", rejectPendingAuthRequests); + + return () => { + clearCloseAuthPopupTimeout(); + + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") removeSignOutListener(rejectPendingAuthRequests); + else window.removeEventListener("beforeunload", rejectPendingAuthRequests); + }; + }, [authRequests, currentAuthRequestIndex, closeAuthPopup]); + + return ( + + {children} + + ); +} diff --git a/libs/core/src/lib/auth/auth.types.ts b/libs/core/src/lib/auth/auth.types.ts new file mode 100644 index 000000000..012c6b580 --- /dev/null +++ b/libs/core/src/lib/auth/auth.types.ts @@ -0,0 +1,215 @@ +import type { Transaction } from "@dha-team/arbundles"; +import { PermissionType } from "../applications/permissions"; +import { AppInfo } from "../applications/application.class"; +import { Gateway } from "../gateways/gateway"; +import { TokenType } from "../tokens/token"; +import { SplitTransaction } from "../injected-api/modules/sign/transaction_builder"; +import { RawDataItem } from "../injected-api/modules/sign_data_item/types"; +import { SubscriptionData } from "../subscriptions/subscription"; + +// COMMON: + +export type PreferredEmailAuth = "otp" | "password"; + +export type AuthRequestStatus = "pending" | "accepted" | "rejected" | "aborted" | "error"; + +interface CommonAuthRequestProps { + url: string; + tabID: number; + authID: string; + requestedAt: number; + completedAt?: number; + status: AuthRequestStatus; +} + +// RESULTS: + +export interface AuthSuccessResult { + type: string; + authID: string; + data?: T; +} + +export interface AuthErrorResult { + type: string; + authID: string; + error: string; +} + +export type AuthResult = AuthSuccessResult | AuthErrorResult; + +export function isAuthErrorResult(data: any): data is AuthErrorResult { + return ( + !!data.type && + !!data.authID && + !!data.error && + typeof data.type === "string" && + typeof data.authID === "string" && + typeof data.error === "string" + ); +} + +// AuthRequestData: + +// CONNECT: + +export interface ConnectAuthRequestData { + type: "connect"; + permissions: PermissionType[]; + appInfo: AppInfo; + gateway?: Gateway; +} + +// ALLOWANCE: + +export interface AllowanceAuthRequestData { + type: "allowance"; + spendingLimitReached: boolean; +} + +// TOKEN AUTH: + +export interface TokenAuthRequestData { + type: "token"; + tokenID: string; + tokenType?: TokenType; + dre?: string; +} + +// DECRYPT + +export interface DecryptAuthRequestData { + type: "decrypt"; + message: number[]; +} + +// SIGN: + +export interface SignAuthRequestData { + type: "sign"; + address: string; + transaction: SplitTransaction; + collectionID: string; +} + +// SIGN KEYSTONE: + +export interface SignKeystoneAuthRequestData { + type: "signKeystone"; + collectionID: string; + keystoneSignType: string; +} + +// SIGNATURE: + +export interface SignatureAuthRequestData { + type: "signature"; + message: number[]; +} + +// SIGN DATA ITEM: + +export interface SignDataItemAuthRequestData { + type: "signDataItem"; + data: RawDataItem; +} + +// BATCH SIGN DATA ITEM: + +export interface BatchSignDataItemAuthRequestData { + type: "batchSignDataItem"; + data: RawDataItem[]; +} + +// SUBSCRIPTION: + +export interface SubscriptionAuthRequestData extends SubscriptionData { + type: "subscription"; +} + +// AuthRequestMessageData: + +export type ConnectAuthRequestMessageData = ConnectAuthRequestData & CommonAuthRequestProps; +export type AllowanceAuthRequestMessageData = AllowanceAuthRequestData & CommonAuthRequestProps; +export type TokenAuthRequestMessageData = TokenAuthRequestData & CommonAuthRequestProps; +export type DecryptAuthRequestMessageData = DecryptAuthRequestData & CommonAuthRequestProps; +export type SignAuthRequestMessageData = SignAuthRequestData & CommonAuthRequestProps; +export type SignKeystoneAuthRequestMessageData = SignKeystoneAuthRequestData & CommonAuthRequestProps; +export type SignatureAuthRequestMessageData = SignatureAuthRequestData & CommonAuthRequestProps; +export type SignDataItemAuthRequestMessageData = SignDataItemAuthRequestData & CommonAuthRequestProps; +export type BatchSignDataItemAuthRequestMessageData = BatchSignDataItemAuthRequestData & CommonAuthRequestProps; +export type SubscriptionAuthRequestMessageData = SubscriptionAuthRequestData & CommonAuthRequestProps; + +// AuthRequest: + +export type ConnectAuthRequest = ConnectAuthRequestMessageData; +export type AllowanceAuthRequest = AllowanceAuthRequestMessageData; +export type TokenAuthRequest = TokenAuthRequestMessageData; +export type DecryptAuthRequest = DecryptAuthRequestMessageData; + +export interface SignAuthRequest extends Omit { + transaction: SplitTransaction | Transaction; +} + +export interface SignKeystoneAuthRequest extends SignKeystoneAuthRequestMessageData { + data?: Buffer; +} + +export type SignatureAuthRequest = SignatureAuthRequestMessageData; +export type SignDataItemAuthRequest = SignDataItemAuthRequestMessageData; +export type BatchSignDataItemAuthRequest = BatchSignDataItemAuthRequestMessageData; + +export type SubscriptionAuthRequest = SubscriptionAuthRequestMessageData; + +// Unions & Misc: + +export type AuthType = AuthRequestData["type"]; + +export type AuthRequestData = + | ConnectAuthRequestData + | AllowanceAuthRequestData + | TokenAuthRequestData + | DecryptAuthRequestData + | SignAuthRequestData + | SignKeystoneAuthRequestData + | SignatureAuthRequestData + | SignDataItemAuthRequestData + | BatchSignDataItemAuthRequestData + | SubscriptionAuthRequestData; + +export type AuthRequestMessageData = + | ConnectAuthRequestMessageData + | AllowanceAuthRequestMessageData + | TokenAuthRequestMessageData + | DecryptAuthRequestMessageData + | SignAuthRequestMessageData + | SignKeystoneAuthRequestMessageData + | SignatureAuthRequestMessageData + | SignDataItemAuthRequestMessageData + | BatchSignDataItemAuthRequestMessageData + | SubscriptionAuthRequestMessageData; + +export type AuthRequest = + | ConnectAuthRequest + | AllowanceAuthRequest + | TokenAuthRequest + | DecryptAuthRequest + | SignAuthRequest + | SignKeystoneAuthRequest + | SignatureAuthRequest + | SignDataItemAuthRequest + | BatchSignDataItemAuthRequest + | SubscriptionAuthRequest; + +export type AuthRequestByType = { + connect: ConnectAuthRequest; + allowance: AllowanceAuthRequest; + token: TokenAuthRequest; + decrypt: DecryptAuthRequest; + sign: SignAuthRequest; + signKeystone: SignKeystoneAuthRequest; + signature: SignatureAuthRequest; + signDataItem: SignDataItemAuthRequest; + batchSignDataItem: BatchSignDataItemAuthRequest; + subscription: SubscriptionAuthRequest; +}; diff --git a/libs/core/src/lib/auth/auth.utils.ts b/libs/core/src/lib/auth/auth.utils.ts new file mode 100644 index 000000000..b29c7cad7 --- /dev/null +++ b/libs/core/src/lib/auth/auth.utils.ts @@ -0,0 +1,413 @@ +import { nanoid } from "nanoid"; +import browser from "webextension-polyfill"; +import { isomorphicOnMessage, isomorphicSendMessage } from "@wanderapp/isomorphic-messaging"; +import { + isAuthErrorResult, + type AuthErrorResult, + type AuthRequestData, + type AuthResult, + type AuthSuccessResult, + type AuthType, + type ConnectAuthRequest, +} from "./auth.types"; +import type { ModuleAppData } from "../injected-api/background/background-modules"; +import { getActiveAddress, getWallets, openOrSelectWelcomePage } from "../wallets/wallets.utils"; +import { + AUTH_POPUP_UNLOCK_REQUEST_TTL_MS, + ERR_MSG_NO_WALLETS_ADDED, + ERR_MSG_UNLOCK_TIMEOUT, +} from "./auth.constants"; +import { log, LOG_GROUP } from "../utils/log/log.utils"; +import { isError } from "../utils/error/error.utils"; +import { Mutex } from "../utils/mutex/mutex"; + +// TODO: Move postEmbeddedMessage nto core: +import { postEmbeddedMessage } from "~utils/_embedded/utils/messages/embedded-messages.utils"; + +const popupMutex = new Mutex(); + +type PopupCallback = (popupTabID: number) => void; + +let popupUpdatedCallbacks: PopupCallback[] = []; +let popupClosedCallbacks: PopupCallback[] = []; + +let POPUP_TAB_ID = -1; + +function setPopupTabID(popupTabID: number) { + log(LOG_GROUP.AUTH, "setPopupTabID =", popupTabID); + + POPUP_TAB_ID = popupTabID; + + if (popupTabID === -1) { + popupClosedCallbacks.forEach((cb) => { + cb(popupTabID); + }); + + popupClosedCallbacks = []; + + return; + } + + popupUpdatedCallbacks.forEach((cb) => { + cb(popupTabID); + }); + + popupUpdatedCallbacks = []; +} + +function onPopupTabUpdated(cb: PopupCallback) { + if (POPUP_TAB_ID !== -1) return cb(POPUP_TAB_ID); + + popupUpdatedCallbacks.push(cb); +} + +export function onPopupClosed(cb: PopupCallback) { + popupClosedCallbacks.push(cb); + + return () => { + const cbIndex = popupClosedCallbacks.indexOf(cb); + + if (cbIndex !== -1) popupClosedCallbacks.splice(cbIndex, 1); + }; +} + +export function resetPopupTabID() { + setPopupTabID(-1); +} + +export function getCachedAuthPopupWindowTabID() { + return POPUP_TAB_ID; +} + +export function getAuthPopupWindowTabID() { + if (POPUP_TAB_ID !== -1) return Promise.resolve(POPUP_TAB_ID); + + return new Promise((resolve) => { + onPopupTabUpdated((popupTabID) => { + resolve(popupTabID); + }); + }); +} + +/** + * Authenticate the user from the background script. + * Creates a popup window to authenticate and returns + * the result of the process. + * + * @param data Data to send to the auth window + */ +export async function requestUserAuthorization( + authRequestData: AuthRequestData, + moduleAppData: ModuleAppData, +) { + log(LOG_GROUP.AUTH, `requestUserAuthorization("${authRequestData.type}")`); + + // create the popup + const { authID, popupWindowTabID } = await createAuthPopup(authRequestData, moduleAppData); + + // wait for the results from the popup + return await getPopupResponse(authID, popupWindowTabID); +} + +/** + * Create or reuse an authenticator popup to handle an `AuthRequest`. + * + * @param data The data sent to the popup + * + * @returns ID of the authentication + */ +export async function createAuthPopup(authRequestData: null | AuthRequestData, moduleAppData: ModuleAppData) { + const unlock = await popupMutex.lock(); + + try { + const [activeAddress, wallets] = await Promise.all([getActiveAddress(), getWallets()]); + + const hasWallets = activeAddress && wallets.length > 0; + + if (!hasWallets) { + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") { + postEmbeddedMessage({ + type: "embedded_open", + data: null, + }); + } else { + openOrSelectWelcomePage(true); + } + + unlock(); + + throw new Error(ERR_MSG_NO_WALLETS_ADDED); + } + + const popupWindowTab: browser.Tabs.Tab | null = + POPUP_TAB_ID >= 0 ? await browser.tabs.get(POPUP_TAB_ID).catch(() => null) : null; + + if (popupWindowTab && !popupWindowTab.url.startsWith(browser.runtime.getURL("tabs/auth.html"))) { + console.warn(`Auth popup URL (${popupWindowTab.url}) doesn't match "tabs/auth.html"`); + } + + // TODO: In Embedded we are already in the right window, so no need to create one, just skip + // this and make the authRequestData make it to the AuthRequestsProvider. + + try { + if (!popupWindowTab) { + // TODO: To center this, the injected tab should send the center or dimensions of the screen: + + const window = await browser.windows.create({ + url: `${browser.runtime.getURL("tabs/auth.html")}#/`, + focused: true, + type: "popup", + + // TODO: Use these dimensions for embedded too... (pass them rather than using a hardcoded value so that we can control updates) + width: 385, + height: 720, + }); + + setPopupTabID(window.tabs[0].id); + } else { + log(LOG_GROUP.AUTH, "reusePopupTabID =", POPUP_TAB_ID); + + await browser.windows.update(popupWindowTab.windowId, { + focused: true, + }); + } + } catch (err) { + console.warn(`Could not ${popupWindowTab ? "focus" : "open"} "tabs/auth.html":`, err); + } + + let authID: string | undefined; + + if (authRequestData) { + // Generate an unique id for the authentication to be checked later: + authID = nanoid(); + + log(LOG_GROUP.AUTH, `isomorphicSendMessage(authID = "${authID}", tabId = ${POPUP_TAB_ID})`); + + await isomorphicSendMessage({ + destination: `web_accessible@${POPUP_TAB_ID}`, + messageId: "auth_request", + data: { + ...authRequestData, + url: moduleAppData.url, + tabID: moduleAppData.tabID, + authID, + requestedAt: Date.now(), + status: "pending", + }, + }); + } + + return { + authID, + popupWindowTabID: POPUP_TAB_ID, + }; + } catch (err) { + console.warn("Unexpected error in `createAuthPopup` =", err); + + throw err; + } finally { + unlock(); + } +} + +let isListeningForAuthResult = false; + +type AuthResultCallback = (data: T) => void; + +const authResultCallbacks = new Map>(); + +function addAuthResultListener(authID: string, fn: AuthResultCallback) { + authResultCallbacks.set(authID, fn); + + if (!isListeningForAuthResult) setAuthResultListener(); +} + +function setAuthResultListener() { + if (isListeningForAuthResult) return; + + isListeningForAuthResult = true; + + isomorphicOnMessage("auth_result", ({ sender, data }) => { + const { authID } = data; + + // validate sender by it's tabId + if (sender.tabId !== POPUP_TAB_ID) { + console.warn( + `auth_result for authID = ${authID} received from tabId = ${sender.tabId}, but ${POPUP_TAB_ID} expected`, + ); + + return; + } + + const authResultCallback = authResultCallbacks.get(data.authID); + + authResultCallbacks.delete(data.authID); + + if (!authResultCallback) { + console.warn(`authID = ${data.authID} doesn't have an "auth_result" listener`); + + return; + } + + authResultCallback(data); + }); +} + +/** + * Await for a browser message from the popup + */ +export function getPopupResponse(authID: string, popupWindowTabID: number) { + log(LOG_GROUP.AUTH, `getPopupResponse(authID = "${authID}", popupWindowTabID = ${popupWindowTabID})`); + + return new Promise>(async (resolve, reject) => { + startKeepAlive(authID); + + // This is redundant, as `auth.provider.ts` will close itself after AUTH_POPUP_UNLOCK_REQUEST_TTL_MS of inactivity + // (not receiving, completing or selecting AuthRequests), but just in case the response ("auth_result") never + // arrives, we have this here to make sure the dApp gets a response, eventually: + const timeoutID = setTimeout(() => { + reject(ERR_MSG_UNLOCK_TIMEOUT); + }, AUTH_POPUP_UNLOCK_REQUEST_TTL_MS); + + addAuthResultListener>(authID, (data) => { + stopKeepAlive(authID); + clearTimeout(timeoutID); + + if (!data) { + log(LOG_GROUP.AUTH, `auth_result for authID = "${authID}" = Empty)`); + + reject(`Missing data from authID = "${authID}"s "auth_result"`); + } else if (isAuthErrorResult(data)) { + log(LOG_GROUP.AUTH, `auth_result for authID = "${authID}" = Error (${data.error})`); + + reject(new Error(data.error)); + } else { + log(LOG_GROUP.AUTH, `auth_result for authID = "${authID}" = Success`); + + resolve(data); + } + }); + }); +} + +/** + * Send the result as a response to the auth + * + * @param type Type of the auth + * @param authID ID of the auth + * @param errorMessage Optional error message. If defined, the auth will fail with this message + * @param data Auth data + */ +export async function replyToAuthRequest(type: AuthType, authID: string, data?: T | Error) { + log(LOG_GROUP.AUTH, `replyToAuthRequest(type = "${type}", authID="${authID}")`); + + const response: AuthResult = isError(data) + ? ({ + type, + authID, + error: data.message, + } satisfies AuthErrorResult) + : ({ + type, + authID, + data, + } satisfies AuthSuccessResult); + + // send the response message + await isomorphicSendMessage({ + destination: "background", + messageId: "auth_result", + data: response, + }); +} + +// KEEP ALIVE ALARM: + +let keepAliveInterval: number | null = null; + +const activeAuthRequests = new Set(); + +const mutex = new Mutex(); + +/** + * Function to send periodic keep-alive messages + */ +export async function startKeepAlive(authID: string) { + const unlock = await mutex.lock(); + + try { + activeAuthRequests.add(authID); + + const activePopups = activeAuthRequests.size; + + if (activePopups > 0 && keepAliveInterval === null) { + log(LOG_GROUP.AUTH, `startKeepAlive(${authID}) =`, activeAuthRequests); + + keepAliveInterval = setInterval( + () => browser.alarms.create("keep-alive", { when: Date.now() + 1 }), + 20000, + ) as unknown as number; + } + } finally { + unlock(); + } +} + +/** + * Function to stop sending keep-alive messages + */ +export async function stopKeepAlive(authID: string) { + const unlock = await mutex.lock(); + + try { + activeAuthRequests.delete(authID); + + const activePopups = activeAuthRequests.size; + + if (activePopups <= 0 && keepAliveInterval !== null) { + log(LOG_GROUP.AUTH, `stopKeepAlive(${authID}) =`, activeAuthRequests); + + browser.alarms.clear("keep-alive"); + clearInterval(keepAliveInterval); + keepAliveInterval = null; + } + } finally { + unlock(); + } +} + +/** + * + */ +export async function resetKeepAlive() { + const unlock = await mutex.lock(); + + try { + activeAuthRequests.clear(); + + if (keepAliveInterval !== null) { + log(LOG_GROUP.AUTH, `resetKeepAlive() =`, activeAuthRequests); + + browser.alarms.clear("keep-alive"); + clearInterval(keepAliveInterval); + keepAliveInterval = null; + } + } finally { + unlock(); + } +} + +/** + * Returns true if both ConnectAuthRequest are equivalent (same app requesting the same permissions). + */ +export function compareConnectAuthRequests( + authRequest1: ConnectAuthRequest, + authRequest2: ConnectAuthRequest, +): boolean { + return ( + authRequest1.appInfo.name === authRequest2.appInfo.name && + authRequest1.appInfo.logo === authRequest2.appInfo.logo && + authRequest1.gateway === authRequest2.gateway && + authRequest1.permissions.toSorted().join("-") === authRequest2.permissions.toSorted().join("-") + ); +} diff --git a/libs/core/src/lib/constants/api.ts b/libs/core/src/lib/constants/api.ts new file mode 100644 index 000000000..d73deb676 --- /dev/null +++ b/libs/core/src/lib/constants/api.ts @@ -0,0 +1 @@ +export const CACHE_API = "https://wander-cache-ruddy.vercel.app"; diff --git a/libs/core/src/lib/gateways/api.ts b/libs/core/src/lib/gateways/api.ts new file mode 100644 index 000000000..fe68d8ffd --- /dev/null +++ b/libs/core/src/lib/gateways/api.ts @@ -0,0 +1,66 @@ +import type GQLResultInterface from "ar-gql/dist/faces"; +import { concatGatewayURL } from "./utils"; +import { findGateway } from "./wayfinder"; +import { type Gateway } from "./gateway"; +import type { GQLEdgeInterface } from "ar-gql/dist/faces"; +import { retryWithDelay } from "../utils/promises/retry"; + +/** + * Run a query on the Arweave Graphql API, + * using the configured gateway + * + * @param query The query string to run + * @param variables GQL variables to pass + * + * @returns Query result + */ + +export async function gql(query: string, variables?: Record, gateway?: Gateway) { + if (!gateway) { + gateway = await findGateway({ graphql: true }); + } + + const gatewayUrl = concatGatewayURL(gateway); + const graphql = JSON.stringify({ + query, + variables, + }); + + // execute the query + const data: GQLResultInterface = await ( + await fetch(gatewayUrl + "/graphql", { + method: "post", + body: graphql, + headers: { + accept: "application/json", + "content-type": "application/json", + }, + }) + ).json(); + + return data; +} + +export async function gqlAll(query: string, variables?: Record, gateway?: Gateway) { + let hasNextPage = true; + let edges: GQLEdgeInterface[] = []; + let cursor = ""; + + while (hasNextPage) { + try { + const res = await retryWithDelay(() => gql(query, { ...variables, after: cursor }, gateway), 2); + const transactions = res.data.transactions; + + if (transactions.edges && transactions.edges.length) { + edges = edges.concat(transactions.edges); + cursor = transactions.edges[transactions.edges.length - 1].cursor; + } + hasNextPage = transactions.pageInfo.hasNextPage; + } catch (error) { + console.error("Error fetching gqlAll: ", error); + return edges; + } + } + + return edges; +} diff --git a/libs/core/src/lib/gateways/ar_protocol.ts b/libs/core/src/lib/gateways/ar_protocol.ts new file mode 100644 index 000000000..789cfc043 --- /dev/null +++ b/libs/core/src/lib/gateways/ar_protocol.ts @@ -0,0 +1,42 @@ +import { isAddressFormat } from "../utils/format/format"; +import { type Gateway } from "./gateway"; +import { concatGatewayURL } from "./utils"; + +const PROTOCOL_PREFIX = "ar"; + +/** + * Get gateway redirect URL for ar:// protocol + * or return false if it is not a protocol request. + */ +export function getRedirectURL(url: URL, gateway: Gateway) { + let redirectURL: string; + + // "ar://" url + const arURL = url.searchParams.get("q"); + + if (!arURL || arURL === "" || !arURL.includes(`${PROTOCOL_PREFIX}://`)) { + return false; + } + + // value (address / permapage / id) + const value = arURL.replace(`${PROTOCOL_PREFIX}://`, ""); + + if (!value || value === "") { + return false; + } + + redirectURL = concatGatewayURL(gateway) + "/" + value; + + // if it is not an Arweave ID, redirect to permapages + if (!isAddressFormat(value)) { + // split path + const paths = value.split("/"); + + redirectURL = + `${gateway.protocol}://` + + `${paths[0]}.${gateway.host}:${gateway.port}` + + `/${paths.slice(1, paths.length).join("/")}`; + } + + return redirectURL; +} diff --git a/libs/core/src/lib/gateways/cache.ts b/libs/core/src/lib/gateways/cache.ts new file mode 100644 index 000000000..74759d00d --- /dev/null +++ b/libs/core/src/lib/gateways/cache.ts @@ -0,0 +1,38 @@ +import browser from "webextension-polyfill"; +import { ExtensionStorage } from "../utils/storage/storage"; +import { GatewayAddressRegistryItem } from "./types"; + + +/** Cache storage name */ +export const CACHE_STORAGE_NAME = "gateways"; +export const UPDATE_ALARM = "update_gateway"; +export const RETRY_ALARM = "update_gateway_retry"; + +/** + * Get cache of ar.io gateway list + */ +export async function getGatewayCache() { + return await ExtensionStorage.get(CACHE_STORAGE_NAME); +} + +/** + * Update ar.io gateway list cache + */ +export async function updateGatewayCache(gateways: GatewayAddressRegistryItem[]) { + return await ExtensionStorage.set(CACHE_STORAGE_NAME, gateways); +} + +/** + * Schedule update to gateway list. + * Refreshes after one day or if in retry mode, + * it'll attempt to call the alarm again in an hour. + */ +export async function scheduleGatewayUpdate(retry = false) { + // return if update alarm has already been scheduled + const gatewayUpdateAlarm = await browser.alarms.get(UPDATE_ALARM); + if (!retry && !!gatewayUpdateAlarm) return; + + browser.alarms.create(retry ? RETRY_ALARM : UPDATE_ALARM, { + [retry ? "when" : "periodInMinutes"]: retry ? Date.now() + 60 * 60 * 1000 : 12 * 60, + }); +} diff --git a/libs/core/src/lib/gateways/gateway.ts b/libs/core/src/lib/gateways/gateway.ts new file mode 100644 index 000000000..6a3a94250 --- /dev/null +++ b/libs/core/src/lib/gateways/gateway.ts @@ -0,0 +1,119 @@ +export interface Gateway { + host: string; + port: number; + protocol: string; +} + +/** + * Well-known gateways + */ +export const suggestedGateways: Gateway[] = [ + { + host: "arweave.net", + port: 443, + protocol: "https", + }, + { + host: "ar-io.net", + port: 443, + protocol: "https", + }, + { + host: "arweave.dev", + port: 443, + protocol: "https", + }, + { + host: "g8way.io", + port: 443, + protocol: "https", + }, +]; + +export const testnets: Gateway[] = [ + { + host: "www.arweave.run", + port: 443, + protocol: "https", + }, + { + host: "testnet.redstone.tools", + port: 443, + protocol: "https", + }, +]; + +export const fallbackGateway = { + host: "ar-io.dev", + port: 443, + protocol: "https", +}; + +export const goldskyGateway: Gateway = { + host: "arweave-search.goldsky.com", + port: 443, + protocol: "https", +}; + +export const clGateway = { + host: "arweave.ar", + port: 443, + protocol: "https", +}; + +export const defaultGateways = [ + { + host: "arweave.net", + port: 443, + protocol: "https", + }, + { + host: "ar-io.dev", + port: 443, + protocol: "https", + }, + clGateway, + { + host: "g8way.io", + port: 443, + protocol: "https", + }, + { + host: "permagate.io", + port: 443, + protocol: "https", + }, + { + host: "defi.ao", + port: 443, + protocol: "https", + }, + { + host: "aoweave.tech", + port: 443, + protocol: "https", + }, +]; + +export const printTxWorkingGateways: Gateway[] = [ + goldskyGateway, + { + host: "permagate.io", + port: 443, + protocol: "https", + }, + { + host: "ar-io.dev", + port: 443, + protocol: "https", + }, + { + host: "arweave.dev", + port: 443, + protocol: "https", + }, +]; + +export const txHistoryGateways = [suggestedGateways[1], suggestedGateways[0], suggestedGateways[3]]; + +export const defaultGateway = suggestedGateways[0]; diff --git a/libs/core/src/lib/gateways/types.ts b/libs/core/src/lib/gateways/types.ts new file mode 100644 index 000000000..30536d499 --- /dev/null +++ b/libs/core/src/lib/gateways/types.ts @@ -0,0 +1,88 @@ +export interface PaginatedResult { + hasMore: boolean; + items: T[]; + limit: number; + nextCursor: string; + sortBy: string; + sorOrder: string; + totalItems: number; +} + +export interface GatewaySettings { + port: number; + protocol: string; + minDelegatedStake: number; + fqdn: string; + delegateRewardShareRatio: number; + autoStake: boolean; + note: string; + allowDelegatedStaking: string | boolean; + label: string; + properties: + | string + | { + GRAPHQL: boolean; + ARNS: boolean; + MAX_PAGE_SIZE: number; + }; +} + +export interface GatewayStats { + failedConsecutiveEpochs: number; + observedEpochCount: number; + passedConsecutiveEpochs: number; + totalEpochCount: number; + prescribedEpochCount: number; + passedEpochCount: number; + failedEpochCount: number; +} + +export interface GatewayWeights { + compositeWeight: number; + observerRewardRatioWeight: number; + normalizedCompositeWeight: number; + tenureWeight: number; + gatewayRewardRatioWeight: number; + stakeWeight: number; +} + +export interface GatewayAddressRegistryItemData { + gatewayAddress: string; + observerAddress: string; + operatorStake: number; + startTimestamp: number; + status: "joined" | "leaving"; + totalDelegatedStake: number; + settings: GatewaySettings; + stats: GatewayStats; + weights: GatewayWeights; +} + +export interface GatewayUnknownCheck { + status: "unknown"; +} + +export interface GatewayPendingCheck { + status: "pending"; +} + +export interface GatewaySuccessCheck { + status: "success"; + value?: number; +} + +export interface GatewayErrorCheck { + status: "error"; + error: string; +} + +export type GatewayCheck = GatewayUnknownCheck | GatewayPendingCheck | GatewaySuccessCheck | GatewayErrorCheck; + +export interface GatewayAddressRegistryItem extends GatewayAddressRegistryItemData { + id: string; + linkFull: string; + linkDisplay: string; + ping: GatewayCheck; + health: GatewayCheck; + properties?: string; +} diff --git a/libs/core/src/lib/gateways/utils.ts b/libs/core/src/lib/gateways/utils.ts new file mode 100644 index 000000000..844dc020f --- /dev/null +++ b/libs/core/src/lib/gateways/utils.ts @@ -0,0 +1,59 @@ +import { defaultGateway, type Gateway } from "./gateway"; +import { findGateway } from "./wayfinder"; + +/** + * Get the full gateway URL string, from the + * provided gateway config. + * This should be used for the UX, with the + * redux gateway config reducer. + */ + +export const concatGatewayURL = (gatewayConfig: Gateway) => + `${gatewayConfig.protocol}://${gatewayConfig.host}:${gatewayConfig.port}`; +/** + * Convert URL string to a Gateway config object + * + * @param url URL to convert + */ + +export function urlToGateway(url: string): Gateway { + const gatewayURL = new URL(url); + + return { + host: gatewayURL.hostname, + port: gatewayURL.port === "" ? 443 : Number(gatewayURL.port), + protocol: gatewayURL.protocol?.replace(":", "") || "http", + }; +} +/** + * Compare two gateway objects + * + * @returns Same or not + */ + +export function compareGateways(first: Partial, second: Partial) { + // compare the count of keys each object has + if (Object.keys(first).length !== Object.keys(second).length) { + return false; + } + + // deep equal object keys + for (const key in first) { + if (first[key] !== second[key]) { + return false; + } + } + + return true; +} + +export const getArweaveLink = async (txId: string) => { + let gateway: Gateway; + try { + gateway = await findGateway({}); + } catch { + gateway = defaultGateway; + } + const url = concatGatewayURL(gateway); + return `${url}/${txId}`; +}; diff --git a/libs/core/src/lib/gateways/wayfinder.ts b/libs/core/src/lib/gateways/wayfinder.ts new file mode 100644 index 000000000..cec78acb6 --- /dev/null +++ b/libs/core/src/lib/gateways/wayfinder.ts @@ -0,0 +1,207 @@ +import { clGateway, defaultGateway, defaultGateways, goldskyGateway, suggestedGateways, type Gateway } from "./gateway"; +import { useEffect, useState } from "react"; +import { getGatewayCache } from "./cache"; +import Arweave from "arweave"; +import { sortGatewaysByOperatorStake } from "../utils/wayfinder/wayfinder"; +import { useAsyncEffect } from "../utils/react/useAsyncEffect"; +import { getSetting } from "../utils/settings"; + +export const FULL_HISTORY: Requirements = { startBlock: 0 }; + +export const STAKED_GQL_FULL_HISTORY: Requirements = { + startBlock: 0, + graphql: true, + ensureStake: true, +}; + +export async function findGateway(requirements: Requirements): Promise { + try { + const gateway = await getSetting("gateways").getValue(); + + // arns or all the chain is needed + if (requirements.arns || requirements.startBlock === 0) { + return defaultGateway; + } else if (requirements.random) { + // This should have been loaded into the cache by handleGatewayUpdateAlarm + const gateways = (await getGatewayCache()) || []; + + if (gateways.length === 0) { + const randomIndex = Math.floor(Math.random() * defaultGateways.length); + return defaultGateways[randomIndex]; + } + + // this could probably be filtered out during the caching process + const filteredGateways = gateways.filter((gateway) => { + return gateway.ping.status === "success" && gateway.health.status === "success"; + }); + const sortedGateways = sortGatewaysByOperatorStake(filteredGateways); + const otherHosts = [gateway.host, defaultGateway.host, clGateway.host, "aoweave.tech", "defi.ao"]; + + const additionalGateways = otherHosts + .filter((host) => !sortedGateways.some((g) => g.settings.fqdn === host)) + .map((host) => ({ + settings: { port: 443, protocol: "https", fqdn: host }, + })); + + const allGateways = [...sortedGateways, ...additionalGateways]; + const randomIndex = Math.floor(Math.random() * sortedGateways.length); + const selectedGateway = allGateways[randomIndex]; + + return { + host: selectedGateway.settings.fqdn, + port: selectedGateway.settings.port, + protocol: selectedGateway.settings.protocol, + }; + } + + return gateway; + } catch (err) { + console.log("err", err); + } + + return defaultGateway; +} + +/** + * Gateway hook that uses wayfinder to select the active gateway. + */ +export function useGateway(requirements: Requirements) { + // currently active gw + const [activeGateway, setActiveGateway] = useState(defaultGateway); + + useAsyncEffect(async () => { + try { + // select recommended gateway using wayfinder + const recommended = await findGateway(requirements); + + setActiveGateway(recommended); + } catch {} + }, [requirements.graphql, requirements.arns, requirements.startBlock, requirements.ensureStake]); + + return activeGateway; +} + +export async function findGraphqlGateways(count?: number) { + try { + const gateways = await getGatewayCache(); + + if (!gateways?.length) { + return suggestedGateways; + } + + const filteredGateways = gateways.filter( + ({ ping, health }) => ping.status === "success" && health.status === "success", + ); + + if (!filteredGateways.length) { + return suggestedGateways; + } + + return sortGatewaysByOperatorStake(filteredGateways) + .filter((gateway: any) => gateway?.properties?.GRAPHQL) + .slice(0, count || filteredGateways.length) + .map(({ settings: { fqdn, port, protocol } }) => ({ + host: fqdn, + port, + protocol, + })); + } catch { + return suggestedGateways; + } +} + +export function useGraphqlGateways(count?: number) { + const [graphqlGateways, setGraphqlGateways] = useState([]); + + useEffect(() => { + const fetchGateways = async () => { + try { + const gateways = await findGraphqlGateways(count); + const hasDefaultGateway = gateways.some((g) => g.host === defaultGateway.host); + const hasGoldskyGateway = gateways.some((g) => g.host === goldskyGateway.host); + + const finalGateways = [...gateways]; + + if (!hasDefaultGateway) { + finalGateways.unshift(defaultGateway); + } + + if (!hasGoldskyGateway) { + const insertionIndex = Math.min(2, finalGateways.length); + finalGateways.splice(insertionIndex, 0, goldskyGateway); + } + + setGraphqlGateways(finalGateways.slice(0, count || finalGateways.length)); + } catch { + setGraphqlGateways(suggestedGateways); + } + }; + + fetchGateways(); + }, [count]); + + return graphqlGateways; +} + +/** + * Retries an Arweave operation with different gateways + * @param operation - Function that takes an Arweave instance and returns a Promise + * @param options - Retry configuration + */ +export async function retryWithGateways( + operation: (arweave: Arweave) => Promise, + { maxAttempts = 3 }: RetryOptions = {}, +): Promise> { + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + const gateway = await findGateway({ random: attempt > 1 }); + + try { + const arweave = new Arweave(gateway); + const result = await operation(arweave); + return { result, arweave, gateway }; + } catch (error) { + lastError = error as Error; + // console.warn(`Gateway attempt ${attempt} failed:`, error); + if (attempt === maxAttempts) { + throw new Error(`Gateway operations failed after ${maxAttempts} attempts. Last error: ${lastError.message}`); + } + } + } + + throw lastError; +} + +type RetryOptions = { + maxAttempts?: number; +}; + +type RetryResult = { + result: T; + gateway: Gateway; + arweave: Arweave; +}; + +export interface Requirements { + /* Whether the gateway should be selected randomly */ + random?: boolean; + /* Whether the gateway should support GraphQL requests */ + graphql?: boolean; + /* Should the gateway support ArNS */ + arns?: boolean; + /** + * The block where the gateway should start syncing data from. + * Set for 0 to include all blocks. + * If undefined, wayfinder will not ensure that the start block + * is 0. + */ + startBlock?: number; + /** + * Ensure that the gateway has a high stake. This is required + * with data that is important to be accurate. If true, wayfinder + * will make sure that the gateway stake is higher than the + * average stake of ar.io nodes. + */ + ensureStake?: boolean; +} diff --git a/libs/core/src/lib/injected-api/background/background-modules.ts b/libs/core/src/lib/injected-api/background/background-modules.ts new file mode 100644 index 000000000..3c13c72ab --- /dev/null +++ b/libs/core/src/lib/injected-api/background/background-modules.ts @@ -0,0 +1,98 @@ +import type { Module } from "../module"; + +// import modules +import permissionsModule from "../modules/permissions"; +import permissions from "../modules/permissions/permissions.background"; +import activeAddressModule from "../modules/active_address"; +import activeAddress from "../modules/active_address/active_address.background"; +import allAddressesModule from "../modules/all_addresses"; +import allAddresses from "../modules/all_addresses/all_addresses.background"; +import publicKeyModule from "../modules/public_key"; +import publicKey from "../modules/public_key/public_key.background"; +import walletNamesModule from "../modules/wallet_names"; +import walletNames from "../modules/wallet_names/wallet_names.background"; +import arweaveConfigModule from "../modules/arweave_config"; +import arweaveConfig from "../modules/arweave_config/arweave_config.background"; +import disconnectModule from "../modules/disconnect"; +import disconnect from "../modules/disconnect/disconnect.background"; +import connectModule from "../modules/connect"; +import connect from "../modules/connect/connect.background"; +import signModule from "../modules/sign"; +import sign from "../modules/sign/sign.background"; +import dispatchModule from "../modules/dispatch"; +import dispatch from "../modules/dispatch/dispatch.background"; +import encryptModule from "../modules/encrypt"; +import encrypt from "../modules/encrypt/encrypt.background"; +import decryptModule from "../modules/decrypt"; +import decrypt from "../modules/decrypt/decrypt.background"; +import signatureModule from "../modules/signature"; +import signature from "../modules/signature/signature.background"; +import addTokenModule from "../modules/add_token"; +import addToken from "../modules/add_token/add_token.background"; +import isTokenAddedModule from "../modules/is_token_added"; +import isTokenAdded from "../modules/is_token_added/is_token_added.background"; +import signMessageModule from "../modules/sign_message"; +import signMessage from "../modules/sign_message/sign_message.background"; +import privateHashModule from "../modules/private_hash"; +import privateHash from "../modules/private_hash/private_hash.background"; +import verifyMessageModule from "../modules/verify_message"; +import verifyMessage from "../modules/verify_message/verify_message.background"; +import signDataItemModule from "../modules/sign_data_item"; +import signDataItem from "../modules/sign_data_item/sign_data_item.background"; +import batchSignDataItemModule from "../modules/batch_sign_data_item"; +import batchSignDataItem from "../modules/batch_sign_data_item/batch_sign_data_item.background"; +import subscriptionModule from "../modules/subscription"; +import subscription from "../modules/subscription/subscription.background"; +import userTokensModule from "../modules/user_tokens"; +import userTokens from "../modules/user_tokens/user_tokens.background"; +import tokenBalanceModule from "../modules/token_balance"; +import tokenBalance from "../modules/token_balance/token_balance.background"; +import wanderTierInfoModule from "../modules/wander_tier_info"; +import wanderTierInfo from "../modules/wander_tier_info/wander_tier_info.background"; + +export interface ModuleAppData { + tabID: number; + url: string; + favicon?: string; +} + +/** + * Extended module function + */ +export type BackgroundModuleFunction = ( + appData: ModuleAppData, + ...params: any[] +) => Promise | ResultType; + +/** Extended module interface */ +export interface BackgroundModule extends Module { + function: BackgroundModuleFunction; +} + +/** Background modules */ +export const backgroundModules: BackgroundModule[] = [ + { ...permissionsModule, function: permissions }, + { ...activeAddressModule, function: activeAddress }, + { ...allAddressesModule, function: allAddresses }, + { ...publicKeyModule, function: publicKey }, + { ...walletNamesModule, function: walletNames }, + { ...arweaveConfigModule, function: arweaveConfig }, + { ...disconnectModule, function: disconnect }, + { ...connectModule, function: connect }, + { ...addTokenModule, function: addToken }, + { ...isTokenAddedModule, function: isTokenAdded }, + { ...signModule, function: sign }, + { ...dispatchModule, function: dispatch }, + { ...encryptModule, function: encrypt }, + { ...decryptModule, function: decrypt }, + { ...signatureModule, function: signature }, + { ...signMessageModule, function: signMessage }, + { ...privateHashModule, function: privateHash }, + { ...verifyMessageModule, function: verifyMessage }, + { ...signDataItemModule, function: signDataItem }, + { ...subscriptionModule, function: subscription }, + { ...userTokensModule, function: userTokens }, + { ...tokenBalanceModule, function: tokenBalance }, + { ...batchSignDataItemModule, function: batchSignDataItem }, + { ...wanderTierInfoModule, function: wanderTierInfo }, +]; diff --git a/libs/core/src/lib/injected-api/background/background-setup.ts b/libs/core/src/lib/injected-api/background/background-setup.ts new file mode 100644 index 000000000..42d0bd89f --- /dev/null +++ b/libs/core/src/lib/injected-api/background/background-setup.ts @@ -0,0 +1,177 @@ +import browser from "webextension-polyfill"; +import { handleApiCallMessage } from "./handlers/message/api-call-message/api-call-message.handler"; +import { handleChunkMessage } from "./handlers/message/chunk-message/chunk-message.handler"; +import { handleInstall } from "./handlers/browser/install/install.handler"; +import { handleProtocol } from "./handlers/browser/protocol/protocol.handler"; +import { handleActiveAddressChange } from "./handlers/storage/active-address-change/active-address-change.handler"; +import { handleWalletsChange } from "./handlers/storage/wallet-change/wallet-change.handler"; +import { handleAppsChange } from "./handlers/storage/apps-change/app-change.handler"; +import { handleAppConfigChange } from "./handlers/storage/app-config-change/app-config-change.handler"; +import { handleTrackBalanceAlarm } from "./handlers/alarms/track-balance/track-balance-alarm.handler"; +import { handleGetPrinters } from "./handlers/browser/printer/get-printers/get-printers.handler"; +import { handleGetCapabilities } from "./handlers/browser/printer/get-capabilities/get-capabilities.handler"; +import { handlePrint } from "./handlers/browser/printer/print/print.handler"; +import { handleNotificationsAlarm } from "./handlers/alarms/notifications/notifications-alarm.handler"; +import { handleSubscriptionsAlarm } from "./handlers/alarms/subscriptions/subscriptions-alarm.handler"; +import { handleAoTokenCacheAlarm } from "./handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler"; +import { handleGatewayUpdateAlarm } from "./handlers/alarms/gateway-update/gateway-update-alarm.handler"; +import { handleSyncLabelsAlarm } from "./handlers/alarms/sync-labels/sync-labels-alarm.handler"; +import { handleWindowClose } from "./handlers/browser/window-close/window-close.handler"; +import { handleKeyRemovalAlarm } from "./handlers/alarms/key-removal/key-removal-alarm.handler"; +import { + handleAoTokensImportAlarm, + handleFairLaunchTokensImportAlarm, +} from "./handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler"; +import { + handleAOYieldAgentAlarm, + handleAOYieldAgentRecentTxsCheck, + handleAOYieldAgentSync, +} from "./handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.js"; +import { handleTransakPurchaseAlarm } from "./handlers/alarms/transak-purchase/transak-purchase-alarm.handler.js"; +import { handleTabClosed, handleTabUpdate } from "./handlers/browser/tabs/tabs.handler"; +import { isomorphicOnMessage } from "@wanderapp/isomorphic-messaging"; +import { handleAuthStateChange } from "./handlers/storage/auth-state-change/auth-state-change.handler.js"; +import { handleRefreshWalletLifetimeSavingsAlarm } from "./handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.js"; +import { ExtensionStorage, PersistentStorage } from "../../utils/storage/storage"; +import { log, LOG_GROUP } from "../../utils/log/log.utils"; +import { initInactivityTracking } from "../../utils/inactivity/inactivity.utils"; + +export function setupBackgroundService() { + log( + LOG_GROUP.SETUP, + `background-setup.ts > setupBackgroundService(VITE_IS_EMBEDDED_APP = "${import.meta.env?.VITE_IS_EMBEDDED_APP}")`, + ); + + // MESSAGES: + // Watch for API call and chunk messages: + isomorphicOnMessage("api_call", handleApiCallMessage); + isomorphicOnMessage("chunk", handleChunkMessage); + + /* + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") { + window.addEventListener("message", (event: MessageEvent) => { + if ( + !event.data || + event.data.app !== "wanderEmbedded" || + event.origin !== getEmbeddedAncestorOrigin() + ) + return; + + console.log("MESSAGE FROM PARENT =", event.data); + + handleApiCallMessage({ + id: "", + timestamp: Date.now(), + data: event.data, + sender: { + tabId: 0, + context: "content-script" + } + }); + + // Example: check if the message is from our SDK + + // if (event.data.type === "FROM_SDK") { + // const incomingMsg = event.data.payload; + // console.log( + // "Iframe received message from WanderEmbedded:", + // incomingMsg + // ); + + // // Respond back + // event.source?.postMessage({ + // type: "FROM_IFRAME", + // payload: `Got your message: ${incomingMsg}` + // }); + // } + }); + } + */ + + // LIFECYCLE: + + // Open welcome page on extension install. + browser.runtime.onInstalled.addListener(handleInstall); + + // ALARMS: + browser.alarms.onAlarm.addListener(handleNotificationsAlarm); + browser.alarms.onAlarm.addListener(handleSubscriptionsAlarm); + browser.alarms.onAlarm.addListener(handleTrackBalanceAlarm); + browser.alarms.onAlarm.addListener(handleGatewayUpdateAlarm); + browser.alarms.onAlarm.addListener(handleSyncLabelsAlarm); + browser.alarms.onAlarm.addListener(handleKeyRemovalAlarm); + browser.alarms.onAlarm.addListener(handleAoTokenCacheAlarm); + browser.alarms.onAlarm.addListener(handleAoTokensImportAlarm); + browser.alarms.onAlarm.addListener(handleFairLaunchTokensImportAlarm); + + // handle keep alive alarm + browser.alarms.onAlarm.addListener((alarm) => { + if (alarm.name === "keep-alive") { + console.log("keep alive alarm"); + } + }); + + // handle fee alarm (send fees asynchronously) + // browser.alarms.onAlarm.addListener(handleFeeAlarm); + + // STORAGE: + + // watch for active address changes / app + // list changes + // and send them to the content script to + // fire the wallet switch event + ExtensionStorage.watch({ + active_address: handleActiveAddressChange, + wallets: handleWalletsChange, + decryption_key: handleAuthStateChange, + }); + PersistentStorage.watch({ apps: handleAppsChange }); + + // listen for app config updates + // `ExtensionStorage.watch` requires a callbackMap param, so this cannot be done using `ExtensionStorage` directly. + browser.storage.onChanged.addListener(handleAppConfigChange); + + if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") return; + + // ONLY BROWSER EXTENSION BELOW THIS LINE: + + // ALARMS: + browser.alarms.onAlarm.addListener(handleAOYieldAgentAlarm); + browser.alarms.onAlarm.addListener(handleAOYieldAgentRecentTxsCheck); + browser.alarms.onAlarm.addListener(handleAOYieldAgentSync); + browser.alarms.onAlarm.addListener(handleRefreshWalletLifetimeSavingsAlarm); + browser.alarms.onAlarm.addListener(handleTransakPurchaseAlarm); + + // When the last window connected to the extension is closed, the decryption key will be removed from memory. This is no needed in the embedded wallet because + // each wallet instance will be removed automatically when its tab/window is closed. + browser.windows.onRemoved.addListener(handleWindowClose); + + // handle tab change (icon, context menus) + browser.tabs.onUpdated.addListener((tabId, changeInfo) => { + handleTabUpdate(tabId, changeInfo); + }); + browser.tabs.onActivated.addListener(({ tabId }) => { + handleTabUpdate(tabId); + }); + browser.tabs.onRemoved.addListener(handleTabClosed); + + // handle ar:// protocol + browser.webNavigation.onBeforeNavigate.addListener(handleProtocol); + + // Initialize inactivity tracking + initInactivityTracking(); + + // print to the permaweb (only on chrome) + // TODO: uncomment this once we have a proper solution + // if (typeof chrome !== "undefined") { + // chrome.printerProvider.onGetCapabilityRequested.addListener( + // handleGetCapabilities + // ); + + // chrome.printerProvider.onGetPrintersRequested.addListener( + // handleGetPrinters + // ); + + // chrome.printerProvider.onPrintRequested.addListener(handlePrint); + // } +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts new file mode 100644 index 000000000..a4994a935 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts @@ -0,0 +1,51 @@ +import { dryrun } from "@permaweb/aoconnect"; +import type { Alarms } from "webextension-polyfill"; +import { PersistentStorage } from "../../../../../utils/storage/storage"; +import { getTokenInfoFromData, TokenInfo } from "../../../../../tokens/aoTokens/ao"; +import { timeoutPromise } from "../../../../../utils/promises/timeout"; +import { AR_PROCESS_ID } from "../../../../../tokens/aoTokens/ao.constants"; + +/** + * Alarm handler for syncing ao tokens + */ +export const handleAoTokenCacheAlarm = async (alarmInfo?: Alarms.Alarm) => { + if (!alarmInfo?.name.startsWith("update_ao_tokens")) return; + + const aoTokens = (await PersistentStorage.get("ao_tokens")) || []; + + const updatedTokens = [...aoTokens]; + + for (const token of aoTokens) { + if (token.processId === AR_PROCESS_ID) continue; + try { + const res = await timeoutPromise( + dryrun({ + Id, + Owner, + process: token.processId, + tags: [{ name: "Action", value: "Info" }], + }), + 6000, + ); + + if (res.Messages && Array.isArray(res.Messages)) { + const tokenInfo = getTokenInfoFromData(res, token.processId); + const updatedToken = { + ...tokenInfo, + lastUpdated: new Date().toISOString(), + }; + + if (updatedToken) { + const index = updatedTokens.findIndex((t) => t.processId === token.processId); + + if (index !== -1) { + updatedTokens[index] = { ...updatedTokens[index], ...updatedToken }; + } + } + } + } catch (err) { + console.error(`Failed to update token with id ${token.processId}:`, err); + } + } + await PersistentStorage.set("ao_tokens", updatedTokens); +}; diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts new file mode 100644 index 000000000..7c90c9df3 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts @@ -0,0 +1,116 @@ +import Arweave from "arweave"; +import type { Alarms } from "webextension-polyfill"; +import { getAoTokens, getAoTokensAutoImportRestrictedIds, getAoTokensCache } from "../../../../../tokens"; +import { PersistentStorage } from "../../../../../utils/storage/storage"; +import { AO_TOKENS, AO_TOKENS_AUTO_IMPORT_RESTRICTED_IDS, AO_TOKENS_IMPORT_TIMESTAMP, AO_TOKENS_LAST_BLOCK_HEIGHT, gateway, getNoticeTransactions, tokenStorageMutex, verifyCollectiblesType } from "../../../../../tokens/aoTokens/sync"; +import { withRetry } from "../../../../../utils/promises/retry"; +import { getTokenInfo } from "../../../../../tokens/aoTokens/ao"; +import { timeoutPromise } from "../../../../../utils/promises/timeout"; +import { getActiveAddress } from "../../../../../wallets/wallets.utils"; +import { checkAndImportFairLaunchTokens } from "../../../../../utils/fair-launch/fair-launch.alarms"; +import { FAIR_LAUNCH_TOKENS_ALARM_NAME } from "../../../../../utils/fair-launch/fair-launch.constants"; + +/** + * Import AO Tokens + */ +export async function handleAoTokensImportAlarm(alarm: Alarms.Alarm) { + if (alarm?.name !== "import_ao_tokens") return; + + try { + const activeAddress = await getActiveAddress(); + + let [aoTokens, aoTokensCache, removedTokenIds = [], lastBlockHeight] = await Promise.all([ + getAoTokens(), + getAoTokensCache(), + getAoTokensAutoImportRestrictedIds(), + PersistentStorage.get(`${AO_TOKENS_LAST_BLOCK_HEIGHT}_${activeAddress}`), + ]); + + let aoTokensIds = new Set(aoTokens.map(({ processId }) => processId)); + const aoTokensCacheIds = new Set(aoTokensCache.map(({ processId }) => processId)); + let tokenIdstoExclude = new Set([...aoTokensIds, ...removedTokenIds]); + const walletTokenIds = new Set([...tokenIdstoExclude, ...aoTokensCacheIds]); + + const arweave = new Arweave(gateway); + const { processIds, maxBlockHeight } = await getNoticeTransactions( + arweave, + activeAddress, + Array.from(walletTokenIds), + 5, + lastBlockHeight, + ); + + if (maxBlockHeight && maxBlockHeight > 0) { + await PersistentStorage.set(`${AO_TOKENS_LAST_BLOCK_HEIGHT}_${activeAddress}`, maxBlockHeight); + } + + const newProcessIds = Array.from(new Set([...processIds, ...aoTokensCacheIds])).filter( + (processId) => !tokenIdstoExclude.has(processId), + ); + + if (newProcessIds.length === 0) { + console.log("No new ao tokens found!"); + return; + } + + const promises = newProcessIds + .filter((processId) => !aoTokensCacheIds.has(processId)) + .map((processId) => + withRetry(async () => { + const token = await timeoutPromise(getTokenInfo(processId), 3000); + return { ...token, processId }; + }, 2), + ); + const results = await Promise.allSettled(promises); + + const tokens = []; + const tokensToRestrict = []; + results.forEach((result) => { + if (result.status === "fulfilled") { + const token = result.value; + if (token.Ticker) { + tokens.push(token); + } else if (!removedTokenIds.includes(token.processId)) { + tokensToRestrict.push(token); + } + } + }); + + const updatedTokens = [...aoTokensCache, ...tokens]; + + const unlock = await tokenStorageMutex.lock(); + try { + aoTokens = await getAoTokens(); + aoTokensIds = new Set(aoTokens.map(({ processId }) => processId)); + tokenIdstoExclude = new Set([...aoTokensIds, ...removedTokenIds]); + + if (tokensToRestrict.length > 0) { + removedTokenIds.push(...tokensToRestrict.map(({ processId }) => processId)); + await PersistentStorage.set(AO_TOKENS_AUTO_IMPORT_RESTRICTED_IDS, removedTokenIds); + } + + let newTokens = updatedTokens.filter((token) => !tokenIdstoExclude.has(token.processId)); + if (newTokens.length === 0) return; + + // Verify collectibles type + newTokens = await verifyCollectiblesType(newTokens, arweave); + + newTokens.forEach((token) => aoTokens.push(token)); + await PersistentStorage.set(AO_TOKENS, aoTokens); + + console.log("Imported ao tokens!"); + } finally { + unlock(); + } + } catch (error: any) { + console.log("Error importing tokens: ", error?.message); + } finally { + await PersistentStorage.set(AO_TOKENS_IMPORT_TIMESTAMP, 0); + } +} + +export async function handleFairLaunchTokensImportAlarm(alarm: Alarms.Alarm) { + if (alarm?.name !== FAIR_LAUNCH_TOKENS_ALARM_NAME) return; + + await checkAndImportFairLaunchTokens(); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.ts new file mode 100644 index 000000000..4f6c4bb8a --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/ao-yield-agent/ao-yield-agent-alarm.handler.ts @@ -0,0 +1,38 @@ +import type { Alarms } from "webextension-polyfill"; +import { AO_YIELD_AGENT_ALARM_NAME, AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME, AO_YIELD_AGENT_SYNC_ALARM_NAME_PREFIX } from "../../../../../agents/constants"; +import { executeAutomaticSwapIfNeeded, checkIfRecentTxSwapSucceeded } from "../../../../../agents/swap"; +import { checkAndSyncAgents } from "../../../../../agents/sync"; + + +/** + * Alarm handler for executing automatic token swaps via AO yield agent. + * Checks if any pending swaps need to be executed based on configured thresholds + * and executes them if conditions are met. + */ + +export async function handleAOYieldAgentAlarm(alarmInfo?: Alarms.Alarm) { + if (alarmInfo?.name !== AO_YIELD_AGENT_ALARM_NAME) return; + + await executeAutomaticSwapIfNeeded(); +} + +/** + * Alarm handler for checking if recent txs have succeeded. + * Checks if any recent txs have succeeded and updates the recent txs list. + */ +export async function handleAOYieldAgentRecentTxsCheck(alarmInfo?: Alarms.Alarm) { + if (alarmInfo?.name !== AO_YIELD_AGENT_RECENT_TXS_CHECK_ALARM_NAME) return; + + await checkIfRecentTxSwapSucceeded(); +} + +/** + * Alarm handler for syncing agents. + * Checks if any agents need to be synced and syncs them if conditions are met. + */ +export async function handleAOYieldAgentSync(alarmInfo?: Alarms.Alarm) { + if (!alarmInfo?.name.startsWith(AO_YIELD_AGENT_SYNC_ALARM_NAME_PREFIX)) return; + + const address = alarmInfo.name.replace(AO_YIELD_AGENT_SYNC_ALARM_NAME_PREFIX, ""); + await checkAndSyncAgents(address); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts new file mode 100644 index 000000000..4b37444c0 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts @@ -0,0 +1,38 @@ +import { type Alarms } from "webextension-polyfill"; +import { ARIO } from "@ar.io/sdk/web"; +import { GatewayAddressRegistryItem } from "../../../../../gateways/types"; +import { RETRY_ALARM, scheduleGatewayUpdate, UPDATE_ALARM, updateGatewayCache } from "../../../../../gateways/cache"; +import { extractGarItems, pingUpdater } from "../../../../../utils/wayfinder/wayfinder"; + +// TODO: Update paths so that libs don't suggest importing from themselves... + +/** + * Gateway cache update call. Usually called by an alarm, + * but will also be executed on install. + */ +export async function handleGatewayUpdateAlarm(alarm?: Alarms.Alarm) { + if (![UPDATE_ALARM, RETRY_ALARM].includes(alarm?.name)) return; + + let garItemsWithChecks: GatewayAddressRegistryItem[] = []; + + try { + // fetch cache + const ario = ARIO.mainnet(); + + const gatewaysResult = await ario.getGateways({ sortBy: "operatorStake", sortOrder: "desc", limit: 5 }); + + const garItems = extractGarItems(gatewaysResult.items); + + // healtcheck + await pingUpdater(garItems, (nextGarItemsWithChecks) => { + garItemsWithChecks = nextGarItemsWithChecks; + }); + + await updateGatewayCache(garItemsWithChecks); + } catch (err) { + console.log("err in handle", err); + + // schedule to try again + await scheduleGatewayUpdate(true); + } +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts new file mode 100644 index 000000000..1c40fc5cb --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts @@ -0,0 +1,16 @@ +import type { Alarms } from "webextension-polyfill"; +import { getDecryptionKey, removeDecryptionKey } from "../../../../../wallets/auth"; + +/** + * Listener for the key removal alarm + */ +export async function handleKeyRemovalAlarm(alarm: Alarms.Alarm) { + if (alarm?.name !== "remove_decryption_key_scheduled") return; + + // check if there is a decryption key + const decryptionKey = await getDecryptionKey(); + if (!decryptionKey) return; + + // remove the decryption key + await removeDecryptionKey(); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/notifications/notifications-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/notifications/notifications-alarm.handler.ts new file mode 100644 index 000000000..8e8673086 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/notifications/notifications-alarm.handler.ts @@ -0,0 +1,108 @@ +import browser, { type Alarms } from "webextension-polyfill"; + +import iconUrl from "url:/assets/icon512.png"; +import { ExtensionStorage } from "../../../../../utils/storage/storage"; +import { getActiveAddress } from "../../../../../wallets/wallets.utils"; +import { arNotificationsHandler } from "./notifications-alarm.utils"; +import { ALL_AR_RECEIVER_QUERY, ALL_AR_SENT_QUERY, AO_LIQUIDOPS_RECEIVER_QUERY, AO_RECEIVER_QUERY, AO_SENT_QUERY, AR_RECEIVER_QUERY, AR_SENT_QUERY } from "../../../../../notifications/utils"; + +export async function handleNotificationsAlarm(alarm?: Alarms.Alarm) { + if (!alarm?.name.startsWith("notifications")) return; + + const notificationSetting: boolean = await ExtensionStorage.get("setting_notifications"); + + let aoNotificationSetting: string[] | undefined = await ExtensionStorage.get("setting_notifications_customize"); + + if (!aoNotificationSetting) { + await ExtensionStorage.set("setting_notifications_customize", ["default"]); + aoNotificationSetting = ["default"]; + } + + const address = await getActiveAddress(); + + try { + const storedNotifications = await ExtensionStorage.get(`notifications_${address}`); + + const parsedNotifications = storedNotifications ? JSON.parse(storedNotifications) : null; + + const aoBlockHeight = parsedNotifications?.aoNotifications?.lastStoredBlockHeight ?? 0; + + const arBalanceBlockHeight = parsedNotifications?.arBalanceNotifications?.lastStoredBlockHeight ?? 0; + + const [arNotifications, newArMaxHeight, newArTransactions] = await arNotificationsHandler( + address, + arBalanceBlockHeight, + notificationSetting, + [ + { + query: !aoNotificationSetting.includes("allTxns") ? AR_RECEIVER_QUERY : ALL_AR_RECEIVER_QUERY, + variables: { address }, + isAllTxns: aoNotificationSetting.includes("allTxns"), + }, + { + query: !aoNotificationSetting.includes("allTxns") ? AR_SENT_QUERY : ALL_AR_SENT_QUERY, + variables: { address }, + isAllTxns: aoNotificationSetting.includes("allTxns"), + }, + ], + ); + + let aoNotifications = []; + let newAoMaxHeight = 0; + let newAoTransactions = []; + if (aoNotificationSetting.includes("default")) { + [aoNotifications, newAoMaxHeight, newAoTransactions] = await arNotificationsHandler( + address, + aoBlockHeight, + notificationSetting, + + [ + { + query: AO_RECEIVER_QUERY, + variables: { address }, + }, + { + query: AO_SENT_QUERY, + variables: { address }, + }, + { + query: AO_LIQUIDOPS_RECEIVER_QUERY, + variables: { address }, + }, + ], + ); + } + const newTransactions = [...newAoTransactions, ...newArTransactions]; + if (newTransactions.length > 0) { + const notificationId = await browser.notifications.create({ + type: "basic", + iconUrl, + title: newTransactions.length === 1 ? "New Transaction" : "New Transactions", + message: `You have ${newTransactions.length} new transaction${newTransactions.length === 1 ? "" : "s"}.`, + }); + + // Listen for clicks on the notification + browser.notifications.onClicked.addListener((clickedNotificationId) => { + if (clickedNotificationId === notificationId) { + browser.tabs.create({ url: browser.runtime.getURL(`tabs/fullscreen.html?tab=feed`) }); + } + }); + } + + await ExtensionStorage.set( + `notifications_${address}`, + JSON.stringify({ + arBalanceNotifications: { + arNotifications, + lastStoredBlockHeight: newArMaxHeight, + }, + aoNotifications: { + aoNotifications, + lastStoredBlockHeight: newAoMaxHeight, + }, + }), + ); + } catch (err) { + console.error("Error updating notifications:", err); + } +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/notifications/notifications-alarm.utils.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/notifications/notifications-alarm.utils.ts new file mode 100644 index 000000000..2974addae --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/notifications/notifications-alarm.utils.ts @@ -0,0 +1,88 @@ +import BigNumber from "bignumber.js"; +import { combineAndSortTransactions, processTransactions } from "../../../../../notifications/utils"; +import { checkTransferStatus } from "../../../../../transactions/transactions"; +import { ExtensionStorage } from "../../../../../utils/storage/storage"; + +export type RawTransaction = { + node: { + id: string; + recipient: string; + owner: { + address: string; + }; + quantity: { + ar: string; + }; + block: { + timestamp: number; + height: number; + }; + tags: Array<{ + name: string; + value: string; + }>; + }; +}; + +export type Transaction = RawTransaction & { + transactionType: string; + quantity: string; + isAo?: boolean; + tokenId?: string; + warpContract?: boolean; +}; + +type ArNotificationsHandlerReturnType = [Transaction[], number, any[]]; + +export async function arNotificationsHandler( + address: string, + lastStoredHeight: number, + notificationSetting: boolean, + queriesConfig: { + query: string; + variables: Record; + isAllTxns?: boolean; + }[], +): Promise { + try { + let transactionDiff = []; + + const queries = queriesConfig.map((config) => gql(config.query, config.variables, suggestedGateways[1])); + let responses = await Promise.all(queries); + responses = responses.map((response, index) => { + if (typeof queriesConfig[index].isAllTxns === "boolean" && !queriesConfig[index].isAllTxns) { + response.data.transactions.edges = response.data.transactions.edges.filter((edge) => + BigNumber(edge.node.quantity.ar).gt(0), + ); + } + return response; + }); + + const combinedTransactions = combineAndSortTransactions(responses); + + await checkTransferStatus(combinedTransactions); + + const enrichedTransactions = processTransactions(combinedTransactions, address); + + const newMaxHeight = Math.max( + ...enrichedTransactions + .filter((tx) => tx.node.block) // Filter out transactions without a block + .map((tx) => tx.node.block.height), + ); + // filters out transactions that are older than last stored height, + if (newMaxHeight !== lastStoredHeight) { + const newTransactions = enrichedTransactions.filter( + (transaction) => transaction.node.block && transaction.node.block.height > lastStoredHeight, + ); + + // if it's the first time loading notifications, don't send a message && notifications are enabled + if (lastStoredHeight !== 0 && notificationSetting) { + await ExtensionStorage.set("new_notifications", true); + transactionDiff = newTransactions; + } + } + return [enrichedTransactions, newMaxHeight, transactionDiff]; + } catch (err) { + console.log("err", err); + } +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts new file mode 100644 index 000000000..40eecd758 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts @@ -0,0 +1,31 @@ +import type { Alarms } from "webextension-polyfill"; +import { getActiveAddress } from "../../../../../wallets/wallets.utils"; +import { addSubscription, getSubscriptionData } from "../../../../../subscriptions"; +import { SubscriptionData } from "../../../../../subscriptions/subscription"; +import { handleSubscriptionPayment } from "../../../../../subscriptions/payments"; + +/** + * + fetch subscription auto withdrawal allowance + * + process dates of subscriptions + * + map through subsciptions + * + activate payments under withdrawal allowance limit + * + notify user of manual payments + */ + +export async function handleSubscriptionsAlarm(alarmInfo?: Alarms.Alarm) { + if (!alarmInfo?.name.startsWith("subscription-alarm-")) return; + + const prefixLength = "subscription-alarm-".length; + const subAddress = alarmInfo.name.substring(prefixLength); + + const activeAddress = await getActiveAddress(); + + const subscriptionData: SubscriptionData[] = await getSubscriptionData(activeAddress); + + const matchingSubscription = subscriptionData.find((sub) => sub.arweaveAccountAddress === subAddress); + + if (matchingSubscription) { + const updated = await handleSubscriptionPayment(matchingSubscription); + await addSubscription(activeAddress, updated); + } +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts new file mode 100644 index 000000000..aa33899ac --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts @@ -0,0 +1,34 @@ +import type { Alarms } from "webextension-polyfill"; +import { getWallets } from "../../../../../wallets/wallets.utils"; +import { getNameServiceProfiles } from "../../../../../nameservice/nameservice"; +import { ExtensionStorage } from "../../../../../utils/storage/storage"; + +/** + * Sync nicknames with ANS labels + */ +export async function handleSyncLabelsAlarm(alarm?: Alarms.Alarm) { + // check alarm name if called from an alarm + if (alarm?.name !== "sync_labels") return; + + // get wallets + const wallets = await getWallets(); + + if (wallets.length === 0) return; + + // get profiles + const profiles = await getNameServiceProfiles( + wallets.map((w) => w.address), + true, + ); + + const find = (addr: string) => profiles.find((w) => w.address === addr)?.name; + + // save updated wallets + await ExtensionStorage.set( + "wallets", + wallets.map((wallet) => ({ + ...wallet, + nickname: find(wallet.address) || wallet.nickname, + })), + ); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.ts new file mode 100644 index 000000000..37d197e17 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/tiers/refresh-wallet-lifetime-savings-alarm.handler.ts @@ -0,0 +1,19 @@ +import type { Alarms } from "webextension-polyfill"; +import { getWalletLifetimeSavingsFromStorage, saveWalletLifetimeSavingsToStorage } from "../../../../../utils/tier/utils"; + +/** + * Alarm handler for refreshing wallet savings. + * Refreshes the wallet savings for the given address. + */ + +export async function handleRefreshWalletLifetimeSavingsAlarm(alarmInfo?: Alarms.Alarm) { + if (!alarmInfo?.name.startsWith("wallet_lifetime_savings_")) return; + + const address = alarmInfo.name.replace("wallet_lifetime_savings_", ""); + + const walletSavings = await getWalletLifetimeSavingsFromStorage(address); + + if (!walletSavings) return; + + await saveWalletLifetimeSavingsToStorage(address, walletSavings.lifetimeSavings, false); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts new file mode 100644 index 000000000..2a58e6f79 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts @@ -0,0 +1,40 @@ +import Arweave from "arweave"; +import browser, { type Alarms } from "webextension-polyfill"; +import BigNumber from "bignumber.js"; +import { getWallets } from "../../../../../wallets/wallets.utils"; +import { defaultGateway } from "../../../../../gateways/gateway"; +import { trackDirect, EventType, setToStartOfNextMonth } from "../../../../../utils/analytics/analytics"; + +export async function handleTrackBalanceAlarm(alarmInfo?: Alarms.Alarm) { + if (!alarmInfo?.name.startsWith("track-balance")) return; + + const wallets = await getWallets(); + const arweave = new Arweave(defaultGateway); + + let totalBalance = BigNumber("0"); + + await Promise.all( + wallets.map(async ({ address }) => { + try { + const balance = arweave.ar.winstonToAr(await arweave.wallets.getBalance(address)); + totalBalance = totalBalance.plus(balance); + } catch (e) { + console.log("invalid", e); + } + }), + ); + + try { + await trackDirect(EventType.BALANCE, { + totalBalance: totalBalance.toFixed(), + }); + + const timer = setToStartOfNextMonth(new Date()); + + browser.alarms.create("track-balance", { + when: timer.getTime(), + }); + } catch (err) { + console.log("err tracking", err); + } +} diff --git a/libs/core/src/lib/injected-api/background/handlers/alarms/transak-purchase/transak-purchase-alarm.handler.ts b/libs/core/src/lib/injected-api/background/handlers/alarms/transak-purchase/transak-purchase-alarm.handler.ts new file mode 100644 index 000000000..5f83cdba6 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/alarms/transak-purchase/transak-purchase-alarm.handler.ts @@ -0,0 +1,14 @@ +import type { Alarms } from "webextension-polyfill"; +import { checkIfTransakPurchaseSucceeded } from "../../../../../transak/transak.alarms"; +import { TRANSAK_PURCHASE_ALARM_NAME_PREFIX } from "../../../../../transak/transak.constants"; + +/** + * Alarm handler for checking if transak purchase has succeeded. + * Checks if any transak purchase has succeeded and updates the transak purchase list. + */ +export async function handleTransakPurchaseAlarm(alarmInfo?: Alarms.Alarm) { + if (!alarmInfo?.name.startsWith(TRANSAK_PURCHASE_ALARM_NAME_PREFIX)) return; + + const address = alarmInfo.name.replace(TRANSAK_PURCHASE_ALARM_NAME_PREFIX, ""); + await checkIfTransakPurchaseSucceeded(address); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/install/install.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/install/install.handler.ts new file mode 100644 index 000000000..8891011c7 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/install/install.handler.ts @@ -0,0 +1,62 @@ +import browser, { type Runtime } from "webextension-polyfill"; +import { scheduleGatewayUpdate } from "../../../../../gateways/cache"; +import { loadTokens } from "../../../../../tokens/token"; +import { ExtensionStorage } from "../../../../../utils/storage/storage"; +import { openOrSelectWelcomePage } from "../../../../../wallets/wallets.utils"; +import { handleGatewayUpdateAlarm } from "../../alarms/gateway-update/gateway-update-alarm.handler"; +import { resetAllPermissions } from "./permissions.handler"; +import { initializeARBalanceMonitor } from "../../../../../utils/analytics/analytics"; +import { scheduleFairLaunchTokensAlarm } from "../../../../../utils/fair-launch/fair-launch.alarms"; + +/** + * On extension installed event handler + */ +export async function handleInstall(details: Runtime.OnInstalledDetailsType) { + // only run on install + if (details.reason === "install") { + openOrSelectWelcomePage(true); + } + + if (details.reason === "update") { + // reset permissions + await resetAllPermissions(); + + const isSplashSeen = Boolean(await ExtensionStorage.get("update_splash_screen_seen")); + // if this is undefined, set update_splash_screen_seen + if (!isSplashSeen) { + // initially set to false + await ExtensionStorage.set("update_splash_screen_seen", false); + } + + // remove astro beta access announcement storage key + ExtensionStorage.remove("astro_beta_access_announcement_shown"); + + // remove name service cache + ExtensionStorage.remove("name_service_cache"); + + // create alarm to sync labels every 6 hours + browser.alarms.create("sync_labels", { delayInMinutes: 1, periodInMinutes: 360 }); + } + + // init monthly AR + await initializeARBalanceMonitor(); + + // initialize alarm to fetch notifications + browser.alarms.create("notifications", { periodInMinutes: 10 }); + + // reset notifications + // await ExtensionStorage.set("show_announcement", true); + + // initialize alarm to update tokens once a week + browser.alarms.create("update_ao_tokens", { + periodInMinutes: 10080, + }); + + // initialize tokens in wallet + await loadTokens(); + + // wayfinder + await scheduleGatewayUpdate(); + await handleGatewayUpdateAlarm(); + await scheduleFairLaunchTokensAlarm(); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/install/permissions.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/install/permissions.handler.ts new file mode 100644 index 000000000..222480226 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/install/permissions.handler.ts @@ -0,0 +1,67 @@ +import { Application } from "../../../../../applications/application.class"; +import { PersistentStorage } from "../../../../../utils/storage/storage"; + +const IS_PERMISSIONS_RESET = "is_permissions_reset"; + +// Add a memory flag to prevent multiple executions even within the same session +let isResetInProgress = false; + +/** + * Reset all permissions for all apps + */ +export const resetAllPermissions = async (): Promise => { + try { + const isPermissionsReset = await PersistentStorage.get(IS_PERMISSIONS_RESET); + // Check both storage and memory flags + if (isPermissionsReset || isResetInProgress) { + return; + } + + // Set the in-progress flag + isResetInProgress = true; + + // Get and validate connected apps + const connectedApps = (await PersistentStorage.get("apps")) || []; + if (!Array.isArray(connectedApps) || connectedApps.length === 0) { + await PersistentStorage.set(IS_PERMISSIONS_RESET, true); + return; + } + + // Process apps in batches to prevent overwhelming the system + const BATCH_SIZE = 5; + const validApps = connectedApps.filter((appUrl): appUrl is string => Boolean(appUrl) && typeof appUrl === "string"); + + for (let i = 0; i < validApps.length; i += BATCH_SIZE) { + const batch = validApps.slice(i, i + BATCH_SIZE); + const results = await Promise.allSettled( + batch.map(async (appUrl) => { + try { + const app = new Application(appUrl); + await app.updateSettings((val) => ({ + ...val, + permissions: [], + })); + } catch (error) { + console.error(`Failed to reset permissions for ${appUrl}:`, error); + throw error; + } + }), + ); + + // Log failures for this batch + results.forEach((result, index) => { + if (result.status === "rejected") { + console.error(`Failed to process app ${i + index}:`, result.reason); + } + }); + } + + // Mark as complete + await PersistentStorage.set(IS_PERMISSIONS_RESET, true); + } catch (error) { + console.error("Error in resetAllPermissions:", error); + } finally { + // Always reset the in-progress flag + isResetInProgress = false; + } +}; diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts new file mode 100644 index 000000000..a241ce7c1 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts @@ -0,0 +1,58 @@ +import { WANDER_PRINTER_ID } from "../printer.constants"; + +/** + * Printer capabilities request callback type + */ +type PrinterInfoCallback = (capabilities: chrome.printerProvider.PrinterCapabilities["capabilities"]) => void; + +/** + * Tells Chrome about the virtual printer's + * capabilities in CDD format + */ +export function handleGetCapabilities(printerId: string, callback: PrinterInfoCallback) { + // only return capabilities for the Wander printer + if (printerId !== WANDER_PRINTER_ID) return; + + // mimic a regular printer's capabilities + callback({ + version: "1.0", + printer: { + supported_content_type: [{ content_type: "application/pdf" }, { content_type: "image/pwg-raster" }], + color: { + option: [{ type: "STANDARD_COLOR", is_default: true }, { type: "STANDARD_MONOCHROME" }], + }, + copies: { + default_copies: 1, + max_copies: 100, + }, + media_size: { + option: [ + { + name: "ISO_A4", + width_microns: 210000, + height_microns: 297000, + is_default: true, + }, + { + name: "NA_LETTER", + width_microns: 215900, + height_microns: 279400, + }, + ], + }, + page_orientation: { + option: [ + { + type: "PORTRAIT", + is_default: true, + }, + { type: "LANDSCAPE" }, + { type: "AUTO" }, + ], + }, + duplex: { + option: [{ type: "NO_DUPLEX", is_default: true }, { type: "LONG_EDGE" }, { type: "SHORT_EDGE" }], + }, + }, + }); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/printer/get-printers/get-printers.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/printer/get-printers/get-printers.handler.ts new file mode 100644 index 000000000..c3a73d133 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/printer/get-printers/get-printers.handler.ts @@ -0,0 +1,21 @@ +import { WANDER_PRINTER_ID } from "../printer.constants"; + +/** + * Printer info request callback type + */ +type PrinterInfoCallback = (p: chrome.printerProvider.PrinterInfo[]) => void; + +/** + * Returns a list of "virtual" printers, + * in our case "Print/Publish to Arweave" + */ +export function handleGetPrinters(callback: PrinterInfoCallback) { + callback([ + { + id: WANDER_PRINTER_ID, + // TODO: Add to i18n: + name: "Print to Arweave", + description: "Publish the content you want to print on Arweave, permanently.", + }, + ]); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/printer/print/print.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/printer/print/print.handler.ts new file mode 100644 index 000000000..b04fd8e2e --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/printer/print/print.handler.ts @@ -0,0 +1,129 @@ +import { createData, ArweaveSigner } from "@dha-team/arbundles"; +import browser from "webextension-polyfill"; +import Arweave from "arweave"; +import { getActiveTab } from "../../../../../../applications/application.utils"; +import { concatGatewayURL } from "../../../../../../gateways/utils"; +import { findGateway } from "../../../../../../gateways/wayfinder"; +import { sleep } from "../../../../../../utils/promises/sleep"; +import { DecryptedWallet, getActiveKeyfile } from "../../../../../../wallets"; +import { freeDecryptedWallet } from "../../../../../../wallets/encryption"; +import { uploadDataToTurbo } from "../../../../../modules/dispatch/uploader"; +import { signAuth } from "../../../../../modules/sign/sign_auth"; +import { WANDER_PRINTER_ID } from "../printer.constants"; + +/** + * Print request (result) callback + */ +type PrintCallback = (result: string) => void; + +/** + * Handles the request from the user to print the page to Arweave + */ +export async function handlePrint(printJob: chrome.printerProvider.PrintJob, resultCallback: PrintCallback) { + // only print for the Wander printer + if (printJob.printerId !== WANDER_PRINTER_ID) return; + + // wallet + let decryptedWallet: DecryptedWallet; + + try { + // build data blog + const data = new Blob([printJob.document], { type: printJob.contentType }); + + // get user wallet + decryptedWallet = await getActiveKeyfile(); + + if (decryptedWallet.type === "hardware") throw new Error("Cannot print with a hardware wallet."); + + // extension manifest + const manifest = browser.runtime.getManifest(); + + // setup tags + const tags = [ + { name: "App-Name", value: manifest.name }, + { name: "App-Version", value: manifest.version }, + { name: "Type", value: "Print-Archive" }, + { name: "Content-Type", value: printJob.contentType }, + { name: "print:title", value: printJob.title }, + { name: "print:timestamp", value: new Date().getTime().toString() }, + ]; + + let transactionId: string; + + // find a gateway to upload and display the result + const gateway = await findGateway({}); + const arweave = Arweave.init(gateway); + + // create data item + const dataSigner = new ArweaveSigner(decryptedWallet.keyfile); + const transactionData = new Uint8Array(await data.arrayBuffer()); + const dataEntry = createData(transactionData, dataSigner, { tags }); + + // calculate reward for the transaction + const reward = await arweave.transactions.getPrice(transactionData.byteLength); + + // get active tab + const activeTab = await getActiveTab(); + + await signAuth( + { + tabID: activeTab.id, + url: activeTab.url, + }, + // @ts-expect-error + { + ...dataEntry.toJSON(), + reward, + sizeInBytes: transactionData.byteLength, + }, + decryptedWallet.address, + ); + + try { + // sign an upload data + await dataEntry.sign(dataSigner); + await uploadDataToTurbo(dataEntry, "https://turbo.ardrive.io"); + + await sleep(2000); + + // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK + resultCallback("OK"); + + transactionId = dataEntry.id; + } catch (error) { + // sign & post if there is something wrong with turbo + + const transaction = await arweave.createTransaction({ data: transactionData }, decryptedWallet.keyfile); + + for (const tag of tags) { + transaction.addTag(tag.name, tag.value); + } + + // sign and upload + await arweave.transactions.sign(transaction, decryptedWallet.keyfile); + const uploader = await arweave.transactions.getUploader(transaction); + + while (!uploader.isComplete) { + await uploader.uploadChunk(); + } + + await sleep(2000); + + // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK + resultCallback("OK"); + + transactionId = transaction.id; + } + + // open in new tab + await chrome.tabs.create({ + url: `${concatGatewayURL(gateway)}/${transactionId}`, + }); + } catch (e) { + console.log("Printing failed:\n", e); + resultCallback("FAILED"); + } + + // free wallet from memory + if (decryptedWallet?.type == "local") freeDecryptedWallet(decryptedWallet.keyfile); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/printer/printer.constants.ts b/libs/core/src/lib/injected-api/background/handlers/browser/printer/printer.constants.ts new file mode 100644 index 000000000..66750307f --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/printer/printer.constants.ts @@ -0,0 +1 @@ +export const WANDER_PRINTER_ID = "wander-permaweb-printer"; diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/protocol/protocol.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/protocol/protocol.handler.ts new file mode 100644 index 000000000..ae4585826 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/protocol/protocol.handler.ts @@ -0,0 +1,31 @@ +import browser, { type WebNavigation } from "webextension-polyfill"; +import { getRedirectURL } from "../../../../../gateways/ar_protocol"; +import { findGateway } from "../../../../../gateways/wayfinder"; + +/** + * Handle custom ar:// protocol, using the + * browser.webNavigation.onBeforeNavigate API. + * + * This is based on the issue in ipfs/ipfs-companion: + * https://github.com/ipfs/ipfs-companion/issues/164#issuecomment-328374052 + * + * Thank you ar.io for the updated method: + * https://github.com/ar-io/wayfinder/blob/main/background.js#L13 + */ +export async function handleProtocol(details: WebNavigation.OnBeforeNavigateDetailsType) { + const gateway = await findGateway({ + arns: true, + ensureStake: true, + }); + + // parse redirect url + const redirectUrl = getRedirectURL(new URL(details.url), gateway); + + // don't do anything if it is not a protocol call + if (!redirectUrl) return; + + // update tab + browser.tabs.update(details.tabId, { + url: redirectUrl, + }); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/tabs/tabs.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/tabs/tabs.handler.ts new file mode 100644 index 000000000..77baf52d3 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/tabs/tabs.handler.ts @@ -0,0 +1,82 @@ +import { isomorphicSendMessage } from "@wanderapp/isomorphic-messaging"; +import browser from "webextension-polyfill"; +import { Application } from "../../../../../applications/application.class"; +import { getTab } from "../../../../../applications/tab"; +import { getCachedAuthPopupWindowTabID, resetPopupTabID, resetKeepAlive } from "../../../../../auth/auth.utils"; +import { getAppURL } from "../../../../../utils/format/format"; +import { updateIcon } from "../../../../../utils/browser-extension/icon"; +import { createContextMenus } from "../../../../../utils/browser-extension/context-menus"; + +/** + * Handle tab updates (icon change, context menus, etc.) + * + * @param tabId ID of the tab to get. + */ +export async function handleTabUpdate(tabID: number, changeInfo?: browser.Tabs.OnUpdatedChangeInfoType) { + const popupTabID = getCachedAuthPopupWindowTabID(); + + if (popupTabID !== -1 && changeInfo?.status === "loading") { + isomorphicSendMessage({ + destination: `web_accessible@${popupTabID}`, + messageId: "auth_tab_reloaded", + data: tabID, + }); + } + + // construct app + const tab = await getTab(tabID); + + // if we cannot parse the tab URL, the extension is not connected + if (!tab?.url) { + updateIcon(false); + createContextMenus(false); + return; + } + + const app = new Application(getAppURL(tab.url)); + + // change icon to "connected" status if + // the site is connected and add the + // context menus + const connected = await app.isConnected(); + + updateIcon(connected); + createContextMenus(connected); +} + +/** + * Notifies the auth popup about closed tab for it to abort AuthRequests coming from those tabs. + * + * @param tabId ID of the closed tab. + */ +export async function handleTabClosed(closedTabID: number) { + const popupTabID = getCachedAuthPopupWindowTabID(); + + // If there's no popup, then we do nothing: + if (popupTabID === -1) return; + + if (closedTabID === popupTabID) { + // If the closed tab was the popup, we reset its ID and the keep-alive alarm: + resetPopupTabID(); + resetKeepAlive(); + + // Make sure there were no duplicate auth popups and, if so, close them too: + try { + const url = browser.runtime.getURL("tabs/auth.html"); + const authPopups = await browser.tabs.query({ url }); + + browser.tabs.remove(authPopups.map((authPopup) => authPopup.id)); + } catch (err) { + console.warn("Error trying to close other auth popups:", err); + } + + return; + } + + // If some other tab was closed and there's a popup, notify the popup in case it has AuthRequest from the closed tab: + isomorphicSendMessage({ + destination: `web_accessible@${popupTabID}`, + messageId: "auth_tab_closed", + data: closedTabID, + }); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/browser/window-close/window-close.handler.ts b/libs/core/src/lib/injected-api/background/handlers/browser/window-close/window-close.handler.ts new file mode 100644 index 000000000..d246d8fc5 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/browser/window-close/window-close.handler.ts @@ -0,0 +1,20 @@ +import browser from "webextension-polyfill"; +import { removeDecryptionKey } from "../../../../../wallets/auth"; + +/** + * Listener for browser close. + * On browser closed, we remove the + * decryptionKey. + */ +export async function handleWindowClose() { + const windows = await browser.windows.getAll(); + + // TODO: Maybe we should be counting connected apps instead? + // return if there are still windows open + if (windows.length > 0) { + return; + } + + // remove the decryption key + await removeDecryptionKey(); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/message/api-call-message/api-call-message.handler.ts b/libs/core/src/lib/injected-api/background/handlers/message/api-call-message/api-call-message.handler.ts new file mode 100644 index 000000000..606a75399 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/message/api-call-message/api-call-message.handler.ts @@ -0,0 +1,112 @@ +import { isExactly, isNotUndefined, isString } from "typed-assert"; +import type { ApiErrorResponse, ApiResponse, ApiSuccessResponse, BaseApiMessage } from "shim"; +import browser from "webextension-polyfill"; +import type { OnMessageCallback } from "@wanderapp/isomorphic-messaging"; +import { getTab } from "../../../../../applications/tab"; +import { getAppURL } from "../../../../../utils/format/format"; +import { backgroundModules, ModuleAppData } from "../../../background-modules"; + +export const handleApiCallMessage: OnMessageCallback<"api_call"> = async ({ data, sender }): Promise => { + // construct base message to extend and return + const baseMessage: BaseApiMessage = { + callID: data.callID, + type: `${data.type}_result`, + data: undefined, + }; + + try { + // validate message + isExactly( + sender.context, + "content-script", + "API call messages are only accepted from the injected-script -> content-script", + ); + isApiCall(data); + + // fix params + if (data.data?.params) { + data.data.params = data.data.params.map((val) => (val === null ? undefined : val)); + } + + // grab the tab where the API call came from + const tab = await getTab(sender.tabId); + + // if the tab is not found, reject the call + isString(tab?.url, "Call coming from invalid tab"); + + // find module to execute + const functionName = data.type.replace("api_", ""); + const mod = backgroundModules.find((mod) => mod.functionName === functionName); + + // if we cannot find the module, we return with an error + isNotUndefined(mod, `API function "${functionName}" not found`); + + // grab app info: + let app = new Application(getAppURL(tab.url)); + + // if the frame ID is defined, the API + // request is not coming from the main tab + // but from an iframe in the tab. + // we need to treat the iframe as a separate + // application to ensure the user does not + // mistake it for the actual app + if (typeof sender.frameId !== "undefined") { + const frame = await browser.webNavigation.getFrame({ + frameId: sender.frameId, + tabId: sender.tabId, + }); + + // update app value with the app belonging to the frame + if (frame?.url) { + app = new Application(getAppURL(frame.url)); + } + } + + // check permissions + const permissionCheck = await app.hasPermissions(mod.permissions); + + if (!permissionCheck.result) { + throw new Error(`Missing permission(s) for "${functionName}": ${permissionCheck.missing.join(", ")}`); + } + + // check if site is blocked + if (await app.isBlocked()) { + throw new Error(`${app.url} is blocked from interacting with Wander`); + } + + // update events + await pushEvent({ + type: data.type, + app: app.url, + date: Date.now(), + }); + + // Record user activity for inactivity tracking + recordActivity(); + + // handle function + const functionResult = await mod.function( + { + tabID: tab.id, + url: app.url, + favicon: tab.favIconUrl, + } satisfies ModuleAppData, + ...(data.data.params || []), + ); + + // return result + return { + ...baseMessage, + data: functionResult, + } satisfies ApiSuccessResponse; + } catch (e) { + console.error(`[Wander API] (${baseMessage.type} / ${data.type})`, e?.message || e); + + // return error + return { + ...baseMessage, + error: true, + data: e?.message || e, + } satisfies ApiErrorResponse; + } +}; diff --git a/libs/core/src/lib/injected-api/background/handlers/message/chunk-message/chunk-message.handler.ts b/libs/core/src/lib/injected-api/background/handlers/message/chunk-message/chunk-message.handler.ts new file mode 100644 index 000000000..3d7d9d686 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/message/chunk-message/chunk-message.handler.ts @@ -0,0 +1,70 @@ +import { isExactly, isString } from "typed-assert"; +import { handleChunk } from "../../../../modules/sign/chunks"; +import type { ApiErrorResponse, ApiResponse, ApiSuccessResponse, BaseApiMessage } from "shim"; +import browser from "webextension-polyfill"; +import type { OnMessageCallback } from "@wanderapp/isomorphic-messaging"; +import { isChunk } from "../../../../../utils/assertions/assertions"; +import { getTab } from "../../../../../applications/tab"; +import { getAppURL } from "../../../../../utils/format/format"; + +export const handleChunkMessage: OnMessageCallback<"chunk"> = async ({ + data, + sender, +}): Promise> => { + // construct base message to extend and return + const baseMessage: BaseApiMessage = { + callID: data.callID, + type: "chunk_result", + data: undefined, + }; + + try { + // validate message + isExactly( + sender.context, + "content-script", + "Chunk messages are only accepted from the injected-script -> content-script", + ); + isChunk(data.data); + + // grab the tab where the chunk came + const tab = await getTab(sender.tabId); + + // if the tab is not found, reject the call + isString(tab?.url, "Call coming from invalid tab"); + + // raw url where the chunk originates from + let url = tab.url; + + // if the frame ID is defined, the API + // request is not coming from the main tab + // but from an iframe in the tab. + // we need to treat the iframe as a separate + // application to ensure the user does not + // mistake it for the actual app + if (typeof sender.frameId !== "undefined") { + const frame = await browser.webNavigation.getFrame({ + frameId: sender.frameId, + tabId: sender.tabId, + }); + + // update url value with the url belonging to the frame + if (frame.url) url = frame.url; + } + + // call the chunk handler + const index = handleChunk(data.data, getAppURL(url)); + + return { + ...baseMessage, + data: index, + } satisfies ApiSuccessResponse; + } catch (e) { + // return error + return { + ...baseMessage, + error: true, + data: e?.message || e, + } satisfies ApiErrorResponse; + } +}; diff --git a/libs/core/src/lib/injected-api/background/handlers/storage/active-address-change/active-address-change.handler.ts b/libs/core/src/lib/injected-api/background/handlers/storage/active-address-change/active-address-change.handler.ts new file mode 100644 index 000000000..307a6034f --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/storage/active-address-change/active-address-change.handler.ts @@ -0,0 +1,55 @@ +import { isomorphicSendMessage } from "@wanderapp/isomorphic-messaging"; +import { Application } from "../../../../../applications/application.class"; +import { forEachTab } from "../../../../../applications/tab"; +import { getCachedAuthPopupWindowTabID } from "../../../../../auth/auth.utils"; +import { getAppURL } from "../../../../../utils/format/format"; +import { StorageChange } from "../../../../../utils/browser-extension/runtime"; + +/** + * Active address change event listener. + * Sends a message to fire the "walletSwitch" + * event in the tab. + */ +export async function handleActiveAddressChange({ oldValue, newValue: newAddress }: StorageChange) { + if (!newAddress || oldValue === newAddress) return; + + // go through all tabs and check if they + // have the permissions to receive the + // wallet switch event + await forEachTab(async (tab) => { + const app = new Application(getAppURL(tab.url)); + + // check required permissions + const permissionCheck = await app.hasPermissions(["ACCESS_ALL_ADDRESSES", "ACCESS_ADDRESS"]); + + // app not connected + if (permissionCheck.has.length === 0) return; + + // trigger emitter + await isomorphicSendMessage({ + destination: `content-script@${tab.id}`, + messageId: "event", + data: { + name: "activeAddress", + value: permissionCheck.result ? newAddress : null, + }, + }); + + const popupTabID = getCachedAuthPopupWindowTabID(); + + if (popupTabID) { + isomorphicSendMessage({ + destination: `web_accessible@${popupTabID}`, + messageId: "auth_active_wallet_change", + data: tab.id, + }); + } + + // trigger event via message + await isomorphicSendMessage({ + destination: `content-script@${tab.id}`, + messageId: "switch_wallet_event", + data: permissionCheck ? newAddress : null, + }); + }); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/storage/app-config-change/app-config-change.handler.ts b/libs/core/src/lib/injected-api/background/handlers/storage/app-config-change/app-config-change.handler.ts new file mode 100644 index 000000000..3cf622e02 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/storage/app-config-change/app-config-change.handler.ts @@ -0,0 +1,92 @@ +import type { Event } from "shim"; +import { isomorphicSendMessage } from "@wanderapp/isomorphic-messaging"; +import { InitAppParams, PREFIX, Application } from "../../../../../applications/application.class"; +import { getStoredApps } from "../../../../../applications/application.utils"; +import { getMissingPermissions } from "../../../../../applications/permissions"; +import { forEachTab } from "../../../../../applications/tab"; +import { compareGateways } from "../../../../../gateways/utils"; +import { StorageChange } from "../../../../../utils/browser-extension/runtime"; +import { getAppURL } from "../../../../../utils/format/format"; + +export async function handleAppConfigChange(changes: Record>, areaName: string) { + // only trigger for storage.local + if (areaName !== "local") return; + + // get connected apps + const storedApps = await getStoredApps(); + + // events to push + const events: { + appURL: string; + event: Event; + }[] = []; + + // iterate through changes + for (const key in changes) { + // continue if not an app config change and + // if the app is added to the stored apps + + if (!key.startsWith(PREFIX) || !storedApps.includes(key.replace(PREFIX, ""))) continue; + + // get values and app + const { oldValue: storedOldValue, newValue: storedNewValue } = changes[key]; + const appURL = key.replace(PREFIX, ""); + const app = new Application(appURL); + // this was changed by atticus because values in storage are stored stringified + const oldValue = JSON.parse((storedOldValue || "{}") as unknown as string) as InitAppParams; + const newValue = JSON.parse((storedNewValue || "{}") as unknown as string) as InitAppParams; + + // check if permission event emitting is needed + // get missing permissions + const missingPermissions = getMissingPermissions(oldValue?.permissions || [], newValue?.permissions || []); + + if (oldValue?.permissions?.length !== newValue?.permissions?.length || missingPermissions.length > 0) { + events.push({ + appURL, + event: { + name: "permissions", + value: newValue?.permissions || [], + }, + }); + } + // check if gateway event emiting is needed + const { result: hasGwPermission } = await app.hasPermissions(["ACCESS_ARWEAVE_CONFIG"]); + + // compare gateway objects + + if (!compareGateways(oldValue?.gateway || {}, newValue?.gateway || {}) && hasGwPermission) { + events.push({ + appURL, + event: { + name: "gateway", + value: newValue?.gateway, + }, + }); + } + } + + const messagePromises: Promise[] = []; + + // send permissions to the appropriate tab + await forEachTab((tab) => { + // return if no tab url is present + if (!tab?.url || !tab?.id) return; + + // filter events needed to be sent to the tab + const eventsForTab = events.filter(({ appURL }) => getAppURL(tab.url) === appURL).map((e) => e.event); + + // send the events + for (const event of eventsForTab) { + // trigger emiter + const messagePromise = isomorphicSendMessage({ + destination: `content-script@${tab.id}`, + messageId: "event", + data: event, + }); + + messagePromises.push(messagePromise); + } + }); + + await Promise.allSettled(messagePromises); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/storage/apps-change/app-change.handler.ts b/libs/core/src/lib/injected-api/background/handlers/storage/apps-change/app-change.handler.ts new file mode 100644 index 000000000..9601916c0 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/storage/apps-change/app-change.handler.ts @@ -0,0 +1,91 @@ +import { isomorphicSendMessage } from "@wanderapp/isomorphic-messaging"; +import { Application } from "../../../../../applications/application.class"; +import { getActiveTab } from "../../../../../applications/application.utils"; +import { forEachTab } from "../../../../../applications/tab"; +import { getCachedAuthPopupWindowTabID } from "../../../../../auth/auth.utils"; +import { createContextMenus } from "../../../../../utils/browser-extension/context-menus"; +import { updateIcon } from "../../../../../utils/browser-extension/icon"; +import { StorageChange } from "../../../../../utils/browser-extension/runtime"; +import { getAppURL } from "../../../../../utils/format/format"; + +/** + * App disconnected listener. Sends a message + * to trigger the disconnected event. + */ +export async function handleAppsChange({ oldValue, newValue }: StorageChange) { + // message to send the event + const triggerEvent = (tabID: number, type: "connect" | "disconnect") => + isomorphicSendMessage({ + destination: `content-script@${tabID}`, + messageId: "event", + data: { + name: type, + value: null, + }, + }); + + // trigger events + forEachTab(async (tab) => { + // get app url + const appURL = getAppURL(tab.url); + + // if the new value is undefined + // and the old value was defined + // we need to emit the disconnect + // event for all tabs that were + // connected + if (!newValue && !!oldValue) { + if (!oldValue.includes(appURL)) return; + + const popupTabID = getCachedAuthPopupWindowTabID(); + + if (popupTabID) { + isomorphicSendMessage({ + destination: `web_accessible@${popupTabID}`, + messageId: "auth_app_disconnected", + data: tab.id, + }); + } + + await triggerEvent(tab.id, "disconnect"); + + return; + } else if (!newValue) { + // if the new value is undefined + // and the old value was also + // undefined, we just return + return; + } + + const oldAppsList = oldValue || []; + + // if the new value includes the app url + // and the old value does not, than the + // app has just been connected + // if the reverse is true, than the app + // has just been disconnected + if (newValue.includes(appURL) && !oldAppsList.includes(appURL)) { + await triggerEvent(tab.id, "connect"); + } else if (!newValue.includes(appURL) && oldAppsList.includes(appURL)) { + const popupTabID = getCachedAuthPopupWindowTabID(); + + if (popupTabID) { + isomorphicSendMessage({ + destination: `web_accessible@${popupTabID}`, + messageId: "auth_app_disconnected", + data: tab.id, + }); + } + + await triggerEvent(tab.id, "disconnect"); + } + }); + + // update icon and context menus + const activeTab = await getActiveTab(); + const app = new Application(getAppURL(activeTab.url)); + const connected = await app.isConnected(); + + await updateIcon(connected); + await createContextMenus(connected); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/storage/auth-state-change/auth-state-change.handler.ts b/libs/core/src/lib/injected-api/background/handlers/storage/auth-state-change/auth-state-change.handler.ts new file mode 100644 index 000000000..6c814a78e --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/storage/auth-state-change/auth-state-change.handler.ts @@ -0,0 +1,20 @@ +import { Application } from "../../../../../applications/application.class"; +import { getActiveTab } from "../../../../../applications/application.utils"; +import { createContextMenus } from "../../../../../utils/browser-extension/context-menus"; +import { updateIcon } from "../../../../../utils/browser-extension/icon"; +import { StorageChange } from "../../../../../utils/browser-extension/runtime"; +import { getAppURL } from "../../../../../utils/format/format"; + +/** + * App disconnected listener. Sends a message + * to trigger the disconnected event. + */ +export async function handleAuthStateChange({ oldValue, newValue }: StorageChange) { + // update icon + const activeTab = await getActiveTab(); + const app = new Application(getAppURL(activeTab.url)); + const connected = await app.isConnected(); + + await updateIcon(connected); + createContextMenus(connected); +} diff --git a/libs/core/src/lib/injected-api/background/handlers/storage/wallet-change/wallet-change.handler.ts b/libs/core/src/lib/injected-api/background/handlers/storage/wallet-change/wallet-change.handler.ts new file mode 100644 index 000000000..89124c982 --- /dev/null +++ b/libs/core/src/lib/injected-api/background/handlers/storage/wallet-change/wallet-change.handler.ts @@ -0,0 +1,68 @@ +import browser from "webextension-polyfill"; +import { isomorphicSendMessage } from "@wanderapp/isomorphic-messaging"; +import { Application } from "../../../../../applications/application.class"; +import { forEachTab } from "../../../../../applications/tab"; +import { StorageChange } from "../../../../../utils/browser-extension/runtime"; +import { getAppURL } from "../../../../../utils/format/format"; +import { setActiveWallet } from "../../../../../wallets"; +import { StoredWallet } from "../../../../../wallets/wallets.types"; +import { getActiveAddress } from "../../../../../wallets/wallets.utils"; + +/** + * Added wallets change listener. + * Fixup active address in case the current + * active address' wallet has been removed. + */ +export async function handleWalletsChange({ newValue, oldValue }: StorageChange) { + const wallets = newValue; + const previousWallets = oldValue || []; + + // addresses + const addresses = wallets.map((w) => w.address); + + // emit wallet change event + await forEachTab(async (tab) => { + const app = new Application(getAppURL(tab.url)); + + // check required permissions + const permissionCheck = await app.hasPermissions(["ACCESS_ALL_ADDRESSES"]); + + // app not connected + if (permissionCheck.has.length === 0) return; + + // trigger emiter + await isomorphicSendMessage({ + destination: `content-script@${tab.id}`, + messageId: "event", + data: { + name: "addresses", + value: permissionCheck.result ? addresses : null, + }, + }); + }); + + // add or remove ANS label change listener + if (wallets.length > 0 && previousWallets.length === 0) { + // add scheduled label refresh if + // Wander has just been set up + browser.alarms.create("sync_labels", { delayInMinutes: 1, periodInMinutes: 360 }); + } else if (wallets.length === 0 && previousWallets.length > 0) { + // remove scheduled label refresh if + // Wander has just been reset + await browser.alarms.clear("sync_labels"); + } + + // remove if there are no wallets added + if (!wallets || wallets.length === 0) { + return await setActiveWallet(undefined); + } + + // get current address + const activeAddress = await getActiveAddress(); + + // check if the active wallet has been removed + if (!wallets.find((w) => w.address === activeAddress)) { + // update active wallet + return setActiveWallet(wallets[0].address); + } +} diff --git a/libs/core/src/lib/injected-api/foreground/foreground-modules.ts b/libs/core/src/lib/injected-api/foreground/foreground-modules.ts new file mode 100644 index 000000000..ed110c211 --- /dev/null +++ b/libs/core/src/lib/injected-api/foreground/foreground-modules.ts @@ -0,0 +1,118 @@ +import type { Module, ModuleFunction } from "../module"; + +// import modules +import permissionsModule from "../modules/permissions"; +import permissions from "../modules/permissions/permissions.foreground"; +import activeAddressModule from "../modules/active_address"; +import activeAddress from "../modules/active_address/active_address.foreground"; +import allAddressesModule from "../modules/all_addresses"; +import allAddresses from "../modules/all_addresses/all_addresses.foreground"; +import publicKeyModule from "../modules/public_key"; +import publicKey from "../modules/public_key/public_key.foreground"; +import walletNamesModule from "../modules/wallet_names"; +import walletNames from "../modules/wallet_names/wallet_names.foreground"; +import arweaveConfigModule from "../modules/arweave_config"; +import arweaveConfig from "../modules/arweave_config/arweave_config.foreground"; +import disconnectModule from "../modules/disconnect"; +import disconnect, { finalizer as disconnectFinalizer } from "../modules/disconnect/disconnect.foreground"; +import addTokenModule from "../modules/add_token"; +import addToken from "../modules/add_token/add_token.foreground"; +import isTokenAddedModule from "../modules/is_token_added"; +import isTokenAdded from "../modules/is_token_added/is_token_added.foreground"; +import connectModule from "../modules/connect"; +import connect from "../modules/connect/connect.foreground"; +import signModule from "../modules/sign"; +import sign, { finalizer as signFinalizer } from "../modules/sign/sign.foreground"; +import dispatchModule from "../modules/dispatch"; +import dispatch, { finalizer as dispatchFinalizer } from "../modules/dispatch/dispatch.foreground"; +import encryptModule from "../modules/encrypt"; +import encrypt, { finalizer as encryptFinalizer } from "../modules/encrypt/encrypt.foreground"; +import decryptModule from "../modules/decrypt"; +import decrypt, { finalizer as decryptFinalizer } from "../modules/decrypt/decrypt.foreground"; +import signatureModule from "../modules/signature"; +import signature, { finalizer as signatureFinalizer } from "../modules/signature/signature.foreground"; +import signMessageModule from "../modules/sign_message"; +import signMessage, { finalizer as signMessageFinalizer } from "../modules/sign_message/sign_message.foreground"; +import subscriptionModule from "../modules/subscription"; +import subscription from "../modules/subscription/subscription.foreground"; +import privateHashModule from "../modules/private_hash"; +import privateHash, { finalizer as privateHashFinalizer } from "../modules/private_hash/private_hash.foreground"; +import verifyMessageModule from "../modules/verify_message"; +import verifyMessage from "../modules/verify_message/verify_message.foreground"; +import batchSignDataItemModule from "../modules/batch_sign_data_item"; +import batchSignDataItem, { + finalizer as batchSignDataItemFinalizer, +} from "../modules/batch_sign_data_item/batch_sign_data_item.foreground"; +import signDataItemModule from "../modules/sign_data_item"; +import signDataItem, { finalizer as signDataItemFinalizer } from "../modules/sign_data_item/sign_data_item.foreground"; +import userTokensModule from "../modules/user_tokens"; +import userTokens from "../modules/user_tokens/user_tokens.foreground"; +import tokenBalanceModule from "../modules/token_balance"; +import tokenBalance from "../modules/token_balance/token_balance.foreground"; +import wanderTierInfoModule from "../modules/wander_tier_info"; +import wanderTierInfo from "../modules/wander_tier_info/wander_tier_info.foreground"; + +/** + * @param result The result from the background script + * @param params The params the background script received + * @param originalParams The params the injected function was called with + */ +export type TransformFinalizer = ( + result: ResultType, + params: ParamsType, + originalParams: OriginalParamsType, +) => any; + +/** Extended module interface */ +export interface ForegroundModule extends Module { + /** + * A function that runs after results were + * returned from the background script. + * This is optional and will be ignored if not set. + */ + finalizer?: ModuleFunction | TransformFinalizer; +} + +/** Foreground modules */ +export const foregroundModules: ForegroundModule[] = [ + { ...permissionsModule, function: permissions }, + { ...activeAddressModule, function: activeAddress }, + { ...allAddressesModule, function: allAddresses }, + { ...publicKeyModule, function: publicKey }, + { ...walletNamesModule, function: walletNames }, + { ...arweaveConfigModule, function: arweaveConfig }, + { ...disconnectModule, function: disconnect, finalizer: disconnectFinalizer }, + { ...connectModule, function: connect }, + { ...signModule, function: sign, finalizer: signFinalizer }, + { ...dispatchModule, function: dispatch, finalizer: dispatchFinalizer }, + { ...encryptModule, function: encrypt, finalizer: encryptFinalizer }, + { ...decryptModule, function: decrypt, finalizer: decryptFinalizer }, + { ...signatureModule, function: signature, finalizer: signatureFinalizer }, + { ...addTokenModule, function: addToken }, + { ...isTokenAddedModule, function: isTokenAdded }, + { + ...signMessageModule, + function: signMessage, + finalizer: signMessageFinalizer, + }, + { + ...privateHashModule, + function: privateHash, + finalizer: privateHashFinalizer, + }, + { ...verifyMessageModule, function: verifyMessage }, + { + ...signDataItemModule, + function: signDataItem, + finalizer: signDataItemFinalizer, + }, + { ...subscriptionModule, function: subscription }, + { ...userTokensModule, function: userTokens }, + { ...tokenBalanceModule, function: tokenBalance }, + { + ...batchSignDataItemModule, + function: batchSignDataItem, + finalizer: batchSignDataItemFinalizer, + }, + { ...wanderTierInfoModule, function: wanderTierInfo }, +]; diff --git a/libs/core/src/lib/injected-api/foreground/foreground-setup-embedded-wallet-sdk.ts b/libs/core/src/lib/injected-api/foreground/foreground-setup-embedded-wallet-sdk.ts new file mode 100644 index 000000000..b1b02ce73 --- /dev/null +++ b/libs/core/src/lib/injected-api/foreground/foreground-setup-embedded-wallet-sdk.ts @@ -0,0 +1,114 @@ +import type { ApiCall, Event } from "shim"; +import { nanoid } from "nanoid"; +import mitt from "mitt"; +import { version } from "../../../../wander-wallet-api/package.json"; +import { isApiErrorResponse, isomorphicSendMessage, setEmbeddedTargetIframe } from "@wanderapp/isomorphic-messaging"; +import { foregroundModules, type ForegroundModule } from "./foreground-modules"; +import { log, LOG_GROUP } from '../../utils/log/log.utils'; +import { MittInjectedEvents } from "../../utils/events/events"; +// import { version as sdkVersion } from "../../../wander-connect-sdk/package.json"; + +export function injectWanderConnectWalletAPI(targetWindowOrIframe: Window | HTMLIFrameElement = window) { + log(LOG_GROUP.SETUP, "injectWanderConnectWalletAPI()"); + + if (!(targetWindowOrIframe instanceof HTMLIFrameElement)) { + throw new Error("Target for Wander Embedded must be an IFRAME element."); + } + + setEmbeddedTargetIframe(targetWindowOrIframe); + + /** Init events */ + const events = mitt(); + + // TODO: Can we get the right type here?: + const walletAPI = { + walletName: import.meta.env?.VITE_IS_EMBEDDED_APP === "1" ? "Wander Connect" : "ArConnect", + walletVersion: version, + events, + } as const; + + for (const mod of foregroundModules) { + walletAPI[mod.functionName] = (...params: any[]) => { + return callForegroundThenBackground(mod, params); + }; + } + + /** + * Utility function to handle the actual "call-foreground-then-background" flow. + * This can be reused by the Proxy handler below. + */ + async function callForegroundThenBackground( + foregroundModule: string | ForegroundModule, + params: any[], + ): Promise { + return new Promise(async (resolve, reject) => { + // 1. Find out what function are we calling: + + const functionName = typeof foregroundModule === "string" ? foregroundModule : foregroundModule.functionName; + + // 2. Get & prepare its params: + // For `sign` and `dispatch`, this is where the params are send in chunks. + const functionParams = typeof foregroundModule === "string" ? params : await foregroundModule.function(...params); + + // TODO: Use a default function for those that do not have/need one and + // see if chunking can be done automatically or if it is needed at all: + + // 3. Prepare the message & payload to send: + const callID = nanoid(); + const data: ApiCall = { + app: import.meta.env?.VITE_IS_EMBEDDED_APP === "1" ? "wanderEmbedded" : "wander", + version, + callID, + type: `api_${functionName}`, + data: { + params: functionParams, + }, + }; + + // 4. Send message to background script (Wander BE) or to the iframe window (Wander Embedded): + + log(LOG_GROUP.API, `${data.type} (${data.callID})...`); + + // send call to the background + const res = await isomorphicSendMessage({ + destination: "background", + messageId: data.type === "chunk" ? "chunk" : "api_call", + data, + }); + + // TODO: If the call above fails, this API call never gets a response. Add timeout? + + log(LOG_GROUP.API, `${data.type} (${data.callID}) =`, res); + + // check for errors + if (isApiErrorResponse(res)) { + return reject(res.data); + } + + const finalizerFn = typeof foregroundModule === "string" ? null : foregroundModule.finalizer; + + // call the finalizer function if it exists + if (finalizerFn) { + try { + const finalizerResult = await finalizerFn(res.data, functionParams, params); + + // TODO: This is a bad check because the result could be falsy: + // if the finalizer transforms data + // update the result + if (finalizerResult) { + res.data = finalizerResult; + } + } catch (err) { + reject(err); + + return; + } + } + + resolve(res.data); + }); + } + + // @ts-expect-error + window.arweaveWallet = walletAPI; +} diff --git a/libs/core/src/lib/injected-api/foreground/foreground-setup-events.ts b/libs/core/src/lib/injected-api/foreground/foreground-setup-events.ts new file mode 100644 index 000000000..c2660aa1c --- /dev/null +++ b/libs/core/src/lib/injected-api/foreground/foreground-setup-events.ts @@ -0,0 +1,52 @@ +import { isomorphicOnMessage } from "@wanderapp/isomorphic-messaging"; + +// Some backend handlers (`src/api/background/handlers/*`) will use `sendMessage(...)` to communicate with the +// `event.ts` content script, which in turn calls `postMessage()`, dispatches events or performs certain actions in the +// content script's context. +// +// In Wander Embedded, instead of using `onMessage`, we should listen for messages coming from the iframe itself. +// This also means that the background scripts, which in Wander Embedded run directly inside the iframe, need to be +// updated to send messages using `postMessage`. +// +// See https://stackoverflow.com/questions/16266474/javascript-listen-for-postmessage-events-from-specific-iframe + +export function setupEventListeners() { + // event emitter events + isomorphicOnMessage("event", ({ data, sender }) => { + if (sender.context !== "background") return; + + // send to mitt instance + postMessage({ + type: "wander_event", + event: data, + }); + }); + + // listen for wallet switches + /** @deprecated */ + isomorphicOnMessage("switch_wallet_event", ({ data, sender }) => { + if (sender.context !== "background") return; + + // dispatch custom event + dispatchEvent( + new CustomEvent("walletSwitch", { + detail: { address: data }, + }), + ); + }); + + // This will never be used for the embedded wallet anyway: + // Copy address in the content script (not possible in the background) + isomorphicOnMessage("copy_address", async ({ sender, data: addr }) => { + if (sender.context !== "background") return; + + const input = document.createElement("input"); + + input.value = addr; + + document.body.appendChild(input); + input.select(); + document.execCommand("Copy"); + document.body.removeChild(input); + }); +} diff --git a/libs/core/src/lib/injected-api/foreground/foreground-setup-wallet-sdk.ts b/libs/core/src/lib/injected-api/foreground/foreground-setup-wallet-sdk.ts new file mode 100644 index 000000000..e5c7fe531 --- /dev/null +++ b/libs/core/src/lib/injected-api/foreground/foreground-setup-wallet-sdk.ts @@ -0,0 +1,195 @@ +import type { ApiCall, ApiResponse, Event } from "shim"; +import { nanoid } from "nanoid"; +import mitt from "mitt"; +import { version } from "../../../../wander-wallet-api/package.json"; +import { isApiErrorResponse } from "@wanderapp/isomorphic-messaging"; +import { MittInjectedEvents } from "../../utils/events/events"; +import { version as sdkVersion } from "../../../wander-connect-sdk/package.json"; +import { ForegroundModule, foregroundModules } from "./foreground-modules"; + +export async function injectWanderWalletAPI(targetWindow: Window = window, embeddedOrigin?: string) { + log(LOG_GROUP.SETUP, "injectWanderWalletAPI()"); + + /** Init events */ + const events = mitt(); + + // TODO: Can we get the right type here?: + const walletAPI = { + walletName: import.meta.env?.VITE_IS_EMBEDDED_APP === "1" ? "Wander Connect" : "ArConnect", + walletVersion: version, + events, + } as const; + + for (const mod of foregroundModules) { + walletAPI[mod.functionName] = (...params: any[]) => { + return callForegroundThenBackground(mod, params); + }; + } + + /** + * Utility function to handle the actual "call-foreground-then-background" flow. + * This can be reused by the Proxy handler below. + */ + async function callForegroundThenBackground( + foregroundModule: string | ForegroundModule, + params: any[], + ): Promise { + return new Promise(async (resolve, reject) => { + // 1. Find out what function are we calling: + + const functionName = typeof foregroundModule === "string" ? foregroundModule : foregroundModule.functionName; + + // 2. Get & prepare its params: + // For `sign` and `dispatch`, this is where the params are send in chunks. + const functionParams = typeof foregroundModule === "string" ? params : await foregroundModule.function(...params); + + // TODO: Use a default function for those that do not have/need one and + // see if chunking can be done automatically or if it is needed at all: + + // 3. Prepare the message & payload to send: + const callID = nanoid(); + const data: ApiCall = { + app: import.meta.env?.VITE_IS_EMBEDDED_APP === "1" ? "wanderEmbedded" : "wander", + version, + callID, + type: `api_${functionName}`, + data: { + params: functionParams, + }, + }; + + // 4. Send message to background script (Wander BE) or to the iframe window (Wander Embedded): + + const targetOrigin = import.meta.env?.VITE_IS_EMBEDDED_APP === "1" ? embeddedOrigin : window.location.origin; + + targetWindow.postMessage(data, targetOrigin); + + // TODO: Note this is replacing the following from `api.content-script.ts`, so the logic to await and get the response is missing with just the + // one-line change above. + // + // const res = await sendMessage( + // data.type === "chunk" ? "chunk" : "api_call", + // data, + // "background" + // ); + // + // window.postMessage(res, window.location.origin); + + // TODO: Replace `postMessage` with `isomorphicSendMessage`, which should be updated to handle + // chunking automatically based on data size, rather than relying on `sendChunk` to be called from + // the foreground scripts manually. + + // 5. Wait for result from background: + window.addEventListener("message", callback); + + // TODO: Declare outside (factory) to facilitate testing? + async function callback(e: MessageEvent) { + // TODO: Make sure the response comes from targetWindow. + // See https://stackoverflow.com/questions/16266474/javascript-listen-for-postmessage-events-from-specific-iframe. + + const { data: res } = e; + + // validate return message + if (!data || `${data.type}_result` !== res.type) return; + + // only resolve when the result matching our callID is delivered + if (data.callID !== res.callID) return; + + window.removeEventListener("message", callback); + + // check for errors + if (isApiErrorResponse(res)) { + return reject(res.data); + } + + const finalizerFn = typeof foregroundModule === "string" ? null : foregroundModule.finalizer; + + // call the finalizer function if it exists + if (finalizerFn) { + try { + const finalizerResult = await finalizerFn(res.data, functionParams, params); + + // TODO: This is a bad check because the result could be falsy: + // if the finalizer transforms data + // update the result + if (finalizerResult) { + res.data = finalizerResult; + } + } catch (err) { + reject(err); + + return; + } + } + + resolve(res.data); + } + }); + } + + // @ts-expect-error + window.arweaveWallet = walletAPI; + + /** Handle events */ + window.addEventListener( + "message", + ( + e: MessageEvent<{ + type: "wander_event"; + event: Event; + }>, + ) => { + if (!e.data || !e.data.event || e.data.type !== "wander_event") return; + + events.emit(e.data.event.name, e.data.event.value); + }, + ); + + // at the end of the injected script, + // we dispatch the wallet loaded event + + async function dispatchArweaveWalletLoaded() { + if (!window.arweaveWallet || window.arweaveWallet.walletName !== "ArConnect") return; + + const permissions = await window.arweaveWallet.getPermissions().catch(() => []); + + // Note that for Wander Connect we just need to dispatch this once, no need to subscribe to the window load event to re-dispatch it: + dispatchEvent( + new CustomEvent("arweaveWalletLoaded", { + detail: { + permissions, + }, + }), + ); + + if (permissions.length > 0) { + events.emit("connect", null); + + const [activeAddress, addresses] = await Promise.all([ + window.arweaveWallet.getActiveAddress().catch(() => ""), + window.arweaveWallet.getAllAddresses().catch(() => []), + ]); + + events.emit("activeAddress", activeAddress); + events.emit("addresses", addresses); + } + } + + // Not sure there's a point in this, as the dApp will never be able to listen for it, but I'm maintaining the same structure we had before: + dispatchArweaveWalletLoaded(); + + // This doesn't work on page reload unless it's a hard load, as the event is dispatched earlier on reload, before the dApp can start listening, so... + // window.addEventListener("load", dispatchArweaveWalletLoaded); + + // ...we can instead monkey patch `window.addEventListener` and re-dispatch the "arweaveWalletLoaded" event as soon as a new listener is added: + + const addEventListener_ = Window.prototype.addEventListener; + + Window.prototype.addEventListener = function (eventName: string, ...args) { + if (eventName === "arweaveWalletLoaded") { + dispatchArweaveWalletLoaded(); + } + + addEventListener_.call(this, eventName, ...args); + }; +} diff --git a/libs/core/src/lib/injected-api/module.ts b/libs/core/src/lib/injected-api/module.ts new file mode 100644 index 000000000..6fe8b6c4b --- /dev/null +++ b/libs/core/src/lib/injected-api/module.ts @@ -0,0 +1,30 @@ +import type { PermissionType } from "../../../applications/permissions"; + +/** + * Why do we need separate modules? + * We don't want to include background functions in the injected + * script or functions from the injected script in the backgroud + * script. Tree shaking is not going to separate these functions, + * so instead we are handling them in two different files (foreground + * and background module files) so we can import them separately. + */ + +/** + * Basic API module props interface + */ +export interface ModuleProperties { + /** The name the function will be injected with into the API */ + functionName: string; + /** Permissions required to execute this function */ + permissions: PermissionType[]; +} + +/** + * Function type for background and injected script API functions + */ +export type ModuleFunction = (...params: any[]) => Promise | ResultType; + +/** Full API module (background/foreground) */ +export interface Module extends ModuleProperties { + function: ModuleFunction; +} diff --git a/libs/core/src/lib/injected-api/modules/README.md b/libs/core/src/lib/injected-api/modules/README.md new file mode 100644 index 000000000..d522c283a --- /dev/null +++ b/libs/core/src/lib/injected-api/modules/README.md @@ -0,0 +1,31 @@ +# Wander modules + +Wander modules construct the individual functions the injected API provides to dApps. A module consists of 3 parts: + +- The module declaration file: provides basic info about the module, such as name and required permissions +- The foreground script file: + - Includes a function that allows the transformation of the submitted params to the function, before sending the + transformed params to the background (runs in the injected script) + - Has an optional "finalizer" function that allows the transformation / validation of the data returned from the + background +- The background script file: has a function that handles the API call in the background (runs in the background script) + +Each module has to be added separately in the two module files (`background.ts` and `foreground.ts`). + +### Examples + +For basic examples on how to create a module, refer to the [example module](example/). + +## Message Passing + +### Wander Browser Extension + +There are 3 contexts here: + +- Background scripts (service worker). +- Content scripts (injected into the page but with its own isolated context). +- Injected scripts (injected into the page in a `