diff --git a/.changeset/big-planes-admire.md b/.changeset/big-planes-admire.md new file mode 100644 index 000000000..eb8f422d1 --- /dev/null +++ b/.changeset/big-planes-admire.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: changed siwe imports to solve issues on some android devices diff --git a/.changeset/bright-points-burn.md b/.changeset/bright-points-burn.md new file mode 100644 index 000000000..cc251e262 --- /dev/null +++ b/.changeset/bright-points-burn.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': minor +'@reown/appkit-coinbase-wagmi-react-native': minor +'@reown/appkit-scaffold-utils-react-native': minor +'@reown/appkit-auth-ethers-react-native': minor +'@reown/appkit-auth-wagmi-react-native': minor +'@reown/appkit-scaffold-react-native': minor +'@reown/appkit-ethers5-react-native': minor +'@reown/appkit-common-react-native': minor +'@reown/appkit-ethers-react-native': minor +'@reown/appkit-wallet-react-native': minor +'@reown/appkit-wagmi-react-native': minor +'@reown/appkit-core-react-native': minor +'@reown/appkit-siwe-react-native': minor +'@reown/appkit-ui-react-native': minor +--- + +feat: social login diff --git a/.changeset/config.json b/.changeset/config.json index 085ab9cc5..65878aa1d 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,6 +1,6 @@ { "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", - "changelog": ["@changesets/changelog-github", { "repo": "WalletConnect/web3modal-react-native" }], + "changelog": ["@changesets/changelog-github", { "repo": "reown-com/appkit-react-native" }], "access": "public", "baseBranch": "main", "commit": false, diff --git a/.changeset/gold-beers-applaud.md b/.changeset/gold-beers-applaud.md new file mode 100644 index 000000000..40ba2799d --- /dev/null +++ b/.changeset/gold-beers-applaud.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: watch token balance diff --git a/.changeset/grumpy-poems-tease.md b/.changeset/grumpy-poems-tease.md new file mode 100644 index 000000000..0ce6e3641 --- /dev/null +++ b/.changeset/grumpy-poems-tease.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: removed nft tab diff --git a/.changeset/mean-elephants-kick.md b/.changeset/mean-elephants-kick.md new file mode 100644 index 000000000..4092e90d0 --- /dev/null +++ b/.changeset/mean-elephants-kick.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: small changes for web diff --git a/.changeset/mean-spoons-confess.md b/.changeset/mean-spoons-confess.md new file mode 100644 index 000000000..92772f25e --- /dev/null +++ b/.changeset/mean-spoons-confess.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: show custom wallets in all wallets view diff --git a/.changeset/nervous-apes-laugh.md b/.changeset/nervous-apes-laugh.md new file mode 100644 index 000000000..c82d4d74a --- /dev/null +++ b/.changeset/nervous-apes-laugh.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: improved loading message in social connections diff --git a/.changeset/old-beers-agree.md b/.changeset/old-beers-agree.md new file mode 100644 index 000000000..4c944e903 --- /dev/null +++ b/.changeset/old-beers-agree.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': minor +'@reown/appkit-coinbase-wagmi-react-native': minor +'@reown/appkit-scaffold-utils-react-native': minor +'@reown/appkit-auth-ethers-react-native': minor +'@reown/appkit-auth-wagmi-react-native': minor +'@reown/appkit-scaffold-react-native': minor +'@reown/appkit-ethers5-react-native': minor +'@reown/appkit-common-react-native': minor +'@reown/appkit-ethers-react-native': minor +'@reown/appkit-wallet-react-native': minor +'@reown/appkit-wagmi-react-native': minor +'@reown/appkit-core-react-native': minor +'@reown/appkit-siwe-react-native': minor +'@reown/appkit-ui-react-native': minor +--- + +feat: added wallet features for universal wallets diff --git a/.changeset/pretty-taxis-type.md b/.changeset/pretty-taxis-type.md new file mode 100644 index 000000000..9f56a5164 --- /dev/null +++ b/.changeset/pretty-taxis-type.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: get custom token balance if provided diff --git a/.changeset/proud-starfishes-care.md b/.changeset/proud-starfishes-care.md new file mode 100644 index 000000000..4a1ba9387 --- /dev/null +++ b/.changeset/proud-starfishes-care.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: changed headers function in event controller diff --git a/.changeset/red-ladybugs-matter.md b/.changeset/red-ladybugs-matter.md new file mode 100644 index 000000000..7c994a734 --- /dev/null +++ b/.changeset/red-ladybugs-matter.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: bump walletconnect deps to 2.17.2 in ethers packages diff --git a/.changeset/serious-tables-compare.md b/.changeset/serious-tables-compare.md new file mode 100644 index 000000000..167a81ac8 --- /dev/null +++ b/.changeset/serious-tables-compare.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': minor +'@reown/appkit-coinbase-wagmi-react-native': minor +'@reown/appkit-scaffold-utils-react-native': minor +'@reown/appkit-auth-ethers-react-native': minor +'@reown/appkit-auth-wagmi-react-native': minor +'@reown/appkit-scaffold-react-native': minor +'@reown/appkit-ethers5-react-native': minor +'@reown/appkit-common-react-native': minor +'@reown/appkit-ethers-react-native': minor +'@reown/appkit-wallet-react-native': minor +'@reown/appkit-wagmi-react-native': minor +'@reown/appkit-core-react-native': minor +'@reown/appkit-siwe-react-native': minor +'@reown/appkit-ui-react-native': minor +--- + +feat: smart accounts for social login diff --git a/.changeset/sixty-comics-greet.md b/.changeset/sixty-comics-greet.md new file mode 100644 index 000000000..6efe115f6 --- /dev/null +++ b/.changeset/sixty-comics-greet.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: updated packages info diff --git a/.changeset/violet-lamps-chew.md b/.changeset/violet-lamps-chew.md new file mode 100644 index 000000000..e69e3d5f6 --- /dev/null +++ b/.changeset/violet-lamps-chew.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: ui changes in social webview to solve android issues diff --git a/.changeset/warm-camels-cheat.md b/.changeset/warm-camels-cheat.md new file mode 100644 index 000000000..58b91bbf5 --- /dev/null +++ b/.changeset/warm-camels-cheat.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: show error message and retry button in case wallet fetch fails diff --git a/.changeset/weak-poems-play.md b/.changeset/weak-poems-play.md new file mode 100644 index 000000000..896f956c4 --- /dev/null +++ b/.changeset/weak-poems-play.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: updated ethereum provider to 2.17.3 diff --git a/.changeset/witty-taxis-smell.md b/.changeset/witty-taxis-smell.md new file mode 100644 index 000000000..d825b7102 --- /dev/null +++ b/.changeset/witty-taxis-smell.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: show wallet view for email wallets diff --git a/.changeset/young-ducks-approve.md b/.changeset/young-ducks-approve.md new file mode 100644 index 000000000..63a64ef38 --- /dev/null +++ b/.changeset/young-ducks-approve.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +feat: improved provider error handling diff --git a/.eslintrc.json b/.eslintrc.json index d53181cc9..f4fb725c4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { "root": true, - "extends": ["@react-native", "prettier"], + "extends": ["@react-native", "prettier", "plugin:valtio/recommended"], "ignorePatterns": ["node_modules/", "build/", "lib/", "dist/", ".turbo", ".expo", "out/"], "rules": { "react/react-in-jsx-scope": 0, diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index fa8f9abf3..696be3602 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -11,7 +11,7 @@ body: id: description attributes: label: Description - description: Please provide a clear and concise description of what the bug is. Include screenshots if needed. Test using the [latest SDK release](https://github.com/WalletConnect/web3modal-react-native/releases) to make sure your issue has not already been fixed. + description: Please provide a clear and concise description of what the bug is. Include screenshots if needed. Test using the [latest SDK release](https://github.com/reown-com/appkit-react-native/releases) to make sure your issue has not already been fixed. validations: required: true - type: input @@ -19,7 +19,7 @@ body: attributes: label: AppKit SDK version description: What is the latest version of AppKit SDK that this issue reproduces on? - placeholder: ex. @web3modal/wagmi-react-native 1.0.0 + placeholder: ex. @reown/appkit-wagmi-react-native 1.0.0 validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bb72647ae..45d78a5b3 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: false contact_links: - name: 🤔 Questions and Help - url: https://github.com/orgs/WalletConnect/discussions/categories/web3modal-sdk-support - about: Looking for help with your app? Please refer to the Wallet Connect community's support resources. - - name: 🚀 Discussions and Proposals - url: https://github.com/orgs/WalletConnect/discussions - about: Discuss the future of AppKit SDK in the Wallet Connect community's discussions and proposals repository. + url: https://github.com/orgs/reown-com/discussions/categories/web3modal-sdk-support + about: Looking for help with your app? Please refer to the Reown community's support resources. diff --git a/.github/docs/development.md b/.github/docs/development.md index 6b6c7a2e1..26e5dde93 100644 --- a/.github/docs/development.md +++ b/.github/docs/development.md @@ -8,7 +8,7 @@ Install dependencies from the repository's root directory (this will also set up yarn ``` -To create your ProjectID, head to [cloud.walletconnect.com](https://cloud.walletconnect.com/) +To create your ProjectID, head to [cloud.reown.com](https://cloud.reown.com/) ## Commands diff --git a/.github/workflows/changesets.yml b/.github/workflows/changesets.yml index 91fe32bf9..a104bc4e6 100644 --- a/.github/workflows/changesets.yml +++ b/.github/workflows/changesets.yml @@ -24,6 +24,10 @@ jobs: - name: uses: ./.github/actions/setup + - name: E2E Tests + uses: ./.github/workflows/e2e.yml + secrets: inherit + - name: Create Release Pull Request or Publish to NPM id: changesets uses: changesets/action@v1 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 19617cf19..3ab77cd0d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,38 +1,71 @@ -name: e2e +name: E2E Tests + on: workflow_dispatch: - pull_request: - branches: - - main + workflow_call: + inputs: + base-url: + description: 'The AppKit App url' + default: 'http://localhost:8081/' + required: false + type: string + wallet-url: + description: 'The wallet url' + default: 'https://react-wallet.walletconnect.com/' + required: false + type: string + secrets: + CLOUD_PROJECT_ID: + required: true jobs: - maestro-cloud: + e2e_tests: + name: 'Playwright Tests' runs-on: ubuntu-latest - defaults: - run: - working-directory: apps/native-cli - outputs: - app: app/build/outputs/apk/release steps: - name: Checkout - uses: actions/checkout@v3 - - - name: Install Java 11 - uses: actions/setup-java@v3 - with: - java-version: 11 - distribution: 'temurin' - cache: gradle - - - run: touch .env && echo "PROJECT_ID=${{ secrets.CLOUD_PROJECT_ID }}" >> .env + uses: actions/checkout@v4 - name: Setup uses: ./.github/actions/setup - - run: yarn android:build + - name: Build SDK + run: | + echo "Building SDK..." + yarn build + + - name: Create ENV file in apps/native + working-directory: ./apps/native/ + run: echo "EXPO_PUBLIC_PROJECT_ID=${{ secrets.CLOUD_PROJECT_ID }}" >> .env + + - name: Install Playwright Browsers + working-directory: ./apps/native/ + run: yarn playwright install chromium + + ## Uncomment to build the web and add ./apps/native/dist to upload the artifact + # - name: Build web app + # working-directory: ./apps/native/ + # run: | + # echo "Building web app..." + # yarn build:web + + - name: Run Playwright tests + working-directory: ./apps/native/ + env: + BASE_URL: ${{ inputs.base-url }} + WALLET_URL: ${{ inputs.wallet-url }} + + ## Uncomment to see better logs in the terminal + # DEBUG: pw:api + run: | + echo "Running tests against $BASE_URL" + yarn playwright:test - - uses: mobile-dev-inc/action-maestro-cloud@v1 + - uses: actions/upload-artifact@v4 + if: failure() with: - api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} - app-file: apps/native-cli/android/app/build/outputs/apk/release/app-release.apk - android-api-level: 31 + name: playwright-report + path: | + ./apps/native/playwright-report/ + ./apps/native/test-results/ + retention-days: 7 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 64e1a66f0..536d7e32c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,3 +12,7 @@ jobs: name: Verify uses: ./.github/workflows/verify.yml secrets: inherit + + e2e: + uses: ./.github/workflows/e2e.yml + secrets: inherit diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 110c2f250..b8d1ab43e 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -20,7 +20,7 @@ jobs: uses: ./.github/actions/setup - name: Publish Snapshots - continue-on-error: true + continue-on-error: false env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 8cae68d3a..b2629a578 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -13,5 +13,14 @@ jobs: - name: Setup uses: ./.github/actions/setup - - name: Pre-Build package - run: yarn run changeset:prepublish + - name: Lint + run: yarn lint + + - name: Prettier + run: yarn prettier + + - name: Build + run: yarn build + + - name: Package Tests + run: yarn test diff --git a/.maestro/w3m-connect-flow.yaml b/.maestro/w3m-connect-flow.yaml index 11cf64d54..155431e5a 100644 --- a/.maestro/w3m-connect-flow.yaml +++ b/.maestro/w3m-connect-flow.yaml @@ -9,7 +9,7 @@ appId: com.walletconnect.web3modal.rnclisdk - tapOn: 'Connect' - tapOn: - id: 'button-all-wallets' + id: 'all-wallets' - tapOn: id: 'button-qr-code' @@ -18,7 +18,7 @@ appId: com.walletconnect.web3modal.rnclisdk id: 'qr-code' - tapOn: - id: 'button-copy-uri' + id: 'copy-link' - openLink: link: https://react-wallet.walletconnect.com/walletconnect @@ -54,10 +54,10 @@ appId: com.walletconnect.web3modal.rnclisdk - back - tapOn: - id: 'button-account' + id: 'account-button' - tapOn: - id: 'button-network' + id: 'w3m-account-select-network' - tapOn: text: 'Polygon' @@ -68,7 +68,7 @@ appId: com.walletconnect.web3modal.rnclisdk text: 'Polygon' - tapOn: - id: 'button-disconnect' + id: 'disconnect-button' - assertVisible: - id: 'button-connect' + id: 'connect-button' diff --git a/.maestro/w3m-ui-flow.yaml b/.maestro/w3m-ui-flow.yaml index 465b6bf7e..161afd6bc 100644 --- a/.maestro/w3m-ui-flow.yaml +++ b/.maestro/w3m-ui-flow.yaml @@ -19,7 +19,7 @@ appId: com.walletconnect.web3modal.rnclisdk id: 'button-back' - tapOn: - id: 'button-all-wallets' + id: 'all-wallets' - tapOn: id: 'button-qr-code' @@ -31,7 +31,7 @@ appId: com.walletconnect.web3modal.rnclisdk id: 'button-back' - tapOn: - id: 'input-search' + id: 'search-wallet-input' # Get a wallet that doesn't come in the first page - inputText: 'Abra Wallet' @@ -57,7 +57,7 @@ appId: com.walletconnect.web3modal.rnclisdk text: 'Open' - tapOn: - id: 'button-close' + id: 'header-close' - assertNotVisible: text: 'Abra Wallet' diff --git a/README.md b/README.md index 6e6bca529..85ca1bb67 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples/tree/main/dapps/W3MWagmi) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples/) #### 🔗 [Website](https://reown.com/appkit) @@ -8,7 +8,7 @@ Your on-ramp to web3 multichain. AppKit is a versatile library that makes it super easy to connect users with your Dapp and start interacting with the blockchain. -![Native](https://github.com/WalletConnect/web3modal-react-native/assets/25931366/d474f3dc-a881-4c16-9f91-cda875d962c1) +![Native](https://github.com/reown-com/appkit-react-native/assets/25931366/d474f3dc-a881-4c16-9f91-cda875d962c1) ## Development diff --git a/apps/gallery/.storybook/main.ts b/apps/gallery/.storybook/main.ts index cf70202b0..72fa226ee 100644 --- a/apps/gallery/.storybook/main.ts +++ b/apps/gallery/.storybook/main.ts @@ -8,8 +8,7 @@ function getAbsolutePath(value) { return dirname(require.resolve(join(value, 'package.json'))); } -/** @type { import('@storybook/react-webpack5').StorybookConfig } */ -const config = { +const config: import('@storybook/react-webpack5').StorybookConfig = { stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: [ @@ -18,7 +17,7 @@ const config = { getAbsolutePath('@storybook/addon-onboarding'), getAbsolutePath('@storybook/addon-interactions'), getAbsolutePath('@storybook/addon-react-native-web'), - getAbsolutePath("@storybook/addon-webpack5-compiler-babel"), + getAbsolutePath('@storybook/addon-webpack5-compiler-babel'), '@chromatic-com/storybook' ], diff --git a/apps/gallery/stories/composites/AccountButton.stories.tsx b/apps/gallery/stories/composites/AccountButton.stories.tsx index 807c542f3..d9d45f862 100644 --- a/apps/gallery/stories/composites/AccountButton.stories.tsx +++ b/apps/gallery/stories/composites/AccountButton.stories.tsx @@ -12,8 +12,8 @@ const meta: Meta = { address: { control: { type: 'text' } }, - isProfileName: { - control: { type: 'boolean' } + profileName: { + control: { type: 'text' } }, networkSrc: { control: { type: 'text' } @@ -26,8 +26,7 @@ const meta: Meta = { avatarSrc: avatarImageSrc, address: '0xDBbD65026a07cFbFa1aa92744E4D69951686077d', networkSrc: networkImageSrc, - balance: '0.527 ETH', - isProfileName: false + balance: '0.527 ETH' } }; @@ -41,7 +40,7 @@ export const Default: Story = { address={args.address} networkSrc={args.networkSrc} balance={args.balance} - isProfileName={args.isProfileName} + profileName={args.profileName} /> ) }; diff --git a/apps/gallery/stories/composites/Chip.stories.tsx b/apps/gallery/stories/composites/Chip.stories.tsx index cd84fbc5c..23419deed 100644 --- a/apps/gallery/stories/composites/Chip.stories.tsx +++ b/apps/gallery/stories/composites/Chip.stories.tsx @@ -1,13 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Chip } from '@reown/appkit-ui-react-native'; -import { - chipOptions, - externalLabel, - externalLink, - iconOptions, - walletImageSrc -} from '../../utils/PresetUtils'; +import { chipOptions, externalLabel, iconOptions, walletImageSrc } from '../../utils/PresetUtils'; const meta: Meta = { component: Chip, @@ -23,16 +17,13 @@ const meta: Meta = { disabled: { control: { type: 'boolean' } }, - link: { - control: { type: 'text' } - }, label: { control: { type: 'text' } }, imageSrc: { control: { type: 'text' } }, - icon: { + rightIcon: { options: iconOptions, control: { type: 'select' } } @@ -41,8 +32,7 @@ const meta: Meta = { variant: 'fill', size: 'md', disabled: false, - icon: 'disconnect', - link: externalLink, + rightIcon: 'disconnect', label: externalLabel, imageSrc: walletImageSrc } @@ -57,10 +47,10 @@ export const Default: Story = { variant={args.variant} size={args.size} disabled={args.disabled} - link={args.link} + onPress={() => {}} label={args.label} imageSrc={args.imageSrc} - icon={args.icon} + rightIcon={args.rightIcon} /> ) }; diff --git a/apps/gallery/stories/composites/ListItem.stories.tsx b/apps/gallery/stories/composites/ListItem.stories.tsx index 70c0883fc..f6fa9a9bf 100644 --- a/apps/gallery/stories/composites/ListItem.stories.tsx +++ b/apps/gallery/stories/composites/ListItem.stories.tsx @@ -10,18 +10,10 @@ const meta: Meta = { imageSrc: { control: { type: 'text' } }, - variant: { - options: ['image', 'icon'], - control: { type: 'select' } - }, icon: { options: iconOptions, control: { type: 'select' } }, - iconVariant: { - options: ['blue', 'overlay'], - control: { type: 'select' } - }, disabled: { control: { type: 'boolean' } }, @@ -33,10 +25,8 @@ const meta: Meta = { } }, args: { - variant: 'image', imageSrc: networkImageSrc, icon: 'swapHorizontal', - iconVariant: 'blue', disabled: false, chevron: true, loading: false @@ -50,10 +40,8 @@ export const Default: Story = { render: (args: any) => ( = { + component: ListSocial, + argTypes: { + logo: { + options: logoOptions, + control: { type: 'select' } + }, + disabled: { + control: { type: 'boolean' } + } + }, + args: { + logo: 'x', + disabled: false + } +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: (args: any) => ( + + + + Continue with{' '} + + {args.logo} + + + + + ) +}; + +const styles = StyleSheet.create({ + text: { + textAlign: 'center' + }, + social: { + textTransform: 'capitalize' + } +}); diff --git a/apps/gallery/stories/composites/NetworkButton.stories.tsx b/apps/gallery/stories/composites/NetworkButton.stories.tsx index ba86b40ca..b6c580fc2 100644 --- a/apps/gallery/stories/composites/NetworkButton.stories.tsx +++ b/apps/gallery/stories/composites/NetworkButton.stories.tsx @@ -6,10 +6,6 @@ import { networkImageSrc } from '../../utils/PresetUtils'; const meta: Meta = { component: NetworkButton, argTypes: { - variant: { - options: ['fill', 'shade'], - control: { type: 'select' } - }, disabled: { control: { type: 'boolean' } }, @@ -21,7 +17,6 @@ const meta: Meta = { } }, args: { - variant: 'fill', disabled: false, children: 'Ethereum', imageSrc: networkImageSrc @@ -33,12 +28,7 @@ type Story = StoryObj; export const Default: Story = { render: args => ( - {}} - > + {}}> {args.children} ) diff --git a/apps/gallery/utils/PresetUtils.ts b/apps/gallery/utils/PresetUtils.ts index 10da20dc2..038fc6fd3 100644 --- a/apps/gallery/utils/PresetUtils.ts +++ b/apps/gallery/utils/PresetUtils.ts @@ -32,8 +32,6 @@ export const avatarImageSrc = export const wcUri = 'wc:139520827546986d057472f8bbd7ef0484409458034b61cca59d908563773c7a@2?relay-protocol=irn&symKey=43b5fad11bf07bc8a0aa12231435a4ad3e72e2d1fa257cf191a90ec5b62cb0a'; -export const externalLink = 'https://www.fireblocks.com'; - export const externalLabel = 'www.fireblocks.com'; export const colorOptions: ColorType[] = [ @@ -151,6 +149,8 @@ export const iconOptions: IconType[] = [ 'extension', 'externalLink', 'facebook', + 'farcaster', + 'farcasterSquare', 'filters', 'github', 'google', @@ -158,6 +158,7 @@ export const iconOptions: IconType[] = [ 'infoCircle', 'mail', 'mobile', + 'more', 'networkPlaceholder', 'nftPlaceholder', 'off', @@ -168,11 +169,10 @@ export const iconOptions: IconType[] = [ 'swapVertical', 'telegram', 'twitch', - 'twitterIcon', - 'twitter', 'wallet', 'walletSmall', 'walletConnect', + 'walletConnectLightBrown', 'walletPlaceholder', 'warningCircle' ]; @@ -197,11 +197,13 @@ export const logoOptions: LogoType[] = [ 'apple', 'discord', 'facebook', + 'farcaster', 'github', 'google', + 'more', 'telegram', 'twitch', - 'twitter' + 'x' ]; export const cardSelectOptions: CardSelectType[] = ['wallet', 'network']; diff --git a/apps/native/.gitignore b/apps/native/.gitignore index e8be304f9..502848c55 100644 --- a/apps/native/.gitignore +++ b/apps/native/.gitignore @@ -14,3 +14,7 @@ web-build/ .DS_Store .env +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/apps/native/App.tsx b/apps/native/App.tsx index c35521764..264779f01 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -1,9 +1,10 @@ -import { StyleSheet, View, useColorScheme } from 'react-native'; +import { Platform, SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import * as Clipboard from 'expo-clipboard'; import '@walletconnect/react-native-compat'; import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import Toast from 'react-native-toast-message'; import { AppKit, @@ -21,17 +22,19 @@ import { AccountView } from './src/views/AccountView'; import { ActionsView } from './src/views/ActionsView'; import { getCustomWallets } from './src/utils/misc'; import { chains } from './src/utils/WagmiUtils'; +import { OpenButton } from './src/components/OpenButton'; +import { DisconnectButton } from './src/components/DisconnectButton'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; const metadata = { name: 'AppKit RN', - description: 'AppKit RN by WalletConnect', - url: 'https://walletconnect.com/', - icons: ['https://avatars.githubusercontent.com/u/37784886'], + description: 'AppKit RN by Reown', + url: 'https://reown.com/appkit', + icons: ['https://avatars.githubusercontent.com/u/179229932'], redirect: { native: 'redirect://', - universal: 'https://lab.web3modal.com/rn_appkit', + universal: 'https://appkit-lab.reown.com/rn_appkit', linkMode: true } }; @@ -44,11 +47,17 @@ const clipboardClient = { const auth = authConnector({ projectId, metadata }); +const extraConnectors = Platform.select({ + ios: [auth], + android: [auth], + default: [] +}); + const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata, - extraConnectors: [auth] + extraConnectors }); const queryClient = new QueryClient(); @@ -62,7 +71,13 @@ createAppKit({ clipboardClient, customWallets, enableAnalytics: true, - metadata + metadata, + debug: true, + features: { + email: true, + socials: ['x', 'discord', 'apple'], + emailShowWallets: true + } }); export default function Native() { @@ -71,7 +86,7 @@ export default function Native() { return ( - + - + + + - + + ); diff --git a/apps/native/app.json b/apps/native/app.json index 52c6b4f97..cd0024e83 100644 --- a/apps/native/app.json +++ b/apps/native/app.json @@ -74,7 +74,12 @@ } }, "web": { - "favicon": "./assets/favicon.png" + "favicon": "./assets/favicon.png", + "output": "single", + "bundler": "metro" + }, + "experiments": { + "baseUrl": "." }, "extra": { "eas": { diff --git a/apps/native/babel.config.js b/apps/native/babel.config.js index 98ae29e0e..425262ac6 100644 --- a/apps/native/babel.config.js +++ b/apps/native/babel.config.js @@ -1,8 +1,11 @@ const path = require('path'); -const uipak = require('../../packages/ui/package.json'); -const corepak = require('../../packages/core/package.json'); -const scaffoldpak = require('../../packages/scaffold/package.json'); -const wagmipak = require('../../packages/wagmi/package.json'); +const uipack = require('../../packages/ui/package.json'); +const corepack = require('../../packages/core/package.json'); +const scaffoldpack = require('../../packages/scaffold/package.json'); +const wagmipack = require('../../packages/wagmi/package.json'); +const authpack = require('../../packages/auth-wagmi/package.json'); +const commonpack = require('../../packages/common/package.json'); +const siwepack = require('../../packages/siwe/package.json'); module.exports = function (api) { api.cache(true); @@ -15,11 +18,18 @@ module.exports = function (api) { { extensions: ['.tsx', '.ts', '.js', '.json'], alias: { - // For development, we want to alias the ui library to the source - [uipak.name]: path.join(__dirname, '../../packages/ui', uipak.source), - [corepak.name]: path.join(__dirname, '../../packages/core', corepak.source), - [scaffoldpak.name]: path.join(__dirname, '../../packages/scaffold', scaffoldpak.source), - [wagmipak.name]: path.join(__dirname, '../../packages/wagmi', wagmipak.source) + // For development, we want to alias the packages to the source + [uipack.name]: path.join(__dirname, '../../packages/ui', uipack.source), + [corepack.name]: path.join(__dirname, '../../packages/core', corepack.source), + [scaffoldpack.name]: path.join( + __dirname, + '../../packages/scaffold', + scaffoldpack.source + ), + [wagmipack.name]: path.join(__dirname, '../../packages/wagmi', wagmipack.source), + [authpack.name]: path.join(__dirname, '../../packages/auth-wagmi', authpack.source), + [commonpack.name]: path.join(__dirname, '../../packages/common', commonpack.source), + [siwepack.name]: path.join(__dirname, '../../packages/siwe', siwepack.source) } } ] diff --git a/apps/native/index.js b/apps/native/index.js index 1d6e981ef..67732634f 100644 --- a/apps/native/index.js +++ b/apps/native/index.js @@ -1,3 +1,4 @@ +import '@expo/metro-runtime'; import { registerRootComponent } from 'expo'; import App from './App'; diff --git a/apps/native/package.json b/apps/native/package.json index 1116eecfb..7afbad09e 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -12,9 +12,14 @@ "dev:android": "expo start --android", "eas:build": "eas build --platform all", "eas:build:local": "eas build --local --platform all", - "eas:update": "eas update --branch preview" + "eas:update": "eas update --branch preview", + "playwright:test": "playwright test", + "playwright:install": "playwright install chromium", + "deploy": "gh-pages --nojekyll -d dist", + "build:web": "expo export -p web" }, "dependencies": { + "@expo/metro-runtime": "~3.2.3", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/netinfo": "11.3.1", "@reown/appkit-auth-wagmi-react-native": "1.0.2", @@ -22,7 +27,7 @@ "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-query-persist-client": "5.56.2", - "@walletconnect/react-native-compat": "2.16.1", + "@walletconnect/react-native-compat": "2.17.1", "expo": "~51.0.24", "expo-application": "~5.9.1", "expo-clipboard": "~6.0.3", @@ -34,17 +39,22 @@ "react-native-get-random-values": "~1.11.0", "react-native-modal": "13.0.1", "react-native-svg": "15.2.0", + "react-native-toast-message": "2.2.1", "react-native-web": "~0.19.10", "react-native-webview": "13.8.6", "uuid": "3.4.0", - "viem": "2.21.6", - "wagmi": "2.12.11" + "viem": "2.21.48", + "wagmi": "2.12.33" }, "devDependencies": { "@babel/core": "^7.24.0", + "@playwright/test": "^1.49.1", + "@types/gh-pages": "^6", + "@types/node": "^22.10.1", "@types/react": "~18.2.79", "@types/react-native": "0.72.2", "babel-plugin-module-resolver": "^5.0.0", + "gh-pages": "^6.2.0", "typescript": "~5.3.3" } } diff --git a/apps/native/playwright.config.ts b/apps/native/playwright.config.ts new file mode 100644 index 000000000..081c8bed3 --- /dev/null +++ b/apps/native/playwright.config.ts @@ -0,0 +1,68 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [['html'], ['list']], + + use: { + baseURL: process.env.BASE_URL || 'http://localhost:8081', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Take a screenshot when the test fails */ + screenshot: 'only-on-failure', + + permissions: ['clipboard-read', 'clipboard-write'], + navigationTimeout: 30000, + actionTimeout: 30000, + video: 'retain-on-failure' + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'], channel: 'chromium' } + } + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn web', + url: 'http://localhost:8081', + reuseExistingServer: !process.env.CI, + timeout: 30000 + + /* Uncomment to see better logs in the terminal */ + // stdout: 'pipe', + // stderr: 'pipe' + }, + + globalTimeout: 600000, + expect: { + timeout: 10000 + } +}); diff --git a/apps/native/src/components/DisconnectButton.tsx b/apps/native/src/components/DisconnectButton.tsx new file mode 100644 index 000000000..d2c002ba7 --- /dev/null +++ b/apps/native/src/components/DisconnectButton.tsx @@ -0,0 +1,21 @@ +import { StyleSheet } from 'react-native'; +import { Button } from '@reown/appkit-ui-react-native'; +import { useAccount, useDisconnect } from 'wagmi'; + +export function DisconnectButton() { + const { isConnected } = useAccount(); + const { disconnect } = useDisconnect(); + + return isConnected ? ( + + ) : null; +} + +const styles = StyleSheet.create({ + button: { + height: 40, + marginVertical: 8 + } +}); diff --git a/apps/native/src/components/OpenButton.tsx b/apps/native/src/components/OpenButton.tsx new file mode 100644 index 000000000..c79b2ed9a --- /dev/null +++ b/apps/native/src/components/OpenButton.tsx @@ -0,0 +1,22 @@ +import { StyleSheet } from 'react-native'; +import { Button } from '@reown/appkit-ui-react-native'; +import { useAppKit } from '@reown/appkit-wagmi-react-native'; +import { useAccount } from 'wagmi'; + +export function OpenButton() { + const { open } = useAppKit(); + const { isConnected } = useAccount(); + + return !isConnected ? ( + + ) : null; +} + +const styles = StyleSheet.create({ + button: { + height: 40, + marginVertical: 8 + } +}); diff --git a/apps/native/src/utils/ToastUtils.ts b/apps/native/src/utils/ToastUtils.ts new file mode 100644 index 000000000..178adc04d --- /dev/null +++ b/apps/native/src/utils/ToastUtils.ts @@ -0,0 +1,18 @@ +import Toast from 'react-native-toast-message'; + +export const ToastUtils = { + showSuccessToast: (title: string, message: string) => { + Toast.show({ + type: 'success', + text1: title, + text2: message + }); + }, + showErrorToast: (title: string, message: string) => { + Toast.show({ + type: 'error', + text1: title, + text2: message + }); + } +}; diff --git a/apps/native/src/utils/WagmiUtils.ts b/apps/native/src/utils/WagmiUtils.ts index ef523b153..f2b1da93c 100644 --- a/apps/native/src/utils/WagmiUtils.ts +++ b/apps/native/src/utils/WagmiUtils.ts @@ -14,7 +14,7 @@ import { sepolia } from 'wagmi/chains'; -export const chains = [ +export const chains: CreateConfigParameters['chains'] = [ mainnet, polygon, avalanche, @@ -27,4 +27,4 @@ export const chains = [ celo, aurora, sepolia -] as CreateConfigParameters['chains']; +]; diff --git a/apps/native/src/utils/misc.ts b/apps/native/src/utils/misc.ts index d9cf4b2c2..8df316f11 100644 --- a/apps/native/src/utils/misc.ts +++ b/apps/native/src/utils/misc.ts @@ -6,17 +6,17 @@ export const getCustomWallets = () => { id: 'rn-wallet', name: 'Wallet(RN)', image_url: - 'https://docs.walletconnect.com/assets/images/web3walletLogo-54d3b546146931ceaf47a3500868a73a.png', + 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', mobile_link: 'rn-web3wallet://', - link_mode: 'https://lab.web3modal.com/rn_walletkit' + link_mode: 'https://appkit-lab.reown.com/rn_walletkit' }, { id: 'flutter-wallet', name: 'Wallet(Flutter)', image_url: - 'https://docs.walletconnect.com/assets/images/web3walletLogo-54d3b546146931ceaf47a3500868a73a.png', + 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', mobile_link: 'wcflutterwallet://', - link_mode: 'https://lab.web3modal.com/flutter_walletkit' + link_mode: 'https://appkit-lab.reown.com/flutter_walletkit' } ]; @@ -25,18 +25,18 @@ export const getCustomWallets = () => { id: 'android-wallet', name: 'Wallet(Android)', image_url: - 'https://docs.walletconnect.com/assets/images/web3walletLogo-54d3b546146931ceaf47a3500868a73a.png', + 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', mobile_link: 'kotlin-web3wallet://', - link_mode: 'https://lab.web3modal.com/wallet' + link_mode: 'https://appkit-lab.reown.com/wallet' }); } else if (Platform.OS === 'ios') { wallets.push({ id: 'ios-wallet', name: 'Wallet(iOS)', image_url: - 'https://docs.walletconnect.com/assets/images/web3walletLogo-54d3b546146931ceaf47a3500868a73a.png', + 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', mobile_link: 'walletapp://', - link_mode: 'https://lab.web3modal.com/wallet' + link_mode: 'https://appkit-lab.reown.com/wallet' }); } diff --git a/apps/native/src/views/AccountView.tsx b/apps/native/src/views/AccountView.tsx index 1f5c8d975..c75c2896b 100644 --- a/apps/native/src/views/AccountView.tsx +++ b/apps/native/src/views/AccountView.tsx @@ -1,5 +1,5 @@ -import { View } from 'react-native'; -import { Text } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; +import { Text, FlexView } from '@reown/appkit-ui-react-native'; import { useAccount, useBalance } from 'wagmi'; export function AccountView() { @@ -7,15 +7,28 @@ export function AccountView() { const { data, isLoading } = useBalance({ address }); return isConnected ? ( - - Wagmi Account Info - {isConnected && {address}} - {isLoading && Fetching balance...} + + Wagmi Account Info + + Address: + {isConnected && {address}} + + {isLoading && Fetching balance...} {data && ( - - Balance: {data?.formatted} {data?.symbol} - + + Balance: + + {data?.formatted} {data?.symbol} + + )} - + ) : null; } + +const styles = StyleSheet.create({ + container: { + marginTop: 32, + gap: 8 + } +}); diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index 84b68d819..ba284e2b0 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -1,39 +1,76 @@ -import { Button, Text } from '@reown/appkit-ui-react-native'; -import { View } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; import { useSignMessage, useAccount, useSendTransaction, useEstimateGas } from 'wagmi'; import { Hex, parseEther } from 'viem'; +import { SendTransactionData, SignMessageData } from 'wagmi/query'; +import { ToastUtils } from '../utils/ToastUtils'; export function ActionsView() { const { isConnected } = useAccount(); - const { data, isError, isPending, isSuccess, signMessage } = useSignMessage(); + + const onSignSuccess = (data: SignMessageData) => { + ToastUtils.showSuccessToast('Signature successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Signature failed', error.message); + }; + + const onSendSuccess = (data: SendTransactionData) => { + ToastUtils.showSuccessToast('Transaction successful', data); + }; + + const onSendError = (error: Error) => { + ToastUtils.showErrorToast('Transaction failed', error.message); + }; + + const { isPending, signMessage } = useSignMessage({ + mutation: { + onSuccess: onSignSuccess, + onError: onSignError + } + }); const TX = { - to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' as Hex, // vitalik.eth + to: '0x704457b418E9Fb723e1Bc0cB98106a6B8Cf87689' as Hex, // Test wallet value: parseEther('0.001'), data: '0x' as Hex }; + const { data: gas, isError: isGasError } = useEstimateGas(TX); const { - data: sendData, isPending: isSending, - isSuccess: isSendSuccess, + sendTransaction - } = useSendTransaction(); + } = useSendTransaction({ + mutation: { + onSuccess: onSendSuccess, + onError: onSendError + } + }); return isConnected ? ( - - Wagmi Actions - - {isSuccess && Signature: {data}} {isGasError && Error estimating gas} - {isError && Error signing message} {isSending && Check Wallet} - {isSendSuccess && Transaction: {JSON.stringify(sendData)}} - + ) : null; } + +const styles = StyleSheet.create({ + container: { + marginTop: 16, + gap: 8 + } +}); diff --git a/apps/native/tests/basic-tests.spec.ts b/apps/native/tests/basic-tests.spec.ts new file mode 100644 index 000000000..22eacb035 --- /dev/null +++ b/apps/native/tests/basic-tests.spec.ts @@ -0,0 +1,56 @@ +import { test, BrowserContext, Page } from '@playwright/test'; +import { ModalPage } from './shared/pages/ModalPage'; +import { ModalValidator } from './shared/validators/ModalValidator'; + +const METAMASK_WALLET_ID = 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96'; + +let context: BrowserContext; +let browserPage: Page; +let modalPage: ModalPage; +let modalValidator: ModalValidator; + +test.beforeAll(async ({ browser }) => { + context = await browser.newContext(); + browserPage = await context.newPage(); + + modalPage = new ModalPage(browserPage); + modalValidator = new ModalValidator(modalPage.page); + + await modalPage.load(); +}); + +test('Should be able to open modal', async () => { + await modalPage.openConnectModal(); + await modalValidator.expectConnectScreen(); + await modalPage.closeModal(); +}); + +test('Should be able to open network view', async () => { + await modalPage.openNetworkModal(); + await modalValidator.expectNetworkScreen(); + await modalPage.closeModal(); +}); + +test('Should be able to open modal with the open hook', async () => { + const openHookButton = modalPage.page.getByTestId('open-hook-button'); + await openHookButton.click(); + await modalValidator.expectConnectScreen(); + await modalPage.closeModal(); +}); + +test('it should display what is a wallet view', async () => { + await modalPage.openConnectModal(); + await modalValidator.expectWhatIsAWalletButton(); + await modalPage.clickWhatIsAWalletButton(); + await modalValidator.expectWhatIsAWalletView(); + await modalPage.clickGetAWalletButton(); + await modalValidator.expectGetAWalletView(); + await modalPage.closeModal(); +}); + +test('it should search for a wallet', async () => { + await modalPage.openConnectModal(); + await modalValidator.expectConnectScreen(); + await modalPage.searchWalletFlow(modalPage, 'MetaMask', METAMASK_WALLET_ID); + await modalPage.closeModal(); +}); diff --git a/apps/native/tests/shared/constants/index.ts b/apps/native/tests/shared/constants/index.ts new file mode 100644 index 000000000..dc2550fef --- /dev/null +++ b/apps/native/tests/shared/constants/index.ts @@ -0,0 +1,11 @@ +import type { SessionParams } from '../types'; + +// Allow localhost +export const BASE_URL = process.env.BASE_URL || 'http://localhost:8081/'; +export const WALLET_URL = process.env.WALLET_URL || 'https://react-wallet.walletconnect.com/'; +export const DEFAULT_SESSION_PARAMS: SessionParams = { + reqAccounts: ['1', '2'], + optAccounts: ['1', '2'], + accept: true +}; +export const DEFAULT_CHAIN_NAME = process.env.DEFAULT_CHAIN_NAME || 'Ethereum'; diff --git a/apps/native/tests/shared/constants/timeouts.ts b/apps/native/tests/shared/constants/timeouts.ts new file mode 100644 index 000000000..03ff123ce --- /dev/null +++ b/apps/native/tests/shared/constants/timeouts.ts @@ -0,0 +1 @@ +export const MAXIMUM_WAIT_CONNECTIONS = 30 * 1000; diff --git a/apps/native/tests/shared/fixtures/timing-fixture.ts b/apps/native/tests/shared/fixtures/timing-fixture.ts new file mode 100644 index 000000000..5c4d3d2bd --- /dev/null +++ b/apps/native/tests/shared/fixtures/timing-fixture.ts @@ -0,0 +1,11 @@ +import { test as base } from '@playwright/test'; + +export type TimingRecords = { item: string; timeMs: number }[]; + +export interface TimingFixture { + timingRecords: TimingRecords; +} + +export const timingFixture = base.extend({ + timingRecords: [] +}); diff --git a/apps/native/tests/shared/fixtures/w3m-fixture.ts b/apps/native/tests/shared/fixtures/w3m-fixture.ts new file mode 100644 index 000000000..86e6ab924 --- /dev/null +++ b/apps/native/tests/shared/fixtures/w3m-fixture.ts @@ -0,0 +1,34 @@ +/* eslint no-console: 0 */ + +import { ModalPage } from '../pages/ModalPage'; +import { timeStart, timeEnd } from '../utils/logs'; +import { timingFixture } from './timing-fixture'; + +// Declare the types of fixtures to use +export interface ModalFixture { + modalPage: ModalPage; + library: string; +} + +// M -> test Modal +export const testM = timingFixture.extend({ + modalPage: async ({ page }, use) => { + timeStart('new ModalPage'); + const modalPage = new ModalPage(page); + timeEnd('new ModalPage'); + timeStart('modalPage.load'); + await modalPage.load(); + timeEnd('modalPage.load'); + await use(modalPage); + } +}); + +export const testMSiwe = timingFixture.extend({ + modalPage: async ({ page }, use) => { + const modalPage = new ModalPage(page); + await modalPage.load(); + await use(modalPage); + } +}); + +export { expect } from '@playwright/test'; diff --git a/apps/native/tests/shared/fixtures/w3m-wallet-fixture.ts b/apps/native/tests/shared/fixtures/w3m-wallet-fixture.ts new file mode 100644 index 000000000..ba2bd8ddb --- /dev/null +++ b/apps/native/tests/shared/fixtures/w3m-wallet-fixture.ts @@ -0,0 +1,99 @@ +/* eslint no-console: 0 */ + +import { testM as base, testMSiwe as siwe } from './w3m-fixture'; +import { WalletPage } from '../pages/WalletPage'; +import { WalletValidator } from '../validators/WalletValidator'; + +import { DEFAULT_SESSION_PARAMS } from '../constants'; +import { timeEnd, timeStart } from '../utils/logs'; + +// Declare the types of fixtures to use +interface ModalWalletFixture { + walletPage: WalletPage; + walletValidator: WalletValidator; +} + +// MW -> test Modal + Wallet +export const testConnectedMW = base.extend({ + walletPage: async ({ context, modalPage, timingRecords }, use) => { + // Setup + let pairingCreatedTime: Date | null = null; + let verificationStartedTime: Date | null = null; + + timeStart('new WalletPage'); + const walletPage = new WalletPage(await context.newPage()); + timeEnd('new WalletPage'); + + walletPage.page.on('console', msg => { + if (msg.text().includes('set') && msg.text().includes('core/pairing/pairing')) { + pairingCreatedTime = new Date(); + } + if (msg.text().includes('resolving attestation')) { + verificationStartedTime = new Date(); + } + if (msg.text().includes('session_proposal') && msg.text().includes('verifyContext')) { + // For some reason this log is emitted twice; so only recording the time once + if (verificationStartedTime) { + const verificationEndedTime = new Date(); + timingRecords.push({ + item: 'sessionProposalVerification', + timeMs: verificationEndedTime.getTime() - verificationStartedTime.getTime() + }); + verificationStartedTime = null; + } + } + }); + + timeStart('walletPage.load'); + await walletPage.load(); + timeEnd('walletPage.load'); + + // Initiate connection + timeStart('modalPage.getConnectUri'); + const uri = await modalPage.getConnectUri(timingRecords); + timeEnd('modalPage.getConnectUri'); + + timeStart('walletPage.connectWithUri'); + await walletPage.connectWithUri(uri); + timeEnd('walletPage.connectWithUri'); + + const connectionInitiated = new Date(); + + // Handle session proposal + timeStart('walletPage.handleSessionProposal'); + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS); + timeEnd('walletPage.handleSessionProposal'); + + const proposalReceived = new Date(); + + timingRecords.push({ + item: 'receiveSessionProposal', + timeMs: proposalReceived.getTime() - connectionInitiated.getTime() + }); + + if (pairingCreatedTime) { + timingRecords.push({ + item: 'pairingReceiveSessionProposal', + timeMs: proposalReceived.getTime() - (pairingCreatedTime as Date).getTime() + }); + } + + const walletValidator = new WalletValidator(walletPage.page); + + timeStart('walletValidator.expectConnected'); + await walletValidator.expectConnected(); + timeEnd('walletValidator.expectConnected'); + + await use(walletPage); + } +}); + +export const testMWSiwe = siwe.extend({ + walletPage: async ({ context }, use) => { + const walletPage = new WalletPage(await context.newPage()); + await walletPage.load(); + await use(walletPage); + } +}); + +export { expect } from '@playwright/test'; diff --git a/apps/native/tests/shared/pages/ModalPage.ts b/apps/native/tests/shared/pages/ModalPage.ts new file mode 100644 index 000000000..a8840f21a --- /dev/null +++ b/apps/native/tests/shared/pages/ModalPage.ts @@ -0,0 +1,279 @@ +import type { Locator, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import { BASE_URL, DEFAULT_SESSION_PARAMS } from '../constants'; +import { WalletValidator } from '../validators/WalletValidator'; +import { WalletPage } from './WalletPage'; +import { TimingRecords } from '../types'; +import { ModalValidator } from '../validators/ModalValidator'; + +export class ModalPage { + private readonly connectButton: Locator; + private readonly url: string; + + constructor(public readonly page: Page) { + this.connectButton = this.page.getByTestId('connect-button'); + this.url = BASE_URL; + } + + async load() { + await this.page.goto(this.url); + } + + assertDefined(value: T | undefined | null): T { + expect(value).toBeDefined(); + + return value!; + } + + async getConnectUri(timingRecords?: TimingRecords): Promise { + await this.connectButton.click(); + await this.openAllWallets(); + await this.openQrCodeView(); + const qrLoadInitiatedTime = new Date(); + + const qrCode = this.page.getByTestId('qr-code'); + await expect(qrCode).toBeVisible(); + const uri = await this.clickCopyLink(); + + const qrLoadedTime = new Date(); + if (timingRecords) { + timingRecords.push({ + item: 'qrLoad', + timeMs: qrLoadedTime.getTime() - qrLoadInitiatedTime.getTime() + }); + } + + return uri; + } + + async getImmidiateConnectUri(timingRecords?: TimingRecords): Promise { + await this.connectButton.click(); + const qrLoadInitiatedTime = new Date(); + + const qrCode = this.page.getByTestId('qr-code'); + await expect(qrCode).toBeVisible(); + const uri = await this.clickCopyLink(); + const qrLoadedTime = new Date(); + if (timingRecords) { + timingRecords.push({ + item: 'qrLoad', + timeMs: qrLoadedTime.getTime() - qrLoadInitiatedTime.getTime() + }); + } + + return uri; + } + + async qrCodeFlow(page: ModalPage, walletPage: WalletPage, immediate?: boolean): Promise { + let uri: string; + await walletPage.load(); + if (immediate) { + uri = await page.getImmidiateConnectUri(); + } else { + uri = await page.getConnectUri(); + } + await walletPage.connectWithUri(uri); + + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS); + const walletValidator = new WalletValidator(walletPage.page); + await walletValidator.expectConnected(); + } + + async searchWalletFlow(page: ModalPage, walletName: string, walletId: string) { + await this.openAllWallets(); + await this.search(walletName); + await this.page.waitForTimeout(1000); + const modalValidator = new ModalValidator(page.page); + await modalValidator.expectAllWalletsListSearchItem(walletId); + } + + async disconnect() { + const accountBtn = this.page.getByTestId('account-button'); + await expect(accountBtn, 'Account button should be visible').toBeVisible(); + await expect(accountBtn, 'Account button should be enabled').toBeEnabled(); + await accountBtn.click(); + const disconnectBtn = this.page.getByTestId('disconnect-button'); + await expect(disconnectBtn, 'Disconnect button should be visible').toBeVisible(); + await expect(disconnectBtn, 'Disconnect button should be enabled').toBeEnabled(); + await disconnectBtn.click(); + } + + async sign() { + const signButton = this.page.getByTestId('sign-message-button'); + await signButton.scrollIntoViewIfNeeded(); + await signButton.click(); + } + + async clickWhatIsAWalletButton() { + await this.page.getByTestId('help-button').click(); + } + + async clickGetAWalletButton() { + await this.page.getByTestId('get-a-wallet-button').click(); + } + + // async promptSiwe() { + // const siweSign = this.page.getByTestId('w3m-connecting-siwe-sign'); + // await expect(siweSign, 'Siwe prompt sign button should be visible').toBeVisible({ + // timeout: 10_000 + // }); + // await expect(siweSign, 'Siwe prompt sign button should be enabled').toBeEnabled(); + // await siweSign.click(); + // } + + // async cancelSiwe() { + // await this.page.getByTestId('w3m-connecting-siwe-cancel').click(); + // } + + async switchNetwork(network: string) { + await this.openAccountModal(); + await this.page.getByTestId('w3m-account-select-network').click(); + await this.page.getByTestId(`w3m-network-switch-${network}`).click(); + // The state is chaing too fast and test runner doesn't wait the loading page. It's fastly checking the network selection button and detect that it's switched already. + await this.page.waitForTimeout(300); + } + + // async clickWalletDeeplink() { + // await this.connectButton.click(); + // await this.page.getByTestId('wallet-selector-react-wallet-v2').click(); + // await this.page.getByTestId('tab-desktop').click(); + // } + + async openAccountModal() { + await this.page.getByTestId('account-button').click(); + } + + async openConnectModal() { + await this.page.getByTestId('connect-button').click(); + } + + async openNetworkModal() { + await this.page.getByTestId('network-button').click(); + } + + async closeModal() { + await this.page.getByTestId('header-close')?.click?.(); + // Wait for the modal fade out animation + await this.page.waitForTimeout(300); + } + + // async switchNetworkWithNetworkButton(networkName: string) { + // const networkButton = this.page.getByTestId('wui-network-button'); + // await networkButton.click(); + + // const networkToSwitchButton = this.page.getByTestId(`w3m-network-switch-${networkName}`); + // await networkToSwitchButton.click(); + // } + + async openAllWallets() { + const allWallets = this.page.getByTestId('all-wallets'); + await expect(allWallets, 'All wallets should be visible').toBeVisible(); + await allWallets.click(); + } + + async openQrCodeView() { + const qrCodeButton = this.page.getByTestId('button-qr-code'); + await expect(qrCodeButton, 'QR code view should be visible').toBeVisible(); + await qrCodeButton.click(); + } + + // async clickAllWalletsListSearchItem(id: string) { + // const allWalletsListSearchItem = this.page.getByTestId(`wallet-search-item-${id}`); + // await expect(allWalletsListSearchItem).toBeVisible(); + // await allWalletsListSearchItem.click(); + // } + + // async clickTabWebApp() { + // const tabWebApp = this.page.getByTestId('tab-webapp'); + // await expect(tabWebApp).toBeVisible(); + // await tabWebApp.click(); + // } + + async clickHookDisconnectButton() { + const disconnectHookButton = this.page.getByTestId('disconnect-hook-button'); + await expect(disconnectHookButton).toBeVisible(); + await disconnectHookButton.click(); + } + + async clickCopyLink() { + const copyLink = this.page.getByTestId('copy-link'); + await expect(copyLink).toBeVisible(); + + let hasCopied = false; + + while (!hasCopied) { + await copyLink.click(); + await this.page.waitForTimeout(500); + + const snackbarMessage = this.page.getByTestId('wui-snackbar-message'); + const snackbarMessageText = await snackbarMessage.textContent(); + + if (snackbarMessageText && snackbarMessageText.startsWith('Link copied')) { + hasCopied = true; + } + } + + return this.page.evaluate(() => navigator.clipboard.readText()); + } + + // async clickOpenWebApp() { + // let url = ''; + + // const openButton = this.page.getByTestId('w3m-connecting-widget-secondary-button'); + // await expect(openButton).toBeVisible(); + // await expect(openButton).toHaveText('Open'); + + // while (!url) { + // await openButton.click(); + // await this.page.waitForTimeout(500); + + // const pages = this.page.context().pages(); + + // // Check if more than 1 tab is open + // if (pages.length > 1) { + // const lastTab = pages[pages.length - 1]; + + // if (lastTab) { + // url = lastTab.url(); + // break; + // } + // } + // } + + // return url; + // } + + async search(value: string) { + const searchInput = this.page.getByTestId('wui-input-text'); + await expect(searchInput, 'Search input should be visible').toBeVisible(); + await searchInput.click(); + await searchInput.fill(value); + } + + async openNetworks() { + await this.page.getByTestId('w3m-account-select-network').click(); + await expect(this.page.getByText('Select network')).toBeVisible(); + } + + // async openProfileView() { + // await this.page.getByTestId('wui-profile-button').click(); + // } + + // async getAddress(): Promise<`0x${string}`> { + // const address = await this.page.getByTestId('w3m-address').textContent(); + // expect(address, 'Address should be present').toBeTruthy(); + + // return address as `0x${string}`; + // } + + // async getChainId(): Promise { + // const chainId = await this.page.getByTestId('w3m-chain-id').textContent(); + // expect(chainId, 'Chain ID should be present').toBeTruthy(); + + // return Number(chainId); + // } + + // async switchNetworkWithHook() { + // await this.page.getByTestId('switch-network-hook-button').click(); + // } +} diff --git a/apps/native/tests/shared/pages/WalletPage.ts b/apps/native/tests/shared/pages/WalletPage.ts new file mode 100644 index 000000000..49f5a2038 --- /dev/null +++ b/apps/native/tests/shared/pages/WalletPage.ts @@ -0,0 +1,125 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { WALLET_URL } from '../constants'; +import type { SessionParams } from '../types'; + +export class WalletPage { + private readonly baseURL = WALLET_URL; + + private gotoHome: Locator; + private vercelPreview: Locator; + + public connectToSingleAccount = false; + + constructor(public page: Page) { + this.gotoHome = this.page.getByTestId('wc-connect'); + this.vercelPreview = this.page.locator('css=vercel-live-feedback'); + } + + async load() { + await this.page.goto(this.baseURL); + } + + loadNewPage(page: Page) { + this.page = page; + this.gotoHome = this.page.getByTestId('wc-connect'); + this.vercelPreview = this.page.locator('css=vercel-live-feedback'); + } + + /** + * Connect by inserting provided URI into the input element + */ + + async connectWithUri(uri: string) { + const isVercelPreview = (await this.vercelPreview.count()) > 0; + if (isVercelPreview) { + await this.vercelPreview.evaluate((iframe: HTMLIFrameElement) => iframe.remove()); + } + /* + * If connecting to a single account manually navigate. + * Otherwise click the home button. + */ + if (this.connectToSingleAccount) { + await this.page.goto(`${this.baseURL}/walletconnect?addressesToApprove=1`); + } else { + await this.gotoHome.click(); + } + const input = this.page.getByTestId('uri-input'); + await input.waitFor({ + state: 'visible', + timeout: 5000 + }); + await input.fill(uri); + const connectButton = this.page.getByTestId('uri-connect-button'); + await expect(connectButton, 'Connect button should be enabled').toBeEnabled({ + timeout: 5000 + }); + await connectButton.click(); + } + + /** + * Handle a session proposal event in the wallet + * @param reqAccounts - required account numbers to select ex/ ['1', '2'] + * @param optAccounts - optional account numbers to select ex/ ['1', '2'] + * @param accept - accept or reject the session + */ + async handleSessionProposal(opts: SessionParams) { + const variant = opts.accept ? `approve` : `reject`; + // `.click` doesn't work here, so we use `.focus` and `Space` + await this.performRequestAction(variant); + } + + async handleRequest({ accept }: { accept: boolean }) { + const variant = accept ? `approve` : `reject`; + // `.click` doesn't work here, so we use `.focus` and `Space` + await this.performRequestAction(variant); + } + + async performRequestAction(variant: string) { + await this.page.waitForLoadState(); + const btn = this.page.getByTestId(`session-${variant}-button`); + await expect(btn, `Session ${variant} element should be visible`).toBeVisible({ + timeout: 30000 + }); + await expect(btn).toBeEnabled(); + await btn.focus(); + await this.page.keyboard.press('Space'); + } + + /** + * Enables testnets in the wallet settings + */ + async enableTestnets() { + await this.page.waitForLoadState(); + const settingsButton = this.page.getByTestId('settings'); + await settingsButton.click(); + const testnetSwitch = this.page.getByTestId('settings-toggle-testnets'); + await testnetSwitch.click(); + expect(testnetSwitch).toHaveAttribute('data-state', 'checked'); + } + + /** + * Switches the network in the wallet + * @param network the network id to switch (e.g. eip155:1 for Ethereum Mainnet) + */ + async switchNetwork(network: string) { + await this.page.waitForLoadState(); + const networkButton = this.page.getByTestId('accounts'); + await networkButton.click(); + const switchNetworkButton = this.page.getByTestId(`chain-switch-button${network}`); + await switchNetworkButton.click(); + await expect(switchNetworkButton).toHaveText('✅'); + } + + /** + * Disconnects the current connection in the wallet + */ + async disconnectConnection() { + await this.page.waitForLoadState(); + const sessionsButton = this.page.getByTestId('sessions'); + await sessionsButton.click(); + const sessionCard = this.page.getByTestId(`session-card`); + await sessionCard.click(); + const disconnectButton = this.page.getByText('Delete'); + await disconnectButton.click(); + } +} diff --git a/apps/native/tests/shared/types/index.ts b/apps/native/tests/shared/types/index.ts new file mode 100644 index 000000000..abf6bc54f --- /dev/null +++ b/apps/native/tests/shared/types/index.ts @@ -0,0 +1,9 @@ +export interface SessionParams { + reqAccounts: string[]; + optAccounts: string[]; + accept: boolean; +} + +export type TimingRecords = { item: string; timeMs: number }[]; + +export type CaipNetworkId = `${string}:${string}`; diff --git a/apps/native/tests/shared/utils/logs.ts b/apps/native/tests/shared/utils/logs.ts new file mode 100644 index 000000000..7bb561477 --- /dev/null +++ b/apps/native/tests/shared/utils/logs.ts @@ -0,0 +1,17 @@ +/* eslint no-console: 0 */ + +const TIMING_LOGS_ENABLED = process.env.TIMING_LOGS === 'true' || false; + +export function timeStart(label?: string | undefined) { + if (!TIMING_LOGS_ENABLED) { + return; + } + console.time(label); +} + +export function timeEnd(label?: string | undefined) { + if (!TIMING_LOGS_ENABLED) { + return; + } + console.timeEnd(label); +} diff --git a/apps/native/tests/shared/utils/timeouts.ts b/apps/native/tests/shared/utils/timeouts.ts new file mode 100644 index 000000000..a74e5eee4 --- /dev/null +++ b/apps/native/tests/shared/utils/timeouts.ts @@ -0,0 +1,9 @@ +import { MAXIMUM_WAIT_CONNECTIONS } from '../constants/timeouts'; + +export function getMaximumWaitConnections(): number { + if (process.env.CI) { + return MAXIMUM_WAIT_CONNECTIONS; + } + + return MAXIMUM_WAIT_CONNECTIONS * 2; +} diff --git a/apps/native/tests/shared/validators/ModalValidator.ts b/apps/native/tests/shared/validators/ModalValidator.ts new file mode 100644 index 000000000..d27de8fbe --- /dev/null +++ b/apps/native/tests/shared/validators/ModalValidator.ts @@ -0,0 +1,165 @@ +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; +import { getMaximumWaitConnections } from '../utils/timeouts'; + +const MAX_WAIT = getMaximumWaitConnections(); + +export class ModalValidator { + constructor(public readonly page: Page) {} + + async expectConnected() { + const accountButton = this.page.getByTestId('account-button'); + await expect(accountButton, 'Account button should be present').toBeAttached({ + timeout: MAX_WAIT + }); + await expect( + this.page.getByTestId('connect-button'), + 'Connect button should not be present' + ).toBeHidden({ + timeout: MAX_WAIT + }); + await this.page.waitForTimeout(500); + } + + async expectBalanceFetched(currency: 'SOL' | 'ETH') { + const accountButton = this.page.getByTestId('account-button'); + await expect(accountButton, `Account button should show balance as ${currency}`).toContainText( + `0.000 ${currency}` + ); + } + + async expectAuthenticated() { + await expect( + this.page.getByTestId('w3m-authentication-status'), + 'Authentication status should be: authenticated' + ).toContainText('authenticated'); + } + + async expectOnSignInEventCalled(toBe: boolean) { + await expect(this.page.getByTestId('siwe-event-onSignIn')).toContainText(`${toBe}`); + } + + async expectUnauthenticated() { + await expect( + this.page.getByTestId('w3m-authentication-status'), + 'Authentication status should be: unauthenticated' + ).toContainText('unauthenticated'); + } + + async expectOnSignOutEventCalled(toBe: boolean) { + await expect(this.page.getByTestId('siwe-event-onSignOut')).toContainText(`${toBe}`); + } + + async expectSignatureDeclined() { + await expect( + this.page.getByText('Signature declined'), + 'Signature declined should be visible' + ).toBeVisible(); + } + + async expectDisconnected() { + await expect( + this.page.getByTestId('connect-button'), + 'Connect button should be present' + ).toBeVisible({ + timeout: MAX_WAIT + }); + } + + async expectConnectScreen() { + await expect(this.page.getByText('Connect wallet')).toBeVisible({ + timeout: MAX_WAIT + }); + } + + async expectNetworkScreen() { + await expect(this.page.getByTestId('what-is-a-network-button')).toBeVisible(); + } + + async expectAddress(expectedAddress: string) { + const address = this.page.getByTestId('w3m-address'); + + await expect(address, 'Correct address should be present').toHaveText(expectedAddress); + } + + // async expectNetwork(network: string) { + // const networkButton = this.page.getByTestId('w3m-account-select-network'); + // await expect(networkButton, `Network button should contain text ${network}`).toHaveText( + // network, + // { + // timeout: 5000 + // } + // ); + // } + + async expectAcceptedSign() { + await expect(this.page.getByText('Signature successful')).toBeVisible({ + timeout: 30 * 1000 + }); + } + + async expectRejectedSign() { + await expect(this.page.getByText('Signature failed')).toBeVisible(); + } + + async expectSwitchedNetwork(network: string) { + const switchNetworkButton = this.page.getByTestId(`w3m-account-select-network-text`); + await expect(switchNetworkButton).toContainText(network); + } + + async expectAllWalletsListSearchItem(id: string) { + const allWalletsListSearchItem = this.page.getByTestId(`wallet-search-item-${id}`); + await expect(allWalletsListSearchItem).toBeVisible(); + } + + async expectAllWallets() { + const allWallets = this.page.getByTestId('all-wallets'); + await expect(allWallets).toBeVisible(); + } + + async expectWhatIsAWalletButton() { + const whatIsAWalletButton = this.page.getByTestId('help-button'); + await expect(whatIsAWalletButton).toBeVisible(); + } + + async expectWhatIsAWalletView() { + const whatIsAWalletView = this.page.getByTestId('what-is-a-wallet-view'); + await expect(whatIsAWalletView).toBeVisible(); + } + + async expectGetAWalletView() { + const getAWalletView = this.page.getByTestId('get-a-wallet-view'); + await expect(getAWalletView).toBeVisible(); + } + + // async expectHeaderText(text: string) { + // const headerText = this.page.getByTestId('header-text'); + // await expect(headerText).toHaveText(text); + // } + + async expectNetworksDisabled(name: string) { + const disabledNetworkButton = this.page.getByTestId(`w3m-network-switch-${name}`); + disabledNetworkButton.click(); + await expect(this.page.getByText('Select network')).toBeVisible(); + } + + async expectToBeConnectedInstantly() { + const accountButton = this.page.getByTestId('account-button'); + await expect(accountButton, 'Account button should be present').toBeAttached({ + timeout: 1000 + }); + } + + async expectModalNotVisible() { + const modal = this.page.getByTestId('w3m-modal'); + await expect(modal).toBeHidden({ + timeout: 2000 + }); + } + + // async expectSnackbar(message: string) { + // await expect(this.page.getByTestId('wui-snackbar-message')).toHaveText(message, { + // timeout: MAX_WAIT + // }); + // } +} diff --git a/apps/native/tests/shared/validators/WalletValidator.ts b/apps/native/tests/shared/validators/WalletValidator.ts new file mode 100644 index 000000000..c6e292e58 --- /dev/null +++ b/apps/native/tests/shared/validators/WalletValidator.ts @@ -0,0 +1,68 @@ +import { expect } from '@playwright/test'; +import type { Locator, Page } from '@playwright/test'; +import { getMaximumWaitConnections } from '../utils/timeouts'; + +const MAX_WAIT = getMaximumWaitConnections(); + +export class WalletValidator { + private gotoSessions: Locator; + + constructor(public page: Page) { + this.gotoSessions = this.page.getByTestId('sessions'); + } + + loadNewPage(page: Page) { + this.page = page; + this.gotoSessions = this.page.getByTestId('sessions'); + } + + async expectConnected() { + await expect( + this.gotoSessions, + 'Approve screen should be closed and sessions tab visible' + ).toBeVisible(); + await this.gotoSessions.click(); + await this.expectSessionCard({ visible: true }); + } + + async expectSessionCard({ visible = true }: { visible?: boolean }) { + if (visible) { + await expect( + this.page.getByTestId('session-card'), + 'Session card should be visible' + ).toBeVisible({ + timeout: MAX_WAIT + }); + } else { + await expect( + this.page.getByTestId('session-card'), + 'Session card should not be visible' + ).not.toBeVisible({ + timeout: MAX_WAIT + }); + } + } + + async expectDisconnected() { + await this.gotoSessions.click(); + await expect( + this.page.getByTestId('session-card'), + 'Session card should not be visible' + ).not.toBeVisible({ + timeout: MAX_WAIT + }); + } + + async expectReceivedSign({ chainName = 'Ethereum' }) { + await expect( + this.page.getByTestId('session-approve-button'), + 'Session approve button should be visible' + ).toBeVisible({ + timeout: MAX_WAIT + }); + await expect( + this.page.getByTestId('request-details-chain'), + 'Request details should contain chain name' + ).toContainText(chainName); + } +} diff --git a/apps/native/tests/wallet.spec.ts b/apps/native/tests/wallet.spec.ts new file mode 100644 index 000000000..6f5eb68ca --- /dev/null +++ b/apps/native/tests/wallet.spec.ts @@ -0,0 +1,126 @@ +import { test, type BrowserContext } from '@playwright/test'; +import { WalletPage } from './shared/pages/WalletPage'; +import { WalletValidator } from './shared/validators/WalletValidator'; +import { ModalPage } from './shared/pages/ModalPage'; +import { ModalValidator } from './shared/validators/ModalValidator'; +import { DEFAULT_CHAIN_NAME } from './shared/constants'; + +let modalPage: ModalPage; +let modalValidator: ModalValidator; +let walletPage: WalletPage; +let walletValidator: WalletValidator; +let context: BrowserContext; + +// -- Setup -------------------------------------------------------------------- +const sampleWalletTest = test.extend<{ library: string }>({ + library: ['wagmi', { option: true }] +}); + +sampleWalletTest.describe.configure({ mode: 'serial' }); + +sampleWalletTest.beforeAll(async ({ browser }) => { + context = await browser.newContext(); + const browserPage = await context.newPage(); + + modalPage = new ModalPage(browserPage); + walletPage = new WalletPage(await context.newPage()); + modalValidator = new ModalValidator(browserPage); + walletValidator = new WalletValidator(walletPage.page); + + await modalPage.load(); + await modalPage.qrCodeFlow(modalPage, walletPage); + await modalValidator.expectConnected(); +}); + +sampleWalletTest.afterAll(async () => { + await modalPage.page.close(); +}); + +// -- Tests -------------------------------------------------------------------- +sampleWalletTest('it should be connected instantly after page refresh', async () => { + await modalPage.page.reload(); + await modalValidator.expectToBeConnectedInstantly(); +}); + +sampleWalletTest('it should show disabled networks', async () => { + const disabledNetworks = 'Gnosis'; + await modalPage.openAccountModal(); + await modalPage.openNetworks(); + await modalValidator.expectNetworksDisabled(disabledNetworks); + await modalPage.closeModal(); +}); + +sampleWalletTest('it should switch networks and sign', async () => { + const chains = ['Polygon', 'Ethereum']; + + async function processChain(index: number) { + if (index >= chains.length) { + return; + } + + const chainName = chains[index] ?? DEFAULT_CHAIN_NAME; + + // -- Switch network -------------------------------------------------------- + const chainNameOnWalletPage = chainName; + await modalPage.switchNetwork(chainName); + await modalValidator.expectSwitchedNetwork(chainName); + await modalPage.closeModal(); + + // -- Sign ------------------------------------------------------------------ + await modalPage.sign(); + await walletValidator.expectReceivedSign({ chainName: chainNameOnWalletPage }); + await walletPage.handleRequest({ accept: true }); + await modalValidator.expectAcceptedSign(); + + await processChain(index + 1); + } + + // Start processing from the first chain + await processChain(0); +}); + +sampleWalletTest('it should show last connected network after refreshing', async () => { + const chainName = 'Polygon'; + + await modalPage.switchNetwork(chainName); + await modalValidator.expectSwitchedNetwork(chainName); + await modalPage.closeModal(); + + await modalPage.page.reload(); + + await modalPage.openAccountModal(); + await modalValidator.expectSwitchedNetwork(chainName); + await modalPage.closeModal(); +}); + +sampleWalletTest('it should reject sign', async () => { + const chainName = 'Polygon'; + await modalPage.sign(); + await walletValidator.expectReceivedSign({ chainName }); + await walletPage.handleRequest({ accept: false }); + await modalValidator.expectRejectedSign(); +}); + +sampleWalletTest('it should disconnect using hook', async () => { + await modalValidator.expectConnected(); + await modalPage.clickHookDisconnectButton(); + await modalValidator.expectDisconnected(); +}); + +sampleWalletTest('it should disconnect and close modal when connecting from wallet', async () => { + await modalValidator.expectDisconnected(); + await modalPage.qrCodeFlow(modalPage, walletPage); + await modalValidator.expectConnected(); + await modalPage.openAccountModal(); + await walletPage.disconnectConnection(); + await walletValidator.expectSessionCard({ visible: false }); + await modalValidator.expectModalNotVisible(); + await walletPage.page.waitForTimeout(500); +}); + +sampleWalletTest('it should disconnect as expected', async () => { + await modalPage.qrCodeFlow(modalPage, walletPage); + await modalValidator.expectConnected(); + await modalPage.disconnect(); + await modalValidator.expectDisconnected(); +}); diff --git a/package.json b/package.json index abf6e6b57..7dbaf2c12 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "gallery": "turbo run dev:gallery", "android": "cd apps/native && yarn android", "ios": "cd apps/native && yarn ios", + "web": "cd apps/native && yarn web", "build": "turbo build", "build:gallery": "turbo run build:gallery", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", @@ -31,7 +32,7 @@ "clean": "turbo clean && rm -rf node_modules && watchman watch-del-all", "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore", "changeset:prepublish": "yarn run clean; yarn install; yarn version:update; yarn run lint && yarn run prettier; yarn run build; yarn run test;", - "changeset:publish": "yarn run changeset:prepublish; yarn run changeset publish", + "changeset:publish": "yarn run changeset:prepublish; yarn run changeset publish --no-git-tag", "changeset:version": "changeset version; yarn run version:update; yarn install --refresh-lockfile", "version:update": "./scripts/bump-version.sh" }, @@ -49,11 +50,12 @@ "@types/jest": "29.5.7", "@types/qrcode": "1.5.5", "@types/react": "^18.2.6", - "@walletconnect/react-native-compat": "2.16.1", + "@walletconnect/react-native-compat": "2.17.2", "babel-jest": "^29.7.0", "eslint": "^8.46.0", "eslint-plugin-ft-flow": "2.0.3", "eslint-plugin-prettier": "5.0.1", + "eslint-plugin-valtio": "^0.6.4", "jest": "29.7.0", "prettier": "3.0.1", "react": "18.2.0", @@ -68,8 +70,8 @@ "tsconfig": "*", "turbo": "2.1.1", "typescript": "5.2.2", - "viem": "2.21.6", - "wagmi": "2.12.11" + "viem": "2.21.48", + "wagmi": "2.12.33" }, "packageManager": "yarn@4.0.2" } diff --git a/packages/auth-ethers/package.json b/packages/auth-ethers/package.json index d68b571d2..03a93b917 100644 --- a/packages/auth-ethers/package.json +++ b/packages/auth-ethers/package.json @@ -24,12 +24,12 @@ "react-native", "ethers" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/auth-ethers/readme.md b/packages/auth-ethers/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/auth-ethers/readme.md +++ b/packages/auth-ethers/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/auth-wagmi/package.json b/packages/auth-wagmi/package.json index 4049c7d65..23546060d 100644 --- a/packages/auth-wagmi/package.json +++ b/packages/auth-wagmi/package.json @@ -24,18 +24,19 @@ "react-native", "wagmi" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public" }, "dependencies": { + "@reown/appkit-core-react-native": "1.0.2", "@reown/appkit-wallet-react-native": "1.0.2" }, "peerDependencies": { diff --git a/packages/auth-wagmi/readme.md b/packages/auth-wagmi/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/auth-wagmi/readme.md +++ b/packages/auth-wagmi/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/auth-wagmi/src/index.ts b/packages/auth-wagmi/src/index.ts index 241cf2b71..b9859d6ab 100644 --- a/packages/auth-wagmi/src/index.ts +++ b/packages/auth-wagmi/src/index.ts @@ -1,7 +1,8 @@ import { createConnector, ChainNotConfiguredError } from 'wagmi'; -import { SwitchChainError, getAddress, type Address } from 'viem'; +import { SwitchChainError, getAddress, type Address, type Hex } from 'viem'; import { AppKitFrameProvider } from '@reown/appkit-wallet-react-native'; +import { StorageUtil } from '@reown/appkit-core-react-native'; export type Metadata = { name: string; @@ -12,8 +13,8 @@ export type Metadata = { type AuthConnectorOptions = { /** - * WalletConnect Cloud Project ID. - * @link https://cloud.walletconnect.com/sign-in. + * Reown Cloud Project ID. + * @link https://cloud.reown.com/sign-in. */ projectId: string; metadata: Metadata; @@ -22,13 +23,15 @@ type AuthConnectorOptions = { type Provider = AppKitFrameProvider; type StorageItemMap = { - '@w3m/connected_connector'?: string; + recentConnectorId?: string; }; authConnector.type = 'appKitAuth' as const; authConnector.id = 'appKitAuth' as const; export function authConnector(parameters: AuthConnectorOptions) { let _provider: AppKitFrameProvider = {} as AppKitFrameProvider; + let _currentAddress: Address | null = null; + let _chainId: number | null = null; return createConnector(config => ({ id: authConnector.id, @@ -39,15 +42,27 @@ export function authConnector(parameters: AuthConnectorOptions) { }, async connect(options = {}) { const provider = await this.getProvider(); + let chainId = options.chainId; await provider.webviewLoadPromise; - const { address, chainId } = await provider.connect({ chainId: options.chainId }); + + if (options.isReconnecting) { + chainId = await provider.getLastUsedChainId(); + if (!chainId) { + throw new Error('ChainId not found in provider'); + } + } + + const { address, chainId: frameChainId } = await provider.connect({ chainId }); + + _chainId = frameChainId as number; + _currentAddress = address as Address; return { - accounts: [address as Address], - account: address as Address, - chainId, + accounts: [_currentAddress as Address], + account: _currentAddress as Address, + chainId: frameChainId as number, chain: { - id: chainId, + id: frameChainId as number, unsuported: false } }; @@ -56,6 +71,8 @@ export function authConnector(parameters: AuthConnectorOptions) { const provider = await this.getProvider(); await provider.webviewLoadPromise; await provider.disconnect(); + _chainId = null; + _currentAddress = null; }, async switchChain({ chainId }) { try { @@ -64,8 +81,15 @@ export function authConnector(parameters: AuthConnectorOptions) { const provider = await this.getProvider(); await provider.webviewLoadPromise; - await provider.switchNetwork(chainId); - config.emitter.emit('change', { chainId: Number(chainId) }); + + // We connect instead, since changing the chain may cause the address to change as well + const response = await provider.connect({ chainId }); + + config.emitter.emit('change', { + chainId: Number(chainId), + accounts: [response.address as Hex] + }); + _chainId = chainId; return chain; } catch (error) { @@ -76,6 +100,8 @@ export function authConnector(parameters: AuthConnectorOptions) { } }, async getAccounts() { + if (_currentAddress) return [_currentAddress]; + const provider = await this.getProvider(); await provider.webviewLoadPromise; @@ -86,6 +112,8 @@ export function authConnector(parameters: AuthConnectorOptions) { ).map(getAddress); }, async getChainId() { + if (_chainId) return _chainId; + const provider = await this.getProvider(); await provider.webviewLoadPromise; const { chainId } = await provider.getChainId(); @@ -97,31 +125,32 @@ export function authConnector(parameters: AuthConnectorOptions) { }, async isAuthorized() { try { + const connectedConnector = await StorageUtil.getConnectedConnector(); + if (connectedConnector && connectedConnector !== 'AUTH') { + return false; + } + const provider = await this.getProvider(); await provider.webviewLoadPromise; - const connectedConnector = await config.storage?.getItem('recentConnectorId'); - - if (connectedConnector !== authConnector.id) { - // isConnected still needs to be called to disable email input loader - provider.isConnected(); + const { isConnected } = await provider.isConnected(); - return false; - } else { - const { isConnected } = await provider.isConnected(); - - return isConnected; - } + return isConnected; } catch (error) { return false; } }, onAccountsChanged(accounts) { if (accounts.length === 0) config.emitter.emit('disconnect'); - else config.emitter.emit('change', { accounts: accounts.map(getAddress) }); + else { + const account = accounts[0] ? getAddress(accounts[0]) : null; + config.emitter.emit('change', { accounts: account ? [account] : undefined }); + _currentAddress = account; + } }, onChainChanged(chain) { const chainId = Number(chain); config.emitter.emit('change', { chainId }); + _chainId = chainId; }, async onDisconnect() { const provider = await this.getProvider(); diff --git a/packages/coinbase-ethers/package.json b/packages/coinbase-ethers/package.json index 2a504a063..b785db9a6 100644 --- a/packages/coinbase-ethers/package.json +++ b/packages/coinbase-ethers/package.json @@ -25,12 +25,12 @@ "coinbase", "ethers" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/coinbase-ethers/readme.md b/packages/coinbase-ethers/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/coinbase-ethers/readme.md +++ b/packages/coinbase-ethers/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/coinbase-wagmi/package.json b/packages/coinbase-wagmi/package.json index 73aefd255..2f9e501c5 100644 --- a/packages/coinbase-wagmi/package.json +++ b/packages/coinbase-wagmi/package.json @@ -25,12 +25,12 @@ "coinbase", "wagmi" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/coinbase-wagmi/readme.md b/packages/coinbase-wagmi/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/coinbase-wagmi/readme.md +++ b/packages/coinbase-wagmi/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/common/package.json b/packages/common/package.json index 70a91eda6..ad8a16e7d 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -11,6 +11,10 @@ "test": "jest --passWithNoTests", "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, + "dependencies": { + "bignumber.js": "9.1.2", + "dayjs": "1.11.10" + }, "files": [ "src", "lib" @@ -23,12 +27,12 @@ "walletconnect", "react-native" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/common/readme.md b/packages/common/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/common/readme.md +++ b/packages/common/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/common/src/contracts/erc20.ts b/packages/common/src/contracts/erc20.ts new file mode 100644 index 000000000..744983257 --- /dev/null +++ b/packages/common/src/contracts/erc20.ts @@ -0,0 +1,95 @@ +export const erc20ABI = [ + { + type: 'function', + name: 'transfer', + stateMutability: 'nonpayable', + inputs: [ + { + name: '_to', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + } + ], + outputs: [ + { + name: '', + type: 'bool' + } + ] + }, + { + type: 'function', + name: 'transferFrom', + stateMutability: 'nonpayable', + inputs: [ + { + name: '_from', + type: 'address' + }, + { + name: '_to', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + } + ], + outputs: [ + { + name: '', + type: 'bool' + } + ] + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address' + } + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + } +]; diff --git a/packages/common/src/contracts/usdt.ts b/packages/common/src/contracts/usdt.ts new file mode 100644 index 000000000..8d2dbe957 --- /dev/null +++ b/packages/common/src/contracts/usdt.ts @@ -0,0 +1,43 @@ +export const usdtABI = [ + { + type: 'function', + name: 'transfer', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'recipient', + type: 'address' + }, + { + name: 'amount', + type: 'uint256' + } + ], + outputs: [] + }, + { + type: 'function', + name: 'transferFrom', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'sender', + type: 'address' + }, + { + name: 'recipient', + type: 'address' + }, + { + name: 'amount', + type: 'uint256' + } + ], + outputs: [ + { + name: '', + type: 'bool' + } + ] + } +]; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index f27beb61e..13f28df44 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,2 +1,10 @@ export { ConstantsUtil } from './utils/ConstantsUtil'; +export { ContractUtil } from './utils/ContractUtil'; +export { DateUtil } from './utils/DateUtil'; +export { NamesUtil } from './utils/NamesUtil'; export { NetworkUtil } from './utils/NetworkUtil'; +export { NumberUtil } from './utils/NumberUtil'; +export { StringUtil } from './utils/StringUtil'; +export { ErrorUtil } from './utils/ErrorUtil'; +export { erc20ABI } from './contracts/erc20'; +export * from './utils/TypeUtil'; diff --git a/packages/common/src/utils/ConstantsUtil.ts b/packages/common/src/utils/ConstantsUtil.ts index 064d54442..4d21f178c 100644 --- a/packages/common/src/utils/ConstantsUtil.ts +++ b/packages/common/src/utils/ConstantsUtil.ts @@ -1,7 +1,25 @@ export const ConstantsUtil = { + WC_NAME_SUFFIX: '.reown.id', + WC_NAME_SUFFIX_LEGACY: '.wcn.id', BLOCKCHAIN_API_RPC_URL: 'https://rpc.walletconnect.org', PULSE_API_URL: 'https://pulse.walletconnect.org', API_URL: 'https://api.web3modal.org', COINBASE_CONNECTOR_ID: 'coinbaseWallet', - COINBASE_EXPLORER_ID: 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa' + COINBASE_EXPLORER_ID: 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', + USDT_CONTRACT_ADDRESSES: [ + // Mainnet + '0xdac17f958d2ee523a2206206994597c13d831ec7', + // Polygon + '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', + // Avalanche + '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7', + // Cosmos + '0x919C1c267BC06a7039e03fcc2eF738525769109c', + // Celo + '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e', + // Binance + '0x55d398326f99059fF775485246999027B3197955', + // Arbitrum + '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + ] }; diff --git a/packages/common/src/utils/ContractUtil.ts b/packages/common/src/utils/ContractUtil.ts new file mode 100644 index 000000000..d102267ea --- /dev/null +++ b/packages/common/src/utils/ContractUtil.ts @@ -0,0 +1,13 @@ +import { erc20ABI } from '../contracts/erc20'; +import { usdtABI } from '../contracts/usdt'; +import { ConstantsUtil } from './ConstantsUtil'; + +export const ContractUtil = { + getERC20Abi: (tokenAddress: string) => { + if (ConstantsUtil.USDT_CONTRACT_ADDRESSES.includes(tokenAddress)) { + return usdtABI; + } + + return erc20ABI; + } +}; diff --git a/packages/common/src/utils/DateUtil.ts b/packages/common/src/utils/DateUtil.ts new file mode 100644 index 000000000..e6c09dcd3 --- /dev/null +++ b/packages/common/src/utils/DateUtil.ts @@ -0,0 +1,47 @@ +import dayjs from 'dayjs'; +import englishLocale from 'dayjs/locale/en.js'; +import relativeTime from 'dayjs/plugin/relativeTime.js'; +import updateLocale from 'dayjs/plugin/updateLocale.js'; + +dayjs.extend(relativeTime); +dayjs.extend(updateLocale); + +const localeObject = { + ...englishLocale, + name: 'en-web3-modal', + relativeTime: { + future: 'in %s', + past: '%s ago', + s: '%d sec', + m: '1 min', + mm: '%d min', + h: '1 hr', + hh: '%d hrs', + d: '1 d', + dd: '%d d', + M: '1 mo', + MM: '%d mo', + y: '1 yr', + yy: '%d yr' + } +}; + +dayjs.locale('en-appkit', localeObject); + +export const DateUtil = { + getYear(date: string = new Date().toISOString()) { + return dayjs(date).year(); + }, + + getRelativeDateFromNow(date: string | number) { + return dayjs(date).locale('en-appkit').fromNow(true); + }, + + formatDate(date: string | number, format = 'DD MMM') { + return dayjs(date).format(format); + }, + + getMonth(month: number) { + return dayjs().month(month).format('MMMM'); + } +}; diff --git a/packages/common/src/utils/ErrorUtil.ts b/packages/common/src/utils/ErrorUtil.ts new file mode 100644 index 000000000..35bf4bc27 --- /dev/null +++ b/packages/common/src/utils/ErrorUtil.ts @@ -0,0 +1,35 @@ +export const ErrorUtil = { + UniversalProviderErrors: { + UNAUTHORIZED_DOMAIN_NOT_ALLOWED: { + message: 'Unauthorized: origin not allowed', + alertErrorKey: 'INVALID_APP_CONFIGURATION' + }, + PROJECT_ID_NOT_CONFIGURED: { + message: 'Project ID is missing', + alertErrorKey: 'PROJECT_ID_NOT_CONFIGURED' + }, + JWT_VALIDATION_ERROR: { + message: 'JWT validation error: JWT Token is not yet valid', + alertErrorKey: 'JWT_TOKEN_NOT_VALID' + } + }, + ALERT_ERRORS: { + INVALID_APP_CONFIGURATION: { + shortMessage: 'Invalid App Configuration', + longMessage: `Bundle ID not found on Allowlist - Please verify that your bundle ID is allowed at https://cloud.reown.com` + }, + SOCIALS_TIMEOUT: { + shortMessage: 'Invalid App Configuration', + longMessage: + 'There was an issue loading the embedded wallet. Please verify that your bundle ID is allowed at https://cloud.reown.com' + }, + JWT_TOKEN_NOT_VALID: { + shortMessage: 'Session Expired', + longMessage: 'Invalid session found - please check your time settings and connect again' + }, + PROJECT_ID_NOT_CONFIGURED: { + shortMessage: 'Project ID Not Configured', + longMessage: 'Project ID Not Configured - update configuration' + } + } +}; diff --git a/packages/common/src/utils/NamesUtil.ts b/packages/common/src/utils/NamesUtil.ts new file mode 100644 index 000000000..312eda743 --- /dev/null +++ b/packages/common/src/utils/NamesUtil.ts @@ -0,0 +1,10 @@ +import { ConstantsUtil } from './ConstantsUtil'; + +export const NamesUtil = { + isReownName(value: string) { + return ( + value?.endsWith(ConstantsUtil.WC_NAME_SUFFIX_LEGACY) || + value?.endsWith(ConstantsUtil.WC_NAME_SUFFIX) + ); + } +}; diff --git a/packages/common/src/utils/NumberUtil.ts b/packages/common/src/utils/NumberUtil.ts new file mode 100644 index 000000000..760071bad --- /dev/null +++ b/packages/common/src/utils/NumberUtil.ts @@ -0,0 +1,31 @@ +import * as BigNumber from 'bignumber.js'; + +export const NumberUtil = { + bigNumber(value: BigNumber.BigNumber.Value) { + return new BigNumber.BigNumber(value); + }, + + /** + * Multiply two numbers represented as strings with BigNumber to handle decimals correctly + * @param a string + * @param b string + * @returns + */ + multiply(a: BigNumber.BigNumber.Value | undefined, b: BigNumber.BigNumber.Value | undefined) { + if (a === undefined || b === undefined) { + return BigNumber.BigNumber(0); + } + + const aBigNumber = new BigNumber.BigNumber(a); + const bBigNumber = new BigNumber.BigNumber(b); + + return aBigNumber.multipliedBy(bBigNumber); + }, + + roundNumber(number: number, threshold: number, fixed: number) { + const roundedNumber = + number.toString().length >= threshold ? Number(number).toFixed(fixed) : number; + + return roundedNumber; + } +}; diff --git a/packages/common/src/utils/StringUtil.ts b/packages/common/src/utils/StringUtil.ts new file mode 100644 index 000000000..024f725f8 --- /dev/null +++ b/packages/common/src/utils/StringUtil.ts @@ -0,0 +1,9 @@ +export const StringUtil = { + capitalize(value?: string) { + if (!value) { + return ''; + } + + return value.charAt(0).toUpperCase() + value.slice(1); + } +}; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts new file mode 100644 index 000000000..208e8fee2 --- /dev/null +++ b/packages/common/src/utils/TypeUtil.ts @@ -0,0 +1,89 @@ +export interface Balance { + name: string; + symbol: string; + chainId: string; + address?: string; + value?: number; + price: number; + quantity: BalanceQuantity; + iconUrl: string; +} + +type BalanceQuantity = { + decimals: string; + numeric: string; +}; + +export type TransactionStatus = 'confirmed' | 'failed' | 'pending'; +export type TransactionDirection = 'in' | 'out' | 'self'; +export type TransactionImage = { + type: 'FUNGIBLE' | 'NFT' | undefined; + url: string | undefined; +}; + +export interface Transaction { + id: string; + metadata: TransactionMetadata; + transfers: TransactionTransfer[]; +} + +export interface TransactionMetadata { + application: { + iconUrl: string | null; + name: string | null; + }; + operationType: string; + hash: string; + minedAt: string; + sentFrom: string; + sentTo: string; + status: TransactionStatus; + nonce: number; + chain?: string; +} + +export interface TransactionTransfer { + fungible_info?: { + name?: string; + symbol?: string; + icon?: { + url: string; + }; + }; + nft_info?: TransactionNftInfo; + direction: TransactionDirection; + quantity: TransactionQuantity; + value?: number; + price?: number; +} + +export interface TransactionNftInfo { + name?: string; + content?: TransactionContent; + flags: TransactionNftInfoFlags; +} + +export interface TransactionNftInfoFlags { + is_spam: boolean; +} + +export interface TransactionContent { + preview?: TransactionPreview; + detail?: TransactionDetail; +} + +export interface TransactionPreview { + url: string; + content_type?: null; +} + +export interface TransactionDetail { + url: string; + content_type?: null; +} + +export interface TransactionQuantity { + numeric: string; +} + +export type SocialProvider = 'apple' | 'x' | 'discord' | 'farcaster'; diff --git a/packages/core/package.json b/packages/core/package.json index ee41a00f1..c43952164 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,12 +26,12 @@ "walletconnect", "react-native" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/core/readme.md b/packages/core/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/core/readme.md +++ b/packages/core/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/core/src/__tests__/controllers/AccountController.test.ts b/packages/core/src/__tests__/controllers/AccountController.test.ts index c309f4755..4783f804c 100644 --- a/packages/core/src/__tests__/controllers/AccountController.test.ts +++ b/packages/core/src/__tests__/controllers/AccountController.test.ts @@ -7,10 +7,17 @@ const balanceSymbol = 'ETH'; const profileName = 'john.eth'; const profileImage = 'https://ipfs.com/0x123.png'; +const initialState = { + isConnected: false, + tokenBalance: [], + preferredAccountType: 'eoa', + smartAccountDeployed: false +}; + // -- Tests -------------------------------------------------------------------- describe('AccountController', () => { it('should have valid default state', () => { - expect(AccountController.state).toEqual({ isConnected: false }); + expect(AccountController.state).toEqual(initialState); }); it('should update state correctly on setIsConnected()', () => { @@ -42,6 +49,6 @@ describe('AccountController', () => { it('should update state correctly on resetAccount()', () => { AccountController.resetAccount(); - expect(AccountController.state).toEqual({ isConnected: false }); + expect(AccountController.state).toEqual(initialState); }); }); diff --git a/packages/core/src/__tests__/controllers/NetworkController.test.ts b/packages/core/src/__tests__/controllers/NetworkController.test.ts index 5018c0657..9202383c5 100644 --- a/packages/core/src/__tests__/controllers/NetworkController.test.ts +++ b/packages/core/src/__tests__/controllers/NetworkController.test.ts @@ -16,6 +16,13 @@ const client: NetworkControllerClient = { Promise.resolve({ approvedCaipNetworkIds, supportsAllNetworks: false }) }; +const initialState = { + _client: client, + supportsAllNetworks: true, + isDefaultCaipNetwork: false, + smartAccountEnabledNetworks: [] +}; + // -- Tests -------------------------------------------------------------------- describe('NetworkController', () => { it('should throw if client not set', () => { @@ -25,11 +32,7 @@ describe('NetworkController', () => { it('should have valid default state', () => { NetworkController.setClient(client); - expect(NetworkController.state).toEqual({ - _client: NetworkController._getClient(), - supportsAllNetworks: true, - isDefaultCaipNetwork: false - }); + expect(NetworkController.state).toEqual(initialState); }); it('should update state correctly on setRequestedCaipNetworks()', () => { diff --git a/packages/core/src/__tests__/controllers/OptionsController.test.ts b/packages/core/src/__tests__/controllers/OptionsController.test.ts index cdcba49d6..e0e536a32 100644 --- a/packages/core/src/__tests__/controllers/OptionsController.test.ts +++ b/packages/core/src/__tests__/controllers/OptionsController.test.ts @@ -1,4 +1,4 @@ -import { OptionsController } from '../../index'; +import { ConstantsUtil, OptionsController } from '../../index'; const MOCK_WALLET_IDS = [ '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', @@ -15,7 +15,9 @@ describe('OptionsController', () => { expect(OptionsController.state).toEqual({ projectId: '', sdkType: 'appkit', - sdkVersion: 'react-native-wagmi-undefined' + sdkVersion: 'react-native-wagmi-undefined', + features: ConstantsUtil.DEFAULT_FEATURES, + debug: false }); }); diff --git a/packages/core/src/__tests__/controllers/RouterController.test.ts b/packages/core/src/__tests__/controllers/RouterController.test.ts index c6a1c09dd..8e5d56f1f 100644 --- a/packages/core/src/__tests__/controllers/RouterController.test.ts +++ b/packages/core/src/__tests__/controllers/RouterController.test.ts @@ -5,7 +5,8 @@ describe('RouterController', () => { it('should have valid default state', () => { expect(RouterController.state).toEqual({ view: 'Connect', - history: ['Connect'] + history: ['Connect'], + transactionStack: [] }); }); @@ -13,7 +14,8 @@ describe('RouterController', () => { RouterController.push('Account'); expect(RouterController.state).toEqual({ view: 'Account', - history: ['Connect', 'Account'] + history: ['Connect', 'Account'], + transactionStack: [] }); }); @@ -21,7 +23,8 @@ describe('RouterController', () => { RouterController.push('Account'); expect(RouterController.state).toEqual({ view: 'Account', - history: ['Connect', 'Account'] + history: ['Connect', 'Account'], + transactionStack: [] }); }); @@ -29,7 +32,8 @@ describe('RouterController', () => { RouterController.goBack(); expect(RouterController.state).toEqual({ view: 'Connect', - history: ['Connect'] + history: ['Connect'], + transactionStack: [] }); }); @@ -37,7 +41,8 @@ describe('RouterController', () => { RouterController.goBack(); expect(RouterController.state).toEqual({ view: 'Connect', - history: ['Connect'] + history: ['Connect'], + transactionStack: [] }); }); @@ -45,7 +50,8 @@ describe('RouterController', () => { RouterController.reset('Account'); expect(RouterController.state).toEqual({ view: 'Account', - history: ['Account'] + history: ['Account'], + transactionStack: [] }); }); @@ -54,7 +60,8 @@ describe('RouterController', () => { RouterController.replace('Networks'); expect(RouterController.state).toEqual({ view: 'Networks', - history: ['Account', 'Networks'] + history: ['Account', 'Networks'], + transactionStack: [] }); }); @@ -67,7 +74,8 @@ describe('RouterController', () => { history: ['Account', 'Networks', 'ConnectingWalletConnect'], data: { wallet: { id: 'test', name: 'TestWallet' } - } + }, + transactionStack: [] }); }); }); diff --git a/packages/core/src/__tests__/controllers/SendController.test.ts b/packages/core/src/__tests__/controllers/SendController.test.ts new file mode 100644 index 000000000..4048144a1 --- /dev/null +++ b/packages/core/src/__tests__/controllers/SendController.test.ts @@ -0,0 +1,57 @@ +import { SendController } from '../../index'; + +// -- Setup -------------------------------------------------------------------- +const token = { + name: 'Optimism', + address: 'eip155:10:0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + symbol: 'OP', + chainId: 'eip155:10', + value: 6.05441523113072, + price: 4.5340112, + quantity: { + decimals: '18', + numeric: '1.335333100000000000' + }, + iconUrl: 'https://token-icons.s3.amazonaws.com/0x4200000000000000000000000000000000000042.png' +}; +const sendTokenAmount = 0.1; +const receiverAddress = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'; +const receiverProfileName = 'john.eth'; +const receiverProfileImageUrl = 'https://ipfs.com/0x123.png'; + +// -- Tests -------------------------------------------------------------------- +describe('SendController', () => { + it('should have valid default state', () => { + expect(SendController.state).toEqual({ loading: false }); + }); + + it('should update state correctly on setToken()', () => { + SendController.setToken(token); + expect(SendController.state.token).toEqual(token); + }); + + it('should update state correctly on setTokenAmount()', () => { + SendController.setTokenAmount(sendTokenAmount); + expect(SendController.state.sendTokenAmount).toEqual(sendTokenAmount); + }); + + it('should update state correctly on receiverAddress()', () => { + SendController.setReceiverAddress(receiverAddress); + expect(SendController.state.receiverAddress).toEqual(receiverAddress); + }); + + it('should update state correctly on receiverProfileName()', () => { + SendController.setReceiverProfileName(receiverProfileName); + expect(SendController.state.receiverProfileName).toEqual(receiverProfileName); + }); + + it('should update state correctly on setReceiverProfileImageUrl()', () => { + SendController.setReceiverProfileImageUrl(receiverProfileImageUrl); + expect(SendController.state.receiverProfileImageUrl).toEqual(receiverProfileImageUrl); + }); + + it('should update state correctly on resetSend()', () => { + SendController.resetSend(); + expect(SendController.state).toEqual({ loading: false }); + }); +}); diff --git a/packages/core/src/__tests__/controllers/SnackController.test.ts b/packages/core/src/__tests__/controllers/SnackController.test.ts index 2192c454f..ec10a043b 100644 --- a/packages/core/src/__tests__/controllers/SnackController.test.ts +++ b/packages/core/src/__tests__/controllers/SnackController.test.ts @@ -1,4 +1,10 @@ -import { SnackController } from '../../index'; +import { OptionsController, SnackController } from '../../index'; + +// Setup +OptionsController.state.debug = true; + +// eslint-disable-next-line no-console +console.error = jest.fn(); // -- Tests -------------------------------------------------------------------- describe('SnackController', () => { @@ -6,7 +12,8 @@ describe('SnackController', () => { expect(SnackController.state).toEqual({ message: '', variant: 'success', - open: false + open: false, + long: false }); }); @@ -15,16 +22,18 @@ describe('SnackController', () => { expect(SnackController.state).toEqual({ message: 'Success Msg', variant: 'success', - open: true + open: true, + long: false }); }); it('should update state correctly on hide()', () => { SnackController.hide(); expect(SnackController.state).toEqual({ - message: 'Success Msg', + message: '', variant: 'success', - open: false + open: false, + long: false }); }); @@ -33,7 +42,18 @@ describe('SnackController', () => { expect(SnackController.state).toEqual({ message: 'Error Msg', variant: 'error', - open: true + open: true, + long: false + }); + }); + + it('should update state correctly on showInternalError()', () => { + SnackController.showInternalError({ shortMessage: 'Error Msg', longMessage: 'Error Msg' }); + expect(SnackController.state).toEqual({ + message: 'Error Msg', + variant: 'error', + open: true, + long: true }); }); }); diff --git a/packages/core/src/controllers/AccountController.ts b/packages/core/src/controllers/AccountController.ts index 116d28f26..808d38f41 100644 --- a/packages/core/src/controllers/AccountController.ts +++ b/packages/core/src/controllers/AccountController.ts @@ -1,8 +1,12 @@ import { proxy } from 'valtio'; import { subscribeKey as subKey } from 'valtio/utils'; +import type { Balance } from '@reown/appkit-common-react-native'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; -import type { CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil'; +import type { AppKitFrameAccountType, CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil'; +import { NetworkController } from './NetworkController'; +import { BlockchainApiController } from './BlockchainApiController'; +import { SnackController } from './SnackController'; // -- Types --------------------------------------------- // export interface AccountControllerState { @@ -11,17 +15,23 @@ export interface AccountControllerState { address?: string; balance?: string; balanceSymbol?: string; + tokenBalance?: Balance[]; profileName?: string; profileImage?: string; addressExplorerUrl?: string; connectedWalletInfo?: ConnectedWalletInfo; + preferredAccountType?: AppKitFrameAccountType; + smartAccountDeployed?: boolean; } type StateKey = keyof AccountControllerState; // -- State --------------------------------------------- // const state = proxy({ - isConnected: false + isConnected: false, + tokenBalance: [], + smartAccountDeployed: false, + preferredAccountType: 'eoa' }); // -- Controller ---------------------------------------- // @@ -50,6 +60,10 @@ export const AccountController = { state.balanceSymbol = balanceSymbol; }, + setTokenBalance(tokenBalance: AccountControllerState['tokenBalance']) { + state.tokenBalance = tokenBalance; + }, + setProfileName(profileName: AccountControllerState['profileName']) { state.profileName = profileName; }, @@ -66,6 +80,37 @@ export const AccountController = { state.addressExplorerUrl = explorerUrl; }, + setPreferredAccountType(accountType: AccountControllerState['preferredAccountType']) { + state.preferredAccountType = accountType; + }, + + setSmartAccountDeployed(smartAccountDeployed: AccountControllerState['smartAccountDeployed']) { + state.smartAccountDeployed = smartAccountDeployed; + }, + + async fetchTokenBalance() { + const chainId = NetworkController.state.caipNetwork?.id; + const address = AccountController.state.address; + + try { + if (address && chainId) { + const response = await BlockchainApiController.getBalance(address, chainId); + + if (!response) { + throw new Error('Failed to fetch token balance'); + } + + const filteredBalances = response.balances.filter( + balance => balance.quantity.decimals !== '0' + ); + + this.setTokenBalance(filteredBalances); + } + } catch (error) { + SnackController.showError('Failed to fetch token balance'); + } + }, + resetAccount() { state.isConnected = false; state.caipAddress = undefined; @@ -75,6 +120,9 @@ export const AccountController = { state.profileName = undefined; state.profileImage = undefined; state.addressExplorerUrl = undefined; + state.tokenBalance = []; state.connectedWalletInfo = undefined; + state.preferredAccountType = 'eoa'; + state.smartAccountDeployed = false; } }; diff --git a/packages/core/src/controllers/ApiController.ts b/packages/core/src/controllers/ApiController.ts index de358ce27..db9766252 100644 --- a/packages/core/src/controllers/ApiController.ts +++ b/packages/core/src/controllers/ApiController.ts @@ -16,6 +16,8 @@ import { NetworkController } from './NetworkController'; import { OptionsController } from './OptionsController'; import { ConnectorController } from './ConnectorController'; import { ConnectionController } from './ConnectionController'; +import { ApiUtil } from '../utils/ApiUtil'; +import { SnackController } from './SnackController'; // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getApiUrl(); @@ -26,6 +28,8 @@ const recommendedEntries = '4'; // -- Types --------------------------------------------- // export interface ApiControllerState { prefetchPromise?: Promise; + prefetchError?: boolean; + prefetchLoading?: boolean; page: number; count: number; featured: WcWallet[]; @@ -62,18 +66,13 @@ export const ApiController = { _getApiHeaders() { const { projectId, sdkType, sdkVersion } = OptionsController.state; - const reactNativeVersion = [ - Platform.constants.reactNativeVersion.major, - Platform.constants.reactNativeVersion.minor, - Platform.constants.reactNativeVersion.patch - ].join('.'); return { 'x-project-id': projectId, 'x-sdk-type': sdkType, 'x-sdk-version': sdkVersion, - 'User-Agent': `${Platform.OS}-${Platform.Version}@rn-${reactNativeVersion}`, - 'Origin': CoreHelperUtil.getBundleId() + 'User-Agent': ApiUtil.getUserAgent(), + 'Origin': ApiUtil.getOrigin() }; }, @@ -330,23 +329,35 @@ export const ApiController = { }, async prefetch() { - // this fetch must resolve first so we filter them in the other wallet requests - await ApiController.fetchInstalledWallets(); - - const promises = [ - ApiController.fetchFeaturedWallets(), - ApiController.fetchRecommendedWallets(), - ApiController.fetchNetworkImages(), - ApiController.fetchConnectorImages() - ]; - if (OptionsController.state.enableAnalytics === undefined) { - promises.push(ApiController.fetchAnalyticsConfig()); - } + try { + state.prefetchError = false; + state.prefetchLoading = true; + // this fetch must resolve first so we filter them in the other wallet requests + await ApiController.fetchInstalledWallets(); + + const promises = [ + ApiController.fetchFeaturedWallets(), + ApiController.fetchRecommendedWallets(), + ApiController.fetchNetworkImages(), + ApiController.fetchConnectorImages() + ]; + if (OptionsController.state.enableAnalytics === undefined) { + promises.push(ApiController.fetchAnalyticsConfig()); + } - state.prefetchPromise = Promise.race([ - CoreHelperUtil.allSettled(promises), - CoreHelperUtil.wait(3000) - ]); + state.prefetchPromise = Promise.race([ + CoreHelperUtil.allSettled(promises), + CoreHelperUtil.wait(3000) + ]); + + state.prefetchPromise.then(() => { + state.prefetchLoading = false; + }); + } catch (error) { + state.prefetchError = true; + state.prefetchLoading = false; + SnackController.showError('Failed to load wallets'); + } }, async fetchAnalyticsConfig() { diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 3a1165062..ddf7eb854 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -3,8 +3,16 @@ import { proxy } from 'valtio'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { FetchUtil } from '../utils/FetchUtil'; import type { + BlockchainApiBalanceResponse, + BlockchainApiGasPriceRequest, + BlockchainApiGasPriceResponse, BlockchainApiIdentityRequest, - BlockchainApiIdentityResponse + BlockchainApiIdentityResponse, + BlockchainApiLookupEnsName, + BlockchainApiTokenPriceRequest, + BlockchainApiTokenPriceResponse, + BlockchainApiTransactionsRequest, + BlockchainApiTransactionsResponse } from '../utils/TypeUtil'; import { OptionsController } from './OptionsController'; @@ -36,6 +44,85 @@ export const BlockchainApiController = { }); }, + fetchTransactions({ + account, + projectId, + cursor, + onramp, + signal, + cache + }: BlockchainApiTransactionsRequest) { + return state.api.get({ + path: `/v1/account/${account}/history`, + params: { + projectId, + cursor, + onramp + }, + signal, + cache + }); + }, + + fetchTokenPrice({ projectId, addresses }: BlockchainApiTokenPriceRequest) { + return state.api.post({ + path: '/v1/fungible/price', + body: { + projectId, + currency: 'usd', + addresses + }, + headers: { + 'Content-Type': 'application/json' + } + }); + }, + + fetchGasPrice({ projectId, chainId }: BlockchainApiGasPriceRequest) { + const { sdkType, sdkVersion } = OptionsController.state; + + return state.api.get({ + path: `/v1/convert/gas-price`, + headers: { + 'Content-Type': 'application/json', + 'x-sdk-type': sdkType, + 'x-sdk-version': sdkVersion + }, + params: { + projectId, + chainId + } + }); + }, + + async getBalance(address: string, chainId?: string, forceUpdate?: string) { + const { sdkType, sdkVersion } = OptionsController.state; + + return state.api.get({ + path: `/v1/account/${address}/balance`, + headers: { + 'x-sdk-type': sdkType, + 'x-sdk-version': sdkVersion + }, + params: { + currency: 'usd', + projectId: OptionsController.state.projectId, + chainId, + forceUpdate + } + }); + }, + + async lookupEnsName(name: string) { + return state.api.get({ + path: `/v1/profile/account/${name}`, + params: { + projectId: OptionsController.state.projectId, + apiVersion: '2' + } + }); + }, + setClientId(clientId: string | null) { state.clientId = clientId; state.api = new FetchUtil({ baseUrl, clientId }); diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 93a842e36..f0c7c8758 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -1,8 +1,14 @@ -import { subscribeKey as subKey } from 'valtio/utils'; import { proxy, ref } from 'valtio'; +import { subscribeKey as subKey } from 'valtio/utils'; +import type { SocialProvider } from '@reown/appkit-common-react-native'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { StorageUtil } from '../utils/StorageUtil'; -import type { Connector, WcWallet } from '../utils/TypeUtil'; +import type { + Connector, + SendTransactionArgs, + WcWallet, + WriteContractArgs +} from '../utils/TypeUtil'; import { RouterController } from './RouterController'; import { ConnectorController } from './ConnectorController'; @@ -21,7 +27,13 @@ export interface ConnectionControllerClient { ) => Promise; connectExternal?: (options: ConnectExternalOptions) => Promise; signMessage: (message: string) => Promise; + sendTransaction: (args: SendTransactionArgs) => Promise<`0x${string}` | null>; + parseUnits: (value: string, decimals: number) => bigint; + formatUnits: (value: bigint, decimals: number) => string; + writeContract: (args: WriteContractArgs) => Promise<`0x${string}` | null>; disconnect: () => Promise; + getEnsAddress: (value: string) => Promise; + getEnsAvatar: (value: string) => Promise; } export interface ConnectionControllerState { @@ -36,7 +48,9 @@ export interface ConnectionControllerState { wcError?: boolean; pressedWallet?: WcWallet; recentWallets?: WcWallet[]; + selectedSocialProvider?: SocialProvider; connectedWalletImageUrl?: string; + connectedSocialProvider?: SocialProvider; } type StateKey = keyof ConnectionControllerState; @@ -73,15 +87,12 @@ export const ConnectionController = { state.wcPromise = this._getClient().connectWalletConnect(uri => { state.wcUri = uri; state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry(); - ConnectorController.setConnectedConnector('WALLET_CONNECT'); - StorageUtil.setConnectedConnector('WALLET_CONNECT'); }, walletUniversalLink); }, async connectExternal(options: ConnectExternalOptions) { await this._getClient().connectExternal?.(options); ConnectorController.setConnectedConnector(options.type); - StorageUtil.setConnectedConnector(options.type); }, async signMessage(message: string) { @@ -112,6 +123,10 @@ export const ConnectionController = { state.recentWallets = wallets; }, + setSelectedSocialProvider(provider: ConnectionControllerState['selectedSocialProvider']) { + state.selectedSocialProvider = provider; + }, + async setConnectedWalletImageUrl(url: ConnectionControllerState['connectedWalletImageUrl']) { state.connectedWalletImageUrl = url; @@ -122,6 +137,40 @@ export const ConnectionController = { } }, + setConnectedSocialProvider(provider: ConnectionControllerState['connectedSocialProvider']) { + state.connectedSocialProvider = provider; + + if (provider) { + StorageUtil.setConnectedSocialProvider(provider); + } else { + StorageUtil.removeConnectedSocialProvider(); + } + }, + + parseUnits(value: string, decimals: number) { + return this._getClient().parseUnits(value, decimals); + }, + + formatUnits(value: bigint, decimals: number) { + return this._getClient().formatUnits(value, decimals); + }, + + async sendTransaction(args: SendTransactionArgs) { + return this._getClient().sendTransaction(args); + }, + + async writeContract(args: WriteContractArgs) { + return this._getClient().writeContract(args); + }, + + async getEnsAddress(value: string) { + return this._getClient().getEnsAddress(value); + }, + + async getEnsAvatar(value: string) { + return this._getClient().getEnsAvatar(value); + }, + clearUri() { state.wcUri = undefined; state.wcPairingExpiry = undefined; @@ -132,16 +181,16 @@ export const ConnectionController = { resetWcConnection() { this.clearUri(); state.pressedWallet = undefined; - state.connectedWalletImageUrl = undefined; + state.selectedSocialProvider = undefined; + ConnectionController.setConnectedWalletImageUrl(undefined); ConnectorController.setConnectedConnector(undefined); StorageUtil.removeWalletConnectDeepLink(); - StorageUtil.removeConnectedWalletImageUrl(); - StorageUtil.removeConnectedConnector(); }, async disconnect() { await this._getClient().disconnect(); this.resetWcConnection(); + // remove transactions RouterController.reset('Connect'); } }; diff --git a/packages/core/src/controllers/ConnectorController.ts b/packages/core/src/controllers/ConnectorController.ts index c634ae52e..a74a4c1c5 100644 --- a/packages/core/src/controllers/ConnectorController.ts +++ b/packages/core/src/controllers/ConnectorController.ts @@ -1,6 +1,7 @@ import { subscribeKey as subKey } from 'valtio/utils'; import { proxy, ref } from 'valtio'; import type { Connector, ConnectorType } from '../utils/TypeUtil'; +import { StorageUtil } from '../utils/StorageUtil'; // -- Types --------------------------------------------- // export interface ConnectorControllerState { @@ -40,8 +41,19 @@ export const ConnectorController = { return state.connectors.find(c => c.type === 'AUTH'); }, - setConnectedConnector(connectorType: ConnectorControllerState['connectedConnector']) { + setConnectedConnector( + connectorType: ConnectorControllerState['connectedConnector'], + saveStorage = true + ) { state.connectedConnector = connectorType; + + if (saveStorage) { + if (connectorType) { + StorageUtil.setConnectedConnector(connectorType); + } else { + StorageUtil.removeConnectedConnector(); + } + } }, setAuthLoading(loading: ConnectorControllerState['authLoading']) { diff --git a/packages/core/src/controllers/EnsController.ts b/packages/core/src/controllers/EnsController.ts new file mode 100644 index 000000000..c80fadf21 --- /dev/null +++ b/packages/core/src/controllers/EnsController.ts @@ -0,0 +1,39 @@ +import { subscribeKey as subKey } from 'valtio/vanilla/utils'; +import { proxy, subscribe as sub } from 'valtio/vanilla'; +import { BlockchainApiController } from './BlockchainApiController'; +import type { BlockchainApiEnsError } from '../utils/TypeUtil'; + +// -- Types --------------------------------------------- // + +export interface EnsControllerState { + loading: boolean; +} + +type StateKey = keyof EnsControllerState; + +// -- State --------------------------------------------- // +const state = proxy({ + loading: false +}); + +// -- Controller ---------------------------------------- // +export const EnsController = { + state, + + subscribe(callback: (newState: EnsControllerState) => void) { + return sub(state, () => callback(state)); + }, + + subscribeKey(key: K, callback: (value: EnsControllerState[K]) => void) { + return subKey(state, key, callback); + }, + + async resolveName(name: string) { + try { + return await BlockchainApiController.lookupEnsName(name); + } catch (e) { + const error = e as BlockchainApiEnsError; + throw new Error(error?.reasons?.[0]?.description || 'Error resolving name'); + } + } +}; diff --git a/packages/core/src/controllers/EventsController.ts b/packages/core/src/controllers/EventsController.ts index 76a4177e3..48e45cb1c 100644 --- a/packages/core/src/controllers/EventsController.ts +++ b/packages/core/src/controllers/EventsController.ts @@ -1,4 +1,3 @@ -import { Platform } from 'react-native'; import { proxy, subscribe as sub } from 'valtio/vanilla'; import { ApiController } from './ApiController'; import { OptionsController } from './OptionsController'; @@ -34,17 +33,6 @@ export const EventsController = { return sub(state, () => callback(state)); }, - _getApiHeaders() { - const { projectId, sdkType, sdkVersion } = OptionsController.state; - - return { - 'x-project-id': projectId, - 'x-sdk-type': sdkType, - 'x-sdk-version': sdkVersion, - 'User-Agent': `${Platform.OS}-${Platform.Version}` - }; - }, - async _sendAnalyticsEvent(data: EventsControllerState['data'], timestamp: number) { if (excluded.includes(data.event)) { return; @@ -53,7 +41,7 @@ export const EventsController = { try { await api.post({ path: '/e', - headers: this._getApiHeaders(), + headers: ApiController._getApiHeaders(), body: { eventId: CoreHelperUtil.getUUID(), bundleId: CoreHelperUtil.getBundleId(), diff --git a/packages/core/src/controllers/ModalController.ts b/packages/core/src/controllers/ModalController.ts index 3da4daf4c..cb67edcad 100644 --- a/packages/core/src/controllers/ModalController.ts +++ b/packages/core/src/controllers/ModalController.ts @@ -5,6 +5,7 @@ import { RouterController } from './RouterController'; import { PublicStateController } from './PublicStateController'; import { EventsController } from './EventsController'; import { ApiController } from './ApiController'; +import { ConnectorController } from './ConnectorController'; // -- Types --------------------------------------------- // export interface ModalControllerState { @@ -34,7 +35,8 @@ export const ModalController = { if (options?.view) { RouterController.reset(options.view); } else if (AccountController.state.isConnected) { - RouterController.reset('Account'); + const isUniversalWallet = ConnectorController.state.connectedConnector === 'AUTH'; + RouterController.reset(isUniversalWallet ? 'Account' : 'AccountDefault'); } else { RouterController.reset('Connect'); } diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index f917efa7b..8d62235f3 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -1,6 +1,7 @@ import { proxy, ref } from 'valtio'; import type { CaipNetwork, CaipNetworkId } from '../utils/TypeUtil'; import { PublicStateController } from './PublicStateController'; +import { NetworkUtil } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface NetworkControllerClient { @@ -18,12 +19,14 @@ export interface NetworkControllerState { caipNetwork?: CaipNetwork; requestedCaipNetworks?: CaipNetwork[]; approvedCaipNetworkIds?: CaipNetworkId[]; + smartAccountEnabledNetworks: number[]; } // -- State --------------------------------------------- // const state = proxy({ supportsAllNetworks: true, - isDefaultCaipNetwork: false + isDefaultCaipNetwork: false, + smartAccountEnabledNetworks: [] }); // -- Controller ---------------------------------------- // @@ -57,12 +60,41 @@ export const NetworkController = { state.requestedCaipNetworks = requestedNetworks; }, + setSmartAccountEnabledNetworks( + smartAccountEnabledNetworks: NetworkControllerState['smartAccountEnabledNetworks'] + ) { + state.smartAccountEnabledNetworks = smartAccountEnabledNetworks; + }, + + checkIfSmartAccountEnabled() { + const networkId = NetworkUtil.caipNetworkIdToNumber(state.caipNetwork?.id); + + if (!networkId) { + return false; + } + + return Boolean(state.smartAccountEnabledNetworks?.includes(Number(networkId))); + }, + async getApprovedCaipNetworksData() { const data = await this._getClient().getApprovedCaipNetworksData(); state.supportsAllNetworks = data.supportsAllNetworks; state.approvedCaipNetworkIds = data.approvedCaipNetworkIds; }, + getApprovedCaipNetworks() { + return state.approvedCaipNetworkIds + ?.map(id => state.requestedCaipNetworks?.find(network => network.id === id)) + .filter(Boolean) as CaipNetwork[]; + }, + + getSmartAccountEnabledNetworks() { + return this.getApprovedCaipNetworks().filter( + network => + state.smartAccountEnabledNetworks?.find(networkId => network.id === `eip155:${networkId}`) + ); + }, + async switchActiveNetwork(network: NetworkControllerState['caipNetwork']) { await this._getClient().switchCaipNetwork(network); state.caipNetwork = network; @@ -75,5 +107,6 @@ export const NetworkController = { } state.approvedCaipNetworkIds = undefined; state.supportsAllNetworks = true; + state.smartAccountEnabledNetworks = []; } }; diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index 6151fdd9e..24fde94a9 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -1,5 +1,14 @@ import { proxy, ref } from 'valtio'; -import type { CustomWallet, Metadata, ProjectId, SdkVersion, Tokens } from '../utils/TypeUtil'; +import type { + CustomWallet, + Features, + Metadata, + ProjectId, + SdkType, + SdkVersion, + Tokens +} from '../utils/TypeUtil'; +import { ConstantsUtil } from '../utils/ConstantsUtil'; // -- Types --------------------------------------------- // export interface ClipboardClient { @@ -15,17 +24,21 @@ export interface OptionsControllerState { customWallets?: CustomWallet[]; tokens?: Tokens; enableAnalytics?: boolean; - sdkType: string; + sdkType: SdkType; sdkVersion: SdkVersion; metadata?: Metadata; isSiweEnabled?: boolean; + features?: Features; + debug?: boolean; } // -- State --------------------------------------------- // const state = proxy({ projectId: '', sdkType: 'appkit', - sdkVersion: 'react-native-wagmi-undefined' + sdkVersion: 'react-native-wagmi-undefined', + features: ConstantsUtil.DEFAULT_FEATURES, + debug: false }); // -- Controller ---------------------------------------- // @@ -76,6 +89,14 @@ export const OptionsController = { state.isSiweEnabled = isSiweEnabled; }, + setFeatures(features: OptionsControllerState['features']) { + state.features = { ...ConstantsUtil.DEFAULT_FEATURES, ...features }; + }, + + setDebug(debug: OptionsControllerState['debug']) { + state.debug = debug; + }, + isClipboardAvailable() { return !!state._clipboardClient; }, @@ -83,7 +104,7 @@ export const OptionsController = { copyToClipboard(value: string) { const client = state._clipboardClient; if (client) { - client.setString(value); + client?.setString(value); } } }; diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 9eecdd34d..e84195189 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -2,25 +2,46 @@ import { proxy } from 'valtio'; import type { WcWallet, CaipNetwork, Connector } from '../utils/TypeUtil'; // -- Types --------------------------------------------- // +type TransactionAction = { + goBack: boolean; + view: RouterControllerState['view'] | null; + close?: boolean; + replace?: boolean; + onSuccess?: () => void; + onCancel?: () => void; +}; + export interface RouterControllerState { view: | 'Account' + | 'AccountDefault' + | 'AllWallets' | 'Connect' - | 'ConnectingWalletConnect' + | 'ConnectSocials' | 'ConnectingExternal' - | 'Networks' - | 'SwitchNetwork' - | 'AllWallets' - | 'WhatIsAWallet' - | 'WhatIsANetwork' - | 'GetWallet' + | 'ConnectingSiwe' + | 'ConnectingSocial' + | 'ConnectingFarcaster' + | 'ConnectingWalletConnect' + | 'Create' | 'EmailVerifyDevice' | 'EmailVerifyOtp' - | 'UpdateEmailWallet' + | 'GetWallet' + | 'Networks' + | 'SwitchNetwork' + | 'Transactions' | 'UpdateEmailPrimaryOtp' | 'UpdateEmailSecondaryOtp' + | 'UpdateEmailWallet' | 'UpgradeEmailWallet' - | 'ConnectingSiwe'; + | 'UpgradeToSmartAccount' + | 'WalletCompatibleNetworks' + | 'WalletReceive' + | 'WalletSend' + | 'WalletSendPreview' + | 'WalletSendSelectToken' + | 'WhatIsANetwork' + | 'WhatIsAWallet'; history: RouterControllerState['view'][]; data?: { connector?: Connector; @@ -29,12 +50,14 @@ export interface RouterControllerState { email?: string; newEmail?: string; }; + transactionStack: TransactionAction[]; } // -- State --------------------------------------------- // const state = proxy({ view: 'Connect', - history: ['Connect'] + history: ['Connect'], + transactionStack: [] }); // -- Controller ---------------------------------------- // @@ -49,6 +72,30 @@ export const RouterController = { } }, + pushTransactionStack(action: TransactionAction) { + state.transactionStack.push(action); + }, + + popTransactionStack(cancel?: boolean) { + const action = state.transactionStack.pop(); + + if (!action) { + return; + } + + if (cancel) { + this.goBack(); + action?.onCancel?.(); + } else { + if (action.goBack) { + this.goBack(); + } else if (action.view) { + this.reset(action.view); + } + action?.onSuccess?.(); + } + }, + reset(view: RouterControllerState['view']) { state.view = view; state.history = [view]; diff --git a/packages/core/src/controllers/SendController.ts b/packages/core/src/controllers/SendController.ts new file mode 100644 index 000000000..3aa0141e1 --- /dev/null +++ b/packages/core/src/controllers/SendController.ts @@ -0,0 +1,234 @@ +import { subscribeKey as subKey } from 'valtio/vanilla/utils'; +import { proxy, ref, subscribe as sub } from 'valtio/vanilla'; +import { ContractUtil, type Balance } from '@reown/appkit-common-react-native'; +import { AccountController } from './AccountController'; +import { ConnectionController } from './ConnectionController'; +import { SnackController } from './SnackController'; +import { CoreHelperUtil } from '../utils/CoreHelperUtil'; +import { EventsController } from './EventsController'; +import { NetworkController } from './NetworkController'; +import { RouterController } from './RouterController'; + +// -- Types --------------------------------------------- // +export interface TxParams { + receiverAddress: string; + sendTokenAmount: number; + gasPrice: bigint; + decimals: string; +} + +export interface ContractWriteParams { + receiverAddress: string; + tokenAddress: string; + sendTokenAmount: number; + decimals: string; +} + +export interface SendControllerState { + token?: Balance; + sendTokenAmount?: number; + receiverAddress?: string; + receiverProfileName?: string; + receiverProfileImageUrl?: string; + gasPrice?: bigint; + gasPriceInUSD?: number; + loading: boolean; +} + +type StateKey = keyof SendControllerState; + +// -- State --------------------------------------------- // +const state = proxy({ + loading: false +}); + +// -- Controller ---------------------------------------- // +export const SendController = { + state, + + subscribe(callback: (newState: SendControllerState) => void) { + return sub(state, () => callback(state)); + }, + + subscribeKey(key: K, callback: (value: SendControllerState[K]) => void) { + return subKey(state, key, callback); + }, + + setToken(token: SendControllerState['token']) { + if (token) { + state.token = ref(token); + } + }, + + setTokenAmount(sendTokenAmount: SendControllerState['sendTokenAmount']) { + state.sendTokenAmount = sendTokenAmount; + }, + + setReceiverAddress(receiverAddress: SendControllerState['receiverAddress']) { + state.receiverAddress = receiverAddress; + }, + + setReceiverProfileImageUrl( + receiverProfileImageUrl: SendControllerState['receiverProfileImageUrl'] + ) { + state.receiverProfileImageUrl = receiverProfileImageUrl; + }, + + setReceiverProfileName(receiverProfileName: SendControllerState['receiverProfileName']) { + state.receiverProfileName = receiverProfileName; + }, + + setGasPrice(gasPrice: SendControllerState['gasPrice']) { + state.gasPrice = gasPrice; + }, + + setGasPriceInUsd(gasPriceInUSD: SendControllerState['gasPriceInUSD']) { + state.gasPriceInUSD = gasPriceInUSD; + }, + + setLoading(loading: SendControllerState['loading']) { + state.loading = loading; + }, + + sendToken() { + if (this.state.token?.address && this.state.sendTokenAmount && this.state.receiverAddress) { + state.loading = true; + EventsController.sendEvent({ + type: 'track', + event: 'SEND_INITIATED', + properties: { + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', + token: this.state.token.address, + amount: this.state.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + this.sendERC20Token({ + receiverAddress: this.state.receiverAddress, + tokenAddress: this.state.token.address, + sendTokenAmount: this.state.sendTokenAmount, + decimals: this.state.token.quantity.decimals + }); + } else if ( + this.state.receiverAddress && + this.state.sendTokenAmount && + this.state.gasPrice && + this.state.token?.quantity.decimals + ) { + state.loading = true; + EventsController.sendEvent({ + type: 'track', + event: 'SEND_INITIATED', + properties: { + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', + token: this.state.token?.symbol, + amount: this.state.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + this.sendNativeToken({ + receiverAddress: this.state.receiverAddress, + sendTokenAmount: this.state.sendTokenAmount, + gasPrice: this.state.gasPrice, + decimals: this.state.token.quantity.decimals + }); + } + }, + + async sendNativeToken(params: TxParams) { + RouterController.pushTransactionStack({ + view: 'Account', + goBack: false + }); + + const to = params.receiverAddress as `0x${string}`; + const address = AccountController.state.address as `0x${string}`; + const value = ConnectionController.parseUnits( + params.sendTokenAmount.toString(), + Number(params.decimals) + ); + const data = '0x'; + + try { + await ConnectionController.sendTransaction({ + to, + address, + data, + value, + gasPrice: params.gasPrice + }); + SnackController.showSuccess('Transaction started'); + EventsController.sendEvent({ + type: 'track', + event: 'SEND_SUCCESS', + properties: { + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', + token: this.state.token?.symbol || '', + amount: params.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + this.resetSend(); + } catch (error) { + state.loading = false; + EventsController.sendEvent({ + type: 'track', + event: 'SEND_ERROR', + properties: { + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', + token: this.state.token?.symbol || '', + amount: params.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + SnackController.showError('Something went wrong'); + } + }, + + async sendERC20Token(params: ContractWriteParams) { + RouterController.pushTransactionStack({ + view: 'Account', + goBack: false + }); + + const amount = ConnectionController.parseUnits( + params.sendTokenAmount.toString(), + Number(params.decimals) + ); + + try { + if ( + AccountController.state.address && + params.sendTokenAmount && + params.receiverAddress && + params.tokenAddress + ) { + const tokenAddress = CoreHelperUtil.getPlainAddress( + params.tokenAddress as `${string}:${string}:${string}` + ) as `0x${string}`; + await ConnectionController.writeContract({ + fromAddress: AccountController.state.address as `0x${string}`, + tokenAddress, + receiverAddress: params.receiverAddress as `0x${string}`, + tokenAmount: amount, + method: 'transfer', + abi: ContractUtil.getERC20Abi(tokenAddress) + }); + SnackController.showSuccess('Transaction started'); + this.resetSend(); + } + } catch (error) { + state.loading = false; + SnackController.showError('Something went wrong'); + } + }, + + resetSend() { + state.token = undefined; + state.sendTokenAmount = undefined; + state.receiverAddress = undefined; + state.receiverProfileImageUrl = undefined; + state.receiverProfileName = undefined; + state.loading = false; + } +}; diff --git a/packages/core/src/controllers/SnackController.ts b/packages/core/src/controllers/SnackController.ts index 331a5fd4d..8a0d91c98 100644 --- a/packages/core/src/controllers/SnackController.ts +++ b/packages/core/src/controllers/SnackController.ts @@ -1,17 +1,25 @@ import { proxy } from 'valtio'; +import { OptionsController } from './OptionsController'; // -- Types --------------------------------------------- // +interface Message { + shortMessage: string; + longMessage?: string; +} + export interface SnackControllerState { message: string; variant: 'error' | 'success'; open: boolean; + long: boolean; } // -- State --------------------------------------------- // const state = proxy({ message: '', variant: 'success', - open: false + open: false, + long: false }); // -- Controller ---------------------------------------- // @@ -30,7 +38,26 @@ export const SnackController = { state.open = true; }, + showInternalError(error: Message) { + const { debug } = OptionsController.state; + + if (debug) { + state.message = error.shortMessage; + state.variant = 'error'; + state.open = true; + state.long = true; + } + + if (error.longMessage) { + // eslint-disable-next-line no-console + console.error(error.longMessage); + } + }, + hide() { state.open = false; + state.long = false; + state.message = ''; + state.variant = 'success'; } }; diff --git a/packages/core/src/controllers/SwapController.ts b/packages/core/src/controllers/SwapController.ts new file mode 100644 index 000000000..35ef82461 --- /dev/null +++ b/packages/core/src/controllers/SwapController.ts @@ -0,0 +1,108 @@ +import { subscribeKey as subKey } from 'valtio/utils'; +import { proxy, subscribe as sub } from 'valtio'; + +import { ConstantsUtil } from '../utils/ConstantsUtil'; +import { SwapApiUtil } from '../utils/SwapApiUtil'; +import { NetworkController } from './NetworkController'; +import { BlockchainApiController } from './BlockchainApiController'; +import { OptionsController } from './OptionsController'; +import { SwapCalculationUtil } from '../utils/SwapCalculationUtil'; + +// -- Constants ---------------------------------------- // +export const INITIAL_GAS_LIMIT = 150000; +export const TO_AMOUNT_DECIMALS = 6; + +// -- Types --------------------------------------------- // + +export interface SwapControllerState { + // Input values + networkPrice: string; + networkTokenSymbol: string; + + // Tokens + tokensPriceMap: Record; + + // Calculations + gasFee: string; + gasPriceInUSD?: number; +} + +type StateKey = keyof SwapControllerState; + +// -- State --------------------------------------------- // +const initialState: SwapControllerState = { + // Input values + networkPrice: '0', + networkTokenSymbol: '', + + // Tokens + tokensPriceMap: {}, + + // Calculations + gasFee: '0', + gasPriceInUSD: 0 +}; + +const state = proxy(initialState); + +// -- Controller ---------------------------------------- // +export const SwapController = { + state, + + subscribe(callback: (newState: SwapControllerState) => void) { + return sub(state, () => callback(state)); + }, + + subscribeKey(key: K, callback: (value: SwapControllerState[K]) => void) { + return subKey(state, key, callback); + }, + + getParams() { + const caipNetwork = NetworkController.state.caipNetwork; + const networkAddress = `${caipNetwork?.id}:${ConstantsUtil.NATIVE_TOKEN_ADDRESS}`; + + return { + networkAddress + }; + }, + + resetState() { + state.tokensPriceMap = initialState.tokensPriceMap; + state.networkPrice = initialState.networkPrice; + state.networkTokenSymbol = initialState.networkTokenSymbol; + }, + + //this + async getNetworkTokenPrice() { + const { networkAddress } = this.getParams(); + + const response = await BlockchainApiController.fetchTokenPrice({ + projectId: OptionsController.state.projectId, + addresses: [networkAddress] + }); + const token = response?.fungibles?.[0]; + const price = token?.price.toString() || '0'; + state.tokensPriceMap[networkAddress] = parseFloat(price); + state.networkTokenSymbol = token?.symbol || ''; + state.networkPrice = price; + }, + + //this + async getInitialGasPrice() { + const res = await SwapApiUtil.fetchGasPrice(); + + if (!res) { + return { gasPrice: null, gasPriceInUsd: null }; + } + + const value = res.standard; + const gasFee = BigInt(value); + const gasLimit = BigInt(INITIAL_GAS_LIMIT); + const gasPrice = SwapCalculationUtil.getGasPriceInUSD(state.networkPrice, gasLimit, gasFee); + + state.gasFee = value; + state.gasPriceInUSD = gasPrice; + + return { gasPrice: gasFee, gasPriceInUSD: state.gasPriceInUSD }; + } +}; diff --git a/packages/core/src/controllers/TransactionsController.ts b/packages/core/src/controllers/TransactionsController.ts new file mode 100644 index 000000000..333f1d5c9 --- /dev/null +++ b/packages/core/src/controllers/TransactionsController.ts @@ -0,0 +1,142 @@ +import type { Transaction } from '@reown/appkit-common-react-native'; +import { proxy, subscribe as sub } from 'valtio/vanilla'; +import { OptionsController } from './OptionsController'; +import { EventsController } from './EventsController'; +import { SnackController } from './SnackController'; +import { NetworkController } from './NetworkController'; +import { BlockchainApiController } from './BlockchainApiController'; +import { AccountController } from './AccountController'; + +// -- Types --------------------------------------------- // +type TransactionByMonthMap = Record; +type TransactionByYearMap = Record; + +export interface TransactionsControllerState { + transactions: Transaction[]; + loading: boolean; + empty: boolean; + next: string | undefined; +} + +// -- State --------------------------------------------- // +const state = proxy({ + transactions: [], + loading: false, + empty: false, + next: undefined +}); + +// -- Controller ---------------------------------------- // +export const TransactionsController = { + state, + + subscribe(callback: (newState: TransactionsControllerState) => void) { + return sub(state, () => callback(state)); + }, + + async fetchTransactions(accountAddress?: string, reset?: boolean) { + const { projectId } = OptionsController.state; + + if (!projectId || !accountAddress) { + throw new Error("Transactions can't be fetched without a projectId and an accountAddress"); + } + + state.loading = true; + + if (reset) { + state.next = undefined; + } + + try { + const response = await BlockchainApiController.fetchTransactions({ + account: accountAddress, + projectId, + cursor: state.next + }); + + const nonSpamTransactions = this.filterSpamTransactions(response?.data ?? []); + let filteredTransactions = [...state.transactions, ...nonSpamTransactions]; + + if (reset) { + filteredTransactions = nonSpamTransactions; + } + + state.loading = false; + + state.transactions = filteredTransactions; + + state.empty = nonSpamTransactions.length === 0; + state.next = response?.next ? response.next : undefined; + } catch (error) { + EventsController.sendEvent({ + type: 'track', + event: 'ERROR_FETCH_TRANSACTIONS', + properties: { + address: accountAddress, + projectId, + cursor: state.next, + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + SnackController.showError('Failed to fetch transactions'); + state.loading = false; + state.empty = true; + state.next = undefined; + } + }, + + getTransactionsByYearAndMonth(transactions: Transaction[]) { + const grouped: TransactionByYearMap = {}; + let filteredTransactions = this.filterByConnectedChain(transactions); + + filteredTransactions.forEach(transaction => { + const year = new Date(transaction.metadata.minedAt).getFullYear(); + const month = new Date(transaction.metadata.minedAt).getMonth(); + + const yearTransactions = grouped[year] ?? {}; + const monthTransactions = yearTransactions[month] ?? []; + + // If there's a transaction with the same id, remove the old one + const newMonthTransactions = monthTransactions.filter(tx => tx.id !== transaction.id); + + grouped[year] = { + ...yearTransactions, + [month]: [...newMonthTransactions, transaction].sort( + (a, b) => new Date(b.metadata.minedAt).getTime() - new Date(a.metadata.minedAt).getTime() + ) + }; + }); + + return grouped; + }, + + filterSpamTransactions(transactions: Transaction[]) { + return transactions.filter(transaction => { + const isAllSpam = transaction.transfers.every( + transfer => transfer.nft_info?.flags.is_spam === true + ); + + return !isAllSpam; + }); + }, + + filterByConnectedChain(transactions: Transaction[]) { + const chainId = NetworkController.state.caipNetwork?.id; + const filteredTransactions = transactions.filter( + transaction => transaction.metadata.chain === chainId + ); + + return filteredTransactions; + }, + + clearCursor() { + state.next = undefined; + }, + + resetTransactions() { + state.transactions = []; + state.loading = false; + state.empty = false; + state.next = undefined; + } +}; diff --git a/packages/core/src/controllers/WebviewController.ts b/packages/core/src/controllers/WebviewController.ts new file mode 100644 index 000000000..3cec52ba7 --- /dev/null +++ b/packages/core/src/controllers/WebviewController.ts @@ -0,0 +1,63 @@ +import type { SocialProvider } from '@reown/appkit-common-react-native'; +import { proxy, subscribe as sub } from 'valtio'; + +// -- Types --------------------------------------------- // +export interface WebviewControllerState { + frameViewVisible: boolean; + webviewVisible: boolean; + webviewUrl?: string; + connecting?: boolean; + connectingProvider?: SocialProvider; + processingAuth?: boolean; +} + +// -- State --------------------------------------------- // +const state = proxy({ + frameViewVisible: false, + webviewVisible: false, + connecting: false, + connectingProvider: undefined, + processingAuth: false +}); + +// -- Controller ---------------------------------------- // +export const WebviewController = { + state, + + subscribe(callback: (newState: WebviewControllerState) => void) { + return sub(state, () => callback(state)); + }, + + setFrameViewVisible(frameViewVisible: WebviewControllerState['frameViewVisible']) { + state.frameViewVisible = frameViewVisible; + }, + + setWebviewVisible(visible: WebviewControllerState['webviewVisible']) { + state.webviewVisible = visible; + }, + + setWebviewUrl(url: WebviewControllerState['webviewUrl']) { + state.webviewUrl = url; + }, + + setConnecting(connecting: WebviewControllerState['connecting']) { + state.connecting = connecting; + }, + + setConnectingProvider(provider: WebviewControllerState['connectingProvider']) { + state.connectingProvider = provider; + }, + + setProcessingAuth(processingAuth: WebviewControllerState['processingAuth']) { + state.processingAuth = processingAuth; + }, + + reset() { + state.frameViewVisible = false; + state.webviewVisible = false; + state.connecting = false; + state.connectingProvider = undefined; + state.processingAuth = false; + state.webviewUrl = undefined; + } +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 26ce21523..236c2789e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,52 +1,65 @@ // -- Controllers ------------------------------------------------------------- -export { ModalController } from './controllers/ModalController'; -export type { ModalControllerArguments, ModalControllerState } from './controllers/ModalController'; +export { + ModalController, + type ModalControllerArguments, + type ModalControllerState +} from './controllers/ModalController'; -export { RouterController } from './controllers/RouterController'; -export type { RouterControllerState } from './controllers/RouterController'; +export { RouterController, type RouterControllerState } from './controllers/RouterController'; -export { AccountController } from './controllers/AccountController'; -export type { AccountControllerState } from './controllers/AccountController'; +export { AccountController, type AccountControllerState } from './controllers/AccountController'; -export { NetworkController } from './controllers/NetworkController'; -export type { - NetworkControllerClient, - NetworkControllerState +export { + NetworkController, + type NetworkControllerClient, + type NetworkControllerState } from './controllers/NetworkController'; -export { ConnectionController } from './controllers/ConnectionController'; -export type { - ConnectionControllerClient, - ConnectionControllerState +export { + ConnectionController, + type ConnectionControllerClient, + type ConnectionControllerState } from './controllers/ConnectionController'; -export { ConnectorController } from './controllers/ConnectorController'; -export type { ConnectorControllerState } from './controllers/ConnectorController'; +export { + ConnectorController, + type ConnectorControllerState +} from './controllers/ConnectorController'; -export { SnackController } from './controllers/SnackController'; -export type { SnackControllerState } from './controllers/SnackController'; +export { SnackController, type SnackControllerState } from './controllers/SnackController'; -export { ApiController } from './controllers/ApiController'; -export type { ApiControllerState } from './controllers/ApiController'; +export { ApiController, type ApiControllerState } from './controllers/ApiController'; -export { AssetController } from './controllers/AssetController'; -export type { AssetControllerState } from './controllers/AssetController'; +export { AssetController, type AssetControllerState } from './controllers/AssetController'; -export { ThemeController } from './controllers/ThemeController'; -export type { ThemeControllerState } from './controllers/ThemeController'; +export { ThemeController, type ThemeControllerState } from './controllers/ThemeController'; -export { OptionsController } from './controllers/OptionsController'; -export type { OptionsControllerState } from './controllers/OptionsController'; +export { OptionsController, type OptionsControllerState } from './controllers/OptionsController'; -export { PublicStateController } from './controllers/PublicStateController'; -export type { PublicStateControllerState } from './controllers/PublicStateController'; +export { + PublicStateController, + type PublicStateControllerState +} from './controllers/PublicStateController'; export { BlockchainApiController } from './controllers/BlockchainApiController'; -export { EventsController } from './controllers/EventsController'; -export type { EventsControllerState } from './controllers/EventsController'; +export { SwapController, type SwapControllerState } from './controllers/SwapController'; + +export { EventsController, type EventsControllerState } from './controllers/EventsController'; + +export { EnsController, type EnsControllerState } from './controllers/EnsController'; + +export { + TransactionsController, + type TransactionsControllerState +} from './controllers/TransactionsController'; + +export { SendController, type SendControllerState } from './controllers/SendController'; + +export { WebviewController, type WebviewControllerState } from './controllers/WebviewController'; // -- Utils ------------------------------------------------------------------- +export { ApiUtil } from './utils/ApiUtil'; export { AssetUtil } from './utils/AssetUtil'; export { ConstantsUtil } from './utils/ConstantsUtil'; export { CoreHelperUtil } from './utils/CoreHelperUtil'; diff --git a/packages/core/src/utils/ApiUtil.ts b/packages/core/src/utils/ApiUtil.ts new file mode 100644 index 000000000..bcf778f05 --- /dev/null +++ b/packages/core/src/utils/ApiUtil.ts @@ -0,0 +1,26 @@ +import { Platform } from 'react-native'; +import { CoreHelperUtil } from './CoreHelperUtil'; + +export const ApiUtil = { + getOrigin() { + return CoreHelperUtil.getBundleId(); + }, + + getReactNativeVersion() { + return [ + Platform.constants?.reactNativeVersion?.major, + Platform.constants?.reactNativeVersion?.minor, + Platform.constants?.reactNativeVersion?.patch + ].join('.'); + }, + + getUserAgent() { + const rnVersion = Platform.select({ + ios: this.getReactNativeVersion(), + android: this.getReactNativeVersion(), + default: 'undefined' + }); + + return `${Platform.OS}-${Platform.Version}@rn-${rnVersion}`; + } +}; diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 0264c7ba2..e580d1077 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -1,3 +1,11 @@ +import type { Features } from './TypeUtil'; + +const defaultFeatures: Features = { + email: true, + emailShowWallets: true, + socials: ['x', 'discord', 'apple'] +}; + export const ConstantsUtil = { FOUR_MINUTES_MS: 240000, @@ -7,5 +15,9 @@ export const ConstantsUtil = { EMAIL_REGEX: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/, - LINKING_ERROR: 'LINKING_ERROR' + LINKING_ERROR: 'LINKING_ERROR', + + NATIVE_TOKEN_ADDRESS: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + + DEFAULT_FEATURES: defaultFeatures }; diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 5425cb26e..99eb1255b 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import { Linking, Platform } from 'react-native'; -import { ConstantsUtil as CommonConstants } from '@reown/appkit-common-react-native'; +import { ConstantsUtil as CommonConstants, type Balance } from '@reown/appkit-common-react-native'; import { ConstantsUtil } from './ConstantsUtil'; import type { CaipAddress, DataWallet, LinkingRecord } from './TypeUtil'; @@ -157,6 +157,27 @@ export const CoreHelperUtil = { return formattedBalance ? `${formattedBalance} ${symbol}` : `0.000 ${symbol || ''}`; }, + isAddress(address: string, chain = 'eip155'): boolean { + switch (chain) { + case 'eip155': + if (!/^(?:0x)?[0-9a-f]{40}$/iu.test(address)) { + return false; + } else if ( + /^(?:0x)?[0-9a-f]{40}$/iu.test(address) || + /^(?:0x)?[0-9A-F]{40}$/iu.test(address) + ) { + return true; + } + + return false; + case 'solana': + return /[1-9A-HJ-NP-Za-km-z]{32,44}$/iu.test(address); + + default: + return false; + } + }, + getApiUrl() { return CommonConstants.API_URL; }, @@ -245,5 +266,21 @@ export const CoreHelperUtil = { .catch(reason => ({ status: 'rejected', reason })) ) ); + }, + + calculateAndFormatBalance(array?: Balance[]) { + if (!array?.length) { + return { dollars: '0', pennies: '00' }; + } + + let sum = 0; + for (const item of array) { + sum += item.value ?? 0; + } + + const roundedNumber = sum.toFixed(2); + const [dollars, pennies] = roundedNumber.split('.'); + + return { dollars, pennies }; } }; diff --git a/packages/core/src/utils/FetchUtil.ts b/packages/core/src/utils/FetchUtil.ts index 99c0df370..b4d6d8057 100644 --- a/packages/core/src/utils/FetchUtil.ts +++ b/packages/core/src/utils/FetchUtil.ts @@ -1,3 +1,5 @@ +import type { RequestCache } from './TypeUtil'; + // -- Types ---------------------------------------------------------------------- interface Options { baseUrl: string; @@ -8,6 +10,8 @@ interface RequestArguments { path: string; headers?: HeadersInit_; params?: Record; + cache?: RequestCache; + signal?: AbortSignal; } interface PostArguments extends RequestArguments { diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index 36ae23a69..e340d58b6 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -1,12 +1,14 @@ /* eslint-disable no-console */ import AsyncStorage from '@react-native-async-storage/async-storage'; import type { ConnectorType, WcWallet } from './TypeUtil'; +import type { SocialProvider } from '@reown/appkit-common-react-native'; // -- Helpers ----------------------------------------------------------------- const WC_DEEPLINK = 'WALLETCONNECT_DEEPLINK_CHOICE'; const RECENT_WALLET = '@w3m/recent'; const CONNECTED_WALLET_IMAGE_URL = '@w3m/connected_wallet_image_url'; const CONNECTED_CONNECTOR = '@w3m/connected_connector'; +const CONNECTED_SOCIAL = '@appkit/connected_social'; // -- Utility ----------------------------------------------------------------- export const StorageUtil = { @@ -90,7 +92,7 @@ export const StorageUtil = { } }, - async getConnectedConnector() { + async getConnectedConnector(): Promise { try { const connector = (await AsyncStorage.getItem(CONNECTED_CONNECTOR)) as ConnectorType; @@ -134,5 +136,33 @@ export const StorageUtil = { } catch { console.info('Unable to remove Connected Wallet Image URL'); } + }, + + async setConnectedSocialProvider(provider: SocialProvider) { + try { + await AsyncStorage.setItem(CONNECTED_SOCIAL, JSON.stringify(provider)); + } catch { + console.info('Unable to set Connected Social Provider'); + } + }, + + async getConnectedSocialProvider() { + try { + const provider = (await AsyncStorage.getItem(CONNECTED_SOCIAL)) as SocialProvider; + + return provider ? JSON.parse(provider) : undefined; + } catch { + console.info('Unable to get Connected Social Provider'); + } + + return undefined; + }, + + async removeConnectedSocialProvider() { + try { + await AsyncStorage.removeItem(CONNECTED_SOCIAL); + } catch { + console.info('Unable to remove Connected Social Provider'); + } } }; diff --git a/packages/core/src/utils/SwapApiUtil.ts b/packages/core/src/utils/SwapApiUtil.ts new file mode 100644 index 000000000..3c47f65c9 --- /dev/null +++ b/packages/core/src/utils/SwapApiUtil.ts @@ -0,0 +1,19 @@ +import { BlockchainApiController } from '../controllers/BlockchainApiController'; +import { OptionsController } from '../controllers/OptionsController'; +import { NetworkController } from '../controllers/NetworkController'; + +export const SwapApiUtil = { + async fetchGasPrice() { + const projectId = OptionsController.state.projectId; + const caipNetwork = NetworkController.state.caipNetwork; + + if (!caipNetwork) { + return null; + } + + return await BlockchainApiController.fetchGasPrice({ + projectId, + chainId: caipNetwork.id + }); + } +}; diff --git a/packages/core/src/utils/SwapCalculationUtil.ts b/packages/core/src/utils/SwapCalculationUtil.ts new file mode 100644 index 000000000..b946c6562 --- /dev/null +++ b/packages/core/src/utils/SwapCalculationUtil.ts @@ -0,0 +1,21 @@ +// -- Types --------------------------------------------- // + +import { NumberUtil } from '@reown/appkit-common-react-native'; + +// -- Util ---------------------------------------- // +export const SwapCalculationUtil = { + getGasPriceInEther(gas: bigint, gasPrice: bigint) { + const totalGasCostInWei = gasPrice * gas; + const totalGasCostInEther = Number(totalGasCostInWei) / 1e18; + + return totalGasCostInEther; + }, + + getGasPriceInUSD(networkPrice: string, gas: bigint, gasPrice: bigint) { + const totalGasCostInEther = SwapCalculationUtil.getGasPriceInEther(gas, gasPrice); + const networkPriceInUSD = NumberUtil.bigNumber(networkPrice); + const gasCostInUSD = networkPriceInUSD.multipliedBy(totalGasCostInEther); + + return gasCostInUSD.toNumber(); + } +}; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 6a5154f8c..0cd0e363c 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,3 +1,10 @@ +import { type EventEmitter } from 'events'; +import type { Balance, SocialProvider, Transaction } from '@reown/appkit-common-react-native'; + +export interface BaseError { + message?: string; +} + export type CaipAddress = `${string}:${string}:${string}`; export type CaipNetworkId = `${string}:${string}`; @@ -49,11 +56,33 @@ export type CaipNamespaces = Record< } >; +export type SdkType = 'appkit'; + export type SdkVersion = | `react-native-wagmi-${string}` | `react-native-ethers5-${string}` | `react-native-ethers-${string}`; +type EnabledSocials = Exclude; + +export type Features = { + /** + * @description Enable or disable the email feature. Enabled by default. + * @type {boolean} + */ + email?: boolean; + /** + * @description Show or hide the regular wallet options when email is enabled. Enabled by default. + * @type {boolean} + */ + emailShowWallets?: boolean; + /** + * @description Enable or disable the socials feature. Enabled by default. + * @type {EnabledSocials[]} + */ + socials?: EnabledSocials[] | false; +}; + // -- ApiController Types ------------------------------------------------------- export interface WcWallet { id: string; @@ -98,6 +127,16 @@ export interface ApiGetAnalyticsConfigResponse { isAnalyticsEnabled: boolean; } +export type RequestCache = + | 'default' + | 'force-cache' + | 'no-cache' + | 'no-store' + | 'only-if-cached' + | 'reload'; + +// -- ThemeController Types --------------------------------------------------- + export type ThemeMode = 'dark' | 'light'; export interface ThemeVariables { @@ -114,6 +153,74 @@ export interface BlockchainApiIdentityResponse { name: string; } +export interface BlockchainApiBalanceResponse { + balances: Balance[]; +} + +export interface BlockchainApiTransactionsRequest { + account: string; + projectId: string; + cursor?: string; + onramp?: 'coinbase'; + signal?: AbortSignal; + cache?: RequestCache; +} + +export interface BlockchainApiTransactionsResponse { + data: Transaction[]; + next: string | null; +} + +export interface BlockchainApiTokenPriceRequest { + projectId: string; + currency?: 'usd' | 'eur' | 'gbp' | 'aud' | 'cad' | 'inr' | 'jpy' | 'btc' | 'eth'; + addresses: string[]; +} + +export interface BlockchainApiTokenPriceResponse { + fungibles: { + name: string; + symbol: string; + iconUrl: string; + price: number; + }[]; +} + +export interface BlockchainApiGasPriceRequest { + projectId: string; + chainId: string; +} + +export interface BlockchainApiGasPriceResponse { + standard: string; + fast: string; + instant: string; +} + +export interface BlockchainApiEnsError extends BaseError { + status: string; + reasons: { name: string; description: string }[]; +} + +export type ReownName = `${string}.reown.id` | `${string}.wcn.id`; + +export interface BlockchainApiLookupEnsName { + name: ReownName; + registered: number; + updated: number; + addresses: Record< + string, + { + address: string; + created: string; + } + >; + attributes: { + avatar?: string; + bio?: string; + }[]; +} + // -- OptionsController Types --------------------------------------------------- export interface Token { address: string; @@ -272,40 +379,204 @@ export type Event = | { type: 'track'; event: 'CLICK_SIGN_SIWE_MESSAGE'; + properties: { + network: string; + isSmartAccount: boolean; + }; } | { type: 'track'; event: 'CLICK_CANCEL_SIWE'; + properties: { + network: string; + isSmartAccount: boolean; + }; } | { type: 'track'; event: 'SIWE_AUTH_SUCCESS'; + properties: { + network: string; + isSmartAccount: boolean; + }; } | { type: 'track'; event: 'SIWE_AUTH_ERROR'; + properties: { + network: string; + isSmartAccount: boolean; + }; + } + | { + type: 'track'; + event: 'CLICK_TRANSACTIONS'; + properties: { + isSmartAccount: boolean; + }; + } + | { + type: 'track'; + event: 'ERROR_FETCH_TRANSACTIONS'; + properties: { + address: string; + projectId: string; + cursor: string | undefined; + isSmartAccount: boolean; + }; + } + | { + type: 'track'; + event: 'LOAD_MORE_TRANSACTIONS'; + properties: { + address: string | undefined; + projectId: string; + cursor: string | undefined; + isSmartAccount: boolean; + }; + } + | { + type: 'track'; + event: 'OPEN_SEND'; + properties: { + isSmartAccount: boolean; + network: string; + }; + } + | { + type: 'track'; + event: 'SEND_INITIATED'; + properties: { + isSmartAccount: boolean; + network: string; + token: string; + amount: number; + }; + } + | { + type: 'track'; + event: 'SEND_SUCCESS'; + properties: { + isSmartAccount: boolean; + network: string; + token: string; + amount: number; + }; + } + | { + type: 'track'; + event: 'SEND_ERROR'; + properties: { + isSmartAccount: boolean; + network: string; + token: string; + amount: number; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_STARTED'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_SUCCESS'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_CANCELED'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_ERROR'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SET_PREFERRED_ACCOUNT_TYPE'; + properties: { + accountType: AppKitFrameAccountType; + network: string; + }; }; +// -- Send Controller Types ------------------------------------- + +export interface SendTransactionArgs { + to: `0x${string}`; + data: `0x${string}`; + value: bigint; + gas?: bigint; + gasPrice: bigint; + address: `0x${string}`; +} + +export interface WriteContractArgs { + receiverAddress: `0x${string}`; + tokenAmount: bigint; + tokenAddress: `0x${string}`; + fromAddress: `0x${string}`; + method: 'send' | 'transfer' | 'call'; + abi: any; +} + // -- Email Types ------------------------------------------------ /** - * Matches type defined for packages/email/src/AppKitFrameProvider.ts + * Matches type defined for packages/wallet/src/AppKitFrameProvider.ts * It's duplicated in order to decouple scaffold from email package */ + +export type AppKitFrameAccountType = 'eoa' | 'smartAccount'; + export interface AppKitFrameProvider { readonly id: string; readonly name: string; + getEventEmitter(): EventEmitter; getSecureSiteURL(): string; getSecureSiteDashboardURL(): string; getSecureSiteIconURL(): string; getSecureSiteHeaders(): Record; - getLoginEmailUsed(): Promise; getEmail(): string | undefined; + getUsername(): string | undefined; + getLastUsedChainId(): Promise; rejectRpcRequest(): void; connectEmail(payload: { email: string }): Promise<{ action: 'VERIFY_DEVICE' | 'VERIFY_OTP'; }>; connectDevice(): Promise; + connectSocial(uri: string): Promise<{ + chainId: string | number; + email: string; + address: string; + accounts?: { + type: AppKitFrameAccountType; + address: string; + }[]; + userName?: string; + }>; + getSocialRedirectUri(payload: { provider: SocialProvider }): Promise<{ + uri: string; + }>; connectOtp(payload: { otp: string }): Promise; + connectFarcaster: () => Promise<{ userName: string }>; + getFarcasterUri(): Promise<{ url: string }>; isConnected(): Promise<{ isConnected: boolean; }>; @@ -326,17 +597,32 @@ export interface AppKitFrameProvider { syncDappData(payload: { projectId: string; sdkVersion: SdkVersion; + sdkType: SdkType; metadata?: Metadata; }): Promise; connect(payload?: { chainId: number | undefined }): Promise<{ chainId: number; - email: string; + email?: string | null; address: string; + smartAccountDeployed: boolean; + preferredAccountType: AppKitFrameAccountType; }>; switchNetwork(chainId: number): Promise<{ chainId: number; }>; + setPreferredAccount(type: AppKitFrameAccountType): Promise<{ + type: AppKitFrameAccountType; + address: string; + }>; + setOnTimeout(callback: () => void): void; + getSmartAccountEnabledNetworks(): Promise<{ + smartAccountEnabledNetworks: number[]; + }>; disconnect(): Promise; request(req: any): Promise; - AuthView: () => JSX.Element | null; + AuthView: () => React.JSX.Element | null; + Webview: () => React.JSX.Element | null; + onSetPreferredAccount: ( + callback: (values: { type: AppKitFrameAccountType; address: string }) => void + ) => void; } diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 983c12120..0e6127b79 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -26,12 +26,12 @@ "react-native", "ethers" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.0.2", "@reown/appkit-scaffold-utils-react-native": "1.0.2", "@reown/appkit-siwe-react-native": "1.0.2", - "@walletconnect/ethereum-provider": "2.16.1" + "@walletconnect/ethereum-provider": "2.17.3" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/ethers/readme.md b/packages/ethers/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/ethers/readme.md +++ b/packages/ethers/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 019d6483f..0e564e183 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -1,10 +1,15 @@ import { + BrowserProvider, + Contract, InfuraProvider, JsonRpcProvider, + JsonRpcSigner, formatEther, + formatUnits, getAddress, hexlify, isHexString, + parseUnits, toUtf8Bytes } from 'ethers'; import { @@ -16,10 +21,13 @@ import { type LibraryOptions, type NetworkControllerClient, type PublicStateControllerState, + type SendTransactionArgs, type Token, - AppKitScaffold + AppKitScaffold, + type WriteContractArgs, + type AppKitFrameAccountType } from '@reown/appkit-scaffold-react-native'; -import { NetworkUtil } from '@reown/appkit-common-react-native'; +import { erc20ABI, ErrorUtil, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import { ConstantsUtil, PresetsUtil, @@ -37,11 +45,17 @@ import { type CombinedProviderType, type AppKitFrameProvider } from '@reown/appkit-scaffold-utils-react-native'; +import { + type AppKitSIWEClient, + SIWEController, + getDidChainId, + getDidAddress +} from '@reown/appkit-siwe-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; +import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; -import type { AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; // -- Types --------------------------------------------------------------------- export interface AppKitClientOptions extends Omit { @@ -100,7 +114,7 @@ export class AppKit extends AppKitScaffold { } if (!appKitOptions.projectId) { - throw new Error('appkit:constructor - projectId is undefined'); + throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); } const networkControllerClient: NetworkControllerClient = { @@ -119,9 +133,9 @@ export class AppKit extends AppKitScaffold { new Promise(async resolve => { const walletChoice = await StorageUtil.getConnectedConnector(); const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; + PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; + const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const provider = await this.getWalletConnectProvider(); const result = getWalletConnectCaipNetworks(provider); @@ -161,9 +175,6 @@ export class AppKit extends AppKitScaffold { // SIWE const params = await siweConfig?.getMessageParams?.(); if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { - const { SIWEController, getDidChainId, getDidAddress } = await import( - '@reown/appkit-siwe-react-native' - ); const result = await WalletConnectProvider.authenticate({ nonce: await siweConfig.getNonce(), methods: OPTIONAL_METHODS, @@ -246,7 +257,6 @@ export class AppKit extends AppKitScaffold { const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; if (siweConfig?.options?.signOutOnDisconnect) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); await SIWEController.signOut(); } @@ -275,6 +285,101 @@ export class AppKit extends AppKitScaffold { }); return signature as `0x${string}`; + }, + + parseUnits: (value: string, decimals: number) => parseUnits(value, decimals), + + formatUnits: (value: bigint, decimals: number) => formatUnits(value, decimals), + + sendTransaction: async (data: SendTransactionArgs) => { + const { chainId, provider, address } = EthersStoreUtil.state; + + if (!provider) { + throw new Error('ethersClient:sendTransaction - provider is undefined'); + } + + if (!address) { + throw new Error('ethersClient:sendTransaction - address is undefined'); + } + + const txParams = { + to: data.to, + value: data.value, + gasLimit: data.gas, + gasPrice: data.gasPrice, + data: data.data, + type: 0 + }; + + const browserProvider = new BrowserProvider(provider, chainId); + const signer = new JsonRpcSigner(browserProvider, address); + const txResponse = await signer.sendTransaction(txParams); + const txReceipt = await txResponse.wait(); + + return (txReceipt?.hash as `0x${string}`) || null; + }, + + writeContract: async (data: WriteContractArgs) => { + const { chainId, provider, address } = EthersStoreUtil.state; + + if (!provider) { + throw new Error('ethersClient:writeContract - provider is undefined'); + } + + if (!address) { + throw new Error('ethersClient:writeContract - address is undefined'); + } + + const browserProvider = new BrowserProvider(provider, chainId); + const signer = new JsonRpcSigner(browserProvider, address); + const contract = new Contract(data.tokenAddress, data.abi, signer); + + if (!contract || !data.method) { + throw new Error('Contract method is undefined'); + } + + const method = contract[data.method]; + if (method) { + const tx = await method(data.receiverAddress, data.tokenAmount); + + return tx; + } + + throw new Error('Contract method is undefined'); + }, + + getEnsAddress: async (value: string) => { + try { + const chainId = Number(this.getCaipNetwork()?.id); + let ensName: string | null = null; + let wcName: boolean | string = false; + + if (NamesUtil.isReownName(value)) { + wcName = (await this?.resolveReownName(value)) || false; + } + + // If on mainnet, fetch from ENS + if (chainId === 1) { + const ensProvider = new InfuraProvider('mainnet'); + ensName = await ensProvider.resolveName(value); + } + + return ensName || wcName || false; + } catch { + return false; + } + }, + + getEnsAvatar: async (value: string) => { + const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); + if (chainId === 1) { + const ensProvider = new InfuraProvider('mainnet'); + const avatar = await ensProvider.getAvatar(value); + + return avatar || false; + } + + return false; } }; @@ -297,8 +402,8 @@ export class AppKit extends AppKitScaffold { this.createProvider(); - EthersStoreUtil.subscribeKey('address', () => { - this.syncAccount(); + EthersStoreUtil.subscribeKey('address', address => { + this.syncAccount({ address }); }); EthersStoreUtil.subscribeKey('chainId', () => { @@ -403,6 +508,7 @@ export class AppKit extends AppKitScaffold { }; this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions); + this.addWalletConnectListeners(this.walletConnectProvider); await this.checkActiveWalletConnectProvider(); } @@ -579,12 +685,10 @@ export class AppKit extends AppKitScaffold { } } - private async syncAccount() { - const address = EthersStoreUtil.state.address; + private async syncAccount({ address }: { address?: Address }) { const chainId = EthersStoreUtil.state.chainId; const isConnected = EthersStoreUtil.state.isConnected; - this.resetAccount(); if (isConnected && address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; @@ -599,6 +703,8 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.close(); + this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); } @@ -673,16 +779,30 @@ export class AppKit extends AppKitScaffold { const chainId = EthersStoreUtil.state.chainId; if (chainId && this.chains) { const chain = this.chains.find(c => c.chainId === chainId); + const token = this.options?.tokens?.[chainId]; if (chain) { const jsonRpcProvider = new JsonRpcProvider(chain.rpcUrl, { chainId, name: chain.name }); + if (jsonRpcProvider) { - const balance = await jsonRpcProvider.getBalance(address); - const formattedBalance = formatEther(balance); - this.setBalance(formattedBalance, chain.currency); + if (token) { + // Get balance from custom token address + const erc20 = new Contract(token.address, erc20ABI, jsonRpcProvider); + // @ts-expect-error + const decimals = await erc20.decimals(); + // @ts-expect-error + const symbol = await erc20.symbol(); + // @ts-expect-error + const balanceOf = await erc20.balanceOf(address); + this.setBalance(formatUnits(balanceOf, decimals), symbol); + } else { + const balance = await jsonRpcProvider.getBalance(address); + const formattedBalance = formatEther(balance); + this.setBalance(formattedBalance, chain.currency); + } } } } @@ -758,6 +878,20 @@ export class AppKit extends AppKitScaffold { } } + private async handleAuthSetPreferredAccount(address: string, type: AppKitFrameAccountType) { + if (!address) { + return; + } + + const chainId = this.getCaipNetwork()?.id; + const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; + this.setCaipAddress(caipAddress); + this.setPreferredAccountType(type); + + await this.syncAccount({ address: address as Address }); + this.setLoading(false); + } + private syncConnectors(config: ProviderType) { const _connectors: Connector[] = []; const EXCLUDED_CONNECTORS = [ConstantsUtil.AUTH_CONNECTOR_ID]; @@ -816,6 +950,7 @@ export class AppKit extends AppKitScaffold { const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); if (connectedConnector === 'AUTH') { + // Set loader until it reconnects this.setLoading(true); } @@ -823,5 +958,32 @@ export class AppKit extends AppKitScaffold { if (isConnected) { this.setAuthProvider(); } + + this.addAuthListeners(this.authProvider); + } + + private async addAuthListeners(authProvider: AppKitFrameProvider) { + authProvider.onSetPreferredAccount(async ({ address, type }) => { + if (address) { + await this.handleAuthSetPreferredAccount(address, type); + } + this.setLoading(false); + }); + + authProvider.setOnTimeout(async () => { + this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }); + } + + private async addWalletConnectListeners(provider: EthereumProvider) { + if (provider) { + provider.signer.client.core.relayer.on('relayer_connect', () => { + provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { + if (payload?.error) { + this.handleAlertError(payload?.error.message); + } + }); + }); + } } } diff --git a/packages/ethers5/package.json b/packages/ethers5/package.json index 25ec6c3d9..2fe1786da 100644 --- a/packages/ethers5/package.json +++ b/packages/ethers5/package.json @@ -26,12 +26,12 @@ "react-native", "ethers" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.0.2", "@reown/appkit-scaffold-utils-react-native": "1.0.2", "@reown/appkit-siwe-react-native": "1.0.2", - "@walletconnect/ethereum-provider": "2.16.1" + "@walletconnect/ethereum-provider": "2.17.3" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/ethers5/readme.md b/packages/ethers5/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/ethers5/readme.md +++ b/packages/ethers5/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index af98e7e10..23f42933c 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -1,5 +1,6 @@ -import { ethers, utils } from 'ethers'; +import { Contract, ethers, utils } from 'ethers'; import { + type AppKitFrameAccountType, type CaipAddress, type CaipNetwork, type CaipNetworkId, @@ -8,7 +9,9 @@ import { type LibraryOptions, type NetworkControllerClient, type PublicStateControllerState, + type SendTransactionArgs, type Token, + type WriteContractArgs, AppKitScaffold } from '@reown/appkit-scaffold-react-native'; import { @@ -28,12 +31,18 @@ import { type CombinedProviderType, type AppKitFrameProvider } from '@reown/appkit-scaffold-utils-react-native'; -import { NetworkUtil } from '@reown/appkit-common-react-native'; +import { + SIWEController, + getDidChainId, + getDidAddress, + type AppKitSIWEClient +} from '@reown/appkit-siwe-react-native'; +import { erc20ABI, ErrorUtil, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; +import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; -import type { AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; // -- Types --------------------------------------------------------------------- export interface AppKitClientOptions extends Omit { @@ -92,7 +101,7 @@ export class AppKit extends AppKitScaffold { } if (!appKitOptions.projectId) { - throw new Error('appkit:constructor - projectId is undefined'); + throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); } const networkControllerClient: NetworkControllerClient = { @@ -111,9 +120,9 @@ export class AppKit extends AppKitScaffold { new Promise(async resolve => { const walletChoice = await StorageUtil.getConnectedConnector(); const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; + PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; + const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const provider = await this.getWalletConnectProvider(); const result = getWalletConnectCaipNetworks(provider); @@ -153,9 +162,6 @@ export class AppKit extends AppKitScaffold { // SIWE const params = await siweConfig?.getMessageParams?.(); if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { - const { SIWEController, getDidChainId, getDidAddress } = await import( - '@reown/appkit-siwe-react-native' - ); const result = await WalletConnectProvider.authenticate({ nonce: await siweConfig.getNonce(), methods: OPTIONAL_METHODS, @@ -237,7 +243,6 @@ export class AppKit extends AppKitScaffold { const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; if (siweConfig?.options?.signOutOnDisconnect) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); await SIWEController.signOut(); } @@ -268,6 +273,95 @@ export class AppKit extends AppKitScaffold { }); return signature as `0x${string}`; + }, + + parseUnits: (value: string, decimals: number) => + ethers.utils.parseUnits(value, decimals).toBigInt(), + + formatUnits: (value: bigint, decimals: number) => ethers.utils.formatUnits(value, decimals), + + sendTransaction: async (data: SendTransactionArgs) => { + const provider = EthersStoreUtil.state.provider; + const address = EthersStoreUtil.state.address; + + if (!provider) { + throw new Error('connectionControllerClient:sendTransaction - provider is undefined'); + } + + if (!address) { + throw new Error('connectionControllerClient:sendTransaction - address is undefined'); + } + + const txParams = { + to: data.to, + value: data.value, + gasLimit: data.gas, + gasPrice: data.gasPrice, + data: data.data, + type: 0 + }; + + const browserProvider = new ethers.providers.Web3Provider(provider); + const signer = browserProvider.getSigner(); + + const txResponse = await signer.sendTransaction(txParams); + const txReceipt = await txResponse.wait(); + + return (txReceipt?.blockHash as `0x${string}`) || null; + }, + + writeContract: async (data: WriteContractArgs) => { + const { chainId, provider, address } = EthersStoreUtil.state; + if (!provider) { + throw new Error('writeContract - provider is undefined'); + } + if (!address) { + throw new Error('writeContract - address is undefined'); + } + const browserProvider = new ethers.providers.Web3Provider(provider, chainId); + const signer = browserProvider.getSigner(address); + const contract = new Contract(data.tokenAddress, data.abi, signer); + if (!contract || !data.method) { + throw new Error('Contract method is undefined'); + } + const method = contract[data.method]; + if (method) { + return await method(data.receiverAddress, data.tokenAmount); + } + throw new Error('Contract method is undefined'); + }, + + getEnsAddress: async (value: string) => { + try { + const { chainId } = EthersStoreUtil.state; + let ensName: string | null = null; + let wcName: boolean | string = false; + + if (NamesUtil.isReownName(value)) { + wcName = (await this.resolveReownName(value)) || false; + } + + if (chainId === 1) { + const ensProvider = new ethers.providers.InfuraProvider('mainnet'); + ensName = await ensProvider.resolveName(value); + } + + return ensName || wcName || false; + } catch { + return false; + } + }, + + getEnsAvatar: async (value: string) => { + const { chainId } = EthersStoreUtil.state; + if (chainId === 1) { + const ensProvider = new ethers.providers.InfuraProvider('mainnet'); + const avatar = await ensProvider.getAvatar(value); + + return avatar || false; + } + + return false; } }; @@ -290,8 +384,8 @@ export class AppKit extends AppKitScaffold { this.createProvider(); - EthersStoreUtil.subscribeKey('address', () => { - this.syncAccount(); + EthersStoreUtil.subscribeKey('address', address => { + this.syncAccount({ address }); }); EthersStoreUtil.subscribeKey('chainId', () => { @@ -394,6 +488,7 @@ export class AppKit extends AppKitScaffold { }; this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions); + this.addWalletConnectListeners(this.walletConnectProvider); await this.checkActiveWalletConnectProvider(); } @@ -570,12 +665,10 @@ export class AppKit extends AppKitScaffold { } } - private async syncAccount() { - const address = EthersStoreUtil.state.address; + private async syncAccount({ address }: { address?: Address }) { const chainId = EthersStoreUtil.state.chainId; const isConnected = EthersStoreUtil.state.isConnected; - this.resetAccount(); if (isConnected && address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; @@ -590,6 +683,8 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.close(); + this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); } @@ -664,6 +759,7 @@ export class AppKit extends AppKitScaffold { const chainId = EthersStoreUtil.state.chainId; if (chainId && this.chains) { const chain = this.chains.find(c => c.chainId === chainId); + const token = this.options?.tokens?.[chainId]; if (chain) { const jsonRpcProvider = new ethers.providers.JsonRpcProvider(chain.rpcUrl, { @@ -671,9 +767,21 @@ export class AppKit extends AppKitScaffold { name: chain.name }); if (jsonRpcProvider) { - const balance = await jsonRpcProvider.getBalance(address); - const formattedBalance = utils.formatEther(balance); - this.setBalance(formattedBalance, chain.currency); + if (token) { + // Get balance from custom token address + const erc20 = new Contract(token.address, erc20ABI, jsonRpcProvider); + // @ts-expect-error + const decimals = await erc20.decimals(); + // @ts-expect-error + const symbol = await erc20.symbol(); + // @ts-expect-error + const balanceOf = await erc20.balanceOf(address); + this.setBalance(utils.formatUnits(balanceOf, decimals), symbol); + } else { + const balance = await jsonRpcProvider.getBalance(address); + const formattedBalance = utils.formatEther(balance); + this.setBalance(formattedBalance, chain.currency); + } } } } @@ -749,6 +857,18 @@ export class AppKit extends AppKitScaffold { } } + private async handleAuthSetPreferredAccount(address: string, type: AppKitFrameAccountType) { + if (!address) { + return; + } + const chainId = this.getCaipNetwork()?.id; + const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; + this.setCaipAddress(caipAddress); + this.setPreferredAccountType(type); + await this.syncAccount({ address: address as Address }); + this.setLoading(false); + } + private syncConnectors(config: ProviderType) { const _connectors: Connector[] = []; const EXCLUDED_CONNECTORS = [ConstantsUtil.AUTH_CONNECTOR_ID]; @@ -807,6 +927,7 @@ export class AppKit extends AppKitScaffold { const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); if (connectedConnector === 'AUTH') { + // Set loader until it reconnects this.setLoading(true); } @@ -814,5 +935,32 @@ export class AppKit extends AppKitScaffold { if (isConnected) { this.setAuthProvider(); } + + this.addAuthListeners(this.authProvider); + } + + private async addAuthListeners(authProvider: AppKitFrameProvider) { + authProvider.onSetPreferredAccount(async ({ address, type }) => { + if (address) { + await this.handleAuthSetPreferredAccount(address, type); + } + this.setLoading(false); + }); + + authProvider.setOnTimeout(async () => { + this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }); + } + + private async addWalletConnectListeners(provider: EthereumProvider) { + if (provider) { + provider.signer.client.core.relayer.on('relayer_connect', () => { + provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { + if (payload?.error) { + this.handleAlertError(payload?.error.message); + } + }); + }); + } } } diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index f08bf06cc..12ce3188d 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -23,12 +23,12 @@ "walletconnect", "react-native" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/scaffold-utils/readme.md b/packages/scaffold-utils/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/scaffold-utils/readme.md +++ b/packages/scaffold-utils/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/scaffold/package.json b/packages/scaffold/package.json index 0cb081595..46033ba31 100644 --- a/packages/scaffold/package.json +++ b/packages/scaffold/package.json @@ -25,12 +25,12 @@ "walletconnect", "react-native" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/scaffold/readme.md b/packages/scaffold/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/scaffold/readme.md +++ b/packages/scaffold/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index e775b3cf9..80d682707 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -13,23 +13,27 @@ import type { ThemeMode, ThemeVariables, Connector, - ConnectedWalletInfo + ConnectedWalletInfo, + Features } from '@reown/appkit-core-react-native'; -import type { SIWEControllerClient } from '@reown/appkit-siwe-react-native'; +import { SIWEController, type SIWEControllerClient } from '@reown/appkit-siwe-react-native'; import { AccountController, BlockchainApiController, ConnectionController, ConnectorController, + EnsController, EventsController, ModalController, NetworkController, OptionsController, PublicStateController, + SnackController, StorageUtil, - ThemeController + ThemeController, + TransactionsController } from '@reown/appkit-core-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; +import { ConstantsUtil, ErrorUtil } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------------------------------- export interface LibraryOptions { @@ -46,6 +50,8 @@ export interface LibraryOptions { enableAnalytics?: OptionsControllerState['enableAnalytics']; _sdkVersion: OptionsControllerState['sdkVersion']; metadata?: OptionsControllerState['metadata']; + debug?: OptionsControllerState['debug']; + features?: Features; } export interface ScaffoldOptions extends LibraryOptions { @@ -60,6 +66,8 @@ export interface OpenOptions { // -- Client -------------------------------------------------------------------- export class AppKitScaffold { + public reportedAlertErrors: Record = {}; + public constructor(options: ScaffoldOptions) { this.initControllers(options); } @@ -134,6 +142,15 @@ export class AppKitScaffold { return EventsController.subscribe(callback); } + public resolveReownName = async (name: string) => { + const wcNameAddress = await EnsController.resolveName(name); + const networkNameAddresses = wcNameAddress?.addresses + ? Object.values(wcNameAddress?.addresses) + : []; + + return networkNameAddresses[0]?.address || false; + }; + // -- Protected ---------------------------------------------------------------- protected setIsConnected: (typeof AccountController)['setIsConnected'] = isConnected => { AccountController.setIsConnected(isConnected); @@ -143,6 +160,8 @@ export class AppKitScaffold { AccountController.setCaipAddress(caipAddress); }; + protected getCaipAddress = () => AccountController.state.caipAddress; + protected setBalance: (typeof AccountController)['setBalance'] = (balance, balanceSymbol) => { AccountController.setBalance(balance, balanceSymbol); }; @@ -193,6 +212,7 @@ export class AppKitScaffold { protected resetWcConnection: (typeof ConnectionController)['resetWcConnection'] = () => { ConnectionController.resetWcConnection(); + TransactionsController.resetTransactions(); }; protected fetchIdentity: (typeof BlockchainApiController)['fetchIdentity'] = request => @@ -212,6 +232,40 @@ export class AppKitScaffold { BlockchainApiController.setClientId(clientId); }; + protected setPreferredAccountType: (typeof AccountController)['setPreferredAccountType'] = + preferredAccountType => { + AccountController.setPreferredAccountType(preferredAccountType); + }; + + protected handleAlertError(error?: string | { shortMessage: string; longMessage: string }) { + if (!error) return; + + if (typeof error === 'object') { + SnackController.showInternalError(error); + + return; + } + + // Check if the error is a universal provider error + const matchedUniversalProviderError = Object.entries(ErrorUtil.UniversalProviderErrors).find( + ([, { message }]) => error?.includes(message) + ); + + const [errorKey, errorValue] = matchedUniversalProviderError ?? []; + + const { message, alertErrorKey } = errorValue ?? {}; + + if (errorKey && message && !this.reportedAlertErrors[errorKey]) { + const alertError = + ErrorUtil.ALERT_ERRORS[alertErrorKey as keyof typeof ErrorUtil.ALERT_ERRORS]; + + if (alertError) { + SnackController.showInternalError(alertError); + this.reportedAlertErrors[errorKey] = true; + } + } + } + // -- Private ------------------------------------------------------------------ private async initControllers(options: ScaffoldOptions) { this.initAsyncValues(options); @@ -226,6 +280,7 @@ export class AppKitScaffold { OptionsController.setCustomWallets(options.customWallets); OptionsController.setEnableAnalytics(options.enableAnalytics); OptionsController.setSdkVersion(options._sdkVersion); + OptionsController.setDebug(options.debug); if (options.clipboardClient) { OptionsController.setClipboardClient(options.clipboardClient); @@ -244,10 +299,12 @@ export class AppKitScaffold { } if (options.siweControllerClient) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); - SIWEController.setSIWEClient(options.siweControllerClient); } + + if (options.features) { + OptionsController.setFeatures(options.features); + } } private async setConnectorExcludedWallets(connectors: Connector[]) { @@ -291,12 +348,18 @@ export class AppKitScaffold { private async initConnectedConnector() { const connectedConnector = await StorageUtil.getConnectedConnector(); if (connectedConnector) { - ConnectorController.setConnectedConnector(connectedConnector); + ConnectorController.setConnectedConnector(connectedConnector, false); } } + private async initSocial() { + const connectedSocialProvider = await StorageUtil.getConnectedSocialProvider(); + ConnectionController.setConnectedSocialProvider(connectedSocialProvider); + } + private async initAsyncValues(options: ScaffoldOptions) { await this.initConnectedConnector(); await this.initRecentWallets(options); + await this.initSocial(); } } diff --git a/packages/scaffold/src/hooks/useDebounceCallback.ts b/packages/scaffold/src/hooks/useDebounceCallback.ts index 8708750c1..70be71f31 100644 --- a/packages/scaffold/src/hooks/useDebounceCallback.ts +++ b/packages/scaffold/src/hooks/useDebounceCallback.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useRef } from 'react'; interface Props { - callback: (args: any) => void; + callback: ((args: any) => any) | ((args: any) => Promise); delay?: number; } diff --git a/packages/scaffold/src/hooks/useTimeout.ts b/packages/scaffold/src/hooks/useTimeout.ts index e99448a0c..90e027955 100644 --- a/packages/scaffold/src/hooks/useTimeout.ts +++ b/packages/scaffold/src/hooks/useTimeout.ts @@ -13,13 +13,19 @@ function useTimeout(delay: number) { timeLeftRef.current -= 1; setTimeLeft(timeLeftRef.current); } else { - clearInterval(interval.current); + if (typeof interval.current === 'number') { + clearInterval(interval.current); + } } }, 1000); }, []); useEffect(() => { - return () => clearInterval(interval.current); + return () => { + if (typeof interval.current === 'number') { + clearInterval(interval.current); + } + }; }, [interval]); return { timeLeft, startTimer }; diff --git a/packages/scaffold/src/modal/w3m-account-button/index.tsx b/packages/scaffold/src/modal/w3m-account-button/index.tsx index d6fe2907e..329ffb3b2 100644 --- a/packages/scaffold/src/modal/w3m-account-button/index.tsx +++ b/packages/scaffold/src/modal/w3m-account-button/index.tsx @@ -33,9 +33,9 @@ export function AccountButton({ balance, disabled, style, testID }: AccountButto return ( ModalController.open()} + address={address} + profileName={profileName} networkSrc={networkImage} imageHeaders={ApiController._getApiHeaders()} avatarSrc={profileImage} diff --git a/packages/scaffold/src/modal/w3m-button/index.tsx b/packages/scaffold/src/modal/w3m-button/index.tsx index 830655db2..e6bf0481d 100644 --- a/packages/scaffold/src/modal/w3m-button/index.tsx +++ b/packages/scaffold/src/modal/w3m-button/index.tsx @@ -30,7 +30,7 @@ export function AppKitButton({ style={accountStyle} balance={balance} disabled={disabled} - testID="button-account" + testID="account-button" /> ) : ( ); } diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index ec2ab1f1e..036d6a576 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -13,9 +13,12 @@ import { ModalController, OptionsController, RouterController, + TransactionsController, type CaipAddress, - type AppKitFrameProvider + type AppKitFrameProvider, + WebviewController } from '@reown/appkit-core-react-native'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; import { AppKitRouter } from '../w3m-router'; import { Header } from '../../partials/w3m-header'; @@ -25,20 +28,20 @@ import styles from './styles'; export function AppKit() { const { open, loading } = useSnapshot(ModalController.state); - const { history, view } = useSnapshot(RouterController.state); - const { connectors } = useSnapshot(ConnectorController.state); + const { connectors, connectedConnector } = useSnapshot(ConnectorController.state); const { caipAddress, isConnected } = useSnapshot(AccountController.state); - const { isSiweEnabled } = OptionsController.state; + const { frameViewVisible, webviewVisible } = useSnapshot(WebviewController.state); const { height } = useWindowDimensions(); const { isLandscape } = useCustomDimensions(); const portraitHeight = height - 120; const landScapeHeight = height * 0.95 - (StatusBar.currentHeight ?? 0); const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; - const modalCoverScreen = view !== 'ConnectingSiwe'; const AuthView = authProvider?.AuthView; + const SocialView = authProvider?.Webview; + const showAuth = !connectedConnector || connectedConnector === 'AUTH'; const onBackButtonPress = () => { - if (history.length > 1) { + if (RouterController.state.history.length > 1) { return RouterController.goBack(); } @@ -51,10 +54,8 @@ export function AppKit() { }; const handleClose = async () => { - if (isSiweEnabled) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); - - if (SIWEController.state.status !== 'success' && isConnected) { + if (OptionsController.state.isSiweEnabled) { + if (SIWEController.state.status !== 'success' && AccountController.state.isConnected) { await ConnectionController.disconnect(); } } @@ -66,10 +67,13 @@ export function AppKit() { return; } - if (isSiweEnabled) { - const newAddress = CoreHelperUtil.getPlainAddress(address); + const newAddress = CoreHelperUtil.getPlainAddress(address); + TransactionsController.resetTransactions(); + TransactionsController.fetchTransactions(newAddress, true); + + if (OptionsController.state.isSiweEnabled) { const newNetworkId = CoreHelperUtil.getNetworkId(address); - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); + const { signOutOnAccountChange, signOutOnNetworkChange } = SIWEController.state._client?.options ?? {}; const session = await SIWEController.getSession(); @@ -92,17 +96,14 @@ export function AppKit() { } } }, - [isSiweEnabled, isConnected, loading] + [isConnected, loading] ); const onSiweNavigation = () => { - const { open: modalOpen } = ModalController.state; - if (modalOpen) { + if (ModalController.state.open) { RouterController.push('ConnectingSiwe'); } else { - ModalController.open({ - view: 'ConnectingSiwe' - }); + ModalController.open({ view: 'ConnectingSiwe' }); } }; @@ -118,7 +119,7 @@ export function AppKit() { <>
@@ -134,7 +136,8 @@ export function AppKit() { - {!!authProvider && AuthView && } + {!!showAuth && AuthView && } + {!!showAuth && SocialView && } ); } diff --git a/packages/scaffold/src/modal/w3m-network-button/index.tsx b/packages/scaffold/src/modal/w3m-network-button/index.tsx index 89f25b6bb..ddffa20f2 100644 --- a/packages/scaffold/src/modal/w3m-network-button/index.tsx +++ b/packages/scaffold/src/modal/w3m-network-button/index.tsx @@ -36,6 +36,7 @@ export function NetworkButton({ disabled, style }: NetworkButtonProps) { style={style} onPress={onNetworkPress} loading={loading} + testID="network-button" > {caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index f51b6d3dd..5cc2a4a4f 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -1,25 +1,38 @@ import { useLayoutEffect, useMemo } from 'react'; import { useSnapshot } from 'valtio'; import { RouterController } from '@reown/appkit-core-react-native'; -import { ConnectingSiweView } from '@reown/appkit-siwe-react-native'; -import { ConnectView } from '../../views/w3m-connect-view'; +import { AccountDefaultView } from '../../views/w3m-account-default-view'; +import { AccountView } from '../../views/w3m-account-view'; import { AllWalletsView } from '../../views/w3m-all-wallets-view'; +import { ConnectView } from '../../views/w3m-connect-view'; +import { ConnectSocialsView } from '../../views/w3m-connect-socials-view'; import { ConnectingView } from '../../views/w3m-connecting-view'; -import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; -import { GetWalletView } from '../../views/w3m-get-wallet-view'; -import { AccountView } from '../../views/w3m-account-view'; -import { NetworksView } from '../../views/w3m-networks-view'; -import { WhatIsNetworkView } from '../../views/w3m-what-is-a-network-view'; -import { NetworkSwitchView } from '../../views/w3m-network-switch-view'; -import { UiUtil } from '../../utils/UiUtil'; import { ConnectingExternalView } from '../../views/w3m-connecting-external-view'; +import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; +import { ConnectingSocialView } from '../../views/w3m-connecting-social-view'; +import { CreateView } from '../../views/w3m-create-view'; +import { ConnectingSiweView } from '@reown/appkit-siwe-react-native'; import { EmailVerifyOtpView } from '../../views/w3m-email-verify-otp-view'; import { EmailVerifyDeviceView } from '../../views/w3m-email-verify-device-view'; +import { GetWalletView } from '../../views/w3m-get-wallet-view'; +import { NetworksView } from '../../views/w3m-networks-view'; +import { NetworkSwitchView } from '../../views/w3m-network-switch-view'; import { UpdateEmailWalletView } from '../../views/w3m-update-email-wallet-view'; import { UpdateEmailPrimaryOtpView } from '../../views/w3m-update-email-primary-otp-view'; import { UpdateEmailSecondaryOtpView } from '../../views/w3m-update-email-secondary-otp-view'; import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-view'; +import { UpgradeToSmartAccountView } from '../../views/w3m-upgrade-to-smart-account-view'; +import { TransactionsView } from '../../views/w3m-transactions-view'; +import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-networks-view'; +import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; +import { WalletSendView } from '../../views/w3m-wallet-send-view'; +import { WalletSendPreviewView } from '../../views/w3m-wallet-send-preview-view'; +import { WalletSendSelectTokenView } from '../../views/w3m-wallet-send-select-token-view'; +import { WhatIsANetworkView } from '../../views/w3m-what-is-a-network-view'; +import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; + +import { UiUtil } from '../../utils/UiUtil'; export function AppKitRouter() { const { view } = useSnapshot(RouterController.state); @@ -30,40 +43,64 @@ export function AppKitRouter() { const ViewComponent = useMemo(() => { switch (view) { - case 'Connect': - return ConnectView; + case 'Account': + return AccountView; + case 'AccountDefault': + return AccountDefaultView; case 'AllWallets': return AllWalletsView; - case 'ConnectingWalletConnect': - return ConnectingView; + case 'Connect': + return ConnectView; + case 'ConnectSocials': + return ConnectSocialsView; case 'ConnectingExternal': return ConnectingExternalView; - case 'WhatIsAWallet': - return WhatIsAWalletView; - case 'WhatIsANetwork': - return WhatIsNetworkView; + case 'ConnectingSiwe': + return ConnectingSiweView; + case 'ConnectingSocial': + return ConnectingSocialView; + case 'ConnectingFarcaster': + return ConnectingFarcasterView; + case 'ConnectingWalletConnect': + return ConnectingView; + case 'Create': + return CreateView; + case 'EmailVerifyDevice': + return EmailVerifyDeviceView; + case 'EmailVerifyOtp': + return EmailVerifyOtpView; case 'GetWallet': return GetWalletView; case 'Networks': return NetworksView; case 'SwitchNetwork': return NetworkSwitchView; - case 'Account': - return AccountView; - case 'EmailVerifyDevice': - return EmailVerifyDeviceView; - case 'EmailVerifyOtp': - return EmailVerifyOtpView; - case 'UpdateEmailWallet': - return UpdateEmailWalletView; + case 'Transactions': + return TransactionsView; case 'UpdateEmailPrimaryOtp': return UpdateEmailPrimaryOtpView; case 'UpdateEmailSecondaryOtp': return UpdateEmailSecondaryOtpView; + case 'UpdateEmailWallet': + return UpdateEmailWalletView; case 'UpgradeEmailWallet': return UpgradeEmailWalletView; - case 'ConnectingSiwe': - return ConnectingSiweView; + case 'UpgradeToSmartAccount': + return UpgradeToSmartAccountView; + case 'WalletCompatibleNetworks': + return WalletCompatibleNetworks; + case 'WalletReceive': + return WalletReceiveView; + case 'WalletSend': + return WalletSendView; + case 'WalletSendPreview': + return WalletSendPreviewView; + case 'WalletSendSelectToken': + return WalletSendSelectTokenView; + case 'WhatIsANetwork': + return WhatIsANetworkView; + case 'WhatIsAWallet': + return WhatIsAWalletView; default: return ConnectView; } diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx new file mode 100644 index 000000000..2e784083f --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -0,0 +1,160 @@ +import { useCallback, useMemo, useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { ScrollView, View, type StyleProp, type ViewStyle, RefreshControl } from 'react-native'; +import { + FlexView, + Link, + ListTransaction, + LoadingSpinner, + Text, + TransactionUtil, + useTheme +} from '@reown/appkit-ui-react-native'; +import { type Transaction, type TransactionImage } from '@reown/appkit-common-react-native'; +import { + AccountController, + AssetUtil, + EventsController, + NetworkController, + OptionsController, + TransactionsController +} from '@reown/appkit-core-react-native'; +import { Placeholder } from '../w3m-placeholder'; +import { getTransactionListItemProps } from './utils'; +import styles from './styles'; + +interface Props { + style?: StyleProp; +} + +export function AccountActivity({ style }: Props) { + const Theme = useTheme(); + const [refreshing, setRefreshing] = useState(false); + const { loading, transactions, next } = useSnapshot(TransactionsController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const handleLoadMore = () => { + TransactionsController.fetchTransactions(AccountController.state.address); + EventsController.sendEvent({ + type: 'track', + event: 'LOAD_MORE_TRANSACTIONS', + properties: { + address: AccountController.state.address, + projectId: OptionsController.state.projectId, + cursor: TransactionsController.state.next, + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + }; + + const onRefresh = useCallback(async () => { + setRefreshing(true); + await TransactionsController.fetchTransactions(AccountController.state.address, true); + setRefreshing(false); + }, []); + + const transactionsByYear = useMemo(() => { + return TransactionsController.getTransactionsByYearAndMonth(transactions as Transaction[]); + }, [transactions]); + + if (loading && !transactions.length) { + return ( + + + + ); + } + + if (!Object.keys(transactionsByYear).length) { + return ( + + ); + } + + return ( + + } + > + {Object.keys(transactionsByYear) + .reverse() + .map(year => ( + + {Object.keys(transactionsByYear[year] || {}) + .reverse() + .map(month => ( + + + {TransactionUtil.getTransactionGroupTitle(year, month)} + + {transactionsByYear[year]?.[month]?.map((transaction: Transaction) => { + const { date, type, descriptions, status, images, isAllNFT, transfers } = + getTransactionListItemProps(transaction); + const hasMultipleTransfers = transfers?.length > 2; + + if (hasMultipleTransfers) { + return transfers.map((transfer, index) => { + const description = TransactionUtil.getTransferDescription(transfer); + + return ( + + ); + }); + } + + return ( + + ); + })} + + ))} + + ))} + {(next || loading) && ( + + {next && !loading && ( + + Load more + + )} + {loading && !refreshing && } + + )} + + ); +} diff --git a/packages/scaffold/src/partials/w3m-account-activity/styles.ts b/packages/scaffold/src/partials/w3m-account-activity/styles.ts new file mode 100644 index 000000000..df9874a45 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-activity/styles.ts @@ -0,0 +1,28 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + paddingHorizontal: Spacing.xs + }, + contentContainer: { + paddingBottom: Spacing.m + }, + separatorText: { + marginVertical: Spacing.xs + }, + transactionItem: { + marginVertical: Spacing.xs + }, + footer: { + height: 40 + }, + placeholder: { + minHeight: 200 + }, + loadMoreButton: { + alignSelf: 'center', + width: 100, + marginVertical: Spacing.xs + } +}); diff --git a/packages/scaffold/src/partials/w3m-account-activity/utils.ts b/packages/scaffold/src/partials/w3m-account-activity/utils.ts new file mode 100644 index 000000000..be865523d --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-activity/utils.ts @@ -0,0 +1,25 @@ +import { DateUtil, type Transaction } from '@reown/appkit-common-react-native'; +import { TransactionUtil } from '@reown/appkit-ui-react-native'; +import type { TransactionType } from '@reown/appkit-ui-react-native/lib/typescript/utils/TypesUtil'; + +export function getTransactionListItemProps(transaction: Transaction) { + const date = DateUtil.formatDate(transaction?.metadata?.minedAt); + const descriptions = TransactionUtil.getTransactionDescriptions(transaction); + + const transfers = transaction?.transfers; + const transfer = transaction?.transfers?.[0]; + const isAllNFT = + Boolean(transfer) && transaction?.transfers?.every(item => Boolean(item.nft_info)); + const images = TransactionUtil.getTransactionImages(transfers); + + return { + date, + direction: transfer?.direction, + descriptions, + isAllNFT, + images, + status: transaction.metadata?.status, + transfers, + type: transaction.metadata?.operationType as TransactionType + }; +} diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx new file mode 100644 index 000000000..a1627cb94 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -0,0 +1,99 @@ +import { useCallback, useState } from 'react'; +import { + RefreshControl, + ScrollView, + StyleSheet, + type StyleProp, + type ViewStyle +} from 'react-native'; +import { useSnapshot } from 'valtio'; +import { + AccountController, + AssetUtil, + NetworkController, + RouterController +} from '@reown/appkit-core-react-native'; +import { + FlexView, + ListItem, + Text, + ListToken, + useTheme, + Spacing +} from '@reown/appkit-ui-react-native'; + +interface Props { + style?: StyleProp; +} + +export function AccountTokens({ style }: Props) { + const Theme = useTheme(); + const [refreshing, setRefreshing] = useState(false); + const { tokenBalance } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const onRefresh = useCallback(async () => { + setRefreshing(true); + AccountController.fetchTokenBalance(); + setRefreshing(false); + }, []); + + const onReceivePress = () => { + RouterController.push('WalletReceive'); + }; + + if (!tokenBalance?.length) { + return ( + + + + Receive funds + + + Transfer tokens on your wallet + + + + ); + } + + return ( + + } + > + {tokenBalance.map(token => ( + + ))} + + ); +} + +const styles = StyleSheet.create({ + receiveButton: { + width: 'auto', + marginHorizontal: Spacing.s + } +}); diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx new file mode 100644 index 000000000..462058ae7 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -0,0 +1,97 @@ +import { useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { Balance, FlexView, IconLink, Tabs } from '@reown/appkit-ui-react-native'; +import { + AccountController, + CoreHelperUtil, + EventsController, + NetworkController, + RouterController +} from '@reown/appkit-core-react-native'; +import type { Balance as BalanceType } from '@reown/appkit-common-react-native'; +import { AccountActivity } from '../w3m-account-activity'; +import { AccountTokens } from '../w3m-account-tokens'; +import styles from './styles'; + +export interface AccountWalletFeaturesProps { + value: string; +} + +export function AccountWalletFeatures() { + const [activeTab, setActiveTab] = useState(0); + const { tokenBalance } = useSnapshot(AccountController.state); + const balance = CoreHelperUtil.calculateAndFormatBalance(tokenBalance as BalanceType[]); + + const onTabChange = (index: number) => { + setActiveTab(index); + if (index === 2) { + onTransactionsPress(); + } + }; + + const onTransactionsPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'CLICK_TRANSACTIONS', + properties: { + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + }; + + const onSendPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'OPEN_SEND', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + RouterController.push('WalletSend'); + }; + + const onReceivePress = () => { + RouterController.push('WalletReceive'); + }; + + return ( + + + + + + + + + + + {activeTab === 0 && } + {activeTab === 1 && } + + + ); +} diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts new file mode 100644 index 000000000..3f8f34afb --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts @@ -0,0 +1,38 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: 400 + }, + balanceText: { + fontSize: 40, + fontWeight: '500' + }, + actionsContainer: { + width: '100%', + marginTop: Spacing.s, + marginBottom: Spacing.l + }, + action: { + flex: 1, + height: 52 + }, + actionLeft: { + marginRight: 8 + }, + actionRight: { + marginLeft: 8 + }, + tab: { + width: '100%', + paddingHorizontal: Spacing.s + }, + tabContainer: { + flex: 1, + width: '100%' + }, + tabContent: { + paddingHorizontal: Spacing.m + } +}); diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx index af76fcd49..94d20443d 100644 --- a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx +++ b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx @@ -1,7 +1,14 @@ import { useEffect, useState } from 'react'; import { useSnapshot } from 'valtio'; import { FlatList, View } from 'react-native'; -import { ApiController, AssetUtil, type WcWallet } from '@reown/appkit-core-react-native'; +import { + ApiController, + AssetUtil, + OptionsController, + SnackController, + type OptionsControllerState, + type WcWallet +} from '@reown/appkit-core-react-native'; import { CardSelect, CardSelectLoader, @@ -12,6 +19,7 @@ import { import styles from './styles'; import { UiUtil } from '../../utils/UiUtil'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { Placeholder } from '../w3m-placeholder'; interface AllWalletsListProps { columns: number; @@ -21,16 +29,17 @@ interface AllWalletsListProps { export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsListProps) { const [loading, setLoading] = useState(false); + const [loadingError, setLoadingError] = useState(false); const [pageLoading, setPageLoading] = useState(false); const { maxWidth, padding } = useCustomDimensions(); - const { installed, featured, recommended, wallets, page, count } = useSnapshot( - ApiController.state - ); + const { installed, featured, recommended, wallets } = useSnapshot(ApiController.state); + const { customWallets } = useSnapshot(OptionsController.state) as OptionsControllerState; const imageHeaders = ApiController._getApiHeaders(); const preloadedWallets = installed.length + featured.length + recommended.length; const loadingItems = columns - ((100 + preloadedWallets) % columns); const walletList = [ + ...(customWallets ?? []), ...installed, ...featured, ...recommended, @@ -58,18 +67,18 @@ export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsLi ); }; - const walletTemplate = ({ item, index }: { item: WcWallet; index: number }) => { - const isInstalled = installed.find(wallet => wallet?.id === item?.id); + const walletTemplate = ({ item }: { item: WcWallet; index: number }) => { + const isInstalled = ApiController.state.installed.find(wallet => wallet?.id === item?.id); if (!item?.id) { return ( - + ); } return ( - + { - setLoading(true); - await ApiController.fetchWallets({ page: 1 }); - UiUtil.createViewTransition(); - setLoading(false); + try { + setLoading(true); + setLoadingError(false); + await ApiController.fetchWallets({ page: 1 }); + UiUtil.createViewTransition(); + setLoading(false); + } catch (error) { + SnackController.showError('Failed to load wallets'); + setLoading(false); + setLoadingError(true); + } }; const fetchNextPage = async () => { - if (walletList.length < count && !pageLoading) { - setPageLoading(true); - await ApiController.fetchWallets({ page: page + 1 }); + try { + if (walletList.length < ApiController.state.count && !pageLoading) { + setPageLoading(true); + await ApiController.fetchWallets({ page: ApiController.state.page + 1 }); + setPageLoading(false); + } + } catch (error) { + SnackController.showError('Failed to load more wallets'); setPageLoading(false); } }; useEffect(() => { - if (!wallets.length) { + if (!ApiController.state.wallets.length) { initialFetch(); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (loading) { return loadingTemplate(20); } + if (loadingError) { + return ( + + ); + } + return ( (false); - const { search, installed } = useSnapshot(ApiController.state); + const [loadingError, setLoadingError] = useState(false); const [prevSearchQuery, setPrevSearchQuery] = useState(''); const imageHeaders = ApiController._getApiHeaders(); const { maxWidth, padding, isLandscape } = useCustomDimensions(); @@ -36,7 +39,7 @@ export function AllWalletsSearch({ const ITEM_HEIGHT = CardSelectHeight + Spacing.xs * 2; const walletTemplate = ({ item }: { item: WcWallet }) => { - const isInstalled = installed.find(wallet => wallet?.id === item?.id); + const isInstalled = ApiController.state.installed.find(wallet => wallet?.id === item?.id); return ( @@ -46,6 +49,7 @@ export function AllWalletsSearch({ name={item?.name ?? 'Unknown'} onPress={() => onItemPress(item)} installed={!!isInstalled} + testID={`wallet-search-item-${item?.id}`} /> ); @@ -71,28 +75,25 @@ export function AllWalletsSearch({ const emptyTemplate = () => { return ( - - - - No wallet found - - + /> ); }; const searchFetch = useCallback(async () => { - setLoading(true); - await ApiController.searchWallet({ search: searchQuery }); - setLoading(false); + try { + setLoading(true); + setLoadingError(false); + await ApiController.searchWallet({ search: searchQuery }); + setLoading(false); + } catch (error) { + SnackController.showError('Failed to load wallets'); + setLoading(false); + setLoadingError(true); + } }, [searchQuery]); useEffect(() => { @@ -106,7 +107,22 @@ export function AllWalletsSearch({ return loadingTemplate(20); } - if (search.length === 0) { + if (loadingError) { + return ( + + ); + } + + if (ApiController.state.search.length === 0) { return emptyTemplate(); } @@ -116,11 +132,10 @@ export function AllWalletsSearch({ fadingEdgeLength={20} bounces={false} numColumns={columns} - data={search} + data={ApiController.state.search} renderItem={walletTemplate} style={styles.container} contentContainerStyle={[styles.contentContainer, { paddingHorizontal: padding + Spacing.xs }]} - ListEmptyComponent={emptyTemplate()} keyExtractor={item => item.id} getItemLayout={(_, index) => ({ length: ITEM_HEIGHT, diff --git a/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts b/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts index 95a07ebed..d425dea39 100644 --- a/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts +++ b/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts @@ -8,9 +8,13 @@ export default StyleSheet.create({ contentContainer: { paddingBottom: Spacing['2xl'] }, + placeholderContainer: { + flex: 0, + height: '90%' + }, emptyContainer: { - height: '100%', - paddingTop: '50%' + flex: 0, + height: '90%' }, emptyLandscape: { paddingTop: '10%' diff --git a/packages/scaffold/src/partials/w3m-connecting-header/index.tsx b/packages/scaffold/src/partials/w3m-connecting-header/index.tsx index 8bfdc5b72..45a11931f 100644 --- a/packages/scaffold/src/partials/w3m-connecting-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-connecting-header/index.tsx @@ -1,22 +1,31 @@ import type { Platform } from '@reown/appkit-core-react-native'; -import { FlexView, Tabs } from '@reown/appkit-ui-react-native'; +import { FlexView, Tabs, type IconType } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; export interface ConnectingHeaderProps { platforms: Platform[]; onSelectPlatform: (platform: Platform) => void; } +interface Tab { + label: string; + icon: IconType; + platform: Platform; +} + export function ConnectingHeader({ platforms, onSelectPlatform }: ConnectingHeaderProps) { const generateTabs = () => { - const tabs = platforms.map(platform => { - if (platform === 'mobile') { - return { label: 'Mobile', icon: 'mobile', platform: 'mobile' } as const; - } else if (platform === 'web') { - return { label: 'Web', icon: 'browser', platform: 'web' } as const; - } else { - return { label: 'QR Code', icon: 'qrCode', platform: 'qrcode' } as const; - } - }); + const tabs = platforms + .map(platform => { + if (platform === 'mobile') { + return { label: 'Mobile', icon: 'mobile', platform: 'mobile' } as const; + } else if (platform === 'web') { + return { label: 'Web', icon: 'browser', platform: 'web' } as const; + } else { + return undefined; + } + }) + .filter(Boolean) as Tab[]; return tabs; }; @@ -32,7 +41,13 @@ export function ConnectingHeader({ platforms, onSelectPlatform }: ConnectingHead return ( - + ); } + +const styles = StyleSheet.create({ + tab: { + maxWidth: '50%' + } +}); diff --git a/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx b/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx index 188b3b54d..fe52031d9 100644 --- a/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx +++ b/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx @@ -33,7 +33,7 @@ interface Props { } export function ConnectingMobile({ onRetry, onCopyUri, isInstalled }: Props) { - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const { maxWidth: width } = useCustomDimensions(); const { wcUri, wcError } = useSnapshot(ConnectionController.state); const [errorType, setErrorType] = useState(); @@ -90,7 +90,7 @@ export function ConnectingMobile({ onRetry, onCopyUri, isInstalled }: Props) { setErrorType('default'); } } - }, [data?.wallet, wcUri]); + }, [wcUri, data]); useEffect(() => { if (wcUri) { @@ -109,7 +109,7 @@ export function ConnectingMobile({ onRetry, onCopyUri, isInstalled }: Props) { {wcError && ( diff --git a/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx b/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx index 646b5ad1b..3a035706b 100644 --- a/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx +++ b/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx @@ -19,8 +19,8 @@ export function ConnectingQrCode() { const qrSize = (windowSize - Spacing.xl * 2) / (isPortrait ? 1 : 1.5); const onCopyAddress = () => { - if (wcUri) { - OptionsController.copyToClipboard(wcUri); + if (ConnectionController.state.wcUri) { + OptionsController.copyToClipboard(ConnectionController.state.wcUri); SnackController.showSuccess('Link copied'); } }; @@ -65,7 +65,7 @@ export function ConnectingQrCode() { color="fg-200" style={styles.copyButton} onPress={onCopyAddress} - testID="button-copy-uri" + testID="copy-link" > Copy link diff --git a/packages/scaffold/src/partials/w3m-connecting-web/index.tsx b/packages/scaffold/src/partials/w3m-connecting-web/index.tsx index 119d290de..6d7a48259 100644 --- a/packages/scaffold/src/partials/w3m-connecting-web/index.tsx +++ b/packages/scaffold/src/partials/w3m-connecting-web/index.tsx @@ -28,7 +28,7 @@ interface ConnectingWebProps { } export function ConnectingWeb({ onCopyUri }: ConnectingWebProps) { - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const { wcUri, wcError } = useSnapshot(ConnectionController.state); const showCopy = OptionsController.isClipboardAvailable(); const bodyMessage = getMessage({ diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index 67b684b3b..846272c83 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -2,59 +2,112 @@ import { useSnapshot } from 'valtio'; import { RouterController, ModalController, - EventsController + EventsController, + type RouterControllerState, + ConnectionController, + ConnectorController, + type AppKitFrameProvider } from '@reown/appkit-core-react-native'; import { IconLink, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { StringUtil } from '@reown/appkit-common-react-native'; -export function Header() { - const { view, history } = useSnapshot(RouterController.state); +import styles from './styles'; +export function Header() { + const { data, view } = useSnapshot(RouterController.state); const onHelpPress = () => { RouterController.push('WhatIsAWallet'); EventsController.sendEvent({ type: 'track', event: 'CLICK_WALLET_HELP' }); }; - const headings = () => { - const connectorName = RouterController.state.data?.connector?.name; - const walletName = RouterController.state.data?.wallet?.name; - const networkName = RouterController.state.data?.network?.name; + const headings = (_data: RouterControllerState['data'], _view: RouterControllerState['view']) => { + const connectorName = _data?.connector?.name; + const walletName = _data?.wallet?.name; + const networkName = _data?.network?.name; + const socialName = ConnectionController.state.selectedSocialProvider + ? StringUtil.capitalize(ConnectionController.state.selectedSocialProvider) + : undefined; return { - Connect: 'Connect wallet', Account: undefined, - ConnectingWalletConnect: walletName ?? 'WalletConnect', - ConnectingExternal: connectorName ?? 'Connect wallet', - ConnectingSiwe: 'Sign In', - Networks: 'Select network', - SwitchNetwork: networkName ?? 'Switch network', + AccountDefault: undefined, AllWallets: 'All wallets', - WhatIsANetwork: 'What is a network?', - WhatIsAWallet: 'What is a wallet?', - GetWallet: 'Get a wallet', + Connect: 'Connect wallet', + ConnectSocials: 'All socials', + ConnectingExternal: connectorName ?? 'Connect wallet', + ConnectingSiwe: undefined, + ConnectingFarcaster: socialName ?? 'Connecting Social', + ConnectingSocial: socialName ?? 'Connecting Social', + ConnectingWalletConnect: walletName ?? 'WalletConnect', + Create: 'Create wallet', EmailVerifyDevice: ' ', EmailVerifyOtp: 'Confirm email', - UpdateEmailWallet: 'Edit email', + GetWallet: 'Get a wallet', + Networks: 'Select network', + SwitchNetwork: networkName ?? 'Switch network', + Transactions: 'Activity', UpdateEmailPrimaryOtp: 'Confirm current email', UpdateEmailSecondaryOtp: 'Confirm new email', - UpgradeEmailWallet: 'Upgrade wallet' - }; + UpdateEmailWallet: 'Edit email', + UpgradeEmailWallet: 'Upgrade wallet', + UpgradeToSmartAccount: undefined, + WalletCompatibleNetworks: 'Compatible networks', + WalletReceive: 'Receive', + WalletSend: 'Send', + WalletSendPreview: 'Review send', + WalletSendSelectToken: 'Select token', + WhatIsANetwork: 'What is a network?', + WhatIsAWallet: 'What is a wallet?' + }[_view]; + }; + + const header = headings(data, view); + + const checkSocial = () => { + if ( + RouterController.state.view === 'ConnectingFarcaster' || + RouterController.state.view === 'ConnectingSocial' + ) { + const socialProvider = ConnectionController.state.selectedSocialProvider; + const authProvider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + + if (authProvider && socialProvider === 'farcaster') { + // TODO: remove this once Farcaster session refresh is implemented + // @ts-expect-error + authProvider.webviewRef?.current?.reload(); + } + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_CANCELED', + properties: { provider: ConnectionController.state.selectedSocialProvider! } + }); + } }; - const header = headings()[view]; + const handleGoBack = () => { + checkSocial(); + RouterController.goBack(); + }; + + const handleClose = () => { + checkSocial(); + ModalController.close(); + }; const dynamicButtonTemplate = () => { - const hideBackViews = ['ConnectingSiwe']; - const showBack = history.length > 1 && !hideBackViews.includes(view); + const noButtonViews = ['ConnectingSiwe']; + + if (noButtonViews.includes(RouterController.state.view)) { + return ; + } + + const showBack = RouterController.state.history.length > 1; return showBack ? ( - + ) : ( - + ); }; @@ -70,10 +123,10 @@ export function Header() { padding={['l', 'xl', bottomPadding, 'xl']} > {dynamicButtonTemplate()} - + {header} - + ); } diff --git a/packages/scaffold/src/partials/w3m-header/styles.ts b/packages/scaffold/src/partials/w3m-header/styles.ts index b90430b6e..f26ba320e 100644 --- a/packages/scaffold/src/partials/w3m-header/styles.ts +++ b/packages/scaffold/src/partials/w3m-header/styles.ts @@ -1,9 +1,8 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ - container: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center' + iconPlaceholder: { + height: 32, + width: 32 } }); diff --git a/packages/scaffold/src/partials/w3m-input-address/index.tsx b/packages/scaffold/src/partials/w3m-input-address/index.tsx new file mode 100644 index 000000000..a7b01ab1b --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-address/index.tsx @@ -0,0 +1,71 @@ +import { useState } from 'react'; +import { TextInput } from 'react-native'; +import { FlexView, useTheme } from '@reown/appkit-ui-react-native'; +import { ConnectionController, SendController } from '@reown/appkit-core-react-native'; + +import { useDebounceCallback } from '../../hooks/useDebounceCallback'; +import styles from './styles'; + +export interface InputAddressProps { + value?: string; +} + +export function InputAddress({ value }: InputAddressProps) { + const Theme = useTheme(); + const [inputValue, setInputValue] = useState(value); + + const onSearch = async (search: string) => { + SendController.setLoading(true); + const address = await ConnectionController.getEnsAddress(search); + SendController.setLoading(false); + + if (address) { + SendController.setReceiverProfileName(search); + SendController.setReceiverAddress(address); + const avatar = await ConnectionController.getEnsAvatar(search); + SendController.setReceiverProfileImageUrl(avatar || undefined); + } else { + SendController.setReceiverAddress(search); + SendController.setReceiverProfileName(undefined); + SendController.setReceiverProfileImageUrl(undefined); + } + }; + + const onDebounceSearch = useDebounceCallback({ callback: onSearch, delay: 800 }); + + const onInputChange = (address: string) => { + setInputValue(address); + SendController.setReceiverAddress(address); + onDebounceSearch(address); + }; + + return ( + + + + ); +} diff --git a/packages/scaffold/src/partials/w3m-input-address/styles.ts b/packages/scaffold/src/partials/w3m-input-address/styles.ts new file mode 100644 index 000000000..58ff557a6 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-address/styles.ts @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + height: 100, + width: '100%', + borderRadius: BorderRadius.s, + borderWidth: StyleSheet.hairlineWidth + }, + input: { + fontSize: 18 + } +}); diff --git a/packages/scaffold/src/partials/w3m-input-token/intex.tsx b/packages/scaffold/src/partials/w3m-input-token/intex.tsx new file mode 100644 index 000000000..1e89612b9 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-token/intex.tsx @@ -0,0 +1,113 @@ +import { useRef, useState } from 'react'; +import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; +import { FlexView, Link, Text, useTheme, TokenButton } from '@reown/appkit-ui-react-native'; +import { NumberUtil, type Balance } from '@reown/appkit-common-react-native'; +import { ConstantsUtil, SendController } from '@reown/appkit-core-react-native'; + +import { getMaxAmount, getSendValue } from './utils'; +import styles from './styles'; + +export interface InputTokenProps { + token?: Balance; + sendTokenAmount?: number; + gasPrice?: number; + style?: StyleProp; + onTokenPress?: () => void; +} + +export function InputToken({ + token, + sendTokenAmount, + gasPrice, + style, + onTokenPress +}: InputTokenProps) { + const Theme = useTheme(); + const valueInputRef = useRef(null); + const [inputValue, setInputValue] = useState(sendTokenAmount?.toString()); + const sendValue = getSendValue(token, sendTokenAmount); + const maxAmount = getMaxAmount(token); + const maxError = token && sendTokenAmount && sendTokenAmount > Number(token.quantity.numeric); + + const onInputChange = (value: string) => { + const formattedValue = value.replace(/,/g, '.'); + setInputValue(formattedValue); + Number(formattedValue) + ? SendController.setTokenAmount(Number(formattedValue)) + : SendController.setTokenAmount(undefined); + }; + + const onMaxPress = () => { + if (token && gasPrice) { + const isNetworkToken = + token.address === undefined || + Object.values(ConstantsUtil.NATIVE_TOKEN_ADDRESS).some( + nativeAddress => token?.address === nativeAddress + ); + + const numericGas = NumberUtil.bigNumber(gasPrice).shiftedBy(-token.quantity.decimals); + + const maxValue = isNetworkToken + ? NumberUtil.bigNumber(token.quantity.numeric).minus(numericGas) + : NumberUtil.bigNumber(token.quantity.numeric); + + SendController.setTokenAmount(Number(maxValue.toFixed(20))); + setInputValue(maxValue.toFixed(20)); + valueInputRef.current?.blur(); + } + }; + + return ( + + + + + + + + {sendValue ?? ''} + + {token && ( + + + {maxAmount ?? ''} + + Max + + )} + + + ); +} diff --git a/packages/scaffold/src/partials/w3m-input-token/styles.ts b/packages/scaffold/src/partials/w3m-input-token/styles.ts new file mode 100644 index 000000000..e35dc185f --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-token/styles.ts @@ -0,0 +1,20 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: 100, + width: '100%', + borderRadius: BorderRadius.s, + borderWidth: StyleSheet.hairlineWidth + }, + input: { + fontSize: 32, + flex: 1, + marginRight: Spacing.xs + }, + sendValue: { + flex: 1, + marginRight: Spacing.xs + } +}); diff --git a/packages/scaffold/src/partials/w3m-input-token/utils.ts b/packages/scaffold/src/partials/w3m-input-token/utils.ts new file mode 100644 index 000000000..38085ed39 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-token/utils.ts @@ -0,0 +1,21 @@ +import { type Balance, NumberUtil } from '@reown/appkit-common-react-native'; +import { UiUtil } from '@reown/appkit-ui-react-native'; + +export function getSendValue(token?: Balance, sendTokenAmount?: number) { + if (token && sendTokenAmount) { + const price = token.price; + const totalValue = price * sendTokenAmount; + + return totalValue ? `$${UiUtil.formatNumberToLocalString(totalValue, 2)}` : 'Incorrect value'; + } + + return null; +} + +export function getMaxAmount(token?: Balance) { + if (token) { + return NumberUtil.roundNumber(Number(token.quantity.numeric), 6, 5); + } + + return null; +} diff --git a/packages/scaffold/src/partials/w3m-placeholder/index.tsx b/packages/scaffold/src/partials/w3m-placeholder/index.tsx new file mode 100644 index 000000000..8fed2e110 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-placeholder/index.tsx @@ -0,0 +1,77 @@ +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { + IconBox, + Text, + FlexView, + Spacing, + type IconType, + Button, + type ColorType +} from '@reown/appkit-ui-react-native'; + +interface Props { + icon?: IconType; + iconColor?: ColorType; + title?: string; + description?: string; + style?: StyleProp; + actionIcon?: IconType; + actionPress?: () => void; + actionTitle?: string; +} + +export function Placeholder({ + icon, + iconColor = 'fg-175', + title, + description, + style, + actionPress, + actionTitle, + actionIcon +}: Props) { + return ( + + {icon && ( + + )} + {title && ( + + {title} + + )} + {description && ( + + {description} + + )} + {actionPress && ( + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + minHeight: 200 + }, + icon: { + marginBottom: Spacing.l + }, + title: { + marginBottom: Spacing['2xs'] + }, + button: { + marginTop: Spacing.m + } +}); diff --git a/packages/scaffold/src/partials/w3m-snackbar/index.tsx b/packages/scaffold/src/partials/w3m-snackbar/index.tsx index 0ac420edf..7d0802c80 100644 --- a/packages/scaffold/src/partials/w3m-snackbar/index.tsx +++ b/packages/scaffold/src/partials/w3m-snackbar/index.tsx @@ -7,7 +7,7 @@ import { Snackbar as SnackbarComponent } from '@reown/appkit-ui-react-native'; import styles from './styles'; export function Snackbar() { - const { open, message, variant } = useSnapshot(SnackController.state); + const { open, message, variant, long } = useSnapshot(SnackController.state); const componentOpacity = useMemo(() => new Animated.Value(0), []); useEffect(() => { @@ -17,17 +17,20 @@ export function Snackbar() { duration: 150, useNativeDriver: true }).start(); - setTimeout(() => { - Animated.timing(componentOpacity, { - toValue: 0, - duration: 300, - useNativeDriver: true - }).start(() => { - SnackController.hide(); - }); - }, 2200); + setTimeout( + () => { + Animated.timing(componentOpacity, { + toValue: 0, + duration: 300, + useNativeDriver: true + }).start(() => { + SnackController.hide(); + }); + }, + long ? 15000 : 2200 + ); } - }, [open, componentOpacity]); + }, [open, long, componentOpacity]); return ( void; + onPress: () => void; + socialProvider?: SocialProvider; + text: string; + style?: StyleProp; +} + +export function AuthButtons({ + onUpgradePress, + onPress, + socialProvider, + text, + style +}: AuthButtonsProps) { + return ( + <> + + {socialProvider ? ( + + + {text} + + + ) : ( + + + {text} + + + )} + + ); +} + +const styles = StyleSheet.create({ + actionButton: { + marginBottom: Spacing.xs + }, + upgradeButton: { + marginBottom: Spacing.s + }, + socialContainer: { + justifyContent: 'flex-start', + width: '100%' + }, + socialText: { + flex: 1, + marginLeft: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-account-view/components/upgrade-wallet-button.tsx b/packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-account-view/components/upgrade-wallet-button.tsx rename to packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx new file mode 100644 index 000000000..22b90082b --- /dev/null +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -0,0 +1,283 @@ +import { useSnapshot } from 'valtio'; +import { useState } from 'react'; +import { Linking, ScrollView } from 'react-native'; +import { + AccountController, + ApiController, + AssetUtil, + ConnectionController, + ConnectorController, + CoreHelperUtil, + EventsController, + ModalController, + NetworkController, + OptionsController, + RouterController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { + Avatar, + Button, + FlexView, + IconLink, + Text, + UiUtil, + Spacing, + ListItem +} from '@reown/appkit-ui-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; + +import styles from './styles'; +import { AuthButtons } from './components/auth-buttons'; + +export function AccountDefaultView() { + const { + address, + profileName, + profileImage, + balance, + balanceSymbol, + addressExplorerUrl, + preferredAccountType + } = useSnapshot(AccountController.state); + const { loading } = useSnapshot(ModalController.state); + const [disconnecting, setDisconnecting] = useState(false); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { connectedConnector } = useSnapshot(ConnectorController.state); + const { connectedSocialProvider } = useSnapshot(ConnectionController.state); + const { history } = useSnapshot(RouterController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const showCopy = OptionsController.isClipboardAvailable(); + const isAuth = connectedConnector === 'AUTH'; + const showBalance = balance && !isAuth; + const showExplorer = addressExplorerUrl && !isAuth; + const showBack = history.length > 1; + const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); + const { padding } = useCustomDimensions(); + + async function onDisconnect() { + try { + setDisconnecting(true); + await ConnectionController.disconnect(); + AccountController.setIsConnected(false); + ModalController.close(); + setDisconnecting(false); + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }); + } catch (error) { + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_ERROR' + }); + } + } + + const onSwitchAccountType = async () => { + try { + if (isAuth) { + ModalController.setLoading(true); + const accountType = + AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + await provider?.setPreferredAccount(accountType); + EventsController.sendEvent({ + type: 'track', + event: 'SET_PREFERRED_ACCOUNT_TYPE', + properties: { + accountType, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + } + } catch (error) { + ModalController.setLoading(false); + SnackController.showError('Error switching account type'); + } + }; + + const getUserEmail = () => { + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + if (!provider) return ''; + + return provider.getEmail(); + }; + + const getUsername = () => { + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + if (!provider) return ''; + + return provider.getUsername(); + }; + + const onExplorerPress = () => { + if (AccountController.state.addressExplorerUrl) { + Linking.openURL(AccountController.state.addressExplorerUrl); + } + }; + + const onCopyAddress = () => { + if (AccountController.state.profileName) { + OptionsController.copyToClipboard(AccountController.state.profileName); + SnackController.showSuccess('Name copied'); + } else if (AccountController.state.address) { + OptionsController.copyToClipboard( + AccountController.state.profileName ?? AccountController.state.address + ); + SnackController.showSuccess('Address copied'); + } + }; + + const onActivityPress = () => { + RouterController.push('Transactions'); + }; + + const onNetworkPress = () => { + RouterController.push('Networks'); + + EventsController.sendEvent({ + type: 'track', + event: 'CLICK_NETWORKS' + }); + }; + + const onUpgradePress = () => { + EventsController.sendEvent({ type: 'track', event: 'EMAIL_UPGRADE_FROM_MODAL' }); + RouterController.push('UpgradeEmailWallet'); + }; + + const onEmailPress = () => { + if (ConnectionController.state.connectedSocialProvider) return; + RouterController.push('UpdateEmailWallet', { email: getUserEmail() }); + }; + + return ( + <> + {showBack && ( + + )} + + + + + + + {profileName + ? UiUtil.getTruncateString({ + string: profileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: address ?? '', + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + })} + + {showCopy && ( + + )} + + {showBalance && ( + + {CoreHelperUtil.formatBalance(balance, balanceSymbol)} + + )} + {showExplorer && ( + + )} + + {isAuth && ( + + )} + + + {caipNetwork?.name} + + + {!isAuth && ( + + Activity + + )} + {showSwitchAccountType && ( + + {`Switch to your ${ + preferredAccountType === 'eoa' ? 'smart account' : 'EOA' + }`} + + )} + + Disconnect + + + + + + ); +} diff --git a/packages/scaffold/src/views/w3m-account-default-view/styles.ts b/packages/scaffold/src/views/w3m-account-default-view/styles.ts new file mode 100644 index 000000000..1d0389f0d --- /dev/null +++ b/packages/scaffold/src/views/w3m-account-default-view/styles.ts @@ -0,0 +1,28 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + backIcon: { + alignSelf: 'flex-end', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + left: Spacing.xl + }, + closeIcon: { + alignSelf: 'flex-end', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + right: Spacing.xl + }, + copyButton: { + marginLeft: Spacing['4xs'] + }, + actionButton: { + marginBottom: Spacing.xs + }, + upgradeButton: { + marginBottom: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index 6dbcee657..14e19d858 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -1,200 +1,105 @@ import { useSnapshot } from 'valtio'; -import { useState } from 'react'; -import { Linking, ScrollView } from 'react-native'; +import { useEffect } from 'react'; +import { ScrollView } from 'react-native'; +import { + AccountPill, + FlexView, + Icon, + IconLink, + NetworkButton, + useTheme, + Promo +} from '@reown/appkit-ui-react-native'; import { AccountController, ApiController, AssetUtil, - ConnectionController, - ConnectorController, - CoreHelperUtil, - EventsController, ModalController, NetworkController, - OptionsController, RouterController, - SnackController, - type AppKitFrameProvider + SendController } from '@reown/appkit-core-react-native'; -import { - Avatar, - Button, - FlexView, - IconLink, - Text, - UiUtil, - Spacing, - ListItem -} from '@reown/appkit-ui-react-native'; + import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { UpgradeWalletButton } from './components/upgrade-wallet-button'; +import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; import styles from './styles'; export function AccountView() { - const { address, profileName, profileImage, balance, balanceSymbol, addressExplorerUrl } = - useSnapshot(AccountController.state); - const [disconnecting, setDisconnecting] = useState(false); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { connectedConnector } = useSnapshot(ConnectorController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const showCopy = OptionsController.isClipboardAvailable(); - const isAuth = connectedConnector === 'AUTH'; + const Theme = useTheme(); const { padding } = useCustomDimensions(); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { address, profileName, profileImage, preferredAccountType } = useSnapshot( + AccountController.state + ); + const showActivate = + preferredAccountType === 'eoa' && NetworkController.checkIfSmartAccountEnabled(); - async function onDisconnect() { - try { - setDisconnecting(true); - await ConnectionController.disconnect(); - AccountController.setIsConnected(false); - ModalController.close(); - setDisconnecting(false); - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_SUCCESS' - }); - } catch (error) { - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_ERROR' - }); - } - } - - const onExplorerPress = () => { - if (addressExplorerUrl) { - Linking.openURL(addressExplorerUrl); - } - }; - - const onCopyAddress = () => { - if (address) { - OptionsController.copyToClipboard(profileName ?? address); - SnackController.showSuccess('Address copied'); - } + const onProfilePress = () => { + RouterController.push('AccountDefault'); }; const onNetworkPress = () => { RouterController.push('Networks'); - - EventsController.sendEvent({ - type: 'track', - event: 'CLICK_NETWORKS' - }); }; - const onUpgradePress = () => { - EventsController.sendEvent({ type: 'track', event: 'EMAIL_UPGRADE_FROM_MODAL' }); - RouterController.push('UpgradeEmailWallet'); + const onActivatePress = () => { + RouterController.push('UpgradeToSmartAccount'); }; - const getUserEmail = () => { - const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; - if (!provider) return ''; + useEffect(() => { + AccountController.fetchTokenBalance(); + SendController.resetSend(); + }, []); - return provider.getEmail(); - }; + useEffect(() => { + AccountController.fetchTokenBalance(); - const addressExplorerTemplate = () => { - if (!addressExplorerUrl) return null; + const balanceInterval = setInterval(() => { + AccountController.fetchTokenBalance(); + }, 10000); - return ( - - ); - }; + return () => { + clearInterval(balanceInterval); + }; + }, []); return ( - <> + + + + - - - - - - {profileName - ? UiUtil.getTruncateString({ - string: profileName, - charsStart: 20, - charsEnd: 0, - truncate: 'end' - }) - : UiUtil.getTruncateString({ - string: address ?? '', - charsStart: 4, - charsEnd: 6, - truncate: 'middle' - })} - - {showCopy && ( - - )} - - {balance && ( - - {CoreHelperUtil.formatBalance(balance, balanceSymbol)} - - )} - {addressExplorerTemplate()} - - {isAuth && ( - <> - - - RouterController.push('UpdateEmailWallet', { email: getUserEmail() }) - } - chevron - testID="button-email" - > - {getUserEmail()} - - - )} - - - {caipNetwork?.name} - - - - Disconnect - - - - - + + {showActivate && ( + + )} + + + + ); } diff --git a/packages/scaffold/src/views/w3m-account-view/styles.ts b/packages/scaffold/src/views/w3m-account-view/styles.ts index a154e7db3..0a256790d 100644 --- a/packages/scaffold/src/views/w3m-account-view/styles.ts +++ b/packages/scaffold/src/views/w3m-account-view/styles.ts @@ -1,7 +1,17 @@ import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; +import { Platform, StyleSheet } from 'react-native'; export default StyleSheet.create({ + contentContainer: { + paddingBottom: Platform.select({ ios: Spacing.s }) + }, + networkIcon: { + alignSelf: 'flex-start', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + left: Spacing.l + }, closeIcon: { alignSelf: 'flex-end', position: 'absolute', @@ -9,13 +19,14 @@ export default StyleSheet.create({ top: Spacing.l, right: Spacing.xl }, - copyButton: { - marginLeft: Spacing['4xs'] - }, - networkButton: { - marginVertical: Spacing.xs + accountPill: { + alignSelf: 'center', + marginBottom: Spacing.s, + marginHorizontal: Spacing.s }, - upgradeButton: { - marginBottom: Spacing.s + promoPill: { + marginTop: Spacing.xs, + marginBottom: Spacing['2xl'], + alignSelf: 'center' } }); diff --git a/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx b/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx index d6e89eb15..bd8a76245 100644 --- a/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx +++ b/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx @@ -1,5 +1,4 @@ import { useState } from 'react'; -import { useSnapshot } from 'valtio'; import { ConnectionController, ConnectorController, @@ -17,7 +16,6 @@ import { useCustomDimensions } from '../../hooks/useCustomDimensions'; export function AllWalletsView() { const Theme = useTheme(); - const { connectors } = useSnapshot(ConnectorController.state); const [searchQuery, setSearchQuery] = useState(''); const { maxWidth } = useCustomDimensions(); const numColumns = 4; @@ -27,7 +25,7 @@ export function AllWalletsView() { const onInputChange = useDebounceCallback({ callback: setSearchQuery }); const onWalletPress = (wallet: WcWallet) => { - const connector = connectors.find(c => c.explorerId === wallet.id); + const connector = ConnectorController.state.connectors.find(c => c.explorerId === wallet.id); if (connector) { RouterController.push('ConnectingExternal', { connector, wallet }); } else { @@ -61,10 +59,10 @@ export function AllWalletsView() { alignItems="center" style={[ styles.header, - { backgroundColor: Theme['bg-125'], shadowColor: Theme['bg-125'], width: maxWidth } + { backgroundColor: Theme['bg-100'], shadowColor: Theme['bg-100'], width: maxWidth } ]} > - + { + ConnectionController.setSelectedSocialProvider(provider); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_STARTED', + properties: { provider } + }); + if (provider === 'farcaster') { + RouterController.push('ConnectingFarcaster'); + } else { + RouterController.push('ConnectingSocial'); + } + }; + + useEffect(() => { + WebviewController.setConnecting(false); + }, []); + + return ( + + + {socialProviders.map(provider => ( + onItemPress(provider)} + style={styles.item} + > + + {StringUtil.capitalize(provider)} + + + ))} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts b/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts new file mode 100644 index 000000000..be837b83c --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + item: { + marginVertical: Spacing['3xs'], + justifyContent: 'flex-start' + }, + text: { + marginLeft: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx index daf346718..581b00b5f 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx @@ -1,3 +1,4 @@ +import { type StyleProp, type ViewStyle } from 'react-native'; import { useSnapshot } from 'valtio'; import { ApiController, @@ -6,8 +7,7 @@ import { type ConnectionControllerState, type WcWallet } from '@reown/appkit-core-react-native'; -import { ListWallet } from '@reown/appkit-ui-react-native'; -import type { StyleProp, ViewStyle } from 'react-native'; +import { ListItemLoader, ListWallet } from '@reown/appkit-ui-react-native'; import { UiUtil } from '../../../utils/UiUtil'; import { filterOutRecentWallets } from '../utils'; @@ -18,7 +18,7 @@ interface Props { } export function AllWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { - const { installed, featured, recommended } = useSnapshot(ApiController.state); + const { installed, featured, recommended, prefetchLoading } = useSnapshot(ApiController.state); const { recentWallets } = useSnapshot(ConnectionController.state) as ConnectionControllerState; const imageHeaders = ApiController._getApiHeaders(); const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; @@ -33,15 +33,22 @@ export function AllWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled return null; } - return list.map(wallet => ( - onWalletPress(wallet)} - style={itemStyle} - installed={!!installed.find(installedWallet => installedWallet.id === wallet.id)} - /> - )); + return prefetchLoading ? ( + <> + + + + ) : ( + list.map(wallet => ( + onWalletPress(wallet)} + style={itemStyle} + installed={!!installed.find(installedWallet => installedWallet.id === wallet.id)} + /> + )) + ); } diff --git a/packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx b/packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx index 5d0f88f58..1e4c76ed1 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx @@ -27,7 +27,7 @@ export function AllWalletsButton({ itemStyle, onPress, isWalletConnectEnabled }: tagVariant="shade" onPress={onPress} style={itemStyle} - testID="button-all-wallets" + testID="all-wallets" /> ); } diff --git a/packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx b/packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx index 620c962fb..4ff60e7ab 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx @@ -1,6 +1,6 @@ import { useSnapshot } from 'valtio'; import { useState } from 'react'; -import { EmailInput, FlexView, Separator, Spacing } from '@reown/appkit-ui-react-native'; +import { EmailInput, FlexView } from '@reown/appkit-ui-react-native'; import { ConnectorController, CoreHelperUtil, @@ -9,15 +9,12 @@ import { SnackController, type AppKitFrameProvider } from '@reown/appkit-core-react-native'; -import { StyleSheet } from 'react-native'; interface Props { - isEmailEnabled: boolean; - showSeparator: boolean; loading?: boolean; } -export function ConnectEmailInput({ isEmailEnabled, showSeparator, loading }: Props) { +export function ConnectEmailInput({ loading }: Props) { const { connectors } = useSnapshot(ConnectorController.state); const [inputLoading, setInputLoading] = useState(false); const [error, setError] = useState(''); @@ -57,29 +54,16 @@ export function ConnectEmailInput({ isEmailEnabled, showSeparator, loading }: Pr } }; - if (!isEmailEnabled) { - return null; - } - return ( - <> - - - - {showSeparator && } - + + + ); } - -const styles = StyleSheet.create({ - emailSeparator: { - marginVertical: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx index 8c0c48643..81f011f7d 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx @@ -15,7 +15,7 @@ interface Props { } export function RecentWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { - const { installed } = useSnapshot(ApiController.state); + const installed = ApiController.state.installed; const { recentWallets } = useSnapshot(ConnectionController.state); const imageHeaders = ApiController._getApiHeaders(); const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; diff --git a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx new file mode 100644 index 000000000..ea2740dbf --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx @@ -0,0 +1,95 @@ +import { StyleSheet } from 'react-native'; +import { FlexView, ListSocial, LogoSelect, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { type SocialProvider, StringUtil } from '@reown/appkit-common-react-native'; +import { + ConnectionController, + EventsController, + RouterController, + WebviewController +} from '@reown/appkit-core-react-native'; + +export interface SocialLoginListProps { + options: readonly SocialProvider[]; + disabled?: boolean; +} + +const MAX_OPTIONS = 6; + +export function SocialLoginList({ options, disabled }: SocialLoginListProps) { + const showBigSocial = options?.length > 2 || options?.length === 1; + const showMoreButton = options?.length > MAX_OPTIONS; + const topSocial = showBigSocial ? options[0] : null; + let bottomSocials = showBigSocial ? options.slice(1) : options; + bottomSocials = showMoreButton ? bottomSocials.slice(0, MAX_OPTIONS - 2) : bottomSocials; + + const onItemPress = (provider: SocialProvider) => { + ConnectionController.setSelectedSocialProvider(provider); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_STARTED', + properties: { provider } + }); + WebviewController.setConnecting(false); + + if (provider === 'farcaster') { + RouterController.push('ConnectingFarcaster'); + } else { + RouterController.push('ConnectingSocial'); + } + }; + + const onMorePress = () => { + RouterController.push('ConnectSocials'); + }; + + return ( + + {topSocial && ( + onItemPress(topSocial)}> + + {`Continue with ${StringUtil.capitalize(topSocial)}`} + + + )} + + {bottomSocials?.map((social: SocialProvider, index) => ( + onItemPress(social)} + style={[ + styles.socialItem, + index === 0 && styles.socialItemFirst, + !showMoreButton && index === bottomSocials.length - 1 && styles.socialItemLast + ]} + /> + ))} + {showMoreButton && ( + + )} + + + ); +} + +const styles = StyleSheet.create({ + topDescription: { + textAlign: 'center' + }, + socialItem: { + flex: 1, + marginHorizontal: Spacing['2xs'] + }, + socialItemFirst: { + marginLeft: 0 + }, + socialItemLast: { + marginRight: 0 + } +}); diff --git a/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx b/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx new file mode 100644 index 000000000..92fdcc2de --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx @@ -0,0 +1,50 @@ +import { RouterController } from '@reown/appkit-core-react-native'; +import { Chip, FlexView, Link, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { Linking, StyleSheet } from 'react-native'; + +export interface WalletGuideProps { + guide: 'explore' | 'get-started'; +} + +export function WalletGuide({ guide }: WalletGuideProps) { + const onExplorerPress = () => { + Linking.openURL('https://explorer.walletconnect.com'); + }; + + const onGetStartedPress = () => { + RouterController.push('Create'); + }; + + return guide === 'explore' ? ( + + + + Looking for a self-custody wallet? + + + + ) : ( + + Haven't got a wallet? + + Get started + + + ); +} + +const styles = StyleSheet.create({ + text: { + marginBottom: Spacing.xs + }, + socialSeparator: { + marginVertical: Spacing.l + } +}); diff --git a/packages/scaffold/src/views/w3m-connect-view/index.tsx b/packages/scaffold/src/views/w3m-connect-view/index.tsx index 3e71ead78..f1222cc8e 100644 --- a/packages/scaffold/src/views/w3m-connect-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/index.tsx @@ -1,31 +1,49 @@ import { useSnapshot } from 'valtio'; -import { Platform, ScrollView } from 'react-native'; +import { Platform, ScrollView, View } from 'react-native'; import { + ApiController, ConnectorController, EventUtil, EventsController, + OptionsController, RouterController, type WcWallet } from '@reown/appkit-core-react-native'; -import { FlexView, Spacing } from '@reown/appkit-ui-react-native'; +import { FlexView, Icon, ListItem, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { ConnectEmailInput } from './components/connect-email-input'; import { useKeyboard } from '../../hooks/useKeyboard'; +import { Placeholder } from '../../partials/w3m-placeholder'; import { ConnectorList } from './components/connectors-list'; import { CustomWalletList } from './components/custom-wallet-list'; import { AllWalletsButton } from './components/all-wallets-button'; import { AllWalletList } from './components/all-wallet-list'; import { RecentWalletList } from './components/recent-wallet-list'; +import { SocialLoginList } from './components/social-login-list'; +import { WalletGuide } from './components/wallet-guide'; import styles from './styles'; export function ConnectView() { - const { connectors, authLoading } = useSnapshot(ConnectorController.state); + const connectors = ConnectorController.state.connectors; + const { authLoading } = useSnapshot(ConnectorController.state); + const { prefetchError } = useSnapshot(ApiController.state); + const { features } = useSnapshot(OptionsController.state); const { padding } = useCustomDimensions(); const { keyboardShown, keyboardHeight } = useKeyboard(); const isWalletConnectEnabled = connectors.some(c => c.type === 'WALLET_CONNECT'); const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); const isCoinbaseEnabled = connectors.some(c => c.type === 'COINBASE'); + const isEmailEnabled = isAuthEnabled && features?.email; + const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; + const showConnectWalletsButton = + isWalletConnectEnabled && isAuthEnabled && !features?.emailShowWallets; + const showSeparator = + isAuthEnabled && + (isEmailEnabled || isSocialEnabled) && + (isWalletConnectEnabled || isCoinbaseEnabled); + const showLoadingError = !showConnectWalletsButton && prefetchError; + const showList = !showConnectWalletsButton && !showLoadingError; const paddingBottom = Platform.select({ android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], @@ -60,33 +78,61 @@ export function ConnectView() { return ( - + {isEmailEnabled && } + {isSocialEnabled && } + {showSeparator && } + - - - - - + {showConnectWalletsButton && ( + + + Continue with a wallet + + + )} + {showLoadingError && ( + + + + + )} + {showList && ( + <> + + + + + + + )} + {isAuthEnabled && } diff --git a/packages/scaffold/src/views/w3m-connect-view/styles.ts b/packages/scaffold/src/views/w3m-connect-view/styles.ts index 7b28e09e6..819b007df 100644 --- a/packages/scaffold/src/views/w3m-connect-view/styles.ts +++ b/packages/scaffold/src/views/w3m-connect-view/styles.ts @@ -4,5 +4,15 @@ import { Spacing } from '@reown/appkit-ui-react-native'; export default StyleSheet.create({ item: { marginVertical: Spacing['3xs'] + }, + socialSeparator: { + marginVertical: Spacing.xs + }, + connectWalletButton: { + justifyContent: 'space-between' + }, + connectWalletEmpty: { + height: 20, + width: 20 } }); diff --git a/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx index 4e6893b3c..e5df102ea 100644 --- a/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx @@ -1,4 +1,3 @@ -import { useSnapshot } from 'valtio'; import { useCallback, useEffect, useState } from 'react'; import { ScrollView } from 'react-native'; import { @@ -24,7 +23,7 @@ import { ConnectingBody, getMessage, type BodyErrorType } from '../../partials/w import styles from './styles'; export function ConnectingExternalView() { - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const connector = data?.connector; const { maxWidth: width } = useCustomDimensions(); const [errorType, setErrorType] = useState(); diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx new file mode 100644 index 000000000..fd7cb308d --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -0,0 +1,140 @@ +import { Linking } from 'react-native'; +import { useCallback, useEffect, useState } from 'react'; +import { + ConnectionController, + ConnectorController, + EventsController, + ModalController, + OptionsController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { + FlexView, + LoadingThumbnail, + IconBox, + Logo, + Text, + Link +} from '@reown/appkit-ui-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectingFarcasterView() { + const { maxWidth: width } = useCustomDimensions(); + const authConnector = ConnectorController.getAuthConnector(); + const [error, setError] = useState(false); + const [processing, setProcessing] = useState(false); + const [url, setUrl] = useState(); + const showCopy = OptionsController.isClipboardAvailable(); + const provider = authConnector?.provider as AppKitFrameProvider; + + const onConnect = useCallback(async () => { + try { + if (provider && authConnector) { + setError(false); + const { url: farcasterUrl } = await provider.getFarcasterUri(); + setUrl(farcasterUrl); + Linking.openURL(farcasterUrl); + + await provider.connectFarcaster(); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', + properties: { provider: 'farcaster' } + }); + setProcessing(true); + await ConnectionController.connectExternal(authConnector); + ConnectionController.setConnectedSocialProvider('farcaster'); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_SUCCESS', + properties: { provider: 'farcaster' } + }); + + setProcessing(false); + ModalController.close(); + } + } catch (e) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_ERROR', + properties: { provider: 'farcaster' } + }); + // TODO: remove this once Farcaster session refresh is implemented + // @ts-expect-error + provider?.webviewRef?.current?.reload(); + SnackController.showError('Something went wrong'); + setError(true); + setProcessing(false); + } + }, [provider, authConnector]); + + const onCopyUrl = () => { + if (url) { + OptionsController.copyToClipboard(url); + SnackController.showSuccess('Link copied'); + } + }; + + useEffect(() => { + return () => { + // TODO: remove this once Farcaster session refresh is implemented + if (!ModalController.state.open) { + // @ts-expect-error + provider.webviewRef?.current?.reload(); + } + }; + // @ts-expect-error + }, [provider.webviewRef]); + + useEffect(() => { + onConnect(); + }, [onConnect]); + + return ( + + <> + + + {error && ( + + )} + + + {processing ? 'Loading user data' : 'Continue in Farcaster'} + + + {processing + ? 'Please wait a moment while we load your data' + : 'Connect in the Farcaster app'} + + {showCopy && ( + + Copy link + + )} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts new file mode 100644 index 000000000..542a8ad28 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + errorIcon: { + position: 'absolute', + bottom: 8, + right: 8, + zIndex: 2 + }, + continueText: { + marginTop: Spacing.m, + marginBottom: Spacing.xs + }, + copyButton: { + marginTop: Spacing.m + } +}); diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx new file mode 100644 index 000000000..dc7b0aaa2 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx @@ -0,0 +1,153 @@ +import { useSnapshot } from 'valtio'; +import { useCallback, useEffect, useState } from 'react'; +import { Platform } from 'react-native'; +import { + ConnectionController, + ConnectorController, + EventsController, + ModalController, + RouterController, + SnackController, + WebviewController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { FlexView, LoadingThumbnail, IconBox, Logo, Text } from '@reown/appkit-ui-react-native'; +import { StringUtil } from '@reown/appkit-common-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectingSocialView() { + const { maxWidth: width } = useCustomDimensions(); + const { processingAuth } = useSnapshot(WebviewController.state); + const { selectedSocialProvider } = useSnapshot(ConnectionController.state); + const authConnector = ConnectorController.getAuthConnector(); + const [error, setError] = useState(false); + const provider = authConnector?.provider as AppKitFrameProvider; + + const onConnect = useCallback(async () => { + try { + if ( + !WebviewController.state.connecting && + provider && + ConnectionController.state.selectedSocialProvider + ) { + const { uri } = await provider.getSocialRedirectUri({ + provider: ConnectionController.state.selectedSocialProvider + }); + WebviewController.setWebviewUrl(uri); + + const isNativeApple = + ConnectionController.state.selectedSocialProvider === 'apple' && Platform.OS === 'ios'; + + WebviewController.setWebviewVisible(!isNativeApple); + WebviewController.setConnecting(true); + WebviewController.setConnectingProvider(ConnectionController.state.selectedSocialProvider); + } + } catch (e) { + WebviewController.setWebviewVisible(false); + WebviewController.setWebviewUrl(undefined); + WebviewController.setConnecting(false); + WebviewController.setConnectingProvider(undefined); + SnackController.showError('Something went wrong'); + setError(true); + } + }, [provider]); + + const socialMessageHandler = useCallback( + async (url: string) => { + try { + if ( + url.includes('/sdk/oauth') && + ConnectionController.state.selectedSocialProvider && + authConnector && + !WebviewController.state.processingAuth + ) { + WebviewController.setProcessingAuth(true); + WebviewController.setWebviewVisible(false); + const parsedUrl = new URL(url); + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', + properties: { provider: ConnectionController.state.selectedSocialProvider } + }); + + await provider?.connectSocial(parsedUrl.search); + await ConnectionController.connectExternal(authConnector); + ConnectionController.setConnectedSocialProvider( + ConnectionController.state.selectedSocialProvider + ); + WebviewController.setConnecting(false); + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_SUCCESS', + properties: { provider: ConnectionController.state.selectedSocialProvider } + }); + + ModalController.close(); + WebviewController.reset(); + } + } catch (e) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_ERROR', + properties: { provider: ConnectionController.state.selectedSocialProvider! } + }); + WebviewController.reset(); + RouterController.goBack(); + SnackController.showError('Something went wrong'); + } + }, + [authConnector, provider] + ); + + useEffect(() => { + onConnect(); + }, [onConnect]); + + useEffect(() => { + if (!provider) return; + + const unsubscribe = provider?.getEventEmitter().addListener('social', socialMessageHandler); + + return () => { + unsubscribe.removeListener('social', socialMessageHandler); + }; + }, [socialMessageHandler, provider]); + + return ( + + + + {error && ( + + )} + + + {processingAuth + ? 'Loading user data' + : `Continue with ${StringUtil.capitalize(selectedSocialProvider)}`} + + + {processingAuth + ? 'Please wait a moment while we load your data' + : 'Connect in the provider window'} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts new file mode 100644 index 000000000..dcb555e83 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + errorIcon: { + position: 'absolute', + bottom: 8, + right: 8, + zIndex: 2 + }, + continueText: { + marginTop: Spacing.m, + marginBottom: Spacing.xs + } +}); diff --git a/packages/scaffold/src/views/w3m-connecting-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-view/index.tsx index f493345e8..44ee537c9 100644 --- a/packages/scaffold/src/views/w3m-connecting-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-view/index.tsx @@ -11,8 +11,10 @@ import { type Platform, OptionsController, ApiController, - EventsController + EventsController, + ConnectorController } from '@reown/appkit-core-react-native'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; @@ -22,7 +24,7 @@ import { UiUtil } from '../../utils/UiUtil'; export function ConnectingView() { const { installed } = useSnapshot(ApiController.state); - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const [lastRetry, setLastRetry] = useState(Date.now()); const isQr = !data?.wallet; const isInstalled = !!installed?.find(wallet => wallet.id === data?.wallet?.id); @@ -48,10 +50,10 @@ export function ConnectingView() { ConnectionController.setWcError(false); ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); await ConnectionController.state.wcPromise; + ConnectorController.setConnectedConnector('WALLET_CONNECT'); AccountController.setIsConnected(true); if (OptionsController.state.isSiweEnabled) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); if (SIWEController.state.status === 'success') { ModalController.close(); } else { diff --git a/packages/scaffold/src/views/w3m-create-view/index.tsx b/packages/scaffold/src/views/w3m-create-view/index.tsx new file mode 100644 index 000000000..dcfa09654 --- /dev/null +++ b/packages/scaffold/src/views/w3m-create-view/index.tsx @@ -0,0 +1,35 @@ +import { Platform, ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { FlexView, Spacing } from '@reown/appkit-ui-react-native'; +import { ConnectEmailInput } from '../w3m-connect-view/components/connect-email-input'; +import { SocialLoginList } from '../w3m-connect-view/components/social-login-list'; +import { WalletGuide } from '../w3m-connect-view/components/wallet-guide'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { ConnectorController, OptionsController } from '@reown/appkit-core-react-native'; +import { useKeyboard } from '../../hooks/useKeyboard'; + +export function CreateView() { + const connectors = ConnectorController.state.connectors; + const { authLoading } = useSnapshot(ConnectorController.state); + const { features } = useSnapshot(OptionsController.state); + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); + const isEmailEnabled = isAuthEnabled && features?.email; + const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing.xl : Spacing.xl, + default: Spacing.xl + }); + + return ( + + + {isEmailEnabled && } + {isSocialEnabled && } + {isAuthEnabled && } + + + ); +} diff --git a/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx b/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx index 2e04562d5..510e75ea2 100644 --- a/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx +++ b/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx @@ -16,7 +16,7 @@ import styles from './styles'; export function EmailVerifyDeviceView() { const Theme = useTheme(); const { connectors } = useSnapshot(ConnectorController.state); - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const { timeLeft, startTimer } = useTimeout(0); const [loading, setLoading] = useState(false); const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; diff --git a/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx b/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx index db0b9489e..4a7fbdf3f 100644 --- a/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx +++ b/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx @@ -1,4 +1,3 @@ -import { useSnapshot } from 'valtio'; import { useState } from 'react'; import { ConnectionController, @@ -15,7 +14,7 @@ import { OtpCodeView } from '../../partials/w3m-otp-code'; export function EmailVerifyOtpView() { const { timeLeft, startTimer } = useTimeout(0); - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const authConnector = ConnectorController.getAuthConnector(); diff --git a/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx index 074593076..3cc7478a1 100644 --- a/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx @@ -1,4 +1,3 @@ -import { useSnapshot } from 'valtio'; import { Linking, Platform, ScrollView } from 'react-native'; import { FlexView, ListWallet } from '@reown/appkit-ui-react-native'; import { ApiController, AssetUtil, type WcWallet } from '@reown/appkit-core-react-native'; @@ -7,7 +6,6 @@ import styles from './styles'; export function GetWalletView() { const { padding } = useCustomDimensions(); - const { recommended } = useSnapshot(ApiController.state); const imageHeaders = ApiController._getApiHeaders(); const onWalletPress = (wallet: WcWallet) => { @@ -19,7 +17,7 @@ export function GetWalletView() { }; const listTemplate = () => { - return recommended.map(wallet => ( + return ApiController.state.recommended.map(wallet => ( + {listTemplate()} (false); const [showRetry, setShowRetry] = useState(false); const network = data?.network!; @@ -89,6 +92,14 @@ export function NetworkSwitchView() { ); } + if (isAuthConnected) { + return ( + + Switching to {network.name} network + + ); + } + return ( <> {`Approve in ${walletName}`} diff --git a/packages/scaffold/src/views/w3m-networks-view/index.tsx b/packages/scaffold/src/views/w3m-networks-view/index.tsx index abb230178..af9386bde 100644 --- a/packages/scaffold/src/views/w3m-networks-view/index.tsx +++ b/packages/scaffold/src/views/w3m-networks-view/index.tsx @@ -1,4 +1,3 @@ -import { useSnapshot } from 'valtio'; import { ScrollView, View } from 'react-native'; import { CardSelect, @@ -17,15 +16,15 @@ import { type CaipNetwork, AccountController, EventsController, - RouterUtil + RouterUtil, + ConnectorController } from '@reown/appkit-core-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; export function NetworksView() { - const { isConnected } = useSnapshot(AccountController.state); const { caipNetwork, requestedCaipNetworks, approvedCaipNetworkIds, supportsAllNetworks } = - useSnapshot(NetworkController.state); + NetworkController.state; const imageHeaders = ApiController._getApiHeaders(); const { maxWidth: width, padding } = useCustomDimensions(); const numColumns = 4; @@ -34,6 +33,7 @@ export function NetworksView() { const itemGap = Math.abs( Math.trunc((usableWidth - numColumns * CardSelectWidth) / numColumns) / 2 ); + const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH'; const onHelpPress = () => { RouterController.push('WhatIsANetwork'); @@ -56,8 +56,8 @@ export function NetworksView() { } const onNetworkPress = async (network: CaipNetwork) => { - if (isConnected && caipNetwork?.id !== network.id) { - if (approvedCaipNetworkIds?.includes(network.id)) { + if (AccountController.state.isConnected && caipNetwork?.id !== network.id) { + if (approvedCaipNetworkIds?.includes(network.id) && !isAuthConnected) { await NetworkController.switchActiveNetwork(network); RouterUtil.navigateAfterNetworkSwitch(['ConnectingSiwe']); @@ -68,10 +68,10 @@ export function NetworksView() { network: network.id } }); - } else if (supportsAllNetworks) { + } else if (supportsAllNetworks || isAuthConnected) { RouterController.push('SwitchNetwork', { network }); } - } else if (!isConnected) { + } else if (!AccountController.state.isConnected) { NetworkController.setCaipNetwork(network); RouterController.push('Connect'); } @@ -89,6 +89,7 @@ export function NetworksView() { ]} > Your connected wallet may not support some of the networks available for this dApp - + What is a network? diff --git a/packages/scaffold/src/views/w3m-transactions-view/index.tsx b/packages/scaffold/src/views/w3m-transactions-view/index.tsx new file mode 100644 index 000000000..b2f662956 --- /dev/null +++ b/packages/scaffold/src/views/w3m-transactions-view/index.tsx @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; +import { AccountActivity } from '../../partials/w3m-account-activity'; + +export function TransactionsView() { + return ; +} + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: Spacing.l, + marginTop: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx b/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx index 50a52f1b2..4a6297f0d 100644 --- a/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx +++ b/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx @@ -1,4 +1,3 @@ -import { useSnapshot } from 'valtio'; import { useState } from 'react'; import { @@ -13,7 +12,7 @@ import { import { OtpCodeView } from '../../partials/w3m-otp-code'; export function UpdateEmailPrimaryOtpView() { - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const authProvider = ConnectorController.getAuthConnector()?.provider as diff --git a/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx index 42590602c..3d8dbeb43 100644 --- a/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx @@ -1,5 +1,4 @@ import { useState } from 'react'; -import { useSnapshot } from 'valtio'; import { Platform } from 'react-native'; import { ConnectorController, @@ -15,7 +14,7 @@ import { useKeyboard } from '../../hooks/useKeyboard'; import styles from './styles'; export function UpdateEmailWalletView() { - const { data } = useSnapshot(RouterController.state); + const { data } = RouterController.state; const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [email, setEmail] = useState(data?.email || ''); diff --git a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx index be8f4fa6b..6b66cf5f2 100644 --- a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { StyleSheet } from 'react-native'; +import { Linking, StyleSheet } from 'react-native'; import { Chip, FlexView, Spacing, Text } from '@reown/appkit-ui-react-native'; import { ConnectorController, type AppKitFrameProvider } from '@reown/appkit-core-react-native'; @@ -7,15 +7,22 @@ export function UpgradeEmailWalletView() { const { connectors } = useSnapshot(ConnectorController.state); const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; + const onLinkPress = () => { + const link = authProvider.getSecureSiteDashboardURL(); + Linking.canOpenURL(link).then(supported => { + if (supported) Linking.openURL(link); + }); + }; + return ( Follow the instructions on You will have to reconnect for security reasons diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx new file mode 100644 index 000000000..ce042b10f --- /dev/null +++ b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx @@ -0,0 +1,106 @@ +import { Linking } from 'react-native'; +import { useEffect, useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { Button, FlexView, IconLink, Link, Text, Visual } from '@reown/appkit-ui-react-native'; +import { + AccountController, + ConnectorController, + EventsController, + ModalController, + NetworkController, + RouterController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import styles from './styles'; + +export function UpgradeToSmartAccountView() { + const { address } = useSnapshot(AccountController.state); + const { loading } = useSnapshot(ModalController.state); + const [initialAddress] = useState(address); + + const onSwitchAccountType = async () => { + try { + ModalController.setLoading(true); + const accountType = + AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + await provider?.setPreferredAccount(accountType); + EventsController.sendEvent({ + type: 'track', + event: 'SET_PREFERRED_ACCOUNT_TYPE', + properties: { + accountType, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + } catch (error) { + ModalController.setLoading(false); + SnackController.showError('Error switching account type'); + } + }; + + const onClose = () => { + ModalController.close(); + ModalController.setLoading(false); + }; + + const onGoBack = () => { + RouterController.goBack(); + ModalController.setLoading(false); + }; + + const onLearnMorePress = () => { + Linking.openURL('https://reown.com/faq'); + }; + + useEffect(() => { + // Go back if the address has changed + if (address && initialAddress !== address) { + RouterController.goBack(); + } + }, [initialAddress, address]); + + return ( + <> + + + + + + + + + + Discover Smart Accounts + + + Access advanced brand new features as username, improved security and a smoother user + experience! + + + + + + + Learn more + + + + ); +} diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts new file mode 100644 index 000000000..f2d23ef07 --- /dev/null +++ b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts @@ -0,0 +1,29 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + }, + title: { + marginTop: Spacing.xl, + marginVertical: Spacing.s + }, + button: { + width: 110 + }, + cancelButton: { + marginRight: Spacing.m + }, + middleIcon: { + marginHorizontal: Spacing.s + }, + closeButton: { + alignSelf: 'flex-end', + right: Spacing.xl, + top: Spacing.l, + position: 'absolute', + zIndex: 2 + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx new file mode 100644 index 000000000..3695bc470 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -0,0 +1,48 @@ +import { ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { FlexView, Text, Banner, NetworkImage } from '@reown/appkit-ui-react-native'; +import { + AccountController, + ApiController, + AssetUtil, + NetworkController +} from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function WalletCompatibleNetworks() { + const { padding } = useCustomDimensions(); + const { preferredAccountType } = useSnapshot(AccountController.state); + const isSmartAccount = + preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); + const approvedNetworks = isSmartAccount + ? NetworkController.getSmartAccountEnabledNetworks() + : NetworkController.getApprovedCaipNetworks(); + const imageHeaders = ApiController._getApiHeaders(); + + return ( + + + + {approvedNetworks.map((network, index) => ( + + + + {network?.name ?? 'Unknown Network'} + + + ))} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts new file mode 100644 index 000000000..de669ca6f --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts @@ -0,0 +1,8 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + image: { + marginRight: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx new file mode 100644 index 000000000..ae41a5175 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -0,0 +1,94 @@ +import { useSnapshot } from 'valtio'; +import { ScrollView, StyleSheet } from 'react-native'; +import { + Chip, + CompatibleNetwork, + FlexView, + QrCode, + Spacing, + Text, + UiUtil +} from '@reown/appkit-ui-react-native'; +import { + AccountController, + ApiController, + AssetUtil, + NetworkController, + OptionsController, + RouterController, + SnackController +} from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; + +export function WalletReceiveView() { + const { address, profileName, preferredAccountType } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { padding } = useCustomDimensions(); + const canCopy = OptionsController.isClipboardAvailable(); + const isSmartAccount = + preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); + const networks = isSmartAccount + ? NetworkController.getSmartAccountEnabledNetworks() + : NetworkController.getApprovedCaipNetworks(); + + const imagesArray = networks + .filter(network => network?.imageId) + .slice(0, 5) + .map(AssetUtil.getNetworkImage) + .filter(Boolean) as string[]; + + const label = UiUtil.getTruncateString({ + string: profileName ?? address ?? '', + charsStart: profileName ? 30 : 4, + charsEnd: profileName ? 0 : 4, + truncate: profileName ? 'end' : 'middle' + }); + + const onNetworkPress = () => { + RouterController.push('WalletCompatibleNetworks'); + }; + + const onCopyAddress = () => { + if (canCopy && AccountController.state.address) { + OptionsController.copyToClipboard(AccountController.state.address); + SnackController.showSuccess('Address copied'); + } + }; + + if (!address) return; + + return ( + + + + + + {canCopy ? 'Copy your address or scan this QR code' : 'Scan this QR code'} + + + + + ); +} + +const styles = StyleSheet.create({ + qrContainer: { + marginVertical: Spacing.xl + }, + networksButton: { + marginTop: Spacing.l + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts new file mode 100644 index 000000000..c866ab3d8 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts @@ -0,0 +1,8 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx new file mode 100644 index 000000000..d71df1740 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx @@ -0,0 +1,101 @@ +import { AssetUtil, type CaipNetwork } from '@reown/appkit-core-react-native'; +import { + BorderRadius, + FlexView, + NetworkImage, + Spacing, + Text, + UiUtil, + useTheme +} from '@reown/appkit-ui-react-native'; +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; + +export interface PreviewSendDetailsProps { + address?: string; + name?: string; + caipNetwork?: CaipNetwork; + networkFee?: number; + style?: StyleProp; +} + +export function PreviewSendDetails({ + address, + name, + caipNetwork, + networkFee, + style +}: PreviewSendDetailsProps) { + const Theme = useTheme(); + + const formattedName = UiUtil.getTruncateString({ + string: name ?? '', + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }); + + const formattedAddress = UiUtil.getTruncateString({ + string: address || '', + charsStart: 6, + charsEnd: 8, + truncate: 'middle' + }); + + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + return ( + + + Details + + + + Network cost + + + ${UiUtil.formatNumberToLocalString(networkFee, 2)} + + + + + {formattedName || 'Address'} + + + {formattedAddress} + + + + + Network + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + borderRadius: BorderRadius.xxs + }, + title: { + marginBottom: Spacing.xs + }, + item: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: Spacing.s, + borderRadius: BorderRadius.xxs, + marginTop: Spacing['2xs'] + }, + networkImage: { + height: 24, + width: 24, + borderRadius: BorderRadius.full + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx new file mode 100644 index 000000000..ea085ecd2 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx @@ -0,0 +1,36 @@ +import { BorderRadius, FlexView, Text, useTheme } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export interface PreviewSendPillProps { + text: string; + children: React.ReactNode; +} + +export function PreviewSendPill({ text, children }: PreviewSendPillProps) { + const Theme = useTheme(); + + return ( + + + {text} + + {children} + + ); +} + +const styles = StyleSheet.create({ + pill: { + borderRadius: BorderRadius.full, + borderWidth: StyleSheet.hairlineWidth + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx new file mode 100644 index 000000000..8b9e7f41a --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx @@ -0,0 +1,134 @@ +import { useSnapshot } from 'valtio'; +import { ScrollView } from 'react-native'; +import { Avatar, Button, FlexView, Icon, Image, Text, UiUtil } from '@reown/appkit-ui-react-native'; +import { NumberUtil } from '@reown/appkit-common-react-native'; +import { + NetworkController, + RouterController, + SendController +} from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { PreviewSendPill } from './components/preview-send-pill'; +import styles from './styles'; +import { PreviewSendDetails } from './components/preview-send-details'; + +export function WalletSendPreviewView() { + const { padding } = useCustomDimensions(); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { + token, + receiverAddress, + receiverProfileName, + receiverProfileImageUrl, + gasPriceInUSD, + loading + } = useSnapshot(SendController.state); + + const getSendValue = () => { + if (SendController.state.token && SendController.state.sendTokenAmount) { + const price = SendController.state.token.price; + const totalValue = price * SendController.state.sendTokenAmount; + + return totalValue.toFixed(2); + } + + return null; + }; + + const getTokenAmount = () => { + const value = SendController.state.sendTokenAmount + ? NumberUtil.roundNumber(SendController.state.sendTokenAmount, 6, 5) + : 'unknown'; + + return `${value} ${SendController.state.token?.symbol}`; + }; + + const formattedAddress = receiverProfileName + ? UiUtil.getTruncateString({ + string: receiverProfileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: receiverAddress || '', + charsStart: 4, + charsEnd: 4, + truncate: 'middle' + }); + + const onSend = () => { + SendController.sendToken(); + }; + + const onCancel = () => { + RouterController.goBack(); + SendController.setLoading(false); + }; + + return ( + + + + + + Send + + + ${getSendValue()} + + + + {token?.iconUrl ? ( + + ) : ( + + )} + + + + + + To + + + + + + + + + + Review transaction carefully + + + + + + + + + ); +} diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts new file mode 100644 index 000000000..432a72c36 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts @@ -0,0 +1,35 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + }, + tokenLogo: { + height: 32, + width: 32, + borderRadius: BorderRadius.full, + marginLeft: Spacing.xs + }, + arrow: { + marginVertical: Spacing.xs + }, + avatar: { + marginLeft: Spacing.xs + }, + details: { + marginTop: Spacing['2xl'], + marginBottom: Spacing.s + }, + reviewIcon: { + marginRight: Spacing['3xs'] + }, + cancelButton: { + flex: 1 + }, + sendButton: { + marginLeft: Spacing.s, + flex: 3 + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx new file mode 100644 index 000000000..aac2d89ab --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { ScrollView } from 'react-native'; +import { FlexView, InputText, ListToken, Text } from '@reown/appkit-ui-react-native'; +import { + AccountController, + AssetUtil, + NetworkController, + RouterController, + SendController +} from '@reown/appkit-core-react-native'; +import type { Balance } from '@reown/appkit-common-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { Placeholder } from '../../partials/w3m-placeholder'; +import styles from './styles'; + +export function WalletSendSelectTokenView() { + const { padding } = useCustomDimensions(); + const { tokenBalance } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const [tokenSearch, setTokenSearch] = useState(''); + const [filteredTokens, setFilteredTokens] = useState(tokenBalance ?? []); + + const onSearchChange = (value: string) => { + setTokenSearch(value); + const filtered = AccountController.state.tokenBalance?.filter(token => + token.name.toLowerCase().includes(value.toLowerCase()) + ); + setFilteredTokens(filtered ?? []); + }; + + const onTokenPress = (token: Balance) => { + SendController.setToken(token); + SendController.setTokenAmount(undefined); + RouterController.goBack(); + }; + + return ( + + + + + + + Your tokens + + {filteredTokens.length ? ( + filteredTokens.map((token, index) => ( + onTokenPress(token)} + /> + )) + ) : ( + + )} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts new file mode 100644 index 000000000..23c2e7c51 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + minHeight: 250, + maxHeight: 600 + }, + title: { + marginBottom: Spacing.xs + }, + tokenList: { + paddingHorizontal: Spacing.m + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx new file mode 100644 index 000000000..31d3f8509 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -0,0 +1,142 @@ +import { useCallback, useEffect } from 'react'; +import { Platform, ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { + AccountController, + CoreHelperUtil, + RouterController, + SendController, + SwapController +} from '@reown/appkit-core-react-native'; +import { + Button, + FlexView, + IconBox, + LoadingSpinner, + Spacing, + Text +} from '@reown/appkit-ui-react-native'; +import { InputToken } from '../../partials/w3m-input-token/intex'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { useKeyboard } from '../../hooks/useKeyboard'; +import { InputAddress } from '../../partials/w3m-input-address'; +import styles from './styles'; + +export function WalletSendView() { + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const { token, sendTokenAmount, receiverAddress, receiverProfileName, loading, gasPrice } = + useSnapshot(SendController.state); + const { tokenBalance } = useSnapshot(AccountController.state); + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], + default: Spacing['2xl'] + }); + + const fetchNetworkPrice = useCallback(async () => { + await SwapController.getNetworkTokenPrice(); + const gas = await SwapController.getInitialGasPrice(); + if (gas?.gasPrice && gas?.gasPriceInUSD) { + SendController.setGasPrice(gas.gasPrice); + SendController.setGasPriceInUsd(gas.gasPriceInUSD); + } + }, []); + + const onSendPress = () => { + RouterController.push('WalletSendPreview'); + }; + + const getActionText = () => { + if (!SendController.state.token) { + return 'Select token'; + } + + if ( + SendController.state.sendTokenAmount && + SendController.state.token && + SendController.state.sendTokenAmount > Number(SendController.state.token.quantity.numeric) + ) { + return 'Insufficient funds'; + } + + if (!SendController.state.sendTokenAmount) { + return 'Add amount'; + } + + if (SendController.state.sendTokenAmount && SendController.state.token?.price) { + const value = SendController.state.sendTokenAmount * SendController.state.token.price; + if (!value) { + return 'Incorrect value'; + } + } + + if ( + SendController.state.receiverAddress && + !CoreHelperUtil.isAddress(SendController.state.receiverAddress) + ) { + return 'Invalid address'; + } + + if (!SendController.state.receiverAddress) { + return 'Add address'; + } + + return 'Preview send'; + }; + + useEffect(() => { + if (!token) { + SendController.setToken(tokenBalance?.[0]); + } + fetchNetworkPrice(); + }, [token, tokenBalance, fetchNetworkPrice]); + + const actionText = getActionText(); + const disabled = actionText !== 'Preview send'; + + return ( + + + RouterController.push('WalletSendSelectToken')} + /> + + + + + + + + ); +} diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts new file mode 100644 index 000000000..7e52d3f4f --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts @@ -0,0 +1,21 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + sendButton: { + width: '100%', + marginTop: Spacing.xl, + borderRadius: BorderRadius.xs + }, + tokenInput: { + marginBottom: Spacing.xs + }, + arrowIcon: { + position: 'absolute', + top: -30, + borderRadius: 20 + }, + addressContainer: { + width: '100%' + } +}); diff --git a/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx b/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx index b201449f8..cb8cca52a 100644 --- a/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx +++ b/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx @@ -3,7 +3,7 @@ import { Button, FlexView, Text, Visual } from '@reown/appkit-ui-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; -export function WhatIsNetworkView() { +export function WhatIsANetworkView() { const { padding } = useCustomDimensions(); const onLearnMorePress = () => { Linking.openURL('https://ethereum.org/en/developers/docs/networks/'); diff --git a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx index 92100124a..17cdcc556 100644 --- a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx @@ -1,5 +1,5 @@ -import { Linking, ScrollView } from 'react-native'; -import { Button, FlexView, Link, Text, Visual } from '@reown/appkit-ui-react-native'; +import { ScrollView } from 'react-native'; +import { Button, FlexView, Text, Visual } from '@reown/appkit-ui-react-native'; import { EventsController, RouterController } from '@reown/appkit-core-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; @@ -12,13 +12,14 @@ export function WhatIsAWalletView() { EventsController.sendEvent({ type: 'track', event: 'CLICK_GET_WALLET' }); }; - const onHelpPress = () => { - Linking.openURL('https://secure.walletconnect.com/dashboard/faq'); - }; - return ( - - + + @@ -57,12 +58,10 @@ export function WhatIsAWalletView() { iconLeft="walletSmall" style={styles.getWalletButton} onPress={onGetWalletPress} + testID="get-a-wallet-button" > Get a wallet - - What is an email wallet? - ); diff --git a/packages/siwe/package.json b/packages/siwe/package.json index fc890f777..41bf4f970 100644 --- a/packages/siwe/package.json +++ b/packages/siwe/package.json @@ -27,12 +27,12 @@ "react-native", "siwe" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/siwe/readme.md b/packages/siwe/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/siwe/readme.md +++ b/packages/siwe/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/index.tsx b/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/index.tsx index 27581c98f..f53f5fcff 100644 --- a/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/index.tsx +++ b/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/index.tsx @@ -25,7 +25,7 @@ export function ConnectingSiwe({ style }: Props) { const Theme = useTheme(); const { metadata } = useSnapshot(OptionsController.state); const { connectedWalletImageUrl, pressedWallet } = useSnapshot(ConnectionController.state); - const { address, profileName, profileImage } = useSnapshot(AccountController.state); + const { address, profileImage } = useSnapshot(AccountController.state); const dappIcon = metadata?.icons[0] || ''; const dappPosition = useAnimatedValue(10); const walletPosition = useAnimatedValue(-10); @@ -106,12 +106,7 @@ export function ConnectingSiwe({ style }: Props) { {walletIcon ? ( ) : ( - + )} diff --git a/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts b/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts index d7391e1d6..b7c00f053 100644 --- a/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts +++ b/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts @@ -1,10 +1,11 @@ +import { BorderRadius } from '@reown/appkit-ui-react-native'; import { StyleSheet } from 'react-native'; export default StyleSheet.create({ dappIcon: { height: 64, width: 64, - borderRadius: 100 + borderRadius: BorderRadius.full }, iconBorder: { width: 74, @@ -13,7 +14,7 @@ export default StyleSheet.create({ justifyContent: 'center' }, dappBorder: { - borderRadius: 100, + borderRadius: BorderRadius.full, zIndex: 2 }, walletBorder: { @@ -22,6 +23,6 @@ export default StyleSheet.create({ height: 72 }, walletAvatar: { - borderRadius: 100 + borderRadius: BorderRadius.full } }); diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx index b7955c678..e5a56f950 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx +++ b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx @@ -1,10 +1,11 @@ import { useSnapshot } from 'valtio'; -import { Button, FlexView, Text } from '@reown/appkit-ui-react-native'; +import { Button, FlexView, IconLink, Text } from '@reown/appkit-ui-react-native'; import { AccountController, ConnectionController, EventsController, ModalController, + NetworkController, OptionsController, RouterController, SnackController @@ -26,14 +27,22 @@ export function ConnectingSiweView() { setIsSigning(true); EventsController.sendEvent({ event: 'CLICK_SIGN_SIWE_MESSAGE', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); try { const session = await SIWEController.signIn(); EventsController.sendEvent({ event: 'SIWE_AUTH_SUCCESS', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); return session; @@ -44,7 +53,11 @@ export function ConnectingSiweView() { return EventsController.sendEvent({ event: 'SIWE_AUTH_ERROR', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); } finally { setIsSigning(false); @@ -63,12 +76,26 @@ export function ConnectingSiweView() { } EventsController.sendEvent({ event: 'CLICK_CANCEL_SIWE', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); }; return ( + + + Sign in + {dappName} needs to connect to your wallet diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts index 900ef0370..42d56456f 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts +++ b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts @@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ logoContainer: { + marginTop: Spacing.xl, marginBottom: Spacing.m }, button: { @@ -14,5 +15,12 @@ export default StyleSheet.create({ subtitle: { marginHorizontal: '10%', marginVertical: Spacing.l + }, + closeButton: { + alignSelf: 'flex-end', + right: Spacing.xl, + top: Spacing.l, + position: 'absolute', + zIndex: 2 } }); diff --git a/packages/ui/package.json b/packages/ui/package.json index 982e890c0..ef27a914d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -26,12 +26,12 @@ "walletconnect", "react-native" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/ui/readme.md b/packages/ui/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/ui/readme.md +++ b/packages/ui/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/ui/src/assets/svg/ArrowBottomCircle.tsx b/packages/ui/src/assets/svg/ArrowBottomCircle.tsx new file mode 100644 index 000000000..4f19838a7 --- /dev/null +++ b/packages/ui/src/assets/svg/ArrowBottomCircle.tsx @@ -0,0 +1,12 @@ +import Svg, { Path, type SvgProps } from 'react-native-svg'; +const SvgArrowBottomCircle = (props: SvgProps) => ( + + + +); +export default SvgArrowBottomCircle; diff --git a/packages/ui/src/assets/svg/Farcaster.tsx b/packages/ui/src/assets/svg/Farcaster.tsx new file mode 100644 index 000000000..323478cca --- /dev/null +++ b/packages/ui/src/assets/svg/Farcaster.tsx @@ -0,0 +1,15 @@ +import Svg, { Path, type SvgProps, Rect } from 'react-native-svg'; +const SvgFarcaster = (props: SvgProps) => ( + + + + + +); +export default SvgFarcaster; diff --git a/packages/ui/src/assets/svg/FarcasterSquare.tsx b/packages/ui/src/assets/svg/FarcasterSquare.tsx new file mode 100644 index 000000000..e6b7062b0 --- /dev/null +++ b/packages/ui/src/assets/svg/FarcasterSquare.tsx @@ -0,0 +1,15 @@ +import Svg, { Path, type SvgProps, Rect } from 'react-native-svg'; +const SvgFarcaster = (props: SvgProps) => ( + + + + + +); +export default SvgFarcaster; diff --git a/packages/ui/src/assets/svg/More.tsx b/packages/ui/src/assets/svg/More.tsx new file mode 100644 index 000000000..fc4cbbdb2 --- /dev/null +++ b/packages/ui/src/assets/svg/More.tsx @@ -0,0 +1,16 @@ +import Svg, { Circle, Path, type SvgProps } from 'react-native-svg'; +const SvgMore = (props: SvgProps) => ( + + + + + +); +export default SvgMore; diff --git a/packages/ui/src/assets/svg/Paperplane.tsx b/packages/ui/src/assets/svg/Paperplane.tsx new file mode 100644 index 000000000..131e45f28 --- /dev/null +++ b/packages/ui/src/assets/svg/Paperplane.tsx @@ -0,0 +1,12 @@ +import Svg, { Path, type SvgProps } from 'react-native-svg'; +const SvgPaperplane = (props: SvgProps) => ( + + + +); +export default SvgPaperplane; diff --git a/packages/ui/src/assets/svg/Twitter.tsx b/packages/ui/src/assets/svg/Twitter.tsx deleted file mode 100644 index d54ecdd1d..000000000 --- a/packages/ui/src/assets/svg/Twitter.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import Svg, { G, Circle, Path, Defs, ClipPath, type SvgProps } from 'react-native-svg'; -const SvgTwitter = (props: SvgProps) => ( - - - - - - - - - - - - - - - - - -); -export default SvgTwitter; diff --git a/packages/ui/src/assets/svg/TwitterIcon.tsx b/packages/ui/src/assets/svg/TwitterIcon.tsx deleted file mode 100644 index 7762aa805..000000000 --- a/packages/ui/src/assets/svg/TwitterIcon.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import Svg, { Path, type SvgProps } from 'react-native-svg'; -const SvgTwitter = (props: SvgProps) => ( - - - -); -export default SvgTwitter; diff --git a/packages/ui/src/assets/svg/WalletConnect.tsx b/packages/ui/src/assets/svg/WalletConnect.tsx index f86565ba5..5989a02ae 100644 --- a/packages/ui/src/assets/svg/WalletConnect.tsx +++ b/packages/ui/src/assets/svg/WalletConnect.tsx @@ -1,4 +1,4 @@ -import Svg, { Path, type SvgProps } from 'react-native-svg'; +import Svg, { ClipPath, Defs, G, Path, type SvgProps } from 'react-native-svg'; const SvgWalletConnect = (props: SvgProps) => ( ( ); export default SvgWalletConnect; + +export const WalletConnectLightBrownSvg = (props: SvgProps) => ( + + + + + + + + + + + +); diff --git a/packages/ui/src/assets/svg/X.tsx b/packages/ui/src/assets/svg/X.tsx new file mode 100644 index 000000000..3abb020ea --- /dev/null +++ b/packages/ui/src/assets/svg/X.tsx @@ -0,0 +1,26 @@ +import Svg, { G, Path, Defs, ClipPath, type SvgProps, Rect } from 'react-native-svg'; +const SvgX = (props: SvgProps) => ( + + + + + + + + + + + + +); +export default SvgX; diff --git a/packages/ui/src/assets/visual/Google.tsx b/packages/ui/src/assets/visual/Google.tsx new file mode 100644 index 000000000..486a89013 --- /dev/null +++ b/packages/ui/src/assets/visual/Google.tsx @@ -0,0 +1,43 @@ +import Svg, { Path, Rect, type SvgProps } from 'react-native-svg'; +const GoogleSvg = (props: SvgProps) => ( + + + + + + + + + + +); + +export default GoogleSvg; diff --git a/packages/ui/src/assets/visual/Lightbulb.tsx b/packages/ui/src/assets/visual/Lightbulb.tsx new file mode 100644 index 000000000..545d436be --- /dev/null +++ b/packages/ui/src/assets/visual/Lightbulb.tsx @@ -0,0 +1,54 @@ +import Svg, { ClipPath, Defs, G, Path, Rect, type SvgProps } from 'react-native-svg'; +const LightbulbSvg = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + +); + +export default LightbulbSvg; diff --git a/packages/ui/src/assets/visual/Pencil.tsx b/packages/ui/src/assets/visual/Pencil.tsx new file mode 100644 index 000000000..08d189244 --- /dev/null +++ b/packages/ui/src/assets/visual/Pencil.tsx @@ -0,0 +1,80 @@ +import Svg, { ClipPath, Defs, G, Path, Rect, type SvgProps } from 'react-native-svg'; + +const PencilSvg = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + +); + +export default PencilSvg; diff --git a/packages/ui/src/assets/visual/Profile.tsx b/packages/ui/src/assets/visual/Profile.tsx index 3c0d3ced9..764583136 100644 --- a/packages/ui/src/assets/visual/Profile.tsx +++ b/packages/ui/src/assets/visual/Profile.tsx @@ -13,8 +13,8 @@ const SvgProfile = (props: SvgProps) => ( diff --git a/packages/ui/src/components/wui-card/index.tsx b/packages/ui/src/components/wui-card/index.tsx index a354f5dbd..0d0eefb39 100644 --- a/packages/ui/src/components/wui-card/index.tsx +++ b/packages/ui/src/components/wui-card/index.tsx @@ -18,7 +18,7 @@ export function Card({ children, style }: CardProps) { enabled={Platform.OS === 'ios'} style={[ styles.container, - { backgroundColor: Theme['bg-100'], borderColor: Theme['gray-glass-005'] }, + { backgroundColor: Theme['bg-100'], borderColor: Theme['gray-glass-015'] }, style ]} > diff --git a/packages/ui/src/components/wui-card/styles.ts b/packages/ui/src/components/wui-card/styles.ts index b7f5974f8..86e49e888 100644 --- a/packages/ui/src/components/wui-card/styles.ts +++ b/packages/ui/src/components/wui-card/styles.ts @@ -4,7 +4,7 @@ import { BorderRadius } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { borderRadius: BorderRadius.l, - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, overflow: 'hidden' } }); diff --git a/packages/ui/src/components/wui-icon/index.tsx b/packages/ui/src/components/wui-icon/index.tsx index d34d7c9e7..e939f6189 100644 --- a/packages/ui/src/components/wui-icon/index.tsx +++ b/packages/ui/src/components/wui-icon/index.tsx @@ -5,6 +5,7 @@ import type { ColorType, IconType, SizeType, ThemeKeys } from '../../utils/Types import AllWalletsSvg from '../../assets/svg/AllWallets'; import AppleSvg from '../../assets/svg/Apple'; import ArrowBottomSvg from '../../assets/svg/ArrowBottom'; +import ArrowBottomCircleSvg from '../../assets/svg/ArrowBottomCircle'; import ArrowLeftSvg from '../../assets/svg/ArrowLeft'; import ArrowRightSvg from '../../assets/svg/ArrowRight'; import ArrowTopSvg from '../../assets/svg/ArrowTop'; @@ -29,6 +30,8 @@ import EtherscanSvg from '../../assets/svg/Etherscan'; import ExtensionSvg from '../../assets/svg/Extension'; import ExternalLinkSvg from '../../assets/svg/ExternalLink'; import FacebookSvg from '../../assets/svg/Facebook'; +import FarcasterSvg from '../../assets/svg/Farcaster'; +import FarcasterSquareSvg from '../../assets/svg/FarcasterSquare'; import FiltersSvg from '../../assets/svg/Filters'; import GithubSvg from '../../assets/svg/Github'; import GoogleSvg from '../../assets/svg/Google'; @@ -36,24 +39,25 @@ import HelpCircleSvg from '../../assets/svg/HelpCircle'; import InfoCircleSvg from '../../assets/svg/InfoCircle'; import MailSvg from '../../assets/svg/Mail'; import MobileSvg from '../../assets/svg/Mobile'; +import MoreSvg from '../../assets/svg/More'; import NetworkPlaceholderSvg from '../../assets/svg/NetworkPlaceholder'; import NftPlaceholderSvg from '../../assets/svg/NftPlaceholder'; import OffSvg from '../../assets/svg/Off'; +import PaperplaneSvg from '../../assets/svg/Paperplane'; import QrCodeSvg from '../../assets/svg/QrCode'; import RefreshSvg from '../../assets/svg/Refresh'; import SearchSvg from '../../assets/svg/Search'; import SwapHorizontalSvg from '../../assets/svg/SwapHorizontal'; import SwapVerticalSvg from '../../assets/svg/SwapVertical'; import TelegramSvg from '../../assets/svg/Telegram'; -import TwitterSvg from '../../assets/svg/Twitter'; +import TwitchSvg from '../../assets/svg/Twitch'; import VerifySvg from '../../assets/svg/Verify'; -import WalletConnectSvg from '../../assets/svg/WalletConnect'; +import WalletConnectSvg, { WalletConnectLightBrownSvg } from '../../assets/svg/WalletConnect'; import WalletSvg from '../../assets/svg/Wallet'; import WalletSmallSvg from '../../assets/svg/WalletSmall'; import WarningCircleSvg from '../../assets/svg/WarningCircle'; -import TwitchSvg from '../../assets/svg/Twitch'; -import TwitterIconSvg from '../../assets/svg/TwitterIcon'; import WalletPlaceholderSvg from '../../assets/svg/WalletPlaceholder'; +import XSvg from '../../assets/svg/X'; import { useTheme } from '../../hooks/useTheme'; import { IconSize } from '../../utils/ThemeUtil'; @@ -61,6 +65,7 @@ const svgOptions: Record JSX.Element> = { allWallets: AllWalletsSvg, apple: AppleSvg, arrowBottom: ArrowBottomSvg, + arrowBottomCircle: ArrowBottomCircleSvg, arrowLeft: ArrowLeftSvg, arrowRight: ArrowRightSvg, arrowTop: ArrowTopSvg, @@ -85,6 +90,8 @@ const svgOptions: Record JSX.Element> = { extension: ExtensionSvg, externalLink: ExternalLinkSvg, facebook: FacebookSvg, + farcaster: FarcasterSvg, + farcasterSquare: FarcasterSquareSvg, filters: FiltersSvg, github: GithubSvg, google: GoogleSvg, @@ -92,9 +99,11 @@ const svgOptions: Record JSX.Element> = { infoCircle: InfoCircleSvg, mail: MailSvg, mobile: MobileSvg, + more: MoreSvg, networkPlaceholder: NetworkPlaceholderSvg, nftPlaceholder: NftPlaceholderSvg, off: OffSvg, + paperplane: PaperplaneSvg, qrCode: QrCodeSvg, refresh: RefreshSvg, search: SearchSvg, @@ -102,19 +111,19 @@ const svgOptions: Record JSX.Element> = { swapVertical: SwapVerticalSvg, telegram: TelegramSvg, twitch: TwitchSvg, - twitter: TwitterSvg, - twitterIcon: TwitterIconSvg, verify: VerifySvg, wallet: WalletSvg, walletSmall: WalletSmallSvg, warningCircle: WarningCircleSvg, walletConnect: WalletConnectSvg, - walletPlaceholder: WalletPlaceholderSvg + walletConnectLightBrown: WalletConnectLightBrownSvg, + walletPlaceholder: WalletPlaceholderSvg, + x: XSvg }; export type IconProps = SvgProps & { name: IconType; - size?: Exclude; + size?: SizeType; color?: ColorType; style?: SvgProps['style']; }; diff --git a/packages/ui/src/components/wui-visual/index.tsx b/packages/ui/src/components/wui-visual/index.tsx index c6b849cb0..99830e391 100644 --- a/packages/ui/src/components/wui-visual/index.tsx +++ b/packages/ui/src/components/wui-visual/index.tsx @@ -14,6 +14,9 @@ import NounSvg from '../../assets/visual/Noun'; import ProfileSvg from '../../assets/visual/Profile'; import SystemSvg from '../../assets/visual/System'; import type { VisualType } from '../../utils/TypesUtil'; +import GoogleSvg from '../../assets/visual/Google'; +import LightbulbSvg from '../../assets/visual/Lightbulb'; +import PencilSvg from '../../assets/visual/Pencil'; const svgOptions: Record JSX.Element> = { browser: BrowserSvg, @@ -21,12 +24,15 @@ const svgOptions: Record JSX.Element> = { defi: DefiSvg, defiAlt: DefiAltSvg, eth: EthSvg, + google: GoogleSvg, layers: LayersSvg, + lightbulb: LightbulbSvg, lock: LockSvg, login: LoginSvg, network: NetworkSvg, nft: NftSvg, noun: NounSvg, + pencil: PencilSvg, profile: ProfileSvg, system: SystemSvg }; diff --git a/packages/ui/src/composites/wui-account-button/index.tsx b/packages/ui/src/composites/wui-account-button/index.tsx index f77410a2b..ccf9e4b99 100644 --- a/packages/ui/src/composites/wui-account-button/index.tsx +++ b/packages/ui/src/composites/wui-account-button/index.tsx @@ -15,7 +15,7 @@ export interface AccountButtonProps { imageHeaders?: Record; avatarSrc?: string; address?: string; - isProfileName?: boolean; + profileName?: string; balance?: string; onPress?: () => void; disabled?: boolean; @@ -28,7 +28,7 @@ export function AccountButton({ imageHeaders, avatarSrc, address, - isProfileName, + profileName, balance, onPress, disabled, @@ -73,6 +73,20 @@ export function AccountButton({ return null; } + const formattedAddress = profileName + ? UiUtil.getTruncateString({ + string: profileName, + charsStart: 18, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: address || '', + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + }); + return ( {address && ( - {UiUtil.getTruncateString({ - string: address, - charsStart: isProfileName ? 18 : 4, - charsEnd: isProfileName ? 0 : 6, - truncate: isProfileName ? 'end' : 'middle' - })} + {formattedAddress} )} diff --git a/packages/ui/src/composites/wui-account-button/styles.ts b/packages/ui/src/composites/wui-account-button/styles.ts index 61f2f0cf1..2e1441204 100644 --- a/packages/ui/src/composites/wui-account-button/styles.ts +++ b/packages/ui/src/composites/wui-account-button/styles.ts @@ -1,12 +1,12 @@ import { StyleSheet } from 'react-native'; -import { Spacing } from '../../utils/ThemeUtil'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { flexDirection: 'row', height: 40, - borderRadius: 100, - borderWidth: 1, + borderRadius: BorderRadius.full, + borderWidth: StyleSheet.hairlineWidth, justifyContent: 'center', alignItems: 'center', paddingHorizontal: Spacing['3xs'] @@ -14,7 +14,7 @@ export default StyleSheet.create({ image: { height: 24, width: 24, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 2 }, avatarPlaceholder: { @@ -35,8 +35,8 @@ export default StyleSheet.create({ alignItems: 'center', paddingLeft: Spacing['3xs'], paddingRight: Spacing.xs, - borderRadius: 100, - borderWidth: 1 + borderRadius: BorderRadius.full, + borderWidth: StyleSheet.hairlineWidth }, address: { marginLeft: Spacing['3xs'] diff --git a/packages/ui/src/composites/wui-account-pill/index.tsx b/packages/ui/src/composites/wui-account-pill/index.tsx new file mode 100644 index 000000000..208b80cc8 --- /dev/null +++ b/packages/ui/src/composites/wui-account-pill/index.tsx @@ -0,0 +1,64 @@ +import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; +import { Avatar } from '../wui-avatar'; +import { UiUtil } from '../../utils/UiUtil'; +import { Text } from '../../components/wui-text'; +import useAnimatedValue from '../../hooks/useAnimatedValue'; +import { useTheme } from '../../hooks/useTheme'; +import styles from './styles'; +import { Icon } from '../../components/wui-icon'; + +export interface AccountPillProps { + onPress: () => void; + address?: string; + profileName?: string; + profileImage?: string; + style?: StyleProp; +} + +const AnimatedPressable = Animated.createAnimatedComponent(Pressable); + +export function AccountPill({ + onPress, + address, + profileName, + profileImage, + style +}: AccountPillProps) { + const Theme = useTheme(); + + const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( + Theme['gray-glass-005'], + Theme['gray-glass-010'] + ); + + const backgroundColor = animatedValue; + const borderColor = Theme['gray-glass-005']; + + return ( + + + + {profileName + ? UiUtil.getTruncateString({ + string: profileName, + charsStart: 17, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: address ?? '', + charsStart: 4, + charsEnd: 4, + truncate: 'middle' + })} + + + + ); +} diff --git a/packages/ui/src/composites/wui-account-pill/styles.ts b/packages/ui/src/composites/wui-account-pill/styles.ts new file mode 100644 index 000000000..4bb2e5f1d --- /dev/null +++ b/packages/ui/src/composites/wui-account-pill/styles.ts @@ -0,0 +1,23 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + height: 44, + minWidth: 160, + maxWidth: 260, + paddingLeft: Spacing.xs, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderRadius: BorderRadius.full, + borderWidth: StyleSheet.hairlineWidth + }, + text: { + marginLeft: Spacing['2xs'] + }, + copyButton: { + marginLeft: Spacing.xs, + marginRight: Spacing.s + } +}); diff --git a/packages/ui/src/composites/wui-avatar/styles.ts b/packages/ui/src/composites/wui-avatar/styles.ts index b16828ba5..d3da925f9 100644 --- a/packages/ui/src/composites/wui-avatar/styles.ts +++ b/packages/ui/src/composites/wui-avatar/styles.ts @@ -1,7 +1,8 @@ import { StyleSheet } from 'react-native'; +import { BorderRadius } from '../../utils/ThemeUtil'; export default StyleSheet.create({ image: { - borderRadius: 100 + borderRadius: BorderRadius.full } }); diff --git a/packages/ui/src/composites/wui-balance/index.tsx b/packages/ui/src/composites/wui-balance/index.tsx new file mode 100644 index 000000000..1f4a9a5ef --- /dev/null +++ b/packages/ui/src/composites/wui-balance/index.tsx @@ -0,0 +1,25 @@ +import { StyleSheet } from 'react-native'; +import { Text } from '../../components/wui-text'; + +export interface BalanceProps { + integer?: string; + decimal?: string; +} + +export function Balance({ integer = '0', decimal = '00' }: BalanceProps) { + return ( + + {`$${integer}`} + + {`.${decimal}`} + + + ); +} + +const styles = StyleSheet.create({ + text: { + fontSize: 40, + fontWeight: '500' + } +}); diff --git a/packages/ui/src/composites/wui-banner/index.tsx b/packages/ui/src/composites/wui-banner/index.tsx new file mode 100644 index 000000000..dfeb02bd3 --- /dev/null +++ b/packages/ui/src/composites/wui-banner/index.tsx @@ -0,0 +1,28 @@ +import type { IconType } from '../../utils/TypesUtil'; +import { FlexView } from '../../layout/wui-flex'; +import { IconBox } from '../wui-icon-box'; +import { Text } from '../../components/wui-text'; +import { useTheme } from '../../hooks/useTheme'; +import styles from './styles'; + +export interface BannerProps { + icon: IconType; + text: string; +} + +export function Banner({ icon, text }: BannerProps) { + const Theme = useTheme(); + + return ( + + + + {text} + + + ); +} diff --git a/packages/ui/src/composites/wui-banner/styles.ts b/packages/ui/src/composites/wui-banner/styles.ts new file mode 100644 index 000000000..9504601ba --- /dev/null +++ b/packages/ui/src/composites/wui-banner/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + padding: Spacing.s, + borderRadius: BorderRadius.s + }, + icon: { + marginRight: Spacing.xs + }, + text: { + flex: 1 + } +}); diff --git a/packages/ui/src/composites/wui-button/index.tsx b/packages/ui/src/composites/wui-button/index.tsx index 46645c167..697c05ca2 100644 --- a/packages/ui/src/composites/wui-button/index.tsx +++ b/packages/ui/src/composites/wui-button/index.tsx @@ -95,16 +95,18 @@ export function Button({ style={[styles.iconLeft, iconStyle]} /> )} - {loading ? ( - - ) : ( - - {children} - - )} + {loading && } + {!loading && + (typeof children === 'string' ? ( + + {children} + + ) : ( + children + ))} {iconRight && ( void; style?: StyleProp; + testID?: string; } function _CardSelect({ @@ -36,7 +37,8 @@ function _CardSelect({ disabled, installed, selected, - style + style, + testID }: CardSelectProps) { const Theme = useTheme(); const normalbackgroundColor = getBackgroundColor({ selected, disabled, pressed: false }); @@ -77,6 +79,7 @@ function _CardSelect({ onPressOut={setStartValue} disabled={disabled} style={[styles.container, { backgroundColor: animatedValue }, style]} + testID={testID} > ; disabled?: boolean; style?: StyleProp; + onPress?: () => void; } export function Chip({ - link, + onPress, imageSrc, - icon, + leftIcon, + rightIcon, variant = 'fill', size = 'md', disabled, @@ -38,10 +40,8 @@ export function Chip({ const themedTextColor = getThemedTextColor(variant, disabled, pressed); const iconSize = size === 'md' ? 'sm' : 'xs'; - const onPress = () => { - Linking.canOpenURL(link).then(supported => { - if (supported) Linking.openURL(link); - }); + const handlePress = () => { + onPress?.(); }; const onPressIn = () => { @@ -78,7 +78,7 @@ export function Chip({ style={[styles.container, styles[`${size}Chip`], { borderColor, backgroundColor }, style]} onPressIn={onPressIn} onPressOut={onPressOut} - onPress={onPress} + onPress={handlePress} > {imageSrc && ( )} + {leftIcon && } - {label || link} + {label} - {icon && ( + {rightIcon && ( void; + networkImages: string[]; + imageHeaders: Record; + style?: StyleProp; +} + +export function CompatibleNetwork({ + text, + onPress, + networkImages, + imageHeaders, + style +}: CompatibleNetworkProps) { + const Theme = useTheme(); + + return ( + + + {text} + + + {networkImages?.map((image, index) => ( + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { + height: 48 + }, + contentContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingRight: 0 + }, + item: { + marginLeft: -5 + } +}); diff --git a/packages/ui/src/composites/wui-connect-button/styles.ts b/packages/ui/src/composites/wui-connect-button/styles.ts index 572cdbc20..8804ce23e 100644 --- a/packages/ui/src/composites/wui-connect-button/styles.ts +++ b/packages/ui/src/composites/wui-connect-button/styles.ts @@ -51,7 +51,7 @@ export default StyleSheet.create({ alignItems: 'center', justifyContent: 'center', borderRadius: BorderRadius.s, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth }, smButton: { height: 32 diff --git a/packages/ui/src/composites/wui-icon-box/index.tsx b/packages/ui/src/composites/wui-icon-box/index.tsx index 070656fc0..bbfb9e8ac 100644 --- a/packages/ui/src/composites/wui-icon-box/index.tsx +++ b/packages/ui/src/composites/wui-icon-box/index.tsx @@ -7,7 +7,7 @@ import styles from './styles'; export interface IconBoxProps { icon: IconType; - size?: Exclude; + size?: Exclude; iconColor?: ColorType; iconSize?: Exclude; background?: boolean; @@ -33,6 +33,9 @@ export function IconBox({ const Theme = useTheme(); let _iconSize: SizeType; switch (size) { + case 'xl': + _iconSize = 'xl'; + break; case 'lg': _iconSize = 'lg'; break; @@ -48,6 +51,9 @@ export function IconBox({ let boxSize: number; switch (size) { + case 'xl': + boxSize = 56; + break; case 'lg': boxSize = 40; break; @@ -58,7 +64,17 @@ export function IconBox({ boxSize = 24; } - const borderRadius = size === 'lg' ? 'xxs' : '3xl'; + let borderRadius: keyof typeof BorderRadius; + switch (size) { + case 'xl': + borderRadius = 's'; + break; + case 'lg': + borderRadius = 'xxs'; + break; + default: + borderRadius = '3xl'; + } const backgroundStyle = { backgroundColor: diff --git a/packages/ui/src/composites/wui-input-text/index.tsx b/packages/ui/src/composites/wui-input-text/index.tsx index 6903e056e..61880fd46 100644 --- a/packages/ui/src/composites/wui-input-text/index.tsx +++ b/packages/ui/src/composites/wui-input-text/index.tsx @@ -97,7 +97,6 @@ export const InputText = forwardRef( style={[styles.outerBorder, { borderColor: outerBorder, borderRadius: outerRadius }]} disabled={disabled} onPress={() => inputRef.current?.focus()} - testID={rest.testID} > ( underlineColorAndroid="transparent" selectTextOnFocus={false} editable={!disabled} + testID="wui-input-text" {...rest} /> {children} diff --git a/packages/ui/src/composites/wui-input-text/styles.ts b/packages/ui/src/composites/wui-input-text/styles.ts index 2cf4398e6..4b18dffa5 100644 --- a/packages/ui/src/composites/wui-input-text/styles.ts +++ b/packages/ui/src/composites/wui-input-text/styles.ts @@ -7,7 +7,7 @@ const baseStyle = { borderRadius: BorderRadius.xxs, alignItems: 'center', paddingHorizontal: Spacing.xs, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth } as ViewStyle; export const outerBorderRadius = { diff --git a/packages/ui/src/composites/wui-list-item-loader/index.tsx b/packages/ui/src/composites/wui-list-item-loader/index.tsx new file mode 100644 index 000000000..b3fb786cb --- /dev/null +++ b/packages/ui/src/composites/wui-list-item-loader/index.tsx @@ -0,0 +1,29 @@ +import type { StyleProp, ViewStyle } from 'react-native'; +import { BorderRadius, WalletImageSize } from '../../utils/ThemeUtil'; +import { useTheme } from '../../hooks/useTheme'; +import { Shimmer } from '../../components/wui-shimmer'; +import { FlexView } from '../../layout/wui-flex'; +import styles from './styles'; + +export interface ListItemLoaderProps { + style?: StyleProp; +} + +export function ListItemLoader({ style }: ListItemLoaderProps) { + const Theme = useTheme(); + + return ( + + + + + ); +} diff --git a/packages/ui/src/composites/wui-list-item-loader/styles.ts b/packages/ui/src/composites/wui-list-item-loader/styles.ts new file mode 100644 index 000000000..f5a3b9381 --- /dev/null +++ b/packages/ui/src/composites/wui-list-item-loader/styles.ts @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + borderRadius: BorderRadius.xs, + padding: Spacing.xs + }, + text: { + marginLeft: Spacing.s + } +}); diff --git a/packages/ui/src/composites/wui-list-item/index.tsx b/packages/ui/src/composites/wui-list-item/index.tsx index 98791b650..9cbacb172 100644 --- a/packages/ui/src/composites/wui-list-item/index.tsx +++ b/packages/ui/src/composites/wui-list-item/index.tsx @@ -5,7 +5,7 @@ import { Image } from '../../components/wui-image'; import { LoadingSpinner } from '../../components/wui-loading-spinner'; import useAnimatedValue from '../../hooks/useAnimatedValue'; import { useTheme } from '../../hooks/useTheme'; -import type { IconType } from '../../utils/TypesUtil'; +import type { ColorType, IconType } from '../../utils/TypesUtil'; import { IconBox } from '../wui-icon-box'; import styles from './styles'; @@ -13,8 +13,9 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export interface ListItemProps { icon?: IconType; - iconVariant?: 'blue' | 'overlay'; - variant?: 'image' | 'icon'; + iconColor?: ColorType; + iconBackgroundColor?: ColorType; + iconBorderColor?: ColorType; imageSrc?: string; imageHeaders?: Record; chevron?: boolean; @@ -23,21 +24,24 @@ export interface ListItemProps { onPress?: () => void; children?: ReactNode; style?: StyleProp; + contentStyle?: StyleProp; testID?: string; } export function ListItem({ children, icon, - variant, imageSrc, imageHeaders, - iconVariant = 'blue', + iconColor = 'fg-200', + iconBackgroundColor, + iconBorderColor = 'gray-glass-005', chevron, loading, disabled, onPress, style, + contentStyle, testID }: ListItemProps) { const Theme = useTheme(); @@ -47,7 +51,7 @@ export function ListItem({ ); function visualTemplate() { - if (variant === 'image' && imageSrc) { + if (imageSrc) { return ( ); - } else if (variant === 'icon' && icon) { - const iconColor = iconVariant === 'blue' ? 'accent-100' : 'fg-200'; - const borderColor = iconVariant === 'blue' ? 'accent-glass-005' : 'gray-glass-005'; - + } else if (icon) { return ( - + ); @@ -81,7 +82,7 @@ export function ListItem({ if (loading) { return ; } else if (chevron) { - return ; + return ; } return null; @@ -101,7 +102,7 @@ export function ListItem({ testID={testID} > {visualTemplate()} - {children} + {children} {rightTemplate()} ); diff --git a/packages/ui/src/composites/wui-list-item/styles.ts b/packages/ui/src/composites/wui-list-item/styles.ts index 4e12fe622..f7c9d79a7 100644 --- a/packages/ui/src/composites/wui-list-item/styles.ts +++ b/packages/ui/src/composites/wui-list-item/styles.ts @@ -13,12 +13,13 @@ export default StyleSheet.create({ content: { flexDirection: 'row', flexGrow: 1, - paddingHorizontal: Spacing.s + paddingHorizontal: Spacing.s, + alignItems: 'center' }, imageContainer: { width: 36, height: 36, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 2, alignItems: 'center', justifyContent: 'center' @@ -26,7 +27,7 @@ export default StyleSheet.create({ image: { width: 32, height: 32, - borderRadius: 100 + borderRadius: BorderRadius.full }, disabledImage: { opacity: 0.4 diff --git a/packages/ui/src/composites/wui-list-social/index.tsx b/packages/ui/src/composites/wui-list-social/index.tsx new file mode 100644 index 000000000..fa600c704 --- /dev/null +++ b/packages/ui/src/composites/wui-list-social/index.tsx @@ -0,0 +1,65 @@ +import { View, Pressable, Animated, type StyleProp, type ViewStyle } from 'react-native'; +import useAnimatedValue from '../../hooks/useAnimatedValue'; +import { useTheme } from '../../hooks/useTheme'; +import type { LogoType } from '../../utils/TypesUtil'; + +import styles from './styles'; +import { Logo } from '../wui-logo'; +import type { ReactNode } from 'react'; + +const AnimatedPressable = Animated.createAnimatedComponent(Pressable); + +export interface ListSocialProps { + children?: ReactNode; + disabled?: boolean; + logo: LogoType; + onPress?: () => void; + style?: StyleProp; + testID?: string; + logoWidth?: number; + logoHeight?: number; +} + +export function ListSocial({ + logo, + children, + disabled, + onPress, + style, + testID, + logoHeight = 40, + logoWidth = 40 +}: ListSocialProps) { + const Theme = useTheme(); + const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( + Theme['gray-glass-002'], + Theme['gray-glass-010'] + ); + + return ( + + + + + {children} + + + ); +} diff --git a/packages/ui/src/composites/wui-list-social/styles.ts b/packages/ui/src/composites/wui-list-social/styles.ts new file mode 100644 index 000000000..83d6f63c3 --- /dev/null +++ b/packages/ui/src/composites/wui-list-social/styles.ts @@ -0,0 +1,28 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + flexDirection: 'row', + height: 56, + width: '100%', + padding: Spacing.s, + alignItems: 'center', + justifyContent: 'space-between', + borderRadius: BorderRadius.s + }, + rightPlaceholder: { + width: 40, + height: 40, + borderRadius: 100 + }, + disabledLogo: { + opacity: 0.4 + }, + border: { + borderRadius: BorderRadius.full, + borderWidth: 2, + alignItems: 'center', + justifyContent: 'center' + } +}); diff --git a/packages/ui/src/composites/wui-list-token/index.tsx b/packages/ui/src/composites/wui-list-token/index.tsx new file mode 100644 index 000000000..30b8662eb --- /dev/null +++ b/packages/ui/src/composites/wui-list-token/index.tsx @@ -0,0 +1,83 @@ +import { Pressable } from 'react-native'; +import { Icon } from '../../components/wui-icon'; +import { Image } from '../../components/wui-image'; +import { Text } from '../../components/wui-text'; +import { useTheme } from '../../hooks/useTheme'; +import { FlexView } from '../../layout/wui-flex'; +import { UiUtil } from '../../utils/UiUtil'; +import styles from './styles'; + +export interface ListTokenProps { + imageSrc: string; + networkSrc?: string; + name: string; + value?: number; + amount?: string; + currency: string; + onPress?: () => void; +} + +export function ListToken({ + imageSrc, + networkSrc, + name, + value, + amount, + currency, + onPress +}: ListTokenProps) { + const Theme = useTheme(); + + return ( + + + + {imageSrc ? ( + + ) : ( + + + + )} + + {networkSrc ? ( + + ) : ( + + )} + + + + {name} + + + {UiUtil.formatNumberToLocalString(amount, 4)} {currency} + + + + + ${value?.toFixed(2) ?? '0.00'} + + + + ); +} diff --git a/packages/ui/src/composites/wui-list-token/styles.ts b/packages/ui/src/composites/wui-list-token/styles.ts new file mode 100644 index 000000000..73afea33a --- /dev/null +++ b/packages/ui/src/composites/wui-list-token/styles.ts @@ -0,0 +1,24 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, WalletImageSize } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + image: { + height: WalletImageSize.sm, + width: WalletImageSize.sm, + borderRadius: BorderRadius.full + }, + networkImageContainer: { + position: 'absolute', + bottom: -2, + left: 24, + borderWidth: 2, + borderRadius: BorderRadius.full, + width: 18, + height: 18 + }, + networkImage: { + width: 14, + height: 14, + borderRadius: BorderRadius.full + } +}); diff --git a/packages/ui/src/composites/wui-list-transaction/index.tsx b/packages/ui/src/composites/wui-list-transaction/index.tsx new file mode 100644 index 000000000..7cb10bb8d --- /dev/null +++ b/packages/ui/src/composites/wui-list-transaction/index.tsx @@ -0,0 +1,65 @@ +import { type TransactionImage, type TransactionStatus } from '@reown/appkit-common-react-native'; + +import type { TransactionType } from '../../utils/TypesUtil'; +import { Text } from '../../components/wui-text'; +import { FlexView } from '../../layout/wui-flex'; +import { IconBox } from '../wui-icon-box'; +import { TransactionVisual } from '../wui-transaction-visual'; +import { getIcon, getTypeLabel, getIconColor } from './utils'; +import styles from './styles'; +import type { StyleProp, ViewStyle } from 'react-native'; + +export interface ListTransactionProps { + date: string; + status?: TransactionStatus; + type?: TransactionType; + descriptions?: string[]; + images?: TransactionImage[]; + networkSrc?: string; + style?: StyleProp; + isAllNFT?: boolean; +} + +export function ListTransaction({ + date, + type, + descriptions, + images, + networkSrc, + style, + isAllNFT, + status +}: ListTransactionProps) { + const joinSymbol = type === 'trade' ? ' → ' : ' - '; + + return ( + + + + + + {type && ( + + )} + + {getTypeLabel(type)} + + + + {descriptions?.join(joinSymbol)} + + + + + {date} + + + ); +} diff --git a/packages/ui/src/composites/wui-list-transaction/styles.ts b/packages/ui/src/composites/wui-list-transaction/styles.ts new file mode 100644 index 000000000..5554145c6 --- /dev/null +++ b/packages/ui/src/composites/wui-list-transaction/styles.ts @@ -0,0 +1,10 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + middleContainer: { + flex: 1 + }, + date: { + textTransform: 'uppercase' + } +}); diff --git a/packages/ui/src/composites/wui-list-transaction/utils.ts b/packages/ui/src/composites/wui-list-transaction/utils.ts new file mode 100644 index 000000000..305581824 --- /dev/null +++ b/packages/ui/src/composites/wui-list-transaction/utils.ts @@ -0,0 +1,83 @@ +import type { TransactionStatus } from '@reown/appkit-common-react-native'; +import type { IconType, TransactionType } from '../../utils/TypesUtil'; + +export const getIcon = (type: TransactionType): IconType => { + switch (type) { + case 'approve': + case 'execute': + return 'checkmark'; + case 'repay': + case 'send': + case 'stake': + case 'withdraw': + return 'arrowTop'; + case 'burn': + case 'cancel': + return 'close'; + case 'trade': + return 'swapHorizontal'; + case 'deploy': + return 'arrowRight'; + default: + return 'arrowBottom'; + } +}; + +//Utils +export const getIconColor = (status?: TransactionStatus) => { + switch (status) { + case 'confirmed': + return 'success-100'; + case 'failed': + return 'error-100'; + case 'pending': + return 'fg-200'; + default: + return 'fg-200'; + } +}; + +export const getTypeLabel = (type?: TransactionType) => { + if (!type) { + return 'Unknown'; + } + + switch (type) { + case 'approve': + return 'Approved'; + case 'bought': + return 'Bought'; + case 'borrow': + return 'Borrowed'; + case 'burn': + return 'Burnt'; + case 'cancel': + return 'Canceled'; + case 'claim': + return 'Claimed'; + case 'deploy': + return 'Deployed'; + case 'deposit': + return 'Deposited'; + case 'execute': + return 'Executed'; + case 'mint': + return 'Minted'; + case 'receive': + return 'Received'; + case 'repay': + return 'Repaid'; + case 'send': + return 'Sent'; + case 'stake': + return 'Staked'; + case 'trade': + return 'Swapped'; + case 'unstake': + return 'Unstaked'; + case 'withdraw': + return 'Withdrawn'; + default: + return 'Unknown'; + } +}; diff --git a/packages/ui/src/composites/wui-list-wallet/index.tsx b/packages/ui/src/composites/wui-list-wallet/index.tsx index 58b8a01b7..ace660e13 100644 --- a/packages/ui/src/composites/wui-list-wallet/index.tsx +++ b/packages/ui/src/composites/wui-list-wallet/index.tsx @@ -1,4 +1,4 @@ -import { Animated, Pressable, View, type StyleProp, type ViewStyle } from 'react-native'; +import { Animated, Pressable, type StyleProp, type ViewStyle, View } from 'react-native'; import { Text } from '../../components/wui-text'; import useAnimatedValue from '../../hooks/useAnimatedValue'; import { useTheme } from '../../hooks/useTheme'; @@ -6,9 +6,9 @@ import type { IconType, TagType } from '../../utils/TypesUtil'; import { Tag } from '../wui-tag'; import { WalletImage } from '../wui-wallet-image'; import { Icon } from '../../components/wui-icon'; +import { IconBox } from '../wui-icon-box'; import styles from './styles'; -import { IconBox } from '../wui-icon-box'; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); diff --git a/packages/ui/src/composites/wui-logo-select/index.tsx b/packages/ui/src/composites/wui-logo-select/index.tsx index 80cc00ebd..3a4c68f7f 100644 --- a/packages/ui/src/composites/wui-logo-select/index.tsx +++ b/packages/ui/src/composites/wui-logo-select/index.tsx @@ -1,4 +1,4 @@ -import { Animated, Pressable } from 'react-native'; +import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; import useAnimatedValue from '../../hooks/useAnimatedValue'; import { useTheme } from '../../hooks/useTheme'; import type { LogoType } from '../../utils/TypesUtil'; @@ -10,20 +10,23 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export interface LogoSelectProps { logo: LogoType; disabled?: boolean; + style?: StyleProp; + onPress?: () => void; } -export function LogoSelect({ logo, disabled }: LogoSelectProps) { +export function LogoSelect({ logo, disabled, style, onPress }: LogoSelectProps) { const Theme = useTheme(); const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( - Theme['gray-glass-005'], + Theme['gray-glass-002'], Theme['gray-glass-010'] ); return ( diff --git a/packages/ui/src/composites/wui-logo-select/styles.ts b/packages/ui/src/composites/wui-logo-select/styles.ts index 8f7f37131..fbffeaabb 100644 --- a/packages/ui/src/composites/wui-logo-select/styles.ts +++ b/packages/ui/src/composites/wui-logo-select/styles.ts @@ -3,9 +3,9 @@ import { BorderRadius } from '../../utils/ThemeUtil'; export default StyleSheet.create({ box: { - height: 50, - width: 50, - borderRadius: BorderRadius.xs, + height: 56, + width: 56, + borderRadius: BorderRadius.s, alignItems: 'center', justifyContent: 'center' }, diff --git a/packages/ui/src/composites/wui-logo/index.tsx b/packages/ui/src/composites/wui-logo/index.tsx index 5185b8e5d..c372c9566 100644 --- a/packages/ui/src/composites/wui-logo/index.tsx +++ b/packages/ui/src/composites/wui-logo/index.tsx @@ -6,9 +6,11 @@ import type { LogoType } from '../../utils/TypesUtil'; export interface LogoProps { logo: LogoType; disabled?: boolean; + height?: number; + width?: number; style?: SvgProps['style']; } -export function Logo({ logo, style }: LogoProps) { - return ; +export function Logo({ width = 40, height = 40, logo, style }: LogoProps) { + return ; } diff --git a/packages/ui/src/composites/wui-network-button/index.tsx b/packages/ui/src/composites/wui-network-button/index.tsx index 11fe94841..53a6a1e5b 100644 --- a/packages/ui/src/composites/wui-network-button/index.tsx +++ b/packages/ui/src/composites/wui-network-button/index.tsx @@ -1,51 +1,57 @@ -import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; +import { Animated, Pressable, View, type StyleProp, type ViewStyle } from 'react-native'; import { Image } from '../../components/wui-image'; import { Text } from '../../components/wui-text'; import { useTheme } from '../../hooks/useTheme'; import { IconBox } from '../wui-icon-box'; +import { LoadingSpinner } from '../../components/wui-loading-spinner'; +import useAnimatedValue from '../../hooks/useAnimatedValue'; import styles from './styles'; -import useAnimatedValue from '../../hooks/useAnimatedValue'; -import { LoadingSpinner } from '../../components/wui-loading-spinner'; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export interface NetworkButtonProps { - children: string; + children: string | React.ReactNode; onPress: () => void; + background?: boolean; + disabled?: boolean; imageSrc?: string; imageHeaders?: Record; - disabled?: boolean; - style?: StyleProp; loading?: boolean; + style?: StyleProp; + testID?: string; } export function NetworkButton({ + children, + onPress, + background = true, + disabled, imageSrc, imageHeaders, - disabled, - onPress, - style, loading, - children + style, + testID }: NetworkButtonProps) { const Theme = useTheme(); const textColor = disabled ? 'fg-300' : 'fg-100'; const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( - Theme['gray-glass-005'], + background ? Theme['gray-glass-005'] : 'transparent', Theme['gray-glass-010'] ); const backgroundColor = disabled ? Theme['gray-glass-015'] : animatedValue; + const borderColor = background ? Theme['gray-glass-005'] : 'transparent'; return ( - - {children} - + {typeof children === 'string' ? ( + + {children} + + ) : ( + {children} + )} ); } diff --git a/packages/ui/src/composites/wui-network-button/styles.ts b/packages/ui/src/composites/wui-network-button/styles.ts index eb36d0188..f2166e82e 100644 --- a/packages/ui/src/composites/wui-network-button/styles.ts +++ b/packages/ui/src/composites/wui-network-button/styles.ts @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import { Spacing } from '../../utils/ThemeUtil'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { @@ -7,11 +7,11 @@ export default StyleSheet.create({ height: 40, alignItems: 'center', justifyContent: 'center', - borderWidth: 1, - borderRadius: 100, + borderWidth: StyleSheet.hairlineWidth, + borderRadius: BorderRadius.full, paddingHorizontal: Spacing['2xs'] }, - text: { + children: { paddingHorizontal: Spacing['2xs'] }, loader: { @@ -20,7 +20,7 @@ export default StyleSheet.create({ image: { height: 24, width: 24, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 2, paddingLeft: Spacing['4xs'] }, diff --git a/packages/ui/src/composites/wui-network-image/index.tsx b/packages/ui/src/composites/wui-network-image/index.tsx index 4dd561ecf..ee3ee9ac8 100644 --- a/packages/ui/src/composites/wui-network-image/index.tsx +++ b/packages/ui/src/composites/wui-network-image/index.tsx @@ -1,49 +1,85 @@ import { Path, Svg, Image, Defs, Pattern } from 'react-native-svg'; +import type { StyleProp, ViewStyle } from 'react-native'; import { useTheme } from '../../hooks/useTheme'; import type { SizeType } from '../../utils/TypesUtil'; -import { PathLg, PathNormal } from './styles'; +import { Icon } from '../../components/wui-icon'; +import { FlexView } from '../../layout/wui-flex'; +import { PathLg, PathNormal, PathSmall, PathXS } from './styles'; export interface NetworkImageProps { imageSrc?: string; imageHeaders?: Record; selected?: boolean; - size?: Exclude; + size?: Exclude; disabled?: boolean; + style?: StyleProp; + borderColor?: string; + borderWidth?: number; } +const sizeToPath = { + lg: PathLg, + md: PathNormal, + sm: PathSmall, + xs: PathXS +}; + +const sizeToHeight = { + lg: 96, + md: 56, + sm: 40, + xs: 20 +}; + export function NetworkImage({ imageSrc, imageHeaders, disabled, selected, - size = 'md' + size = 'md', + style, + borderColor, + borderWidth = 1 }: NetworkImageProps) { const Theme = useTheme(); - const isLg = size === 'lg'; - const svgWidth = isLg ? 96 : 56; - const svgHeight = isLg ? 96 : 56; const svgStroke = selected ? Theme['accent-100'] : Theme['gray-glass-010']; const opacity = disabled ? 0.5 : 1; return ( - + - - + + {imageSrc ? ( + + ) : ( + + + + )} - + {!imageSrc && } + ); } diff --git a/packages/ui/src/composites/wui-network-image/styles.ts b/packages/ui/src/composites/wui-network-image/styles.ts index 7041b19ce..dbd445cae 100644 --- a/packages/ui/src/composites/wui-network-image/styles.ts +++ b/packages/ui/src/composites/wui-network-image/styles.ts @@ -3,3 +3,9 @@ export const PathLg = export const PathNormal = 'M24.0002 2.34328C26.4754 0.914219 29.525 0.914219 32.0002 2.34328L48.2489 11.7245C50.7241 13.1535 52.2489 15.7946 52.2489 18.6527V37.4151C52.2489 40.2732 50.7241 42.9142 48.2489 44.3433L32.0002 53.7245C29.525 55.1535 26.4754 55.1535 24.0002 53.7245L7.75146 44.3433C5.27625 42.9142 3.75146 40.2732 3.75146 37.4151V18.6527C3.75146 15.7946 5.27626 13.1535 7.75146 11.7245L24.0002 2.34328Z'; + +export const PathSmall = + 'M17.1428 1.67377C18.9108 0.653013 21.0891 0.653014 22.8571 1.67377L34.4633 8.37463C36.2313 9.39539 37.3205 11.2818 37.3205 13.3233V26.7251C37.3205 28.7666 36.2313 30.653 34.4633 31.6738L22.8571 38.3746C21.0891 39.3954 18.9108 39.3954 17.1428 38.3746L5.53659 31.6738C3.76858 30.653 2.67944 28.7666 2.67944 26.7251V13.3233C2.67944 11.2818 3.76858 9.39539 5.53659 8.37463L17.1428 1.67377Z'; + +export const PathXS = + 'M8.57153 0.836886C9.45553 0.326507 10.5447 0.326507 11.4287 0.836886L17.2318 4.18731C18.1158 4.69769 18.6604 5.64091 18.6604 6.66167V13.3625C18.6604 14.3833 18.1158 15.3265 17.2318 15.8369L11.4287 19.1873C10.5447 19.6977 9.45553 19.6977 8.57153 19.1873L2.76841 15.8369C1.88441 15.3265 1.33984 14.3833 1.33984 13.3625V6.66167C1.33984 5.64091 1.88441 4.69769 2.76841 4.18731L8.57153 0.836886Z'; diff --git a/packages/ui/src/composites/wui-otp/index.tsx b/packages/ui/src/composites/wui-otp/index.tsx index 713d0d21d..ebf83152c 100644 --- a/packages/ui/src/composites/wui-otp/index.tsx +++ b/packages/ui/src/composites/wui-otp/index.tsx @@ -3,8 +3,7 @@ import { type NativeSyntheticEvent, TextInput, type TextInputKeyPressEventData, - View, - Platform + View } from 'react-native'; import { InputNumeric, type InputNumericProps } from '../wui-input-numeric'; import styles from './styles'; @@ -90,8 +89,7 @@ export function Otp({ length, style, onChangeText, autoFocus }: OtpProps) { inputRef={refArray[index]} onChangeText={text => _onChangeText(text, index)} onKeyPress={(e: any) => onKeyPress(e, index)} - textContentType="oneTimeCode" - autoComplete={Platform.OS === 'android' ? 'sms-otp' : 'one-time-code'} + autoComplete="off" /> ))} diff --git a/packages/ui/src/composites/wui-promo/index.tsx b/packages/ui/src/composites/wui-promo/index.tsx new file mode 100644 index 000000000..19a3597c9 --- /dev/null +++ b/packages/ui/src/composites/wui-promo/index.tsx @@ -0,0 +1,42 @@ +import { Pressable, StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { Icon } from '../../components/wui-icon'; +import { Text } from '../../components/wui-text'; +import { useTheme } from '../../hooks/useTheme'; +import { FlexView } from '../../layout/wui-flex'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export interface PromoProps { + text: string; + style?: StyleProp; + onPress?: () => void; +} + +export function Promo({ text, style, onPress }: PromoProps) { + const Theme = useTheme(); + + return ( + + + + {text} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + borderRadius: BorderRadius.full + }, + icon: { + marginLeft: Spacing['2xs'] + } +}); diff --git a/packages/ui/src/composites/wui-qr-code/index.tsx b/packages/ui/src/composites/wui-qr-code/index.tsx index 582a3ae2b..67f0a69aa 100644 --- a/packages/ui/src/composites/wui-qr-code/index.tsx +++ b/packages/ui/src/composites/wui-qr-code/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { View } from 'react-native'; +import { View, type StyleProp, type ViewStyle } from 'react-native'; import Svg from 'react-native-svg'; import { Icon } from '../../components/wui-icon'; import { Image } from '../../components/wui-image'; @@ -8,29 +8,37 @@ import { Text } from '../../components/wui-text'; import { FlexView } from '../../layout/wui-flex'; import { QRCodeUtil } from '../../utils/QRCodeUtil'; import { BorderRadius, LightTheme, Spacing } from '../../utils/ThemeUtil'; +import type { IconType } from '../../utils/TypesUtil'; import styles from './styles'; export interface QrCodeProps { size: number; uri?: string; imageSrc?: string; + icon?: IconType; testID?: string; + arenaClear?: boolean; + style?: StyleProp; } -export function QrCode({ size, uri, imageSrc, testID }: QrCodeProps) { +const LABEL_HEIGHT = 18; + +export function QrCode({ size, uri, imageSrc, testID, arenaClear, icon, style }: QrCodeProps) { const Theme = LightTheme; const containerPadding = Spacing.l; const qrSize = size - containerPadding * 2; + const logoSize = arenaClear ? 0 : qrSize / 4; + const dots = useMemo( - () => (uri ? QRCodeUtil.generate(uri, qrSize, qrSize / 4) : []), - [uri, qrSize] + () => (uri ? QRCodeUtil.generate(uri, qrSize, logoSize) : []), + [uri, qrSize, logoSize] ); - const shimmerTemplate = () => { - return ; - }; - const logoTemplate = () => { + if (arenaClear) { + return null; + } + if (imageSrc) { return ( @@ -68,7 +77,7 @@ export function QrCode({ size, uri, imageSrc, testID }: QrCodeProps) { {logoTemplate()} - + UX by{' '} Reown @@ -76,6 +85,6 @@ export function QrCode({ size, uri, imageSrc, testID }: QrCodeProps) { ) : ( - shimmerTemplate() + ); } diff --git a/packages/ui/src/composites/wui-search-bar/index.tsx b/packages/ui/src/composites/wui-search-bar/index.tsx index bbecd5dab..3c619226e 100644 --- a/packages/ui/src/composites/wui-search-bar/index.tsx +++ b/packages/ui/src/composites/wui-search-bar/index.tsx @@ -10,15 +10,13 @@ export interface SearchBarProps { onSubmitEditing?: TextInputProps['onSubmitEditing']; onChangeText?: TextInputProps['onChangeText']; inputStyle?: TextInputProps['style']; - testID?: string; } export function SearchBar({ placeholder = 'Search wallet', onSubmitEditing, onChangeText, - inputStyle, - testID + inputStyle }: SearchBarProps) { const [showClear, setShowClear] = useState(false); const inputRef = useRef(null); @@ -38,7 +36,6 @@ export function SearchBar({ inputStyle={inputStyle} returnKeyType="search" disableFullscreenUI - testID={testID} > {showClear && ( - + {message} diff --git a/packages/ui/src/composites/wui-snackbar/styles.ts b/packages/ui/src/composites/wui-snackbar/styles.ts index 1ef657b09..7c0ced601 100644 --- a/packages/ui/src/composites/wui-snackbar/styles.ts +++ b/packages/ui/src/composites/wui-snackbar/styles.ts @@ -1,12 +1,13 @@ import { StyleSheet } from 'react-native'; +import { BorderRadius } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { height: 40, flexDirection: 'row', paddingHorizontal: 8, - borderRadius: 100, - borderWidth: 1, + borderRadius: BorderRadius.full, + borderWidth: StyleSheet.hairlineWidth, alignItems: 'center' }, text: { diff --git a/packages/ui/src/composites/wui-tabs/index.tsx b/packages/ui/src/composites/wui-tabs/index.tsx index f71114cb3..7514e823b 100644 --- a/packages/ui/src/composites/wui-tabs/index.tsx +++ b/packages/ui/src/composites/wui-tabs/index.tsx @@ -1,5 +1,12 @@ import { useRef, useState } from 'react'; -import { Animated, Pressable, View } from 'react-native'; +import { + Animated, + Pressable, + View, + type LayoutChangeEvent, + type StyleProp, + type ViewStyle +} from 'react-native'; import { Icon } from '../../components/wui-icon'; import { Text } from '../../components/wui-text'; import { useTheme } from '../../hooks/useTheme'; @@ -8,13 +15,16 @@ import styles from './styles'; export interface TabsProps { onTabChange: (index: number) => void; - tabs: TabOptionType[]; + tabs: TabOptionType[] | string[]; + style?: StyleProp; } -export function Tabs({ tabs, onTabChange }: TabsProps) { +export function Tabs({ tabs, onTabChange, style }: TabsProps) { const Theme = useTheme(); const [activeTab, setActiveTab] = useState(0); const animatedPosition = useRef(new Animated.Value(0)); + const [viewWidth, setViewWidth] = useState(1); + const tabWidth = Math.trunc(viewWidth / tabs.length) - 2; const onTabPress = (index: number) => { setActiveTab(index); @@ -28,27 +38,41 @@ export function Tabs({ tabs, onTabChange }: TabsProps) { const markPosition = animatedPosition.current.interpolate({ inputRange: [0, tabs.length - 1], - outputRange: [0, 100 * (tabs.length - 1)] + outputRange: [0, tabWidth * (tabs.length - 1)] }); + const onLayout = (event: LayoutChangeEvent) => { + const { width } = event.nativeEvent.layout; + setViewWidth(width); + }; + return ( - + {tabs.map((option, index) => { const isActive = index === activeTab; + const isString = typeof option === 'string'; return ( - onTabPress(index)} key={option.label} style={styles.tabItem}> - {option.icon && ( + onTabPress(index)} + key={isString ? option : option.label} + style={[styles.tabItem, { width: tabWidth }]} + > + {!isString && option.icon && ( )} - {option.label} + {isString ? option : option.label} ); diff --git a/packages/ui/src/composites/wui-tabs/styles.ts b/packages/ui/src/composites/wui-tabs/styles.ts index 7d1dca5de..0fdd348e6 100644 --- a/packages/ui/src/composites/wui-tabs/styles.ts +++ b/packages/ui/src/composites/wui-tabs/styles.ts @@ -4,6 +4,7 @@ import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { height: 34, + width: '100%', flexDirection: 'row', alignItems: 'center', paddingHorizontal: Spacing['3xs'], @@ -13,7 +14,6 @@ export default StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', - width: 100, paddingVertical: Spacing['2xs'] }, tabIcon: { @@ -22,8 +22,7 @@ export default StyleSheet.create({ activeMark: { position: 'absolute', height: 28, - width: 100, - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, borderRadius: BorderRadius['3xl'], margin: Spacing['3xs'] } diff --git a/packages/ui/src/composites/wui-token-button/index.tsx b/packages/ui/src/composites/wui-token-button/index.tsx new file mode 100644 index 000000000..b7a489e2a --- /dev/null +++ b/packages/ui/src/composites/wui-token-button/index.tsx @@ -0,0 +1,29 @@ +import type { Balance } from '@reown/appkit-common-react-native'; +import { Image } from '../../components/wui-image'; +import { Text } from '../../components/wui-text'; +import { Button } from '../wui-button'; +import styles from './styles'; + +export interface TokenButtonProps { + onPress?: () => void; + token?: Balance; +} + +export function TokenButton({ token, onPress }: TokenButtonProps) { + if (!token) { + return ( + + ); + } + + return ( + + ); +} diff --git a/packages/ui/src/composites/wui-token-button/styles.ts b/packages/ui/src/composites/wui-token-button/styles.ts new file mode 100644 index 000000000..7ece57a06 --- /dev/null +++ b/packages/ui/src/composites/wui-token-button/styles.ts @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + selectButton: { + height: 40, + paddingHorizontal: Spacing.m + }, + container: { + height: 40 + }, + image: { + width: 24, + height: 24, + borderRadius: BorderRadius.full, + marginRight: Spacing['2xs'] + } +}); diff --git a/packages/ui/src/composites/wui-transaction-visual/index.tsx b/packages/ui/src/composites/wui-transaction-visual/index.tsx new file mode 100644 index 000000000..29d4e7c41 --- /dev/null +++ b/packages/ui/src/composites/wui-transaction-visual/index.tsx @@ -0,0 +1,78 @@ +import type { TransactionImage } from '@reown/appkit-common-react-native'; + +import { FlexView } from '../../layout/wui-flex'; +import { Icon } from '../../components/wui-icon'; +import { Image } from '../../components/wui-image'; +import { useTheme } from '../../hooks/useTheme'; +import styles from './styles'; + +export interface TransactionVisualProps { + images?: TransactionImage[]; + networkSrc?: string; + isAllNFT?: boolean; +} + +export function TransactionVisual({ images, networkSrc, isAllNFT }: TransactionVisualProps) { + const Theme = useTheme(); + const backgroundColor = Theme['bg-200']; + const isFirstNFT = Boolean(images?.[0]?.type === 'NFT'); + const filteredImages = images?.filter(image => image.url); + const [firstImage, secondImage] = filteredImages ?? []; + const hasOneImage = filteredImages?.length === 1; + const hasTwoImages = filteredImages && filteredImages?.length > 1; + + return ( + + {!filteredImages?.length && ( + + + + )} + {hasOneImage && firstImage?.url && ( + + )} + {hasTwoImages && firstImage?.url && secondImage?.url && ( + + + + + + + + + )} + + {networkSrc ? ( + + ) : ( + + )} + + + ); +} diff --git a/packages/ui/src/composites/wui-transaction-visual/styles.ts b/packages/ui/src/composites/wui-transaction-visual/styles.ts new file mode 100644 index 000000000..4bc04db84 --- /dev/null +++ b/packages/ui/src/composites/wui-transaction-visual/styles.ts @@ -0,0 +1,36 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + image: { + height: 40, + width: 40, + borderRadius: BorderRadius.full, + marginRight: Spacing.s + }, + imageNft: { + borderRadius: BorderRadius.xxs + }, + halfContainer: { + overflow: 'hidden', + width: 20, + marginRight: 2 + }, + halfRight: { + left: -20 + }, + networkImageContainer: { + position: 'absolute', + bottom: -2, + left: 24, + borderWidth: 2, + borderRadius: BorderRadius.full, + width: 18, + height: 18 + }, + networkImage: { + width: 14, + height: 14, + borderRadius: BorderRadius.full + } +}); diff --git a/packages/ui/src/composites/wui-wallet-image/styles.ts b/packages/ui/src/composites/wui-wallet-image/styles.ts index 610fd4b63..1a0f1f5b3 100644 --- a/packages/ui/src/composites/wui-wallet-image/styles.ts +++ b/packages/ui/src/composites/wui-wallet-image/styles.ts @@ -32,7 +32,7 @@ export default StyleSheet.create({ borderRadius: BorderRadius.m }, border: { - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, position: 'absolute' } }); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 00c9a3ccf..94c1bf2ab 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -9,8 +9,11 @@ export { Visual, type VisualProps } from './components/wui-visual'; export { Shimmer, type ShimmerProps } from './components/wui-shimmer'; export { AccountButton, type AccountButtonProps } from './composites/wui-account-button'; +export { AccountPill, type AccountPillProps } from './composites/wui-account-pill'; export { ActionEntry, type ActionEntryProps } from './composites/wui-action-entry'; export { Avatar, type AvatarProps } from './composites/wui-avatar'; +export { Balance, type BalanceProps } from './composites/wui-balance'; +export { Banner, type BannerProps } from './composites/wui-banner'; export { Button, type ButtonProps } from './composites/wui-button'; export { CardSelectLoader, @@ -24,6 +27,10 @@ export { type CardSelectProps } from './composites/wui-card-select'; export { Chip, type ChipProps } from './composites/wui-chip'; +export { + CompatibleNetwork, + type CompatibleNetworkProps +} from './composites/wui-compatible-network'; export { ConnectButton, type ConnectButtonProps } from './composites/wui-connect-button'; export { EmailInput, type EmailInputProps } from './composites/wui-email-input'; export { IconBox, type IconBoxProps } from './composites/wui-icon-box'; @@ -33,17 +40,23 @@ export { InputNumeric, type InputNumericProps } from './composites/wui-input-num export { InputText, type InputTextProps } from './composites/wui-input-text'; export { Link, type LinkProps } from './composites/wui-link'; export { ListItem, type ListItemProps } from './composites/wui-list-item'; +export { ListItemLoader, type ListItemLoaderProps } from './composites/wui-list-item-loader'; +export { ListSocial, type ListSocialProps } from './composites/wui-list-social'; +export { ListToken, type ListTokenProps } from './composites/wui-list-token'; +export { ListTransaction, type ListTransactionProps } from './composites/wui-list-transaction'; export { ListWallet, type ListWalletProps } from './composites/wui-list-wallet'; export { Logo, type LogoProps } from './composites/wui-logo'; export { LogoSelect, type LogoSelectProps } from './composites/wui-logo-select'; export { NetworkButton, type NetworkButtonProps } from './composites/wui-network-button'; export { NetworkImage, type NetworkImageProps } from './composites/wui-network-image'; export { Otp, type OtpProps } from './composites/wui-otp'; +export { Promo, type PromoProps } from './composites/wui-promo'; export { QrCode, type QrCodeProps } from './composites/wui-qr-code'; export { SearchBar, type SearchBarProps } from './composites/wui-search-bar'; export { Snackbar, type SnackbarProps } from './composites/wui-snackbar'; export { Tabs, type TabsProps } from './composites/wui-tabs'; export { Tag, type TagProps } from './composites/wui-tag'; +export { TokenButton, type TokenButtonProps } from './composites/wui-token-button'; export { Tooltip, type TooltipProps } from './composites/wui-tooltip'; export { WalletImage, type WalletImageProps } from './composites/wui-wallet-image'; @@ -66,6 +79,7 @@ export type { VisualType } from './utils/TypesUtil'; export { UiUtil } from './utils/UiUtil'; +export { TransactionUtil } from './utils/TransactionUtil'; export { Spacing, BorderRadius } from './utils/ThemeUtil'; export { useTheme } from './hooks/useTheme'; diff --git a/packages/ui/src/layout/wui-flex/index.tsx b/packages/ui/src/layout/wui-flex/index.tsx index fdee104bf..e155cd4b0 100644 --- a/packages/ui/src/layout/wui-flex/index.tsx +++ b/packages/ui/src/layout/wui-flex/index.tsx @@ -1,4 +1,4 @@ -import { View, type StyleProp, type ViewStyle } from 'react-native'; +import { type StyleProp, type ViewStyle, View } from 'react-native'; import type { FlexAlignType, @@ -9,7 +9,6 @@ import type { FlexWrapType, SpacingType } from '../../utils/TypesUtil'; - import { UiUtil } from '../../utils/UiUtil'; export interface FlexViewProps { diff --git a/packages/ui/src/layout/wui-separator/index.tsx b/packages/ui/src/layout/wui-separator/index.tsx index 701d00316..b438c59ab 100644 --- a/packages/ui/src/layout/wui-separator/index.tsx +++ b/packages/ui/src/layout/wui-separator/index.tsx @@ -1,4 +1,4 @@ -import { View, type StyleProp, type ViewStyle } from 'react-native'; +import { type StyleProp, type ViewStyle, View } from 'react-native'; import { Text } from '../../components/wui-text'; import { FlexView } from '../../layout/wui-flex'; import { useTheme } from '../../hooks/useTheme'; diff --git a/packages/ui/src/utils/ThemeUtil.ts b/packages/ui/src/utils/ThemeUtil.ts index 5fa3e4fb4..9671a12d8 100644 --- a/packages/ui/src/utils/ThemeUtil.ts +++ b/packages/ui/src/utils/ThemeUtil.ts @@ -155,7 +155,8 @@ export const BorderRadius = { 's': 20, 'm': 28, 'l': 36, - '3xl': 80 + '3xl': 80, + 'full': 100 }; export const IconSize = { @@ -163,7 +164,8 @@ export const IconSize = { xs: 12, sm: 14, md: 16, - lg: 20 + lg: 20, + xl: 24 }; export const SpinnerSize = { diff --git a/packages/ui/src/utils/TransactionUtil.ts b/packages/ui/src/utils/TransactionUtil.ts new file mode 100644 index 000000000..b05374079 --- /dev/null +++ b/packages/ui/src/utils/TransactionUtil.ts @@ -0,0 +1,173 @@ +import { DateUtil } from '@reown/appkit-common-react-native'; +import type { + TransactionTransfer, + Transaction, + TransactionImage +} from '@reown/appkit-common-react-native'; +import type { TransactionType } from './TypesUtil'; +import { UiUtil } from './UiUtil'; + +// -- Helpers --------------------------------------------- // +const FLOAT_FIXED_VALUE = 2; +const SMALL_FLOAT_FIXED_VALUE = 4; +const plusTypes: TransactionType[] = ['receive', 'deposit', 'borrow', 'claim']; +const minusTypes: TransactionType[] = ['withdraw', 'repay', 'burn']; + +export const TransactionUtil = { + getTransactionGroupTitle(year: string, month: string) { + const currentYear = DateUtil.getYear().toString(); + const monthName = DateUtil.getMonth(parseInt(month)); + const isCurrentYear = year === currentYear; + const groupTitle = isCurrentYear ? monthName : `${monthName} ${year}`; + + return groupTitle; + }, + + getTransactionImages(transfers: TransactionTransfer[]): TransactionImage[] { + const [transfer, secondTransfer] = transfers; + const isAllNFT = Boolean(transfer) && transfers?.every(item => Boolean(item.nft_info)); + const haveMultipleTransfers = transfers?.length > 1; + const haveTwoTransfers = transfers?.length === 2; + + if (haveTwoTransfers && !isAllNFT) { + return [this.getTransactionImage(transfer), this.getTransactionImage(secondTransfer)]; + } + + if (haveMultipleTransfers) { + return transfers.map(item => this.getTransactionImage(item)); + } + + return [this.getTransactionImage(transfer)]; + }, + + getTransactionImage(transfer?: TransactionTransfer): TransactionImage { + return { + type: TransactionUtil.getTransactionTransferTokenType(transfer), + url: TransactionUtil.getTransactionImageURL(transfer) + }; + }, + + getTransactionImageURL(transfer: TransactionTransfer | undefined) { + let imageURL; + const isNFT = Boolean(transfer?.nft_info); + const isFungible = Boolean(transfer?.fungible_info); + + if (transfer && isNFT) { + imageURL = transfer?.nft_info?.content?.preview?.url; + } else if (transfer && isFungible) { + imageURL = transfer?.fungible_info?.icon?.url; + } + + return imageURL; + }, + + getTransactionTransferTokenType(transfer?: TransactionTransfer): 'FUNGIBLE' | 'NFT' | undefined { + if (transfer?.fungible_info) { + return 'FUNGIBLE'; + } else if (transfer?.nft_info) { + return 'NFT'; + } + + return undefined; + }, + + getTransactionDescriptions(transaction: Transaction) { + const type = transaction?.metadata?.operationType as TransactionType; + + const transfers = transaction?.transfers; + const haveTransfer = transaction?.transfers?.length > 0; + const haveMultipleTransfers = transaction?.transfers?.length > 1; + const isSendOrReceive = type === 'send' || type === 'receive'; + const isFungible = + haveTransfer && transfers?.every(transfer => Boolean(transfer?.fungible_info)); + const [firstTransfer, secondTransfer] = transfers; + + let firstDescription = this.getTransferDescription(firstTransfer); + let secondDescription = this.getTransferDescription(secondTransfer); + + if (!haveTransfer) { + if (isSendOrReceive && isFungible) { + firstDescription = UiUtil.getTruncateString({ + string: transaction?.metadata.sentFrom, + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + }); + secondDescription = UiUtil.getTruncateString({ + string: transaction?.metadata.sentTo, + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + }); + + return [firstDescription, secondDescription]; + } + + return [transaction.metadata.status]; + } + + if (haveMultipleTransfers) { + return transfers.map(item => this.getTransferDescription(item)); + } + + let prefix = ''; + if (plusTypes.includes(type)) { + prefix = '+'; + } else if (minusTypes.includes(type)) { + prefix = '-'; + } + + firstDescription = prefix.concat(firstDescription); + + if (isSendOrReceive) { + const isSend = type === 'send'; + const address = UiUtil.getTruncateString({ + string: isSend ? transaction.metadata.sentTo : transaction.metadata.sentFrom, + charsStart: 4, + charsEnd: 4, + truncate: 'middle' + }); + const arrow = isSend ? '→' : '←'; + firstDescription = firstDescription.concat(` ${arrow} ${address}`); + } + + return [firstDescription]; + }, + + getTransferDescription(transfer?: TransactionTransfer) { + let description = ''; + + if (!transfer) { + return description; + } + + if (transfer?.nft_info) { + description = transfer?.nft_info?.name || '-'; + } else if (transfer?.fungible_info) { + description = this.getFungibleTransferDescription(transfer) ?? '-'; + } + + return description; + }, + + getFungibleTransferDescription(transfer?: TransactionTransfer) { + if (!transfer) { + return null; + } + + const quantity = this.getQuantityFixedValue(transfer?.quantity.numeric); + const description = [quantity, transfer?.fungible_info?.symbol].join(' ').trim(); + + return description; + }, + + getQuantityFixedValue(value: string | undefined) { + if (!value) { + return null; + } + + const parsedValue = parseFloat(value); + + return parsedValue.toFixed(parsedValue > 1 ? FLOAT_FIXED_VALUE : SMALL_FLOAT_FIXED_VALUE); + } +}; diff --git a/packages/ui/src/utils/TypesUtil.ts b/packages/ui/src/utils/TypesUtil.ts index 10a14ff0c..a6f323441 100644 --- a/packages/ui/src/utils/TypesUtil.ts +++ b/packages/ui/src/utils/TypesUtil.ts @@ -91,14 +91,36 @@ export type ColorType = | 'error-100' | 'fg-100' | 'fg-150' + | 'fg-175' | 'fg-200' | 'fg-250' | 'fg-275' | 'fg-300' + | 'bg-100' + | 'bg-125' + | 'bg-150' + | 'bg-175' + | 'bg-200' + | 'bg-225' + | 'bg-250' + | 'bg-275' + | 'bg-300' + | 'accent-glass-020' + | 'accent-glass-015' + | 'accent-glass-010' + | 'accent-glass-005' | 'gray-glass-020' + | 'gray-glass-010' + | 'gray-glass-005' | 'inverse-000' | 'inverse-100' - | 'success-100'; + | 'success-100' + | 'teal-100' + | 'magenta-100' + | 'indigo-100' + | 'orange-100' + | 'purple-100' + | 'yellow-100'; export type SizeType = 'xl' | 'lg' | 'md' | 'sm' | 'xs' | 'xxs'; @@ -112,6 +134,7 @@ export type IconType = | 'allWallets' | 'apple' | 'arrowBottom' + | 'arrowBottomCircle' | 'arrowLeft' | 'arrowRight' | 'arrowTop' @@ -136,6 +159,8 @@ export type IconType = | 'extension' | 'externalLink' | 'facebook' + | 'farcaster' + | 'farcasterSquare' | 'filters' | 'github' | 'google' @@ -143,9 +168,11 @@ export type IconType = | 'infoCircle' | 'mail' | 'mobile' + | 'more' | 'networkPlaceholder' | 'nftPlaceholder' | 'off' + | 'paperplane' | 'qrCode' | 'refresh' | 'search' @@ -153,14 +180,14 @@ export type IconType = | 'swapVertical' | 'telegram' | 'twitch' - | 'twitter' - | 'twitterIcon' | 'verify' | 'wallet' | 'walletSmall' | 'walletConnect' + | 'walletConnectLightBrown' | 'walletPlaceholder' - | 'warningCircle'; + | 'warningCircle' + | 'x'; export type VisualType = | 'browser' @@ -168,12 +195,15 @@ export type VisualType = | 'defi' | 'defiAlt' | 'eth' + | 'google' | 'layers' + | 'lightbulb' | 'lock' | 'login' | 'network' | 'nft' | 'noun' + | 'pencil' | 'profile' | 'system'; @@ -181,11 +211,14 @@ export type LogoType = | 'apple' | 'discord' | 'facebook' + | 'farcaster' + | 'farcasterSquare' | 'github' | 'google' + | 'more' | 'telegram' | 'twitch' - | 'twitter'; + | 'x'; export type TagType = 'main' | 'shade' | 'error' | 'success'; @@ -193,7 +226,7 @@ export type CardSelectType = 'wallet' | 'network'; export type TabOptionType = { icon: IconType; - label: string; + label?: string; }; export type SpacingType = @@ -238,3 +271,22 @@ export type TruncateOptions = { charsEnd: number; truncate: TruncateType; }; + +export type TransactionType = + | 'approve' + | 'bought' + | 'borrow' + | 'burn' + | 'cancel' + | 'claim' + | 'deploy' + | 'deposit' + | 'execute' + | 'mint' + | 'receive' + | 'repay' + | 'send' + | 'stake' + | 'trade' + | 'unstake' + | 'withdraw'; diff --git a/packages/ui/src/utils/UiUtil.ts b/packages/ui/src/utils/UiUtil.ts index e4678f6b2..bca68b003 100644 --- a/packages/ui/src/utils/UiUtil.ts +++ b/packages/ui/src/utils/UiUtil.ts @@ -68,5 +68,23 @@ export const UiUtil = { getWalletName(name: string, short = true) { return short ? name.split(' ')[0] : name; + }, + + formatNumberToLocalString(value: string | number | undefined, decimals = 2) { + if (value === undefined) { + return '0.00'; + } + + if (typeof value === 'number') { + return value.toLocaleString('en-US', { + maximumFractionDigits: decimals, + minimumFractionDigits: decimals + }); + } + + return parseFloat(value).toLocaleString('en-US', { + maximumFractionDigits: decimals, + minimumFractionDigits: decimals + }); } }; diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index a731acd30..124fe2289 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -27,12 +27,12 @@ "react-native", "wagmi" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/wagmi/readme.md b/packages/wagmi/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/wagmi/readme.md +++ b/packages/wagmi/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index aa5e3b841..0fde4faed 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -1,18 +1,29 @@ -import { formatUnits, type Hex } from 'viem'; +import { formatUnits, type Hex, parseUnits } from 'viem'; import { type GetAccountReturnType, + type GetEnsAddressReturnType, + type Connector as WagmiConnector, connect, + reconnect, disconnect, signMessage, + getAccount, switchChain, watchAccount, watchConnectors, getEnsName, getEnsAvatar as wagmiGetEnsAvatar, - getBalance + getEnsAddress as wagmiGetEnsAddress, + getBalance, + prepareTransactionRequest, + sendTransaction as wagmiSendTransaction, + waitForTransactionReceipt, + writeContract as wagmiWriteContract } from '@wagmi/core'; +import { normalize } from 'viem/ens'; import { mainnet, type Chain } from '@wagmi/core/chains'; -import { EthereumProvider, OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; +import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; +import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { type CaipAddress, type CaipNetwork, @@ -22,8 +33,11 @@ import { type LibraryOptions, type NetworkControllerClient, type PublicStateControllerState, + type SendTransactionArgs, type Token, - AppKitScaffold + AppKitScaffold, + type WriteContractArgs, + type AppKitFrameProvider } from '@reown/appkit-scaffold-react-native'; import { ConstantsUtil, @@ -31,12 +45,18 @@ import { PresetsUtil, StorageUtil } from '@reown/appkit-scaffold-utils-react-native'; -import { NetworkUtil } from '@reown/appkit-common-react-native'; -import { type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; +import { NetworkUtil, NamesUtil, ErrorUtil } from '@reown/appkit-common-react-native'; +import { + SIWEController, + getDidChainId, + getDidAddress, + type AppKitSIWEClient +} from '@reown/appkit-siwe-react-native'; import { getCaipDefaultChain, getAuthCaipNetworks, - getWalletConnectCaipNetworks + getWalletConnectCaipNetworks, + requireCaipAddress } from './utils/helpers'; import { defaultWagmiConfig } from './utils/defaultWagmiConfig'; @@ -76,7 +96,7 @@ export class AppKit extends AppKitScaffold { } if (!appKitOptions.projectId) { - throw new Error('appkit:constructor - projectId is undefined'); + throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); } const networkControllerClient: NetworkControllerClient = { @@ -90,9 +110,9 @@ export class AppKit extends AppKitScaffold { async getApprovedCaipNetworksData() { const walletChoice = await StorageUtil.getConnectedConnector(); const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; + PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; + const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const connector = wagmiConfig.connectors.find( @@ -144,9 +164,6 @@ export class AppKit extends AppKitScaffold { siweParams && Object.keys(siweParams || {}).length > 0 ) { - const { SIWEController, getDidChainId, getDidAddress } = await import( - '@reown/appkit-siwe-react-native' - ); // @ts-expect-error - setting requested chains beforehand avoids wagmi auto disconnecting the session when `connect` is called because it things chains are stale await connector.setRequestedChainsIds(siweParams.chains); const result = await provider.authenticate( @@ -227,9 +244,89 @@ export class AppKit extends AppKitScaffold { this.setClientId(null); if (siweConfig?.options?.signOutOnDisconnect) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); await SIWEController.signOut(); } + }, + + sendTransaction: async (data: SendTransactionArgs) => { + const { chainId } = getAccount(this.wagmiConfig); + + const txParams = { + account: data.address, + to: data.to, + value: data.value, + gas: data.gas, + gasPrice: data.gasPrice, + data: data.data, + chainId, + type: 'legacy' as const + }; + + await prepareTransactionRequest(this.wagmiConfig, txParams); + const tx = await wagmiSendTransaction(this.wagmiConfig, txParams); + + await waitForTransactionReceipt(this.wagmiConfig, { hash: tx, timeout: 25000 }); + + return tx; + }, + + writeContract: async (data: WriteContractArgs) => { + const caipAddress = this.getCaipAddress() || ''; + const account = requireCaipAddress(caipAddress); + const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); + + const tx = await wagmiWriteContract(wagmiConfig, { + chainId, + address: data.tokenAddress, + account, + abi: data.abi, + functionName: data.method, + args: [data.receiverAddress, data.tokenAmount] + }); + + return tx; + }, + + parseUnits, + + formatUnits, + + getEnsAddress: async (value: string) => { + try { + if (!this.wagmiConfig) { + throw new Error( + 'networkControllerClient:getApprovedCaipNetworksData - wagmiConfig is undefined' + ); + } + const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); + let ensName: boolean | GetEnsAddressReturnType = false; + let wcName: boolean | string = false; + if (NamesUtil.isReownName(value)) { + wcName = (await this.resolveReownName(value)) || false; + } + if (chainId === 1) { + ensName = await wagmiGetEnsAddress(this.wagmiConfig, { + name: normalize(value), + chainId + }); + } + + return ensName || wcName || false; + } catch { + return false; + } + }, + getEnsAvatar: async (value: string) => { + const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); + if (chainId !== mainnet.id) { + return false; + } + const avatar = await wagmiGetEnsAvatar(this.wagmiConfig, { + name: normalize(value), + chainId + }); + + return avatar || false; } }; @@ -248,7 +345,6 @@ export class AppKit extends AppKitScaffold { this.syncRequestedNetworks([...wagmiConfig.chains]); this.syncConnectors([...wagmiConfig.connectors]); - this.listenAuthConnector([...wagmiConfig.connectors]); watchConnectors(wagmiConfig, { onChange: connectors => this.syncConnectors([...connectors]) @@ -308,7 +404,6 @@ export class AppKit extends AppKitScaffold { GetAccountReturnType, 'address' | 'isConnected' | 'chainId' | 'connector' | 'isConnecting' | 'isReconnecting' >) { - this.resetAccount(); this.syncNetwork(address, chainId, isConnected); this.setLoading(!!connector && (isConnecting || isReconnecting)); @@ -324,6 +419,8 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.close(); + this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); } @@ -394,7 +491,8 @@ export class AppKit extends AppKitScaffold { if (chain) { const balance = await getBalance(this.wagmiConfig, { address, - chainId: chain.id + chainId: chain.id, + token: this.options?.tokens?.[chainId]?.address as Hex }); const formattedBalance = formatUnits(balance.value, balance.decimals); this.setBalance(formattedBalance, balance.symbol); @@ -448,9 +546,27 @@ export class AppKit extends AppKitScaffold { }); this.setConnectors(_connectors); + this.syncWalletConnectListeners(filteredConnectors); this.syncAuthConnector(filteredConnectors); } + private async syncWalletConnectListeners( + connectors: AppKitClientOptions['wagmiConfig']['connectors'] + ) { + const connector = connectors.find(({ id }) => id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID); + if (connector) { + const provider = (await connector.getProvider()) as EthereumProvider; + + provider.signer.client.core.relayer.on('relayer_connect', () => { + provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { + if (payload?.error) { + this.handleAlertError(payload?.error.message); + } + }); + }); + } + } + private async syncAuthConnector(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { const authConnector = connectors.find(({ id }) => id === ConstantsUtil.AUTH_CONNECTOR_ID); if (authConnector) { @@ -461,15 +577,27 @@ export class AppKit extends AppKitScaffold { name: 'Auth', provider }); + this.addAuthListeners(authConnector); } } - private async listenAuthConnector(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { - const connector = connectors.find(c => c.id === ConstantsUtil.AUTH_CONNECTOR_ID); - + private async addAuthListeners(connector: WagmiConnector) { const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); - if (connector && connectedConnector === 'AUTH') { + + if (connectedConnector === 'AUTH') { + // Set loader until it reconnects super.setLoading(true); } + + const provider = (await connector.getProvider()) as AppKitFrameProvider; + + provider.onSetPreferredAccount(async () => { + await reconnect(this.wagmiConfig, { connectors: [connector] }); + this.setLoading(false); + }); + + provider.setOnTimeout(async () => { + this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }); } } diff --git a/packages/wagmi/src/connectors/WalletConnectConnector.ts b/packages/wagmi/src/connectors/WalletConnectConnector.ts index 5e9245519..4c82bbb40 100644 --- a/packages/wagmi/src/connectors/WalletConnectConnector.ts +++ b/packages/wagmi/src/connectors/WalletConnectConnector.ts @@ -29,8 +29,8 @@ type WalletConnectConnector = Connector & { export type WalletConnectParameters = { /** - * WalletConnect Cloud Project ID. - * @link https://cloud.walletconnect.com/sign-in. + * Reown Cloud Project ID. + * @link https://cloud.reown.com/sign-in. */ projectId: EthereumProviderOptions['projectId']; /** @@ -65,7 +65,7 @@ export type WalletConnectParameters = { isNewChainsStale?: boolean; /** * Metadata for your app. - * @link https://docs.walletconnect.com/appkit/react-native/core/installation#implementation + * @link https://docs.reown.com/appkit/react-native/core/installation#implementation */ metadata: EthereumProviderOptions['metadata']; } & Omit< diff --git a/packages/wagmi/src/utils/helpers.ts b/packages/wagmi/src/utils/helpers.ts index 94e9828dd..791ff8544 100644 --- a/packages/wagmi/src/utils/helpers.ts +++ b/packages/wagmi/src/utils/helpers.ts @@ -7,7 +7,7 @@ import { PresetsUtil, ConstantsUtil } from '@reown/appkit-scaffold-utils-react-n import type { Connector } from '@wagmi/core'; import { EthereumProvider } from '@walletconnect/ethereum-provider'; import type { AppKitClientOptions } from '../client'; -import { http } from 'viem'; +import { http, type Hex } from 'viem'; export function getCaipDefaultChain(chain?: AppKitClientOptions['defaultChain']) { if (!chain) { @@ -56,3 +56,15 @@ export function getTransport({ chainId, projectId }: { chainId: number; projectI return http(`${RPC_URL}/v1/?chainId=${ConstantsUtil.EIP155}:${chainId}&projectId=${projectId}`); } + +export function requireCaipAddress(caipAddress: string) { + if (!caipAddress) { + throw new Error('No CAIP address provided'); + } + const account = caipAddress.split(':')[2] as Hex; + if (!account) { + throw new Error('Invalid CAIP address'); + } + + return account; +} diff --git a/packages/wallet/package.json b/packages/wallet/package.json index 62d5185b3..c3eb98eb7 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -23,12 +23,12 @@ "walletconnect", "react-native" ], - "repository": "https://github.com/WalletConnect/web3modal-react-native", + "repository": "https://github.com/reown-com/appkit-react-native", "author": "Reown (https://reown.com)", - "homepage": "https://github.com/WalletConnect/web3modal-react-native", + "homepage": "https://reown.com/appkit", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/WalletConnect/web3modal-react-native/issues" + "url": "https://github.com/reown-com/appkit-react-native/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/wallet/readme.md b/packages/wallet/readme.md index 3642f3238..60524ccdc 100644 --- a/packages/wallet/readme.md +++ b/packages/wallet/readme.md @@ -1,6 +1,6 @@ #### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) -#### 🔎 [Examples](https://github.com/WalletConnect/react-native-examples) +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) #### 🔗 [Website](https://reown.com/appkit) diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index 10dd10320..a7d20567a 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { Animated, Appearance, Linking, Platform, SafeAreaView, StyleSheet } from 'react-native'; import { WebView, type WebViewMessageEvent } from 'react-native-webview'; @@ -8,25 +8,34 @@ import { OptionsController, ModalController, type OptionsControllerState, - StorageUtil + RouterController, + WebviewController, + AccountController, + NetworkController, + ConnectionController, + SnackController } from '@reown/appkit-core-react-native'; +import { ErrorUtil } from '@reown/appkit-common-react-native'; import { useTheme, BorderRadius } from '@reown/appkit-ui-react-native'; import type { AppKitFrameProvider } from './AppKitFrameProvider'; -import { AppKitFrameConstants, AppKitFrameRpcConstants } from './AppKitFrameConstants'; +import { AppKitFrameConstants } from './AppKitFrameConstants'; +import { AppKitFrameHelpers } from './AppKitFrameHelpers'; +import type { AppKitFrameTypes } from './AppKitFrameTypes'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); -export function AuthWebview() { +function _AuthWebview() { const webviewRef = useRef(null); const Theme = useTheme(); - const { connectors } = useSnapshot(ConnectorController.state); - const { projectId, sdkVersion } = useSnapshot(OptionsController.state) as OptionsControllerState; - const [isVisible, setIsVisible] = useState(false); + const authConnector = ConnectorController.getAuthConnector(); + const { projectId, sdkVersion, sdkType } = useSnapshot( + OptionsController.state + ) as OptionsControllerState; + const { frameViewVisible } = useSnapshot(WebviewController.state); const [isBackdropVisible, setIsBackdropVisible] = useState(false); const animatedHeight = useRef(new Animated.Value(0)); const backdropOpacity = useRef(new Animated.Value(0)); const webviewOpacity = useRef(new Animated.Value(0)); - const authConnector = connectors.find(c => c.type === 'AUTH'); const provider = authConnector?.provider as AppKitFrameProvider; const parseMessage = (event: WebViewMessageEvent) => { @@ -45,30 +54,11 @@ export function AuthWebview() { }; const handleMessage = (e: WebViewMessageEvent) => { - let event = parseMessage(e); - - provider.onMessage(event); - - provider.onRpcRequest(event, () => { - if (!AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(event.payload.method)) { - setIsVisible(true); - } - }); - - provider.onRpcResponse(event, () => { - setIsVisible(false); - }); - - provider.onIsConnected(event, () => { - ConnectorController.setAuthLoading(false); - ModalController.setLoading(false); - }); - - provider.onNotConnected(event, () => { - ConnectorController.setAuthLoading(false); - ModalController.setLoading(false); - StorageUtil.removeConnectedConnector(); - }); + try { + let event = parseMessage(e); + + provider.onMessage(event); + } catch (error) {} }; const show = animatedHeight.current.interpolate({ @@ -76,32 +66,88 @@ export function AuthWebview() { outputRange: ['0%', '80%'] }); + useEffect(() => {}, [provider]); + useEffect(() => { Animated.timing(animatedHeight.current, { - toValue: isVisible ? 1 : 0, + toValue: frameViewVisible ? 1 : 0, duration: 200, useNativeDriver: false }).start(); Animated.timing(webviewOpacity.current, { - toValue: isVisible ? 1 : 0, + toValue: frameViewVisible ? 1 : 0, duration: 300, useNativeDriver: false }).start(); - if (isVisible) { + if (frameViewVisible) { setIsBackdropVisible(true); } Animated.timing(backdropOpacity.current, { - toValue: isVisible ? 0.7 : 0, + toValue: frameViewVisible ? 0.7 : 0, duration: 300, useNativeDriver: false - }).start(() => setIsBackdropVisible(isVisible)); - }, [animatedHeight, backdropOpacity, isVisible, setIsBackdropVisible]); + }).start(() => setIsBackdropVisible(frameViewVisible)); + }, [animatedHeight, backdropOpacity, frameViewVisible, setIsBackdropVisible]); useEffect(() => { - provider?.setWebviewRef(webviewRef); + if (provider) { + provider.setWebviewRef(webviewRef); + provider.onRpcRequest((request: AppKitFrameTypes.RPCRequest) => { + if (AppKitFrameHelpers.checkIfRequestExists(request)) { + if (!AppKitFrameHelpers.checkIfRequestIsAllowed(request)) { + WebviewController.setFrameViewVisible(true); + } + } + }); + + provider.onRpcSuccess((_, request) => { + const isSafeRequest = AppKitFrameHelpers.checkIfRequestIsSafe(request); + if (isSafeRequest) { + return; + } + + if (RouterController.state.transactionStack.length === 0) { + ModalController.close(); + } else { + RouterController?.popTransactionStack(); + } + WebviewController.setFrameViewVisible(false); + }); + + provider.onRpcError(() => { + if (ModalController.state.open) { + if (RouterController.state.transactionStack.length === 0) { + ModalController.close(); + } else { + RouterController?.popTransactionStack(true); + } + } + WebviewController.setFrameViewVisible(false); + }); + + provider.onIsConnected(({ smartAccountDeployed, preferredAccountType }) => { + provider.getSmartAccountEnabledNetworks(); + AccountController.setPreferredAccountType(preferredAccountType); + AccountController.setSmartAccountDeployed(smartAccountDeployed); + ConnectorController.setAuthLoading(false); + ModalController.setLoading(false); + }); + + provider.onNotConnected(() => { + ConnectorController.setAuthLoading(false); + ModalController.setLoading(false); + if (ConnectorController.state.connectedConnector === 'AUTH') { + ConnectionController.disconnect(); + } + }); + + provider.onGetSmartAccountEnabledNetworks(({ smartAccountEnabledNetworks }) => { + return NetworkController.setSmartAccountEnabledNetworks(smartAccountEnabledNetworks); + }); + } }, [provider, webviewRef]); return provider ? ( @@ -153,20 +199,26 @@ export function AuthWebview() { '--w3m-background': Theme['bg-100'] } }); - provider?.syncDappData?.({ projectId, sdkVersion }); + provider?.syncDappData?.({ projectId, sdkVersion, sdkType }); provider?.onWebviewLoaded(); + provider?.isConnected(); }, 1500); } }} onError={({ nativeEvent }) => { provider?.onWebviewLoadError(nativeEvent.description); }} + onHttpError={() => { + SnackController.showInternalError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }} /> ) : null; } +export const AuthWebview = memo(_AuthWebview); + const styles = StyleSheet.create({ backdrop: { position: 'absolute', diff --git a/packages/wallet/src/AppKitFrameConstants.ts b/packages/wallet/src/AppKitFrameConstants.ts index a3e28fcfa..98bd1afed 100644 --- a/packages/wallet/src/AppKitFrameConstants.ts +++ b/packages/wallet/src/AppKitFrameConstants.ts @@ -1,8 +1,8 @@ export const AppKitFrameConstants = { SECURE_SITE_SDK: 'https://secure-mobile.walletconnect.com/mobile-sdk', SECURE_SITE_ORIGIN: 'https://secure.walletconnect.com', - SECURE_SITE_DASHBOARD: `https://secure.walletconnect.com/dashboard`, - SECURE_SITE_ICON: `https://secure.walletconnect.com/images/favicon.png`, + SECURE_SITE_DASHBOARD: `https://secure.reown.com/dashboard`, + SECURE_SITE_ICON: `https://secure.reown.com/images/favicon.png`, APP_EVENT_KEY: '@w3m-app/', FRAME_EVENT_KEY: '@w3m-frame/', RPC_METHOD_KEY: 'RPC_', @@ -13,17 +13,25 @@ export const AppKitFrameConstants = { LAST_USED_CHAIN_KEY: 'LAST_USED_CHAIN_KEY', LAST_EMAIL_LOGIN_TIME: 'LAST_EMAIL_LOGIN_TIME', // Also present in core/src/utils/StorageUtil.ts EMAIL: 'EMAIL', + SOCIAL_USERNAME: 'SOCIAL_USERNAME', + SMART_ACCOUNT_ENABLED_NETWORKS: 'SMART_ACCOUNT_ENABLED_NETWORKS', FRAME_MESSAGES_HANDLER: ` window.addEventListener('message', ({ data, origin }) => { - window.ReactNativeWebView.postMessage(JSON.stringify({ ...data, origin })) - }) + window.ReactNativeWebView.postMessage( + JSON.stringify({ ...data, origin }, (key, value) => + typeof value === 'bigint' ? value.toString() : value + ) + ); + }); `, APP_SWITCH_NETWORK: '@w3m-app/SWITCH_NETWORK', APP_CONNECT_EMAIL: '@w3m-app/CONNECT_EMAIL', APP_CONNECT_DEVICE: '@w3m-app/CONNECT_DEVICE', APP_CONNECT_OTP: '@w3m-app/CONNECT_OTP', + APP_CONNECT_SOCIAL: '@w3m-app/CONNECT_SOCIAL', + APP_GET_SOCIAL_REDIRECT_URI: '@w3m-app/GET_SOCIAL_REDIRECT_URI', APP_GET_USER: '@w3m-app/GET_USER', APP_SIGN_OUT: '@w3m-app/SIGN_OUT', APP_IS_CONNECTED: '@w3m-app/IS_CONNECTED', @@ -35,6 +43,10 @@ export const AppKitFrameConstants = { APP_AWAIT_UPDATE_EMAIL: '@w3m-app/AWAIT_UPDATE_EMAIL', APP_SYNC_THEME: '@w3m-app/SYNC_THEME', APP_SYNC_DAPP_DATA: '@w3m-app/SYNC_DAPP_DATA', + APP_CONNECT_FARCASTER: '@w3m-app/CONNECT_FARCASTER', + APP_GET_FARCASTER_URI: '@w3m-app/GET_FARCASTER_URI', + APP_GET_SMART_ACCOUNT_ENABLED_NETWORKS: '@w3m-app/GET_SMART_ACCOUNT_ENABLED_NETWORKS', + APP_SET_PREFERRED_ACCOUNT: '@w3m-app/SET_PREFERRED_ACCOUNT', FRAME_SWITCH_NETWORK_ERROR: '@w3m-frame/SWITCH_NETWORK_ERROR', FRAME_SWITCH_NETWORK_SUCCESS: '@w3m-frame/SWITCH_NETWORK_SUCCESS', @@ -44,8 +56,16 @@ export const AppKitFrameConstants = { FRAME_CONNECT_DEVICE_SUCCESS: '@w3m-frame/CONNECT_DEVICE_SUCCESS', FRAME_CONNECT_OTP_SUCCESS: '@w3m-frame/CONNECT_OTP_SUCCESS', FRAME_CONNECT_OTP_ERROR: '@w3m-frame/CONNECT_OTP_ERROR', + FRAME_CONNECT_SOCIAL_SUCCESS: '@w3m-frame/CONNECT_SOCIAL_SUCCESS', + FRAME_CONNECT_SOCIAL_ERROR: '@w3m-frame/CONNECT_SOCIAL_ERROR', + FRAME_CONNECT_FARCASTER_SUCCESS: '@w3m-frame/CONNECT_FARCASTER_SUCCESS', + FRAME_CONNECT_FARCASTER_ERROR: '@w3m-frame/CONNECT_FARCASTER_ERROR', + FRAME_GET_FARCASTER_URI_SUCCESS: '@w3m-frame/GET_FARCASTER_URI_SUCCESS', + FRAME_GET_FARCASTER_URI_ERROR: '@w3m-frame/GET_FARCASTER_URI_ERROR', FRAME_GET_USER_SUCCESS: '@w3m-frame/GET_USER_SUCCESS', FRAME_GET_USER_ERROR: '@w3m-frame/GET_USER_ERROR', + FRAME_GET_SOCIAL_REDIRECT_URI_SUCCESS: '@w3m-frame/GET_SOCIAL_REDIRECT_URI_SUCCESS', + FRAME_GET_SOCIAL_REDIRECT_URI_ERROR: '@w3m-frame/GET_SOCIAL_REDIRECT_URI_ERROR', FRAME_SIGN_OUT_SUCCESS: '@w3m-frame/SIGN_OUT_SUCCESS', FRAME_SIGN_OUT_ERROR: '@w3m-frame/SIGN_OUT_ERROR', FRAME_IS_CONNECTED_SUCCESS: '@w3m-frame/IS_CONNECTED_SUCCESS', @@ -64,7 +84,13 @@ export const AppKitFrameConstants = { FRAME_SYNC_THEME_SUCCESS: '@w3m-frame/SYNC_THEME_SUCCESS', FRAME_SYNC_THEME_ERROR: '@w3m-frame/SYNC_THEME_ERROR', FRAME_SYNC_DAPP_DATA_SUCCESS: '@w3m-frame/SYNC_DAPP_DATA_SUCCESS', - FRAME_SYNC_DAPP_DATA_ERROR: '@w3m-frame/SYNC_DAPP_DATA_ERROR' + FRAME_SYNC_DAPP_DATA_ERROR: '@w3m-frame/SYNC_DAPP_DATA_ERROR', + FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS: + '@w3m-frame/GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS', + FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_ERROR: + '@w3m-frame/GET_SMART_ACCOUNT_ENABLED_NETWORKS_ERROR', + FRAME_SET_PREFERRED_ACCOUNT_SUCCESS: '@w3m-frame/SET_PREFERRED_ACCOUNT_SUCCESS', + FRAME_SET_PREFERRED_ACCOUNT_ERROR: '@w3m-frame/SET_PREFERRED_ACCOUNT_ERROR' } as const; export const AppKitFrameRpcConstants = { @@ -102,10 +128,22 @@ export const AppKitFrameRpcConstants = { 'eth_newPendingTransactionFilter', 'eth_sendRawTransaction', 'eth_syncing', - 'eth_uninstallFilter' + 'eth_uninstallFilter', + 'wallet_getCapabilities', + 'wallet_getCallsStatus' + ], + NOT_SAFE_RPC_METHODS: [ + 'personal_sign', + 'eth_signTypedData_v4', + 'eth_sendTransaction', + 'wallet_sendCalls', + 'wallet_grantPermissions' ], - NOT_SAFE_RPC_METHODS: ['personal_sign', 'eth_signTypedData_v4', 'eth_sendTransaction'], GET_CHAIN_ID: 'eth_chainId', RPC_METHOD_NOT_ALLOWED_MESSAGE: 'Requested RPC call is not allowed', - RPC_METHOD_NOT_ALLOWED_UI_MESSAGE: 'Action not allowed' + RPC_METHOD_NOT_ALLOWED_UI_MESSAGE: 'Action not allowed', + ACCOUNT_TYPES: { + EOA: 'eoa', + SMART_ACCOUNT: 'smartAccount' + } as const }; diff --git a/packages/wallet/src/AppKitFrameHelpers.ts b/packages/wallet/src/AppKitFrameHelpers.ts index 297a5052d..212fd6219 100644 --- a/packages/wallet/src/AppKitFrameHelpers.ts +++ b/packages/wallet/src/AppKitFrameHelpers.ts @@ -23,22 +23,18 @@ export const AppKitFrameHelpers = { } }, - checkIfRequestExists(request: unknown) { - const method = this.getRequestMethod(request); - + checkIfRequestExists(request: AppKitFrameTypes.RPCRequest) { return ( - AppKitFrameRpcConstants.NOT_SAFE_RPC_METHODS.includes(method) || - AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(method) + AppKitFrameRpcConstants.NOT_SAFE_RPC_METHODS.includes(request.method) || + AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(request.method) ); }, - getRequestMethod(request: unknown) { - return (request as { payload: AppKitFrameTypes.RPCRequest })?.payload?.method; + checkIfRequestIsAllowed(request: AppKitFrameTypes.RPCRequest) { + return AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(request.method); }, - checkIfRequestIsAllowed(request: unknown) { - const method = this.getRequestMethod(request); - - return AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(method); + checkIfRequestIsSafe(request: AppKitFrameTypes.RPCRequest) { + return AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(request.method); } }; diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index df75457ac..b9d1fd2c0 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -1,5 +1,6 @@ +import { EventEmitter } from 'events'; import type { RefObject } from 'react'; -import type WebView from 'react-native-webview'; +import WebView from 'react-native-webview'; import { CoreHelperUtil } from '@reown/appkit-core-react-native'; import type { AppKitFrameTypes } from './AppKitFrameTypes'; import { AppKitFrameConstants, AppKitFrameRpcConstants } from './AppKitFrameConstants'; @@ -7,25 +8,10 @@ import { AppKitFrameStorage } from './AppKitFrameStorage'; import { AppKitFrameHelpers } from './AppKitFrameHelpers'; import { AppKitFrameSchema } from './AppKitFrameSchema'; import { AuthWebview } from './AppKitAuthWebview'; +import { AppKitWebview } from './AppKitWebview'; // -- Types ----------------------------------------------------------- -type Resolver = { resolve: (value: T) => void; reject: (reason?: unknown) => void } | undefined; -type ConnectEmailResolver = Resolver; -type ConnectDeviceResolver = Resolver; -type ConnectOtpResolver = Resolver; -type ConnectResolver = Resolver; -type DisconnectResolver = Resolver; -type IsConnectedResolver = Resolver; -type GetChainIdResolver = Resolver; -type SwitchChainResolver = Resolver; -type RpcRequestResolver = Resolver; -type UpdateEmailResolver = Resolver; -type UpdateEmailPrimaryOtpResolver = Resolver; -type UpdateEmailSecondaryOtpResolver = Resolver< - AppKitFrameTypes.Responses['FrameUpdateEmailSecondaryOtpResolver'] ->; -type SyncThemeResolver = Resolver; -type SyncDappDataResolver = Resolver; +type AppEventType = Omit; // -- Provider -------------------------------------------------------- export class AppKitFrameProvider { @@ -37,6 +23,19 @@ export class AppKitFrameProvider { private email: string | undefined; + private username: string | undefined; + + private rpcRequestHandler?: (request: AppKitFrameTypes.RPCRequest) => void; + + private rpcSuccessHandler?: ( + response: AppKitFrameTypes.RPCResponse, + request: AppKitFrameTypes.RPCRequest + ) => void; + + private rpcErrorHandler?: (error: Error, request: AppKitFrameTypes.RPCRequest) => void; + + private onTimeout?: () => void; + public webviewLoadPromise: Promise; public webviewLoadPromiseResolver: @@ -48,33 +47,13 @@ export class AppKitFrameProvider { public AuthView = AuthWebview; - private connectEmailResolver: ConnectEmailResolver = undefined; - - private connectDeviceResolver: ConnectDeviceResolver = undefined; - - private connectOtpResolver: ConnectOtpResolver | undefined = undefined; - - private connectResolver: ConnectResolver = undefined; - - private disconnectResolver: DisconnectResolver = undefined; - - private isConnectedResolver: IsConnectedResolver = undefined; - - private getChainIdResolver: GetChainIdResolver = undefined; - - private switchChainResolver: SwitchChainResolver = undefined; - - private rpcRequestResolver: RpcRequestResolver = undefined; - - private updateEmailResolver: UpdateEmailResolver = undefined; - - private updateEmailPrimaryOtpResolver: UpdateEmailPrimaryOtpResolver = undefined; - - private updateEmailSecondaryOtpResolver: UpdateEmailSecondaryOtpResolver = undefined; + public Webview = AppKitWebview; - private syncThemeResolver: SyncThemeResolver = undefined; + private openRpcRequests: Array< + AppKitFrameTypes.RPCRequest & { abortController: AbortController } + > = []; - private syncDappDataResolver: SyncDappDataResolver = undefined; + public events: EventEmitter = new EventEmitter(); public constructor(projectId: string, metadata: AppKitFrameTypes.Metadata) { this.webviewLoadPromise = new Promise((resolve, reject) => { @@ -83,81 +62,17 @@ export class AppKitFrameProvider { this.metadata = metadata; this.projectId = projectId; - this.getAsyncEmail().then(email => { - this.email = email; - }); + this.loadAsyncValues(); + + this.events.setMaxListeners(Number.POSITIVE_INFINITY); } public setWebviewRef(webviewRef: RefObject) { this.webviewRef = webviewRef; } - public onMessage(e: AppKitFrameTypes.FrameEvent) { - this.onFrameEvent(e, event => { - // console.log('💻 received', e); // eslint-disable-line no-console - switch (event.type) { - case AppKitFrameConstants.FRAME_CONNECT_EMAIL_SUCCESS: - return this.onConnectEmailSuccess(event); - case AppKitFrameConstants.FRAME_CONNECT_EMAIL_ERROR: - return this.onConnectEmailError(event); - case AppKitFrameConstants.FRAME_CONNECT_DEVICE_SUCCESS: - return this.onConnectDeviceSuccess(); - case AppKitFrameConstants.FRAME_CONNECT_DEVICE_ERROR: - return this.onConnectDeviceError(event); - case AppKitFrameConstants.FRAME_CONNECT_OTP_SUCCESS: - return this.onConnectOtpSuccess(); - case AppKitFrameConstants.FRAME_CONNECT_OTP_ERROR: - return this.onConnectOtpError(event); - case AppKitFrameConstants.FRAME_GET_USER_SUCCESS: - return this.onConnectSuccess(event); - case AppKitFrameConstants.FRAME_GET_USER_ERROR: - return this.onConnectError(event); - case AppKitFrameConstants.FRAME_IS_CONNECTED_SUCCESS: - return this.onIsConnectedSuccess(event); - case AppKitFrameConstants.FRAME_IS_CONNECTED_ERROR: - return this.onIsConnectedError(event); - case AppKitFrameConstants.FRAME_GET_CHAIN_ID_SUCCESS: - return this.onGetChainIdSuccess(event); - case AppKitFrameConstants.FRAME_GET_CHAIN_ID_ERROR: - return this.onGetChainIdError(event); - case AppKitFrameConstants.FRAME_SIGN_OUT_SUCCESS: - return this.onSignOutSuccess(); - case AppKitFrameConstants.FRAME_SIGN_OUT_ERROR: - return this.onSignOutError(event); - case AppKitFrameConstants.FRAME_SWITCH_NETWORK_SUCCESS: - return this.onSwitchChainSuccess(event); - case AppKitFrameConstants.FRAME_SWITCH_NETWORK_ERROR: - return this.onSwitchChainError(event); - case AppKitFrameConstants.FRAME_RPC_REQUEST_SUCCESS: - return this.onRpcRequestSuccess(event); - case AppKitFrameConstants.FRAME_RPC_REQUEST_ERROR: - return this.onRpcRequestError(event); - case AppKitFrameConstants.FRAME_SESSION_UPDATE: - return this.onSessionUpdate(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_SUCCESS: - return this.onUpdateEmailSuccess(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_ERROR: - return this.onUpdateEmailError(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_PRIMARY_OTP_SUCCESS: - return this.onUpdateEmailPrimaryOtpSuccess(); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_PRIMARY_OTP_ERROR: - return this.onUpdateEmailPrimaryOtpError(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_SECONDARY_OTP_SUCCESS: - return this.onUpdateEmailSecondaryOtpSuccess(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_SECONDARY_OTP_ERROR: - return this.onUpdateEmailSecondaryOtpError(event); - case AppKitFrameConstants.FRAME_SYNC_THEME_SUCCESS: - return this.onSyncThemeSuccess(); - case AppKitFrameConstants.FRAME_SYNC_THEME_ERROR: - return this.onSyncThemeError(event); - case AppKitFrameConstants.FRAME_SYNC_DAPP_DATA_SUCCESS: - return this.onSyncDappDataSuccess(); - case AppKitFrameConstants.FRAME_SYNC_DAPP_DATA_ERROR: - return this.onSyncDappDataError(event); - default: - return null; - } - }); + public onMessage(event: AppKitFrameTypes.FrameEvent) { + this.events.emit('message', event); } public onWebviewLoaded() { @@ -168,6 +83,10 @@ export class AppKitFrameProvider { this.webviewLoadPromiseResolver?.reject(error); } + public setOnTimeout(callback: () => void) { + this.onTimeout = callback; + } + // -- Extended Methods ------------------------------------------------ public getSecureSiteURL() { return `${AppKitFrameConstants.SECURE_SITE_SDK}?projectId=${this.projectId}`; @@ -185,135 +104,219 @@ export class AppKitFrameProvider { return { 'X-Bundle-Id': CoreHelperUtil.getBundleId() }; } - public async getLoginEmailUsed() { - const email = await AppKitFrameStorage.get(AppKitFrameConstants.EMAIL_LOGIN_USED_KEY); - - return Boolean(email); - } - public getEmail() { return this.email; } + public getUsername() { + return this.username; + } + public rejectRpcRequest() { - this.rpcRequestResolver?.reject(); + try { + this.openRpcRequests.forEach(({ abortController, method }) => { + if (!AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(method)) { + abortController.abort(); + } + }); + this.openRpcRequests = []; + } catch (e) {} + } + + public getEventEmitter() { + return this.events; } public async connectEmail(payload: AppKitFrameTypes.Requests['AppConnectEmailRequest']) { await this.webviewLoadPromise; await AppKitFrameHelpers.checkIfAllowedToTriggerEmail(); - this.postAppEvent({ type: AppKitFrameConstants.APP_CONNECT_EMAIL, payload }); - return new Promise( - (resolve, reject) => { - this.connectEmailResolver = { resolve, reject }; - } - ); + const response = await this.appEvent<'ConnectEmail'>({ + type: AppKitFrameConstants.APP_CONNECT_EMAIL, + payload + } as AppKitFrameTypes.AppEvent); + + this.setNewLastEmailLoginTime(); + + return response; } public async connectDevice() { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_CONNECT_DEVICE }); - return new Promise((resolve, reject) => { - this.connectDeviceResolver = { resolve, reject }; - }); + const response = await this.appEvent<'ConnectDevice'>({ + type: AppKitFrameConstants.APP_CONNECT_DEVICE + } as AppKitFrameTypes.AppEvent); + + return response; + } + + public async connectSocial(uri: string) { + const response = await this.appEvent<'ConnectSocial'>({ + type: AppKitFrameConstants.APP_CONNECT_SOCIAL, + payload: { uri } + } as AppKitFrameTypes.AppEvent); + + if (response.userName) { + this.setSocialLoginSuccess(response.userName); + } + + return response; + } + + public async getFarcasterUri() { + const response = await this.appEvent<'GetFarcasterUri'>({ + type: AppKitFrameConstants.APP_GET_FARCASTER_URI + } as AppKitFrameTypes.AppEvent); + + return response; + } + + public async connectFarcaster() { + const response = await this.appEvent<'ConnectFarcaster'>({ + type: AppKitFrameConstants.APP_CONNECT_FARCASTER + } as AppKitFrameTypes.AppEvent); + + if (response.userName) { + this.setSocialLoginSuccess(response.userName); + } + + return response; } public async connectOtp(payload: AppKitFrameTypes.Requests['AppConnectOtpRequest']) { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_CONNECT_OTP, payload }); - return new Promise((resolve, reject) => { - this.connectOtpResolver = { resolve, reject }; - }); + const response = await this.appEvent<'ConnectOtp'>({ + type: AppKitFrameConstants.APP_CONNECT_OTP, + payload + } as AppKitFrameTypes.AppEvent); + + return response; } public async isConnected() { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'IsConnected'>({ type: AppKitFrameConstants.APP_IS_CONNECTED, payload: undefined - }); + } as AppKitFrameTypes.AppEvent); - return new Promise( - (resolve, reject) => { - this.isConnectedResolver = { resolve, reject }; - } - ); + if (!response.isConnected) { + this.deleteLoginCache(); + } + + return response; } public async getChainId() { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_GET_CHAIN_ID }); - return new Promise((resolve, reject) => { - this.getChainIdResolver = { resolve, reject }; - }); + const response = await this.appEvent<'GetChainId'>({ + type: AppKitFrameConstants.APP_GET_CHAIN_ID + } as AppKitFrameTypes.AppEvent); + + this.setLastUsedChainId(response.chainId); + + return response; + } + + public async getSocialRedirectUri( + payload: AppKitFrameTypes.Requests['AppGetSocialRedirectUriRequest'] + ) { + return this.appEvent<'GetSocialRedirectUri'>({ + type: AppKitFrameConstants.APP_GET_SOCIAL_REDIRECT_URI, + payload + } as AppKitFrameTypes.AppEvent); } public async updateEmail(payload: AppKitFrameTypes.Requests['AppUpdateEmailRequest']) { await this.webviewLoadPromise; await AppKitFrameHelpers.checkIfAllowedToTriggerEmail(); - this.postAppEvent({ type: AppKitFrameConstants.APP_UPDATE_EMAIL, payload }); - return new Promise( - (resolve, reject) => { - this.updateEmailResolver = { resolve, reject }; - } - ); + const response = await this.appEvent<'UpdateEmail'>({ + type: AppKitFrameConstants.APP_UPDATE_EMAIL, + payload + } as AppKitFrameTypes.AppEvent); + + this.setNewLastEmailLoginTime(); + + return response; } public async updateEmailPrimaryOtp( payload: AppKitFrameTypes.Requests['AppUpdateEmailPrimaryOtpRequest'] ) { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'UpdateEmailPrimaryOtp'>({ type: AppKitFrameConstants.APP_UPDATE_EMAIL_PRIMARY_OTP, payload - }); + } as AppKitFrameTypes.AppEvent); - return new Promise((resolve, reject) => { - this.updateEmailPrimaryOtpResolver = { resolve, reject }; - }); + return response; } public async updateEmailSecondaryOtp( payload: AppKitFrameTypes.Requests['AppUpdateEmailSecondaryOtpRequest'] ) { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'UpdateEmailSecondaryOtp'>({ type: AppKitFrameConstants.APP_UPDATE_EMAIL_SECONDARY_OTP, payload - }); + } as AppKitFrameTypes.AppEvent); - return new Promise( - (resolve, reject) => { - this.updateEmailSecondaryOtpResolver = { resolve, reject }; - } - ); + this.setEmailLoginSuccess(response.newEmail); + + return response; } public async syncTheme(payload: AppKitFrameTypes.Requests['AppSyncThemeRequest']) { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_SYNC_THEME, payload }); - return new Promise((resolve, reject) => { - this.syncThemeResolver = { resolve, reject }; - }); + const response = await this.appEvent<'SyncTheme'>({ + type: AppKitFrameConstants.APP_SYNC_THEME, + payload + } as AppKitFrameTypes.AppEvent); + + return response; } public async syncDappData(payload: AppKitFrameTypes.Requests['AppSyncDappDataRequest']) { await this.webviewLoadPromise; const metadata = payload.metadata ?? this.metadata; - this.postAppEvent({ + + const response = await this.appEvent<'SyncDappData'>({ type: AppKitFrameConstants.APP_SYNC_DAPP_DATA, payload: { ...payload, metadata } - }); + } as AppKitFrameTypes.AppEvent); - return new Promise((resolve, reject) => { - this.syncDappDataResolver = { resolve, reject }; - }); + return response; + } + + public async getSmartAccountEnabledNetworks() { + try { + const response = await this.appEvent<'GetSmartAccountEnabledNetworks'>({ + type: AppKitFrameConstants.APP_GET_SMART_ACCOUNT_ENABLED_NETWORKS + } as AppKitFrameTypes.AppEvent); + this.persistSmartAccountEnabledNetworks(response.smartAccountEnabledNetworks); + + return response; + } catch (error) { + this.persistSmartAccountEnabledNetworks([]); + throw error; + } + } + + public async setPreferredAccount(type: AppKitFrameTypes.AccountType) { + const response = await this.appEvent<'SetPreferredAccount'>({ + type: AppKitFrameConstants.APP_SET_PREFERRED_ACCOUNT, + payload: { type } + } as AppKitFrameTypes.AppEvent); + + return response; } // -- Provider Methods ------------------------------------------------ @@ -322,80 +325,91 @@ export class AppKitFrameProvider { const chainId = payload?.chainId ?? lastUsedChain ?? 1; await this.webviewLoadPromise; - this.postAppEvent({ + const response = await this.appEvent<'GetUser'>({ type: AppKitFrameConstants.APP_GET_USER, - payload: { chainId } - }); + payload: { ...payload, chainId } + } as AppKitFrameTypes.AppEvent); - return new Promise((resolve, reject) => { - this.connectResolver = { resolve, reject }; - }); + if (response.email) { + this.setEmailLoginSuccess(response.email); + } + + this.setLastUsedChainId(Number(response.chainId)); + + return response; } public async switchNetwork(chainId: number) { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'SwitchNetwork'>({ type: AppKitFrameConstants.APP_SWITCH_NETWORK, payload: { chainId } - }); + } as AppKitFrameTypes.AppEvent); - return new Promise( - (resolve, reject) => { - this.switchChainResolver = { resolve, reject }; - } - ); + this.setLastUsedChainId(response.chainId); + + return response; } public async disconnect() { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_SIGN_OUT }); - return new Promise((resolve, reject) => { - this.disconnectResolver = { resolve, reject }; + const response = await this.appEvent<'SignOut'>({ + type: AppKitFrameConstants.APP_SIGN_OUT }); + + this.deleteLoginCache(); + + return response; } - public async request(req: AppKitFrameTypes.RPCRequest) { - if (AppKitFrameRpcConstants.GET_CHAIN_ID === req.method) { - return await this.getLastUsedChainId(); + public async request(req: AppKitFrameTypes.RPCRequest): Promise { + try { + if (AppKitFrameRpcConstants.GET_CHAIN_ID === req.method) { + return this.getLastUsedChainId(); + } + + this.rpcRequestHandler?.(req); + const response = await this.appEvent<'Rpc'>({ + type: AppKitFrameConstants.APP_RPC_REQUEST, + payload: req + } as AppKitFrameTypes.AppEvent); + this.rpcSuccessHandler?.(response, req); + + return response; + } catch (error) { + this.rpcErrorHandler?.(error as Error, req); + throw error; } - await this.webviewLoadPromise; - this.postAppEvent({ - type: AppKitFrameConstants.APP_RPC_REQUEST, - payload: req - }); + } - return new Promise((resolve, reject) => { - this.rpcRequestResolver = { resolve, reject }; - }); + public onRpcRequest(callback: (request: AppKitFrameTypes.RPCRequest) => void) { + this.rpcRequestHandler = callback; } - public onRpcRequest(event: AppKitFrameTypes.AppEvent, callback: (request: unknown) => void) { - this.onAppEvent(event, appEvent => { - if (appEvent.type.includes(AppKitFrameConstants.RPC_METHOD_KEY)) { - callback(appEvent); - } - }); + public onRpcSuccess( + callback: (response: AppKitFrameTypes.FrameEvent, request: AppKitFrameTypes.RPCRequest) => void + ) { + this.rpcSuccessHandler = callback; } - public onRpcResponse(event: AppKitFrameTypes.FrameEvent, callback: (request: unknown) => void) { - this.onFrameEvent(event, frameEvent => { - if (frameEvent.type.includes(AppKitFrameConstants.RPC_METHOD_KEY)) { - callback(frameEvent); - } - }); + public onRpcError(callback: (error: Error) => void) { + this.rpcErrorHandler = callback; } - public onIsConnected(event: AppKitFrameTypes.FrameEvent, callback: () => void) { - this.onFrameEvent(event, frameEvent => { + public onIsConnected( + callback: (response: AppKitFrameTypes.Responses['FrameGetUserResponse']) => void + ) { + this.onFrameEvent(frameEvent => { if (frameEvent.type === AppKitFrameConstants.FRAME_GET_USER_SUCCESS) { - callback(); + callback(frameEvent.payload); } }); } - public onNotConnected(event: AppKitFrameTypes.FrameEvent, callback: () => void) { - this.onFrameEvent(event, frameEvent => { + public onNotConnected(callback: () => void) { + this.onFrameEvent(frameEvent => { if (frameEvent.type === AppKitFrameConstants.FRAME_IS_CONNECTED_ERROR) { callback(); } @@ -408,191 +422,37 @@ export class AppKitFrameProvider { }); } - // -- Promise Handlers ------------------------------------------------ - private onConnectEmailSuccess( - event: Extract - ) { - this.connectEmailResolver?.resolve(event.payload); - this.setNewLastEmailLoginTime(); - } - - private onConnectEmailError( - event: Extract - ) { - this.connectEmailResolver?.reject(event.payload.message); - } - - private onConnectDeviceSuccess() { - this.connectDeviceResolver?.resolve(undefined); - } - - private onConnectDeviceError( - event: Extract + public onGetSmartAccountEnabledNetworks( + callback: ( + response: AppKitFrameTypes.Responses['FrameGetSmartAccountEnabledNetworksResponse'] + ) => void ) { - this.connectDeviceResolver?.reject(event.payload.message); - } - - private onConnectOtpSuccess() { - this.connectOtpResolver?.resolve(undefined); - } - - private onConnectOtpError( - event: Extract - ) { - this.connectOtpResolver?.reject(event.payload.message); - } - - private onConnectSuccess( - event: Extract - ) { - this.setEmailLoginSuccess(event.payload.email); - this.setLastUsedChainId(event.payload.chainId); - this.connectResolver?.resolve(event.payload); - } - - private onConnectError( - event: Extract - ) { - this.connectResolver?.reject(event.payload.message); - } - - private onIsConnectedSuccess( - event: Extract - ) { - if (!event.payload.isConnected) { - this.deleteEmailLoginCache(); - } - this.isConnectedResolver?.resolve(event.payload); - } - - private onIsConnectedError( - event: Extract - ) { - this.isConnectedResolver?.reject(event.payload.message); - } - - private onGetChainIdSuccess( - event: Extract - ) { - this.setLastUsedChainId(event.payload.chainId); - this.getChainIdResolver?.resolve(event.payload); - } - - private onGetChainIdError( - event: Extract - ) { - this.getChainIdResolver?.reject(event.payload.message); - } - - private onSignOutSuccess() { - this.disconnectResolver?.resolve(undefined); - this.deleteEmailLoginCache(); - } - - private onSignOutError( - event: Extract - ) { - this.disconnectResolver?.reject(event.payload.message); - } - - private onSwitchChainSuccess( - event: Extract - ) { - this.setLastUsedChainId(event.payload.chainId); - this.switchChainResolver?.resolve(event.payload); - } - - private onSwitchChainError( - event: Extract - ) { - this.switchChainResolver?.reject(event.payload.message); - } - - private onRpcRequestSuccess( - event: Extract - ) { - this.rpcRequestResolver?.resolve(event.payload); + this.onFrameEvent(frameEvent => { + if ( + frameEvent.type === AppKitFrameConstants.FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS + ) { + callback(frameEvent.payload); + } + }); } - private onRpcRequestError( - event: Extract + public onSetPreferredAccount( + callback: (response: AppKitFrameTypes.Responses['FrameSetPreferredAccountResponse']) => void ) { - this.rpcRequestResolver?.reject(event.payload.message); + this.onFrameEvent(frameEvent => { + if (frameEvent.type === AppKitFrameConstants.FRAME_SET_PREFERRED_ACCOUNT_SUCCESS) { + callback(frameEvent.payload); + } + }); } - private onSessionUpdate( - event: Extract - ) { - const { payload } = event; - if (payload) { - // Ilja TODO: this.setSessionToken(payload.token) + public async getLastUsedChainId() { + const chainId = await AppKitFrameStorage.get(AppKitFrameConstants.LAST_USED_CHAIN_KEY); + if (chainId) { + return Number(chainId); } - } - - private onUpdateEmailSuccess( - event: Extract - ) { - this.updateEmailResolver?.resolve(event.payload); - this.setNewLastEmailLoginTime(); - } - - private onUpdateEmailError( - event: Extract - ) { - this.updateEmailResolver?.reject(event.payload.message); - } - - private onUpdateEmailPrimaryOtpSuccess() { - this.updateEmailPrimaryOtpResolver?.resolve(undefined); - } - - private onUpdateEmailPrimaryOtpError( - event: Extract< - AppKitFrameTypes.FrameEvent, - { type: '@w3m-frame/UPDATE_EMAIL_PRIMARY_OTP_ERROR' } - > - ) { - this.updateEmailPrimaryOtpResolver?.reject(event.payload.message); - } - - private onUpdateEmailSecondaryOtpSuccess( - event: Extract< - AppKitFrameTypes.FrameEvent, - { type: '@w3m-frame/UPDATE_EMAIL_SECONDARY_OTP_SUCCESS' } - > - ) { - const { newEmail } = event.payload; - this.setEmailLoginSuccess(newEmail); - this.updateEmailSecondaryOtpResolver?.resolve({ newEmail }); - } - - private onUpdateEmailSecondaryOtpError( - event: Extract< - AppKitFrameTypes.FrameEvent, - { type: '@w3m-frame/UPDATE_EMAIL_SECONDARY_OTP_ERROR' } - > - ) { - this.updateEmailSecondaryOtpResolver?.reject(event.payload.message); - } - - private onSyncThemeSuccess() { - this.syncThemeResolver?.resolve(undefined); - } - private onSyncThemeError( - event: Extract - ) { - this.syncThemeResolver?.reject(event.payload.message); - } - - private onSyncDappDataSuccess() { - this.syncDappDataResolver?.resolve(undefined); - } - - private onSyncDappDataError( - event: Extract - ) { - this.syncDappDataResolver?.reject(event.payload.message); + return undefined; } // -- Private Methods ------------------------------------------------- @@ -600,6 +460,11 @@ export class AppKitFrameProvider { AppKitFrameStorage.set(AppKitFrameConstants.LAST_EMAIL_LOGIN_TIME, Date.now().toString()); } + private setSocialLoginSuccess(username: string) { + AppKitFrameStorage.set(AppKitFrameConstants.SOCIAL_USERNAME, username); + this.username = username; + } + private setEmailLoginSuccess(email: string) { AppKitFrameStorage.set(AppKitFrameConstants.EMAIL, email); AppKitFrameStorage.set(AppKitFrameConstants.EMAIL_LOGIN_USED_KEY, 'true'); @@ -607,49 +472,127 @@ export class AppKitFrameProvider { this.email = email; } - private deleteEmailLoginCache() { + private deleteLoginCache() { AppKitFrameStorage.delete(AppKitFrameConstants.EMAIL_LOGIN_USED_KEY); AppKitFrameStorage.delete(AppKitFrameConstants.EMAIL); AppKitFrameStorage.delete(AppKitFrameConstants.LAST_USED_CHAIN_KEY); + AppKitFrameStorage.delete(AppKitFrameConstants.SOCIAL_USERNAME); this.email = undefined; + this.username = undefined; } private setLastUsedChainId(chainId: number) { AppKitFrameStorage.set(AppKitFrameConstants.LAST_USED_CHAIN_KEY, String(chainId)); } - private async getLastUsedChainId() { - const chainId = await AppKitFrameStorage.get(AppKitFrameConstants.LAST_USED_CHAIN_KEY); - if (chainId) { - return Number(chainId); - } - - return undefined; + private persistSmartAccountEnabledNetworks(networks: number[]) { + AppKitFrameStorage.set(AppKitFrameConstants.SMART_ACCOUNT_ENABLED_NETWORKS, networks.join(',')); } - private onFrameEvent( - event: AppKitFrameTypes.FrameEvent, - callback: (event: AppKitFrameTypes.FrameEvent) => void + private async registerFrameEventHandler( + id: string, + callback: (event: AppKitFrameTypes.FrameEvent) => void, + signal: AbortSignal ) { - if ( - !event.type?.includes(AppKitFrameConstants.FRAME_EVENT_KEY) || - event.origin !== AppKitFrameConstants.SECURE_SITE_ORIGIN - ) { - return; + const eventEmitter = this.events; + function eventHandler(data: AppKitFrameTypes.FrameEvent) { + if (!data.type?.includes(AppKitFrameConstants.FRAME_EVENT_KEY)) { + return; + } + const frameEvent = AppKitFrameSchema.frameEvent.parse(data); + if (frameEvent.id === id) { + callback(frameEvent); + eventEmitter.removeListener('message', eventHandler); + } } - const frameEvent = AppKitFrameSchema.frameEvent.parse(event); - callback(frameEvent); + + eventEmitter.addListener('message', eventHandler); + signal.addEventListener('abort', () => { + eventEmitter.removeListener('message', eventHandler); + }); } - private onAppEvent( - event: AppKitFrameTypes.AppEvent, - callback: (event: AppKitFrameTypes.AppEvent) => void - ) { - if (!event.type?.includes(AppKitFrameConstants.APP_EVENT_KEY)) { - return; + private async appEvent( + event: AppEventType + ): Promise { + await this.webviewLoadPromise; + let timer: NodeJS.Timeout; + + function replaceEventType(type: AppEventType['type']) { + return type.replace('@w3m-app/', ''); } - const appEvent = AppKitFrameSchema.appEvent.parse(event); - callback(appEvent); + + const type = replaceEventType(event.type); + + const shouldCheckForTimeout = [ + AppKitFrameConstants.APP_IS_CONNECTED, + AppKitFrameConstants.APP_GET_USER, + AppKitFrameConstants.APP_CONNECT_EMAIL, + AppKitFrameConstants.APP_CONNECT_DEVICE, + AppKitFrameConstants.APP_CONNECT_OTP, + AppKitFrameConstants.APP_CONNECT_SOCIAL, + AppKitFrameConstants.APP_GET_SOCIAL_REDIRECT_URI, + AppKitFrameConstants.APP_GET_FARCASTER_URI + ] + .map(replaceEventType) + .includes(type); + + if (shouldCheckForTimeout && this.onTimeout) { + // 15 seconds timeout + timer = setTimeout(this.onTimeout, 30000); + } + + return new Promise((resolve, reject) => { + const id = Math.random().toString(36).substring(7); + + this.postAppEvent({ ...event, id } as AppKitFrameTypes.AppEvent); + const abortController = new AbortController(); + if (type === 'RPC_REQUEST') { + const rpcEvent = event as Extract< + AppKitFrameTypes.AppEvent, + { type: '@w3m-app/RPC_REQUEST' } + >; + this.openRpcRequests = [...this.openRpcRequests, { ...rpcEvent.payload, abortController }]; + } + abortController.signal.addEventListener('abort', () => { + if (type === 'RPC_REQUEST') { + reject(new Error('Request was aborted')); + } + }); + + function handler(frameEvent: AppKitFrameTypes.FrameEvent) { + if (frameEvent.type === `@w3m-frame/${type}_SUCCESS`) { + if (timer) { + clearTimeout(timer); + } + if ('payload' in frameEvent) { + resolve(frameEvent.payload); + } + resolve(undefined as unknown as AppKitFrameTypes.Responses[`Frame${T}Response`]); + } else if (frameEvent.type === `@w3m-frame/${type}_ERROR`) { + if ('payload' in frameEvent) { + reject(new Error(frameEvent.payload?.message || 'An error occurred')); + } + reject(new Error('An error occurred')); + } + } + this.registerFrameEventHandler(id, handler, abortController.signal); + }); + } + + private onFrameEvent(callback: (event: AppKitFrameTypes.FrameEvent) => void) { + const eventHandler = (event: AppKitFrameTypes.FrameEvent) => { + if ( + !event.type?.includes(AppKitFrameConstants.FRAME_EVENT_KEY) || + event.origin !== AppKitFrameConstants.SECURE_SITE_ORIGIN + ) { + return; + } + // console.log('💻 received', event); // eslint-disable-line no-console + callback(event); + }; + + this.events.addListener('message', eventHandler); } private postAppEvent(event: AppKitFrameTypes.AppEvent) { @@ -674,9 +617,10 @@ export class AppKitFrameProvider { ); } - private async getAsyncEmail() { + private async loadAsyncValues() { const email = await AppKitFrameStorage.get(AppKitFrameConstants.EMAIL); - - return email; + this.email = email; + const username = await AppKitFrameStorage.get(AppKitFrameConstants.SOCIAL_USERNAME); + this.username = username; } } diff --git a/packages/wallet/src/AppKitFrameSchema.ts b/packages/wallet/src/AppKitFrameSchema.ts index 6ee16be62..3d7ffe04e 100644 --- a/packages/wallet/src/AppKitFrameSchema.ts +++ b/packages/wallet/src/AppKitFrameSchema.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { AppKitFrameConstants } from './AppKitFrameConstants'; +import { AppKitFrameConstants, AppKitFrameRpcConstants } from './AppKitFrameConstants'; // -- Helpers ---------------------------------------------------------------- const zError = z.object({ message: z.string() }); @@ -32,6 +32,10 @@ export const GetTransactionByHashResponse = z.object({ export const AppSwitchNetworkRequest = z.object({ chainId: z.number() }); export const AppConnectEmailRequest = z.object({ email: z.string().email() }); export const AppConnectOtpRequest = z.object({ otp: z.string() }); +export const AppConnectSocialRequest = z.object({ uri: z.string() }); +export const AppGetSocialRedirectUriRequest = z.object({ + provider: z.enum(['google', 'github', 'apple', 'facebook', 'x', 'discord', 'farcaster']) +}); export const AppGetUserRequest = z.object({ chainId: z.optional(z.number()) }); export const AppUpdateEmailRequest = z.object({ email: z.string().email() }); export const AppUpdateEmailPrimaryOtpRequest = z.object({ otp: z.string() }); @@ -55,10 +59,16 @@ export const AppSyncDappDataRequest = z.object({ | `react-native-ethers5-${string}` | `react-native-ethers-${string}` >, + sdkType: z.enum(['appkit']), projectId: z.string() }); export const AppSetPreferredAccountRequest = z.object({ type: z.string() }); +const AccountTypeEnum = z.enum([ + AppKitFrameRpcConstants.ACCOUNT_TYPES.EOA, + AppKitFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT +]); + export const FrameConnectEmailResponse = z.object({ action: z.enum(['VERIFY_DEVICE', 'VERIFY_OTP']) }); @@ -66,14 +76,49 @@ export const FrameUpdateEmailResponse = z.object({ action: z.enum(['VERIFY_PRIMARY_OTP', 'VERIFY_SECONDARY_OTP']) }); export const FrameGetUserResponse = z.object({ - email: z.string().email(), + email: z.string().email().optional().nullable(), address: z.string(), - chainId: z.number() + chainId: z.number(), + smartAccountDeployed: z.boolean(), + preferredAccountType: AccountTypeEnum }); export const FrameIsConnectedResponse = z.object({ isConnected: z.boolean() }); export const FrameGetChainIdResponse = z.object({ chainId: z.number() }); export const FrameSwitchNetworkResponse = z.object({ chainId: z.number() }); -export const FrameUpdateEmailSecondaryOtpResolver = z.object({ newEmail: z.string().email() }); +export const FrameUpdateEmailSecondaryOtpResponse = z.object({ newEmail: z.string().email() }); +export const FrameGetSocialRedirectUriResponse = z.object({ uri: z.string() }); + +export const FrameConnectSocialResponse = z.object({ + email: z.string(), + address: z.string(), + chainId: z.string().or(z.number()), + accounts: z + .array( + z.object({ + address: z.string(), + type: AccountTypeEnum + }) + ) + .optional(), + userName: z.string().optional() +}); + +export const FrameGetFarcasterUriResponse = z.object({ + url: z.string() +}); + +export const FrameConnectFarcasterResponse = z.object({ + userName: z.string() +}); + +export const FrameSetPreferredAccountResponse = z.object({ + type: AccountTypeEnum, + address: z.string() +}); + +export const FrameGetSmartAccountEnabledNetworksResponse = z.object({ + smartAccountEnabledNetworks: z.array(z.number()) +}); export const RpcResponse = z.any(); @@ -258,28 +303,62 @@ export const FrameSession = z.object({ token: z.string() }); +export const EventSchema = z.object({ + // Remove optional once all packages are updated + id: z.string().optional() +}); + export const AppKitFrameSchema = { // -- App Events ----------------------------------------------------------- - appEvent: z - .object({ type: zType('APP_SWITCH_NETWORK'), payload: AppSwitchNetworkRequest }) + appEvent: EventSchema.extend({ + type: zType('APP_SWITCH_NETWORK'), + payload: AppSwitchNetworkRequest + }) + + .or(EventSchema.extend({ type: zType('APP_CONNECT_EMAIL'), payload: AppConnectEmailRequest })) + + .or(EventSchema.extend({ type: zType('APP_CONNECT_DEVICE') })) - .or(z.object({ type: zType('APP_CONNECT_EMAIL'), payload: AppConnectEmailRequest })) + .or(EventSchema.extend({ type: zType('APP_CONNECT_OTP'), payload: AppConnectOtpRequest })) - .or(z.object({ type: zType('APP_CONNECT_DEVICE') })) + .or( + EventSchema.extend({ + type: zType('APP_CONNECT_SOCIAL'), + payload: AppConnectSocialRequest + }) + ) - .or(z.object({ type: zType('APP_CONNECT_OTP'), payload: AppConnectOtpRequest })) + .or(EventSchema.extend({ type: zType('APP_GET_USER'), payload: z.optional(AppGetUserRequest) })) - .or(z.object({ type: zType('APP_GET_USER'), payload: z.optional(AppGetUserRequest) })) + .or( + EventSchema.extend({ + type: zType('APP_GET_SOCIAL_REDIRECT_URI'), + payload: AppGetSocialRedirectUriRequest + }) + ) - .or(z.object({ type: zType('APP_SIGN_OUT') })) + .or(EventSchema.extend({ type: zType('APP_GET_FARCASTER_URI') })) - .or(z.object({ type: zType('APP_IS_CONNECTED'), payload: z.optional(FrameSession) })) + .or(EventSchema.extend({ type: zType('APP_CONNECT_FARCASTER') })) - .or(z.object({ type: zType('APP_GET_CHAIN_ID') })) + .or(EventSchema.extend({ type: zType('APP_SIGN_OUT') })) + + .or(EventSchema.extend({ type: zType('APP_IS_CONNECTED'), payload: z.optional(FrameSession) })) + + .or(EventSchema.extend({ type: zType('APP_GET_CHAIN_ID') })) + + .or(EventSchema.extend({ type: zType('APP_GET_SMART_ACCOUNT_ENABLED_NETWORKS') })) .or( - z.object({ + EventSchema.extend({ + type: zType('APP_SET_PREFERRED_ACCOUNT'), + payload: AppSetPreferredAccountRequest + }) + ) + + .or( + EventSchema.extend({ type: zType('APP_RPC_REQUEST'), payload: RpcPersonalSignRequest.or(RpcEthSendTransactionRequest) .or(RpcEthAccountsRequest) @@ -322,96 +401,208 @@ export const AppKitFrameSchema = { }) ) - .or(z.object({ type: zType('APP_UPDATE_EMAIL'), payload: AppUpdateEmailRequest })) + .or(EventSchema.extend({ type: zType('APP_UPDATE_EMAIL'), payload: AppUpdateEmailRequest })) .or( - z.object({ + EventSchema.extend({ type: zType('APP_UPDATE_EMAIL_PRIMARY_OTP'), payload: AppUpdateEmailPrimaryOtpRequest }) ) .or( - z.object({ + EventSchema.extend({ type: zType('APP_UPDATE_EMAIL_SECONDARY_OTP'), payload: AppUpdateEmailSecondaryOtpRequest }) ) - .or(z.object({ type: zType('APP_SYNC_THEME'), payload: AppSyncThemeRequest })) + .or(EventSchema.extend({ type: zType('APP_SYNC_THEME'), payload: AppSyncThemeRequest })) - .or(z.object({ type: zType('APP_SYNC_DAPP_DATA'), payload: AppSyncDappDataRequest })), + .or(EventSchema.extend({ type: zType('APP_SYNC_DAPP_DATA'), payload: AppSyncDappDataRequest })), // -- Frame Events --------------------------------------------------------- - frameEvent: z - .object({ type: zType('FRAME_SWITCH_NETWORK_ERROR'), payload: zError, origin: z.string() }) + frameEvent: EventSchema.extend({ + type: zType('FRAME_SWITCH_NETWORK_ERROR'), + payload: zError, + origin: z.string() + }) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_SWITCH_NETWORK_SUCCESS'), payload: FrameSwitchNetworkResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_CONNECT_EMAIL_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_EMAIL_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_CONNECT_EMAIL_SUCCESS'), payload: FrameConnectEmailResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_CONNECT_OTP_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_OTP_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or(EventSchema.extend({ type: zType('FRAME_CONNECT_OTP_SUCCESS'), origin: z.string() })) + + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_DEVICE_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_SOCIAL_SUCCESS'), + payload: FrameConnectSocialResponse, + origin: z.string() + }) + ) + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_SOCIAL_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_GET_FARCASTER_URI_SUCCESS'), + payload: FrameGetFarcasterUriResponse, + origin: z.string() + }) + ) - .or(z.object({ type: zType('FRAME_CONNECT_OTP_SUCCESS'), origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_GET_FARCASTER_URI_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ type: zType('FRAME_CONNECT_DEVICE_ERROR'), payload: zError, origin: z.string() }) + EventSchema.extend({ + type: zType('FRAME_CONNECT_FARCASTER_SUCCESS'), + payload: FrameConnectFarcasterResponse, + origin: z.string() + }) ) - .or(z.object({ type: zType('FRAME_CONNECT_DEVICE_SUCCESS'), origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_FARCASTER_ERROR'), + payload: zError, + origin: z.string() + }) + ) - .or(z.object({ type: zType('FRAME_GET_USER_ERROR'), payload: zError, origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_CONNECT_DEVICE_SUCCESS'), origin: z.string() })) .or( - z.object({ + EventSchema.extend({ + type: zType('FRAME_GET_USER_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ type: zType('FRAME_GET_USER_SUCCESS'), payload: FrameGetUserResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_SIGN_OUT_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_GET_SOCIAL_REDIRECT_URI_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_GET_SOCIAL_REDIRECT_URI_SUCCESS'), + payload: FrameGetSocialRedirectUriResponse, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_SIGN_OUT_ERROR'), + payload: zError, + origin: z.string() + }) + ) - .or(z.object({ type: zType('FRAME_SIGN_OUT_SUCCESS'), origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_SIGN_OUT_SUCCESS'), origin: z.string() })) - .or(z.object({ type: zType('FRAME_IS_CONNECTED_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_IS_CONNECTED_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_IS_CONNECTED_SUCCESS'), payload: FrameIsConnectedResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_GET_CHAIN_ID_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_GET_CHAIN_ID_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_GET_CHAIN_ID_SUCCESS'), payload: FrameGetChainIdResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_RPC_REQUEST_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_RPC_REQUEST_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_RPC_REQUEST_SUCCESS'), payload: RpcResponse, origin: z.string() @@ -419,13 +610,23 @@ export const AppKitFrameSchema = { ) .or( - z.object({ type: zType('FRAME_SESSION_UPDATE'), payload: FrameSession, origin: z.string() }) + EventSchema.extend({ + type: zType('FRAME_SESSION_UPDATE'), + payload: FrameSession, + origin: z.string() + }) ) - .or(z.object({ type: zType('FRAME_UPDATE_EMAIL_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_UPDATE_EMAIL_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_SUCCESS'), payload: FrameUpdateEmailResponse, origin: z.string() @@ -433,17 +634,22 @@ export const AppKitFrameSchema = { ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_PRIMARY_OTP_ERROR'), payload: zError, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_UPDATE_EMAIL_PRIMARY_OTP_SUCCESS'), origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_UPDATE_EMAIL_PRIMARY_OTP_SUCCESS'), + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_SECONDARY_OTP_ERROR'), payload: zError, origin: z.string() @@ -451,20 +657,61 @@ export const AppKitFrameSchema = { ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_SECONDARY_OTP_SUCCESS'), - payload: FrameUpdateEmailSecondaryOtpResolver, + payload: FrameUpdateEmailSecondaryOtpResponse, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_SYNC_THEME_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or(EventSchema.extend({ type: zType('FRAME_SYNC_THEME_SUCCESS'), origin: z.string() })) + + .or( + EventSchema.extend({ + type: zType('FRAME_SYNC_DAPP_DATA_ERROR'), + payload: zError, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_SYNC_THEME_ERROR'), payload: zError, origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_SYNC_DAPP_DATA_SUCCESS'), origin: z.string() })) - .or(z.object({ type: zType('FRAME_SYNC_THEME_SUCCESS'), origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS'), + payload: FrameGetSmartAccountEnabledNetworksResponse, + origin: z.string() + }) + ) .or( - z.object({ type: zType('FRAME_SYNC_DAPP_DATA_ERROR'), payload: zError, origin: z.string() }) + EventSchema.extend({ + type: zType('FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_ERROR'), + payload: zError, + origin: z.string() + }) ) - .or(z.object({ type: zType('FRAME_SYNC_DAPP_DATA_SUCCESS'), origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_SET_PREFERRED_ACCOUNT_SUCCESS'), + payload: FrameSetPreferredAccountResponse, + origin: z.string() + }) + ) + .or( + EventSchema.extend({ + type: zType('FRAME_SET_PREFERRED_ACCOUNT_ERROR'), + payload: zError, + origin: z.string() + }) + ) }; diff --git a/packages/wallet/src/AppKitFrameTypes.ts b/packages/wallet/src/AppKitFrameTypes.ts index bc29ab59e..40b712b34 100644 --- a/packages/wallet/src/AppKitFrameTypes.ts +++ b/packages/wallet/src/AppKitFrameTypes.ts @@ -48,15 +48,25 @@ import { FrameSession, AppGetUserRequest, AppUpdateEmailRequest, - FrameUpdateEmailSecondaryOtpResolver, + FrameUpdateEmailSecondaryOtpResponse, AppUpdateEmailPrimaryOtpRequest, AppUpdateEmailSecondaryOtpRequest, AppSyncThemeRequest, RpcEthChainId, FrameSwitchNetworkResponse, AppSyncDappDataRequest, - FrameUpdateEmailResponse + FrameUpdateEmailResponse, + FrameGetSocialRedirectUriResponse, + FrameConnectSocialResponse, + AppGetSocialRedirectUriRequest, + AppConnectSocialRequest, + FrameGetFarcasterUriResponse, + FrameConnectFarcasterResponse, + AppSetPreferredAccountRequest, + FrameSetPreferredAccountResponse, + FrameGetSmartAccountEnabledNetworksResponse } from './AppKitFrameSchema'; +import type { AppKitFrameRpcConstants } from './AppKitFrameConstants'; export namespace AppKitFrameTypes { export type AppEvent = z.infer; @@ -73,6 +83,10 @@ export namespace AppKitFrameTypes { AppSyncDappDataRequest: z.infer; AppUpdateEmailPrimaryOtpRequest: z.infer; AppUpdateEmailSecondaryOtpRequest: z.infer; + AppGetSocialRedirectUriRequest: z.infer; + AppConnectSocialRequest: z.infer; + AppSetPreferredAccountRequest: z.infer; + AppGetSmartAccountEnabledNetworksRequest: undefined; } export interface Responses { @@ -80,9 +94,24 @@ export namespace AppKitFrameTypes { FrameGetChainIdResponse: z.infer; FrameGetUserResponse: z.infer; FrameIsConnectedResponse: z.infer; - FrameUpdateEmailSecondaryOtpResolver: z.infer; FrameSwitchNetworkResponse: z.infer; FrameUpdateEmailResponse: z.infer; + FrameConnectOtpResponse: undefined; + FrameGetSocialRedirectUriResponse: z.infer; + FrameConnectSocialResponse: z.infer; + FrameGetFarcasterUriResponse: z.infer; + FrameConnectFarcasterResponse: z.infer; + FrameSyncThemeResponse: undefined; + FrameSyncDappDataResponse: undefined; + FrameUpdateEmailPrimaryOtpResponse: undefined; + FrameUpdateEmailSecondaryOtpResponse: z.infer; + FrameConnectDeviceResponse: undefined; + FrameSignOutResponse: undefined; + FrameGetSmartAccountEnabledNetworksResponse: z.infer< + typeof FrameGetSmartAccountEnabledNetworksResponse + >; + FrameSetPreferredAccountResponse: z.infer; + FrameRpcResponse: RPCResponse; } export interface Network { @@ -139,4 +168,29 @@ export namespace AppKitFrameTypes { export type RPCResponse = z.infer; export type FrameSessionType = z.infer; + + export type AccountType = + (typeof AppKitFrameRpcConstants.ACCOUNT_TYPES)[keyof typeof AppKitFrameRpcConstants.ACCOUNT_TYPES]; + + export type ProviderRequestType = + | 'GetUser' + | 'ConnectDevice' + | 'ConnectEmail' + | 'ConnectFarcaster' + | 'ConnectSocial' + | 'ConnectOtp' + | 'GetFarcasterUri' + | 'GetSocialRedirectUri' + | 'SwitchNetwork' + | 'UpdateEmail' + | 'SyncTheme' + | 'SyncDappData' + | 'UpdateEmailPrimaryOtp' + | 'UpdateEmailSecondaryOtp' + | 'GetChainId' + | 'GetSmartAccountEnabledNetworks' + | 'SetPreferredAccount' + | 'IsConnected' + | 'SignOut' + | 'Rpc'; } diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx new file mode 100644 index 000000000..6682daab3 --- /dev/null +++ b/packages/wallet/src/AppKitWebview.tsx @@ -0,0 +1,146 @@ +import { useSnapshot } from 'valtio'; +import { useEffect, useRef, useState } from 'react'; +import { Animated, Pressable, SafeAreaView, StyleSheet } from 'react-native'; +import { WebView } from 'react-native-webview'; +import { + ConnectionController, + ConnectorController, + EventsController, + RouterController, + WebviewController +} from '@reown/appkit-core-react-native'; +import { useTheme, BorderRadius, IconLink, Spacing } from '@reown/appkit-ui-react-native'; +import type { AppKitFrameProvider } from './AppKitFrameProvider'; +import { AppKitFrameConstants } from './AppKitFrameConstants'; + +const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); +const AnimatedPressable = Animated.createAnimatedComponent(Pressable); + +export function AppKitWebview() { + const webviewRef = useRef(null); + const Theme = useTheme(); + const authConnector = ConnectorController.getAuthConnector(); + const { webviewVisible, webviewUrl } = useSnapshot(WebviewController.state); + const [isBackdropVisible, setIsBackdropVisible] = useState(false); + const backdropOpacity = useRef(new Animated.Value(0)); + const webviewOpacity = useRef(new Animated.Value(0)); + const provider = authConnector?.provider as AppKitFrameProvider; + const display = webviewVisible ? 'flex' : 'none'; + + const onClose = () => { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_CANCELED', + properties: { provider: ConnectionController.state.selectedSocialProvider! } + }); + + WebviewController.setWebviewVisible(false); + WebviewController.setConnecting(false); + WebviewController.setConnectingProvider(undefined); + RouterController.goBack(); + }; + + useEffect(() => { + Animated.timing(webviewOpacity.current, { + toValue: webviewVisible ? 1 : 0, + duration: 300, + useNativeDriver: true + }).start(({ finished }) => { + if (finished && !webviewVisible) { + WebviewController.setWebviewUrl(''); + } + }); + + if (webviewVisible) { + setIsBackdropVisible(true); + } + + Animated.timing(backdropOpacity.current, { + toValue: webviewVisible ? 0.7 : 0, + duration: 300, + useNativeDriver: true + }).start(() => setIsBackdropVisible(webviewVisible)); + }, [backdropOpacity, webviewVisible, setIsBackdropVisible]); + + if (!webviewUrl) return null; + + return provider ? ( + <> + + + + { + if ( + !navState.loading && + navState.url.includes(`${AppKitFrameConstants.SECURE_SITE_ORIGIN}/sdk/oauth`) + ) { + provider.events.emit('social', navState.url); + } + }} + /> + + + ) : null; +} + +const styles = StyleSheet.create({ + backdrop: { + position: 'absolute', + width: '100%', + height: '100%', + top: 0, + zIndex: 999 + }, + container: { + bottom: 0, + position: 'absolute', + height: '80%', + width: '100%', + borderTopLeftRadius: BorderRadius.l, + borderTopRightRadius: BorderRadius.l, + zIndex: 999 + }, + hidden: { + display: 'none' + }, + webview: { + borderTopLeftRadius: BorderRadius.l, + borderTopRightRadius: BorderRadius.l + }, + closeButton: { + top: -60, + right: 0, + zIndex: 999, + position: 'absolute', + margin: Spacing.l + } +}); diff --git a/yarn.lock b/yarn.lock index dd88de752..d17c20ea0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:^1.10.1": + version: 1.11.0 + resolution: "@adraffy/ens-normalize@npm:1.11.0" + checksum: 5111d0f1a273468cb5661ed3cf46ee58de8f32f84e2ebc2365652e66c1ead82649df94c736804e2b9cfa831d30ef24e1cc3575d970dbda583416d3a98d8870a6 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.2.0": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" @@ -88,6 +95,8 @@ __metadata: resolution: "@apps/native@workspace:apps/native" dependencies: "@babel/core": "npm:^7.24.0" + "@expo/metro-runtime": "npm:~3.2.3" + "@playwright/test": "npm:^1.49.1" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-community/netinfo": "npm:11.3.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.0.2" @@ -95,27 +104,31 @@ __metadata: "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" "@tanstack/react-query-persist-client": "npm:5.56.2" + "@types/gh-pages": "npm:^6" + "@types/node": "npm:^22.10.1" "@types/react": "npm:~18.2.79" "@types/react-native": "npm:0.72.2" - "@walletconnect/react-native-compat": "npm:2.16.1" + "@walletconnect/react-native-compat": "npm:2.17.1" babel-plugin-module-resolver: "npm:^5.0.0" expo: "npm:~51.0.24" expo-application: "npm:~5.9.1" expo-clipboard: "npm:~6.0.3" expo-status-bar: "npm:~1.12.1" expo-updates: "npm:~0.25.21" + gh-pages: "npm:^6.2.0" react: "npm:18.2.0" react-dom: "npm:18.2.0" react-native: "npm:0.74.3" react-native-get-random-values: "npm:~1.11.0" react-native-modal: "npm:13.0.1" react-native-svg: "npm:15.2.0" + react-native-toast-message: "npm:2.2.1" react-native-web: "npm:~0.19.10" react-native-webview: "npm:13.8.6" typescript: "npm:~5.3.3" uuid: "npm:3.4.0" - viem: "npm:2.21.6" - wagmi: "npm:2.12.11" + viem: "npm:2.21.48" + wagmi: "npm:2.12.33" languageName: unknown linkType: soft @@ -168,7 +181,7 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9": +"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9": version: 7.22.9 resolution: "@babel/compat-data@npm:7.22.9" checksum: 1334264b041f8ad4e33036326970c9c26754eb5c04b3af6c223fe6da988cbb8a8542b5526f49ec1ac488210d2f710484a0e4bcd30256294ae3f261d0141febad @@ -196,7 +209,7 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4": +"@babel/compat-data@npm:^7.25.2": version: 7.25.4 resolution: "@babel/compat-data@npm:7.25.4" checksum: 50d79734d584a28c69d6f5b99adfaa064d0f41609a378aef04eb06accc5b44f8520e68549eba3a082478180957b7d5783f1bfb1672e4ae8574e797ce8bae79fa @@ -380,7 +393,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.24.8, @babel/generator@npm:^7.24.9": +"@babel/generator@npm:^7.24.9": version: 7.24.10 resolution: "@babel/generator@npm:7.24.10" dependencies: @@ -431,16 +444,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 0ed84abf848c79fb1cd4c1ddac12c771d32c1904d87fc3087f33cfdeb0c2e0db4e7892b74b407d9d8d0c000044f3645a7391a781f788da8410c290bb123a1f13 - languageName: node - linkType: hard - "@babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.22.10, @babel/helper-compilation-targets@npm:^7.22.5, @babel/helper-compilation-targets@npm:^7.22.6": version: 7.22.10 resolution: "@babel/helper-compilation-targets@npm:7.22.10" @@ -480,29 +483,29 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-compilation-targets@npm:7.25.2" +"@babel/helper-compilation-targets@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-compilation-targets@npm:7.24.8" dependencies: - "@babel/compat-data": "npm:^7.25.2" + "@babel/compat-data": "npm:^7.24.8" "@babel/helper-validator-option": "npm:^7.24.8" browserslist: "npm:^4.23.1" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: de10e986b5322c9f807350467dc845ec59df9e596a5926a3b5edbb4710d8e3b8009d4396690e70b88c3844fe8ec4042d61436dd4b92d1f5f75655cf43ab07e99 + checksum: 2885c44ef6aaf82b7e4352b30089bb09fbe08ed5ec24eb452c2bdc3c021e2a65ab412f74b3d67ec1398da0356c730b33a2ceca1d67d34c85080d31ca6efa9aec languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-compilation-targets@npm:7.24.8" +"@babel/helper-compilation-targets@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-compilation-targets@npm:7.25.2" dependencies: - "@babel/compat-data": "npm:^7.24.8" + "@babel/compat-data": "npm:^7.25.2" "@babel/helper-validator-option": "npm:^7.24.8" browserslist: "npm:^4.23.1" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 2885c44ef6aaf82b7e4352b30089bb09fbe08ed5ec24eb452c2bdc3c021e2a65ab412f74b3d67ec1398da0356c730b33a2ceca1d67d34c85080d31ca6efa9aec + checksum: de10e986b5322c9f807350467dc845ec59df9e596a5926a3b5edbb4710d8e3b8009d4396690e70b88c3844fe8ec4042d61436dd4b92d1f5f75655cf43ab07e99 languageName: node linkType: hard @@ -544,23 +547,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.4" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: a765d9e0482e13cf96642fa8aa28e6f7d4d7d39f37840d6246e5e10a7c47f47c52d52522edd3073f229449d17ec0db6f9b7b5e398bff6bb0b4994d65957a164c - languageName: node - linkType: hard - "@babel/helper-create-class-features-plugin@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-create-class-features-plugin@npm:7.24.8" @@ -593,19 +579,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - regexpu-core: "npm:^5.3.1" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 85a7e3639c118856fb1113f54fb7e3bf7698171ddfd0cd6fccccd5426b3727bc1434fe7f69090441dcde327feef9de917e00d35e47ab820047057518dd675317 - languageName: node - linkType: hard - "@babel/helper-define-polyfill-provider@npm:^0.4.2": version: 0.4.2 resolution: "@babel/helper-define-polyfill-provider@npm:0.4.2" @@ -636,21 +609,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.6.2": - version: 0.6.2 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" - lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: f777fe0ee1e467fdaaac059c39ed203bdc94ef2465fb873316e9e1acfc511a276263724b061e3b0af2f6d7ad3ff174f2bb368fde236a860e0f650fda43d7e022 - languageName: node - linkType: hard - "@babel/helper-environment-visitor@npm:^7.18.9, @babel/helper-environment-visitor@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-environment-visitor@npm:7.22.5" @@ -684,16 +642,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/helper-function-name@npm:7.23.0" - dependencies: - "@babel/template": "npm:^7.22.15" - "@babel/types": "npm:^7.23.0" - checksum: d771dd1f3222b120518176733c52b7cadac1c256ff49b1889dbbe5e3fed81db855b8cc4e40d949c9d3eae0e795e8229c1c8c24c0e83f27cfa6ee3766696c6428 - languageName: node - linkType: hard - "@babel/helper-function-name@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-function-name@npm:7.24.7" @@ -713,15 +661,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-hoist-variables@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95 - languageName: node - linkType: hard - "@babel/helper-member-expression-to-functions@npm:^7.22.15": version: 7.23.0 resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0" @@ -823,32 +762,32 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.25.0, @babel/helper-module-transforms@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-module-transforms@npm:7.25.2" +"@babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.24.9": + version: 7.24.9 + resolution: "@babel/helper-module-transforms@npm:7.24.9" dependencies: + "@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/helper-module-imports": "npm:^7.24.7" "@babel/helper-simple-access": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" "@babel/helper-validator-identifier": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.2" peerDependencies: "@babel/core": ^7.0.0 - checksum: adaa15970ace0aee5934b5a633789b5795b6229c6a9cf3e09a7e80aa33e478675eee807006a862aa9aa517935d81f88a6db8a9f5936e3a2a40ec75f8062bc329 + checksum: e27bca43bc113731ee4f2b33a4c5bf9c7eebf4d64487b814c305cbd5feb272c29fcd3d79634ba03131ade171e5972bc7ede8dbc83ba0deb02f1e62d318c87770 languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.24.9": - version: 7.24.9 - resolution: "@babel/helper-module-transforms@npm:7.24.9" +"@babel/helper-module-transforms@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-module-transforms@npm:7.25.2" dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/helper-module-imports": "npm:^7.24.7" "@babel/helper-simple-access": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" "@babel/helper-validator-identifier": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.2" peerDependencies: "@babel/core": ^7.0.0 - checksum: e27bca43bc113731ee4f2b33a4c5bf9c7eebf4d64487b814c305cbd5feb272c29fcd3d79634ba03131ade171e5972bc7ede8dbc83ba0deb02f1e62d318c87770 + checksum: adaa15970ace0aee5934b5a633789b5795b6229c6a9cf3e09a7e80aa33e478675eee807006a862aa9aa517935d81f88a6db8a9f5936e3a2a40ec75f8062bc329 languageName: node linkType: hard @@ -891,7 +830,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.18.9, @babel/helper-remap-async-to-generator@npm:^7.22.5": +"@babel/helper-remap-async-to-generator@npm:^7.18.9, @babel/helper-remap-async-to-generator@npm:^7.22.5, @babel/helper-remap-async-to-generator@npm:^7.22.9": version: 7.22.9 resolution: "@babel/helper-remap-async-to-generator@npm:7.22.9" dependencies: @@ -917,19 +856,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.24.7, @babel/helper-remap-async-to-generator@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-wrap-function": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 0d17b5f7bb6a607edc9cc62fff8056dd9f341bf2f919884f97b99170d143022a5e7ae57922c4891e4fc360ad291e708d2f8cd8989f1d3cd7a17600159984f5a6 - languageName: node - linkType: hard - "@babel/helper-replace-supers@npm:^7.22.5, @babel/helper-replace-supers@npm:^7.22.9": version: 7.22.9 resolution: "@babel/helper-replace-supers@npm:7.22.9" @@ -956,19 +882,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-replace-supers@npm:7.25.0" - dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: b4b6650ab3d56c39a259367cd97f8df2f21c9cebb3716fea7bca40a150f8847bfb82f481e98927c7c6579b48a977b5a8f77318a1c6aeb497f41ecd6dbc3fdfef - languageName: node - linkType: hard - "@babel/helper-simple-access@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-simple-access@npm:7.22.5" @@ -1117,17 +1030,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-wrap-function@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-wrap-function@npm:7.25.0" - dependencies: - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: d54601a98384c191cbc1ff07b03a19e288ef8d5c6bfafe270b2a303d96e7304eb296002921ed464cc1b105a547d1db146eb86b0be617924dee1ba1b379cdc216 - languageName: node - linkType: hard - "@babel/helpers@npm:^7.22.10": version: 7.22.10 resolution: "@babel/helpers@npm:7.22.10" @@ -1273,29 +1175,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.3" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 814b4d3f102e7556a5053d1acf57ef601cfcff39a2c81b8cdc6a5c842e3cb9838f5925d1466a5f1e6416e74c9c83586a3c07fbd7fb8610a396c2becdf9ae5790 - languageName: node - linkType: hard - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 9645a1f47b3750acadb1353c02e71cc712d072aafe5ce115ed3a886bc14c5d9200cfb0b5b5e60e813baa549b800cf798f8714019fd246c699053cf68c428e426 - languageName: node - linkType: hard - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.15": version: 7.22.15 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.22.15" @@ -1307,14 +1186,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0 - checksum: ed1ce1c90cac46c01825339fd0f2a96fa071b016fb819d8dfaf8e96300eae30e74870cb47e4dc80d4ce2fb287869f102878b4f3b35bc927fec8b1d0d76bcf612 + checksum: 573bd9b1984d74e3663cb7f5f317646223020107681e8dcffe68b041bd620ebbb35c0cc05f4ee20f2da502d02a9633e2b477596e71f4f7802f72c02e948f38af languageName: node linkType: hard @@ -1331,28 +1210,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.7" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5" + "@babel/plugin-transform-optional-chaining": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.13.0 - checksum: aeb6e7aa363a47f815cf956ea1053c5dd8b786a17799f065c9688ba4b0051fe7565d258bbe9400bfcbfb3114cb9fda66983e10afe4d750bc70ff75403e15dd36 - languageName: node - linkType: hard - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 45988025537a9d4a27b610fd696a18fd9ba9336621a69b4fb40560eeb10c79657f85c92a37f30c7c8fb29c22970eea0b373315795a891f1a05549a6cfe5a6bfe + checksum: 1e38dcd28d2dc5012f96550a3fa1330d71fc923607ceccc91e83c0b7dd3eaeb4d8c632946909c389964acb3e35c888f81653e2d24f7cc02a83fe39a64ca59e89 languageName: node linkType: hard @@ -1604,17 +1471,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.24.7": - version: 7.25.6 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.25.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 55afa63b1b1355bcc1d85a9ad9d2c78983e27beee38e232d5c1ab59eac39127ce3c3817d6686e3ab1d0aff5edd8e38a6852885c65d3e518accdd183a445ef411 - languageName: node - linkType: hard - "@babel/plugin-syntax-import-attributes@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-import-attributes@npm:7.22.5" @@ -1626,17 +1482,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.25.6 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.25.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 0e9359cf2d117476310961dfcfd7204ed692e933707da10d6194153d3996cd2ea5b7635fc90d720dce3612083af89966bb862561064a509c350320dc98644751 - languageName: node - linkType: hard - "@babel/plugin-syntax-import-meta@npm:^7.10.4, @babel/plugin-syntax-import-meta@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" @@ -1825,14 +1670,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.7" +"@babel/plugin-transform-async-generator-functions@npm:^7.22.10": + version: 7.22.10 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.22.10" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-remap-async-to-generator": "npm:^7.22.9" + "@babel/plugin-syntax-async-generators": "npm:^7.8.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 6ac05a54e5582f34ac6d5dc26499e227227ec1c7fa6fc8de1f3d40c275f140d3907f79bbbd49304da2d7008a5ecafb219d0b71d78ee3290ca22020d878041245 + checksum: d0ea9119838e801752b37c8002ab61664be2c31564033d119d468236fbe63d72a41e37f5e348df41e1d610b787e5c6658a67f8bfea41a9f8f4b7dfde479817b2 languageName: node linkType: hard @@ -1850,20 +1698,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-remap-async-to-generator": "npm:^7.25.0" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/traverse": "npm:^7.25.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: efed6f6be90b25ad77c15a622a0dc0b22dbf5d45599c207ab8fbc4e959aef21f574fa467d9cf872e45de664a46c32334e78dee2332d82f5f27e26249a34a0920 - languageName: node - linkType: hard - "@babel/plugin-transform-async-to-generator@npm:^7.20.0, @babel/plugin-transform-async-to-generator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-async-to-generator@npm:7.22.5" @@ -1877,19 +1711,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.7" - dependencies: - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-remap-async-to-generator": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 83c82e243898875af8457972a26ab29baf8a2078768ee9f35141eb3edff0f84b165582a2ff73e90a9e08f5922bf813dbf15a85c1213654385198f4591c0dc45d - languageName: node - linkType: hard - "@babel/plugin-transform-block-scoped-functions@npm:^7.0.0, @babel/plugin-transform-block-scoped-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.22.5" @@ -1901,18 +1722,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 113e86de4612ae91773ff5cb6b980f01e1da7e26ae6f6012127415d7ae144e74987bc23feb97f63ba4bc699331490ddea36eac004d76a20d5369e4cc6a7f61cd - languageName: node - linkType: hard - -"@babel/plugin-transform-block-scoping@npm:^7.0.0": +"@babel/plugin-transform-block-scoping@npm:^7.0.0, @babel/plugin-transform-block-scoping@npm:^7.22.10": version: 7.22.10 resolution: "@babel/plugin-transform-block-scoping@npm:7.22.10" dependencies: @@ -1934,17 +1744,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 382931c75a5d0ea560387e76cb57b03461300527e4784efcb2fb62f36c1eb0ab331327b6034def256baa0cad9050925a61f9c0d56261b6afd6a29c3065fb0bd4 - languageName: node - linkType: hard - "@babel/plugin-transform-class-properties@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-class-properties@npm:7.22.5" @@ -1957,18 +1756,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 0b41bc8a5920d3d17c7c06220b601cf43e0a32ac34f05f05cd0cdf08915e4521b1b707cb1e60942b4fc68a5dfac09f0444a8720e0c72ce76fb039e8ec5263115 - languageName: node - linkType: hard - "@babel/plugin-transform-class-static-block@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-class-static-block@npm:7.22.11" @@ -1982,20 +1769,20 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-class-static-block@npm:7.24.7" +"@babel/plugin-transform-class-static-block@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-class-static-block@npm:7.22.5" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-create-class-features-plugin": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" peerDependencies: "@babel/core": ^7.12.0 - checksum: b0ade39a3d09dce886f79dbd5907c3d99b48167eddb6b9bbde24a0598129654d7017e611c20494cdbea48b07ac14397cd97ea34e3754bbb2abae4e698128eccb + checksum: 23814d00b2966e8dab7a60934622853698b2cb861a8667c006e000d8e5a50aba4d221c52852552562e7f38e32ad5c7778125ef602c2d2f1c4f9d8f790a9f27e9 languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.0.0": +"@babel/plugin-transform-classes@npm:^7.0.0, @babel/plugin-transform-classes@npm:^7.22.6": version: 7.22.6 resolution: "@babel/plugin-transform-classes@npm:7.22.6" dependencies: @@ -2033,22 +1820,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-classes@npm:7.25.4" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.4" - globals: "npm:^11.1.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: c68424d9dd64860825111aa4a4ed5caf29494b7a02ddb9c36351d768c41e8e05127d89274795cdfcade032d9d299e6c677418259df58c71e68f1741583dcf467 - languageName: node - linkType: hard - "@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-computed-properties@npm:7.22.5" @@ -2061,19 +1832,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/template": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 25636dbc1f605c0b8bc60aa58628a916b689473d11551c9864a855142e36742fe62d4a70400ba3b74902338e77fb3d940376c0a0ba154b6b7ec5367175233b49 - languageName: node - linkType: hard - -"@babel/plugin-transform-destructuring@npm:^7.0.0, @babel/plugin-transform-destructuring@npm:^7.20.0": +"@babel/plugin-transform-destructuring@npm:^7.0.0, @babel/plugin-transform-destructuring@npm:^7.20.0, @babel/plugin-transform-destructuring@npm:^7.22.10": version: 7.22.10 resolution: "@babel/plugin-transform-destructuring@npm:7.22.10" dependencies: @@ -2095,17 +1854,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-destructuring@npm:7.24.8" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 804968c1d5f5072c717505296c1e5d5ec33e90550423de66de82bbcb78157156e8470bbe77a04ab8c710a88a06360a30103cf223ac7eff4829adedd6150de5ce - languageName: node - linkType: hard - "@babel/plugin-transform-dotall-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-dotall-regex@npm:7.22.5" @@ -2118,52 +1866,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.7" +"@babel/plugin-transform-duplicate-keys@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.22.5" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 793f14c9494972d294b7e7b97b747f47874b6d57d7804d3443c701becf5db192c9311be6a1835c07664486df1f5c60d33196c36fb7e11a53015e476b4c145b33 - languageName: node - linkType: hard - -"@babel/plugin-transform-duplicate-keys@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0-0 checksum: 82772fdcc1301358bc722c1316bea071ad0cd5893ca95b08e183748e044277a93ee90f9c641ac7873a00e4b31a8df7cf8c0981ca98d01becb4864a11b22c09d1 languageName: node linkType: hard -"@babel/plugin-transform-duplicate-keys@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 75ff7ec1117ac500e77bf20a144411d39c0fdd038f108eec061724123ce6d1bb8d5bd27968e466573ee70014f8be0043361cdb0ef388f8a182d1d97ad67e51b9 - languageName: node - linkType: hard - -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 1c9b57ddd9b33696e88911d0e7975e1573ebc46219c4b30eb1dc746cbb71aedfac6f6dab7fdfdec54dd58f31468bf6ab56b157661ea4ffe58f906d71f89544c8 - languageName: node - linkType: hard - "@babel/plugin-transform-dynamic-import@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-dynamic-import@npm:7.22.11" @@ -2176,15 +1889,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7" +"@babel/plugin-transform-dynamic-import@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: eeda48372efd0a5103cb22dadb13563c975bce18ae85daafbb47d57bb9665d187da9d4fe8d07ac0a6e1288afcfcb73e4e5618bf75ff63fddf9736bfbf225203b + checksum: 82fb6fa0b6f7c7760ac21ebcb856a01579c9e64a325d5bb8841591b58b2d92024169f10f4ca2b34b45376999b352974138c94fc1d5cc330e00beeeb1bda51425 languageName: node linkType: hard @@ -2200,18 +1913,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.7" - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: ace3e11c94041b88848552ba8feb39ae4d6cad3696d439ff51445bd2882d8b8775d85a26c2c0edb9b5e38c9e6013cc11b0dea89ec8f93c7d9d7ee95e3645078c - languageName: node - linkType: hard - "@babel/plugin-transform-export-namespace-from@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-export-namespace-from@npm:7.22.11" @@ -2224,15 +1925,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.7" +"@babel/plugin-transform-export-namespace-from@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 4e144d7f1c57bc63b4899dbbbdfed0880f2daa75ea9c7251c7997f106e4b390dc362175ab7830f11358cb21f6b972ca10a43a2e56cd789065f7606b082674c0c + checksum: d5d301dde2d6e7f9e4db12ac70e19153f0e8d17406ad733a8f7d01de77d123588fe90c7f5b8cc086420594ec1e7d20abc5e08323f9ad9704a19c6c87ca03eb59 languageName: node linkType: hard @@ -2248,7 +1949,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.0.0": +"@babel/plugin-transform-for-of@npm:^7.0.0, @babel/plugin-transform-for-of@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-for-of@npm:7.22.5" dependencies: @@ -2270,18 +1971,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-for-of@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 77629b1173e55d07416f05ba7353caa09d2c2149da2ca26721ab812209b63689d1be45116b68eadc011c49ced59daf5320835b15245eb7ae93ae0c5e8277cfc0 - languageName: node - linkType: hard - "@babel/plugin-transform-function-name@npm:^7.0.0, @babel/plugin-transform-function-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-function-name@npm:7.22.5" @@ -2295,19 +1984,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.25.1": - version: 7.25.1 - resolution: "@babel/plugin-transform-function-name@npm:7.25.1" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: e74912174d5e33d1418b840443c2e226a7b76cc017c1ed20ee30a566e4f1794d4a123be03180da046241576e8b692731807ba1f52608922acf1cb2cb6957593f - languageName: node - linkType: hard - "@babel/plugin-transform-json-strings@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-json-strings@npm:7.22.11" @@ -2320,15 +1996,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-json-strings@npm:7.24.7" +"@babel/plugin-transform-json-strings@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-json-strings@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-json-strings": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 17c72cd5bf3e90e722aabd333559275f3309e3fa0b9cea8c2944ab83ae01502c71a2be05da5101edc02b3fc8df15a8dbb9b861cbfcc8a52bf5e797cf01d3a40a + checksum: 64ee0f3497822d312b609d3b8a5a2617337d1624292e89f5e90fd25b5bc91a20beadfa91730b5b199b5a027284ced5d59748d99e8ab81ee7bdac38236e6b61ca languageName: node linkType: hard @@ -2343,17 +2019,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/plugin-transform-literals@npm:7.25.2" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 0796883217b0885d37e7f6d350773be349e469a812b6bf11ccf862a6edf65103d3e7c849529d65381b441685c12e756751d8c2489a0fd3f8139bb5ef93185f58 - languageName: node - linkType: hard - "@babel/plugin-transform-logical-assignment-operators@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.22.11" @@ -2366,15 +2031,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.7" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: dbe882eb9053931f2ab332c50fc7c2a10ef507d6421bd9831adbb4cb7c9f8e1e5fbac4fbd2e007f6a1bf1df1843547559434012f118084dc0bf42cda3b106272 + checksum: bfacdafa8018d1607897015e1ea0f98edbefee16b4409d5f37c37df0d2058dde2e55586dd79f8479a0cd603ff06272216de077f071bc49c96014edfe1629bd26 languageName: node linkType: hard @@ -2389,14 +2054,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.7" +"@babel/plugin-transform-modules-amd@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-modules-amd@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e789ae359bdf2d20e90bedef18dfdbd965c9ebae1cee398474a0c349590fda7c8b874e1a2ceee62e47e5e6ec1730e76b0f24e502164357571854271fc12cc684 + checksum: 157ae3b58a50ca52e361860ecab2b608bc9228ea6c760112a35302990976f8936b8d75a2b21925797eed7b3bab4930a3f447193127afef9a21b7b6463ff0b422 languageName: node linkType: hard @@ -2412,18 +2078,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-amd@npm:7.24.7" - dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 6df7de7fce34117ca4b2fa07949b12274c03668cbfe21481c4037b6300796d50ae40f4f170527b61b70a67f26db906747797e30dbd0d9809a441b6e220b5728f - languageName: node - linkType: hard - "@babel/plugin-transform-modules-commonjs@npm:^7.0.0, @babel/plugin-transform-modules-commonjs@npm:^7.13.8, @babel/plugin-transform-modules-commonjs@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.22.5" @@ -2450,7 +2104,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.24.8": +"@babel/plugin-transform-modules-commonjs@npm:^7.24.7": version: 7.24.8 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" dependencies: @@ -2463,31 +2117,31 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.0" +"@babel/plugin-transform-modules-systemjs@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.22.5" dependencies: "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-module-transforms": "npm:^7.23.0" + "@babel/helper-module-transforms": "npm:^7.22.5" "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-validator-identifier": "npm:^7.22.20" + "@babel/helper-validator-identifier": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 04c5cef7d6921bb9c9073cea389289099124e78cd1e3b7e020e3c085d486b48efadd9a42c0c0d963a9b1c3d5465c3151229092ea719997e53427f36935c84178 + checksum: 25d7ada275039523541cfc3efd91cd3d9cfc77e7b9dd6a51e7d9ad842d2cb3e0f26aee29426aa56ac72f61247268369680f2bdc1171bb00a16cfd00bbb325a6c languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0" +"@babel/plugin-transform-modules-systemjs@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.0" dependencies: - "@babel/helper-module-transforms": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-validator-identifier": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" + "@babel/helper-hoist-variables": "npm:^7.22.5" + "@babel/helper-module-transforms": "npm:^7.23.0" + "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-validator-identifier": "npm:^7.22.20" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: fca6198da71237e4bb1274b3b67a0c81d56013c9535361242b6bfa87d70a9597854aadb45d4d8203369be4a655e158be2a5d20af0040b1f8d1bfc47db3ad7b68 + checksum: 04c5cef7d6921bb9c9073cea389289099124e78cd1e3b7e020e3c085d486b48efadd9a42c0c0d963a9b1c3d5465c3151229092ea719997e53427f36935c84178 languageName: node linkType: hard @@ -2503,18 +2157,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-umd@npm:7.24.7" - dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 7791d290121db210e4338b94b4a069a1a79e4c7a8d7638d8159a97b281851bbed3048dac87a4ae718ad963005e6c14a5d28e6db2eeb2b04e031cee92fb312f85 - languageName: node - linkType: hard - "@babel/plugin-transform-named-capturing-groups-regex@npm:^7.0.0, @babel/plugin-transform-named-capturing-groups-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.22.5" @@ -2527,18 +2169,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.24.7" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 41a0b0f2d0886318237440aa3b489f6d0305361d8671121777d9ff89f9f6de9d0c02ce93625049061426c8994064ef64deae8b819d1b14c00374a6a2336fb5d9 - languageName: node - linkType: hard - "@babel/plugin-transform-new-target@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-new-target@npm:7.22.5" @@ -2550,17 +2180,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-new-target@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 2540808a35e1a978e537334c43dab439cf24c93e7beb213a2e71902f6710e60e0184316643790c0a6644e7a8021e52f7ab8165e6b3e2d6651be07bdf517b67df - languageName: node - linkType: hard - "@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.22.11" @@ -2573,15 +2192,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.7" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 7243c8ff734ed5ef759dd8768773c4b443c12e792727e759a1aec2c7fa2bfdd24f1ecb42e292a7b3d8bd3d7f7b861cf256a8eb4ba144fc9cc463892c303083d9 + checksum: 66f7237d59060954fc0ba0c5d9e7081580421014b446080b3efedb3d4be9a4346f50974c5886a4ec7962db9992e5e1c5e26cb76801728b4d9626ac2eb09c26f7 languageName: node linkType: hard @@ -2597,15 +2216,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.7" +"@babel/plugin-transform-numeric-separator@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e18e09ca5a6342645d00ede477731aa6e8714ff357efc9d7cda5934f1703b3b6fb7d3298dce3ce3ba53e9ff1158eab8f1aadc68874cc21a6099d33a1ca457789 + checksum: 921d6ff2165eb782c28a6c06e9eb0dc17400c9476b000a7f8b8dfa95c122c22be4adee7bc15f035a1e4269842b3a68b0a2f20e4437025a6e0fbe16e479a879b8 languageName: node linkType: hard @@ -2638,17 +2257,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.7" +"@babel/plugin-transform-object-rest-spread@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.22.5" dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/compat-data": "npm:^7.22.5" + "@babel/helper-compilation-targets": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-transform-parameters": "npm:^7.24.7" + "@babel/plugin-transform-parameters": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 9ad64bc003f583030f9da50614b485852f8edac93f8faf5d1cd855201a4852f37c5255ae4daf70dd4375bdd4874e16e39b91f680d4668ec219ba05441ce286eb + checksum: ab93b8f84e4ed6629ea258d94b597976598a1990035b4d5178c8d117908a48a36f0f03dd2f4a3375393a23a588ecc7817c099ac88a80f8307475b9a25e4d08e0 languageName: node linkType: hard @@ -2664,18 +2284,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-object-super@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 770cebb4b4e1872c216b17069db9a13b87dfee747d359dc56d9fcdd66e7544f92dc6ab1861a4e7e0528196aaff2444e4f17dc84efd8eaf162d542b4ba0943869 - languageName: node - linkType: hard - "@babel/plugin-transform-optional-catch-binding@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.22.11" @@ -2688,45 +2296,45 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.7" +"@babel/plugin-transform-optional-catch-binding@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 1e2f10a018f7d03b3bde6c0b70d063df8d5dd5209861d4467726cf834f5e3d354e2276079dc226aa8e6ece35f5c9b264d64b8229a8bb232829c01e561bcfb07a + checksum: a15bfa5b36f5f1f61521cc1c73e1e394fbd08aef82a416e2e43f5fc7b43830f17d4c9a5605f1b69ed2bbbacd6f49f5e4f9a3e8e0b7a83841bc95e8ef2116f0a9 languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.22.15, @babel/plugin-transform-optional-chaining@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.23.0" +"@babel/plugin-transform-optional-chaining@npm:^7.22.10, @babel/plugin-transform-optional-chaining@npm:^7.22.5": + version: 7.22.10 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.22.10" dependencies: "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5" "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2bf605b908c75f8d7616e8be52e4656983f2b027032260fbf5279f28297a67a1a28ec3ed60cd5760537dbd08a021246b8092ce06fb2418884390230b807142b3 + checksum: 18ee2fff4f922141a31025445a40834d0af7bb398e39d4ad5825de8a8de54f56585b7db4a4d1c0811ed106b780be26e7626806579387787c473ff4fed77778bb languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.24.7, @babel/plugin-transform-optional-chaining@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8" +"@babel/plugin-transform-optional-chaining@npm:^7.22.15, @babel/plugin-transform-optional-chaining@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.23.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5" "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 4ffbe1aad7dec7c9aa2bf6ceb4b2f91f96815b2784f2879bde80e46934f59d64a12cb2c6262e40897c4754d77d2c35d8a5cfed63044fdebf94978b1ed3d14b17 + checksum: 2bf605b908c75f8d7616e8be52e4656983f2b027032260fbf5279f28297a67a1a28ec3ed60cd5760537dbd08a021246b8092ce06fb2418884390230b807142b3 languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.0.0, @babel/plugin-transform-parameters@npm:^7.20.7": +"@babel/plugin-transform-parameters@npm:^7.0.0, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-parameters@npm:7.22.5" dependencies: @@ -2759,17 +2367,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-parameters@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 53bf190d6926771545d5184f1f5f3f5144d0f04f170799ad46a43f683a01fab8d5fe4d2196cf246774530990c31fe1f2b9f0def39f0a5ddbb2340b924f5edf01 - languageName: node - linkType: hard - "@babel/plugin-transform-private-methods@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-private-methods@npm:7.22.5" @@ -2782,18 +2379,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 7abdb427c3984a2c8a2e9d806297d8509b02f78a3501b7760e544be532446e9df328b876daa8fc38718f3dce7ccc45083016ee7aeaab169b81c142bc18700794 - languageName: node - linkType: hard - "@babel/plugin-transform-private-property-in-object@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-private-property-in-object@npm:7.22.11" @@ -2808,17 +2393,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.7" +"@babel/plugin-transform-private-property-in-object@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.22.5" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-annotate-as-pure": "npm:^7.22.5" + "@babel/helper-create-class-features-plugin": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c6fa7defb90b1b0ed46f24ff94ff2e77f44c1f478d1090e81712f33cf992dda5ba347016f030082a2f770138bac6f4a9c2c1565e9f767a125901c77dd9c239ba + checksum: f178191da005d986fdeb30ef74ea0d28878e6225d305d931ce925d87b7df432f5bb29e32173cff2a5c408cee7abc9f25fab09530d4f419ce5cc29a44a89f7a55 languageName: node linkType: hard @@ -2833,17 +2418,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-property-literals@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 52564b58f3d111dc02d241d5892a4b01512e98dfdf6ef11b0ed62f8b11b0acacccef0fc229b44114fe8d1a57a8b70780b11bdd18b807d3754a781a07d8f57433 - languageName: node - linkType: hard - "@babel/plugin-transform-react-display-name@npm:^7.0.0, @babel/plugin-transform-react-display-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-display-name@npm:7.22.5" @@ -2866,17 +2440,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-display-name@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: c14a07a9e75723c96f1a0a306b8a8e899ff1c6a0cc3d62bcda79bb1b54e4319127b258651c513a1a47da152cdc22e16525525a30ae5933a2980c7036fd0b4d24 - languageName: node - linkType: hard - "@babel/plugin-transform-react-jsx-development@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx-development@npm:7.22.5" @@ -2888,17 +2451,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-development@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-jsx-development@npm:7.24.7" - dependencies: - "@babel/plugin-transform-react-jsx": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: fce647db50f90a5291681f0f97865d9dc76981262dff71d6d0332e724b85343de5860c26f9e9a79e448d61e1d70916b07ce91e8c7f2b80dceb4b16aee41794d8 - languageName: node - linkType: hard - "@babel/plugin-transform-react-jsx-self@npm:^7.0.0": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx-self@npm:7.22.5" @@ -2966,21 +2518,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.24.7": - version: 7.25.2 - resolution: "@babel/plugin-transform-react-jsx@npm:7.25.2" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/plugin-syntax-jsx": "npm:^7.24.7" - "@babel/types": "npm:^7.25.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 8c5b515f38118471197605e02bea54a8a4283010e3c55bad8cfb78de59ad63612b14d40baca63689afdc9d57b147aac4c7794fe5f7736c9e1ed6dd38784be624 - languageName: node - linkType: hard - "@babel/plugin-transform-react-pure-annotations@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.22.5" @@ -3005,18 +2542,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-pure-annotations@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.24.7" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: fae517d293d9c93b7b920458c3e4b91cb0400513889af41ba184a5f3acc8bfef27242cc262741bb8f87870df376f1733a0d0f52b966d342e2aaaf5607af8f73d - languageName: node - linkType: hard - "@babel/plugin-transform-regenerator@npm:^7.22.10": version: 7.22.10 resolution: "@babel/plugin-transform-regenerator@npm:7.22.10" @@ -3029,18 +2554,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-regenerator@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - regenerator-transform: "npm:^0.15.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: d2dc2c788fdae9d97217e70d46ba8ca9db0035c398dc3e161552b0c437113719a75c04f201f9c91ddc8d28a1da60d0b0853f616dead98a396abb9c845c44892b - languageName: node - linkType: hard - "@babel/plugin-transform-reserved-words@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-reserved-words@npm:7.22.5" @@ -3052,17 +2565,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-reserved-words@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 2229de2768615e7f5dc0bbc55bc121b5678fd6d2febd46c74a58e42bb894d74cd5955c805880f4e02d0e1cf94f6886270eda7fafc1be9305a1ec3b9fd1d063f5 - languageName: node - linkType: hard - "@babel/plugin-transform-runtime@npm:^7.0.0": version: 7.22.10 resolution: "@babel/plugin-transform-runtime@npm:7.22.10" @@ -3090,17 +2592,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 41b155bdbb3be66618358488bf7731b3b2e8fff2de3dbfd541847720a9debfcec14db06a117abedd03c9cd786db20a79e2a86509a4f19513f6e1b610520905cf - languageName: node - linkType: hard - "@babel/plugin-transform-spread@npm:^7.0.0, @babel/plugin-transform-spread@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-spread@npm:7.22.5" @@ -3113,18 +2604,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-spread@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: facba1553035f76b0d2930d4ada89a8cd0f45b79579afd35baefbfaf12e3b86096995f4b0c402cf9ee23b3f2ea0a4460c3b1ec0c192d340962c948bb223d4e66 - languageName: node - linkType: hard - "@babel/plugin-transform-sticky-regex@npm:^7.0.0, @babel/plugin-transform-sticky-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-sticky-regex@npm:7.22.5" @@ -3136,36 +2615,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 5a74ed2ed0a3ab51c3d15fcaf09d9e2fe915823535c7a4d7b019813177d559b69677090e189ec3d5d08b619483eb5ad371fbcfbbff5ace2a76ba33ee566a1109 - languageName: node - linkType: hard - "@babel/plugin-transform-template-literals@npm:^7.0.0, @babel/plugin-transform-template-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-template-literals@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 1fc597716edf9f5c7bc74e2fead4d7751467500486dd17092af90ccbd65c5fc4a1db2e9c86e9ed1a9f206f6a3403bbc07eab50b0c2b8e50f819b4118f2cf71ef - languageName: node - linkType: hard - -"@babel/plugin-transform-template-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-template-literals@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3630f966257bcace122f04d3157416a09d40768c44c3a800855da81146b009187daa21859d1c3b7d13f4e19e8888e60613964b175b2275d451200fb6d8d6cfe6 + checksum: 1fc597716edf9f5c7bc74e2fead4d7751467500486dd17092af90ccbd65c5fc4a1db2e9c86e9ed1a9f206f6a3403bbc07eab50b0c2b8e50f819b4118f2cf71ef languageName: node linkType: hard @@ -3180,17 +2637,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 2f570a4fbbdc5fd85f48165a97452826560051e3b8efb48c3bb0a0a33ee8485633439e7b71bfe3ef705583a1df43f854f49125bd759abdedc195b2cf7e60012a - languageName: node - linkType: hard - "@babel/plugin-transform-typescript@npm:^7.22.15": version: 7.22.15 resolution: "@babel/plugin-transform-typescript@npm:7.22.15" @@ -3244,17 +2690,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 8b18e2e66af33471a6971289492beff5c240e56727331db1d34c4338a6a368a82a7ed6d57ec911001b6d65643aed76531e1e7cac93265fb3fb2717f54d845e69 - languageName: node - linkType: hard - "@babel/plugin-transform-unicode-property-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.22.5" @@ -3267,18 +2702,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.7" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: bc57656eb94584d1b74a385d378818ac2b3fca642e3f649fead8da5fb3f9de22f8461185936915dfb33d5a9104e62e7a47828331248b09d28bb2d59e9276de3e - languageName: node - linkType: hard - "@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-unicode-regex@npm:7.22.5" @@ -3291,18 +2714,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 83f72a345b751566b601dc4d07e9f2c8f1bc0e0c6f7abb56ceb3095b3c9d304de73f85f2f477a09f8cc7edd5e65afd0ff9e376cdbcbea33bc0c28f3705b38fd9 - languageName: node - linkType: hard - "@babel/plugin-transform-unicode-sets-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.22.5" @@ -3315,18 +2726,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: f65749835a98d8d6242e961f9276bdcdb09020e791d151ccc145acaca9a66f025b2c7cb761104f139180d35eb066a429596ee6edece81f5fd9244e0edb97d7ec - languageName: node - linkType: hard - "@babel/preset-env@npm:^7.18.2": version: 7.23.2 resolution: "@babel/preset-env@npm:7.23.2" @@ -3418,26 +2817,23 @@ __metadata: linkType: hard "@babel/preset-env@npm:^7.22.10": - version: 7.25.4 - resolution: "@babel/preset-env@npm:7.25.4" + version: 7.22.10 + resolution: "@babel/preset-env@npm:7.22.10" dependencies: - "@babel/compat-data": "npm:^7.25.4" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-validator-option": "npm:^7.24.8" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.3" - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.0" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.0" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.0" + "@babel/compat-data": "npm:^7.22.9" + "@babel/helper-compilation-targets": "npm:^7.22.10" + "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-validator-option": "npm:^7.22.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.22.5" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.22.5" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/plugin-syntax-class-properties": "npm:^7.12.13" "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" - "@babel/plugin-syntax-import-assertions": "npm:^7.24.7" - "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" + "@babel/plugin-syntax-import-assertions": "npm:^7.22.5" + "@babel/plugin-syntax-import-attributes": "npm:^7.22.5" "@babel/plugin-syntax-import-meta": "npm:^7.10.4" "@babel/plugin-syntax-json-strings": "npm:^7.8.3" "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" @@ -3449,64 +2845,64 @@ __metadata: "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" - "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" - "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" - "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7" - "@babel/plugin-transform-block-scoping": "npm:^7.25.0" - "@babel/plugin-transform-class-properties": "npm:^7.25.4" - "@babel/plugin-transform-class-static-block": "npm:^7.24.7" - "@babel/plugin-transform-classes": "npm:^7.25.4" - "@babel/plugin-transform-computed-properties": "npm:^7.24.7" - "@babel/plugin-transform-destructuring": "npm:^7.24.8" - "@babel/plugin-transform-dotall-regex": "npm:^7.24.7" - "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.0" - "@babel/plugin-transform-dynamic-import": "npm:^7.24.7" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.24.7" - "@babel/plugin-transform-export-namespace-from": "npm:^7.24.7" - "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.25.1" - "@babel/plugin-transform-json-strings": "npm:^7.24.7" - "@babel/plugin-transform-literals": "npm:^7.25.2" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" - "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7" - "@babel/plugin-transform-modules-amd": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" - "@babel/plugin-transform-modules-systemjs": "npm:^7.25.0" - "@babel/plugin-transform-modules-umd": "npm:^7.24.7" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" - "@babel/plugin-transform-new-target": "npm:^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" - "@babel/plugin-transform-object-super": "npm:^7.24.7" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" - "@babel/plugin-transform-parameters": "npm:^7.24.7" - "@babel/plugin-transform-private-methods": "npm:^7.25.4" - "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" - "@babel/plugin-transform-property-literals": "npm:^7.24.7" - "@babel/plugin-transform-regenerator": "npm:^7.24.7" - "@babel/plugin-transform-reserved-words": "npm:^7.24.7" - "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" - "@babel/plugin-transform-spread": "npm:^7.24.7" - "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" - "@babel/plugin-transform-template-literals": "npm:^7.24.7" - "@babel/plugin-transform-typeof-symbol": "npm:^7.24.8" - "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7" - "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.4" + "@babel/plugin-transform-arrow-functions": "npm:^7.22.5" + "@babel/plugin-transform-async-generator-functions": "npm:^7.22.10" + "@babel/plugin-transform-async-to-generator": "npm:^7.22.5" + "@babel/plugin-transform-block-scoped-functions": "npm:^7.22.5" + "@babel/plugin-transform-block-scoping": "npm:^7.22.10" + "@babel/plugin-transform-class-properties": "npm:^7.22.5" + "@babel/plugin-transform-class-static-block": "npm:^7.22.5" + "@babel/plugin-transform-classes": "npm:^7.22.6" + "@babel/plugin-transform-computed-properties": "npm:^7.22.5" + "@babel/plugin-transform-destructuring": "npm:^7.22.10" + "@babel/plugin-transform-dotall-regex": "npm:^7.22.5" + "@babel/plugin-transform-duplicate-keys": "npm:^7.22.5" + "@babel/plugin-transform-dynamic-import": "npm:^7.22.5" + "@babel/plugin-transform-exponentiation-operator": "npm:^7.22.5" + "@babel/plugin-transform-export-namespace-from": "npm:^7.22.5" + "@babel/plugin-transform-for-of": "npm:^7.22.5" + "@babel/plugin-transform-function-name": "npm:^7.22.5" + "@babel/plugin-transform-json-strings": "npm:^7.22.5" + "@babel/plugin-transform-literals": "npm:^7.22.5" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.22.5" + "@babel/plugin-transform-member-expression-literals": "npm:^7.22.5" + "@babel/plugin-transform-modules-amd": "npm:^7.22.5" + "@babel/plugin-transform-modules-commonjs": "npm:^7.22.5" + "@babel/plugin-transform-modules-systemjs": "npm:^7.22.5" + "@babel/plugin-transform-modules-umd": "npm:^7.22.5" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.22.5" + "@babel/plugin-transform-new-target": "npm:^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.22.5" + "@babel/plugin-transform-numeric-separator": "npm:^7.22.5" + "@babel/plugin-transform-object-rest-spread": "npm:^7.22.5" + "@babel/plugin-transform-object-super": "npm:^7.22.5" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.22.5" + "@babel/plugin-transform-optional-chaining": "npm:^7.22.10" + "@babel/plugin-transform-parameters": "npm:^7.22.5" + "@babel/plugin-transform-private-methods": "npm:^7.22.5" + "@babel/plugin-transform-private-property-in-object": "npm:^7.22.5" + "@babel/plugin-transform-property-literals": "npm:^7.22.5" + "@babel/plugin-transform-regenerator": "npm:^7.22.10" + "@babel/plugin-transform-reserved-words": "npm:^7.22.5" + "@babel/plugin-transform-shorthand-properties": "npm:^7.22.5" + "@babel/plugin-transform-spread": "npm:^7.22.5" + "@babel/plugin-transform-sticky-regex": "npm:^7.22.5" + "@babel/plugin-transform-template-literals": "npm:^7.22.5" + "@babel/plugin-transform-typeof-symbol": "npm:^7.22.5" + "@babel/plugin-transform-unicode-escapes": "npm:^7.22.10" + "@babel/plugin-transform-unicode-property-regex": "npm:^7.22.5" + "@babel/plugin-transform-unicode-regex": "npm:^7.22.5" + "@babel/plugin-transform-unicode-sets-regex": "npm:^7.22.5" "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.10.6" - babel-plugin-polyfill-regenerator: "npm:^0.6.1" - core-js-compat: "npm:^3.37.1" + "@babel/types": "npm:^7.22.10" + babel-plugin-polyfill-corejs2: "npm:^0.4.5" + babel-plugin-polyfill-corejs3: "npm:^0.8.3" + babel-plugin-polyfill-regenerator: "npm:^0.5.2" + core-js-compat: "npm:^3.31.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ed210a1974b5a1e7f80a933c87253907ec869457cea900bc97892642fa9a690c47627a9bac08a7c9495deb992a2b15f308ffca2741e1876ba47172c96fa27e14 + checksum: 56552a5298e4bdb89a075f88638e3dfb4937e9e781ba682a1a4c9c68551b6471ed79e5d85d8d006421645e8c9ff500f18efb341d76cead5f110aefb6bdbac098 languageName: node linkType: hard @@ -3582,18 +2978,18 @@ __metadata: linkType: hard "@babel/preset-react@npm:^7.22.5": - version: 7.24.7 - resolution: "@babel/preset-react@npm:7.24.7" + version: 7.22.5 + resolution: "@babel/preset-react@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" - "@babel/plugin-transform-react-display-name": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx-development": "npm:^7.24.7" - "@babel/plugin-transform-react-pure-annotations": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-validator-option": "npm:^7.22.5" + "@babel/plugin-transform-react-display-name": "npm:^7.22.5" + "@babel/plugin-transform-react-jsx": "npm:^7.22.5" + "@babel/plugin-transform-react-jsx-development": "npm:^7.22.5" + "@babel/plugin-transform-react-pure-annotations": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 9658b685b25cedaadd0b65c4e663fbc7f57394b5036ddb4c99b1a75b0711fb83292c1c625d605c05b73413fc7a6dc20e532627f6a39b6dc8d4e00415479b054c + checksum: 60c1fde93d5a6bda03b3d2bb61bcbf056925fd0b01e84d789eaf2a06f639d8714e93735a75da0221fd7a8407c6b4fea7b4fbc35de5ff5d5a299aecb1c82fd530 languageName: node linkType: hard @@ -3744,7 +3140,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4": +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.22.10, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.6, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.7.4": version: 7.25.6 resolution: "@babel/traverse@npm:7.25.6" dependencies: @@ -3759,78 +3155,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.22.10, @babel/traverse@npm:^7.7.4": - version: 7.22.10 - resolution: "@babel/traverse@npm:7.22.10" - dependencies: - "@babel/code-frame": "npm:^7.22.10" - "@babel/generator": "npm:^7.22.10" - "@babel/helper-environment-visitor": "npm:^7.22.5" - "@babel/helper-function-name": "npm:^7.22.5" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.22.10" - "@babel/types": "npm:^7.22.10" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - checksum: 8e8b63b053962908408ed9d954810e93f241122222db115327ed5876d020f420fc115ef2d79623c2a4928447ddc002ec220be2a152b241d19de2480c88e10cfb - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.23.2": - version: 7.23.2 - resolution: "@babel/traverse@npm:7.23.2" - dependencies: - "@babel/code-frame": "npm:^7.22.13" - "@babel/generator": "npm:^7.23.0" - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.23.0" - "@babel/types": "npm:^7.23.0" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - checksum: d096c7c4bab9262a2f658298a3c630ae4a15a10755bb257ae91d5ab3e3b2877438934859c8d34018b7727379fe6b26c4fa2efc81cf4c462a7fe00caf79fa02ff - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/traverse@npm:7.23.6" - dependencies: - "@babel/code-frame": "npm:^7.23.5" - "@babel/generator": "npm:^7.23.6" - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.23.6" - "@babel/types": "npm:^7.23.6" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 5b4ebb94a00a7e1daf111e4b0b45a7998d5b7598637a14e75e855e88cc1b702789e09a958726b5d599a003be1e9032dbdfde4b88ea6061332228738950d5582d - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/traverse@npm:7.24.8" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.8" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-hoist-variables": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.8" - "@babel/types": "npm:^7.24.8" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 67a5cc35824455cdb54fb9e196a44b3186283e29018a9c2331f51763921e18e891b3c60c283615a27540ec8eb4c8b89f41c237b91f732a7aa518b2eb7a0d434d - languageName: node - linkType: hard - "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.22.10 resolution: "@babel/types@npm:7.22.10" @@ -4211,17 +3535,15 @@ __metadata: languageName: node linkType: hard -"@coinbase/wallet-sdk@npm:4.0.4": - version: 4.0.4 - resolution: "@coinbase/wallet-sdk@npm:4.0.4" +"@coinbase/wallet-sdk@npm:4.2.3": + version: 4.2.3 + resolution: "@coinbase/wallet-sdk@npm:4.2.3" dependencies: - buffer: "npm:^6.0.3" + "@noble/hashes": "npm:^1.4.0" clsx: "npm:^1.2.1" eventemitter3: "npm:^5.0.1" - keccak: "npm:^3.0.3" - preact: "npm:^10.16.0" - sha.js: "npm:^2.4.11" - checksum: 7c8c39688c144b5305ac59d847023f7dce9ccffdd8ed6fdcc690c03980ce7cf8f88caff4e0cf0a1f081bcfd61ebe6a590970771505f86700f9b798a0e8e2dc88 + preact: "npm:^10.24.2" + checksum: ce27b5bfdcbc79e896cd262baf0483073ac854986b518e29a23af9c70b3bb6a75d6c15e5e34355095249d46fa8a8eda4682b63ec82812e92cbadba56b8706190 languageName: node linkType: hard @@ -4234,6 +3556,15 @@ __metadata: languageName: node linkType: hard +"@ecies/ciphers@npm:^0.2.0": + version: 0.2.1 + resolution: "@ecies/ciphers@npm:0.2.1" + peerDependencies: + "@noble/ciphers": ^1.0.0 + checksum: 0ce13f5f8216047afde68afe549021c65145af2d2f08da032552487f170c47fd480c11fa358b5cdcc29e70928e5d81e037b6a5963a20ba74d3c242881ba5bb50 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/aix-ppc64@npm:0.23.1" @@ -4421,15 +3752,15 @@ __metadata: linkType: hard "@eslint-community/regexpp@npm:^4.6.1": - version: 4.11.1 - resolution: "@eslint-community/regexpp@npm:4.11.1" - checksum: fbcc1cb65ef5ed5b92faa8dc542e035269065e7ebcc0b39c81a4fe98ad35cfff20b3c8df048641de15a7757e07d69f85e2579c1a5055f993413ba18c055654f8 + version: 4.6.2 + resolution: "@eslint-community/regexpp@npm:4.6.2" + checksum: da800788298f8419f4c4e04eaa4e3c97e7f57537e822e7b150de662e420e3d437816b863e490807bd0b00e715b0989f9d8864bf54357cbcfa84e4255b910789d languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/eslintrc@npm:2.1.4" +"@eslint/eslintrc@npm:^2.1.1": + version: 2.1.1 + resolution: "@eslint/eslintrc@npm:2.1.1" dependencies: ajv: "npm:^6.12.4" debug: "npm:^4.3.2" @@ -4440,14 +3771,14 @@ __metadata: js-yaml: "npm:^4.1.0" minimatch: "npm:^3.1.2" strip-json-comments: "npm:^3.1.1" - checksum: 32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + checksum: 104ec8997206eabc87de84b87a2852efce0ff98730d377061734da2554c79c9b6d417fbe66248ef5566a0501ef41fddec3a00f79b77731102903586a63b2ed34 languageName: node linkType: hard -"@eslint/js@npm:8.57.1": - version: 8.57.1 - resolution: "@eslint/js@npm:8.57.1" - checksum: b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223 +"@eslint/js@npm:^8.46.0": + version: 8.46.0 + resolution: "@eslint/js@npm:8.46.0" + checksum: 674c5800e4e9829322aa84195b23c59db326cb42190ac0284bdfe70b2442d544837f3006d8d8c166afaa86ab7072df1b77f7fdb43a60aa2bb1ede90d82e38540 languageName: node linkType: hard @@ -5201,6 +4532,15 @@ __metadata: languageName: node linkType: hard +"@expo/metro-runtime@npm:~3.2.3": + version: 3.2.3 + resolution: "@expo/metro-runtime@npm:3.2.3" + peerDependencies: + react-native: "*" + checksum: a5357c32663e516833feed8f6fd899e1a6ab6acf79b198e860bb82076512e07f95730420eefc87a354d4004d9482b29fbecadcbdcf59b6f8737bba4da03e405e + languageName: node + linkType: hard + "@expo/osascript@npm:^2.0.31": version: 2.0.33 resolution: "@expo/osascript@npm:2.0.33" @@ -5340,14 +4680,14 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.13.0": - version: 0.13.0 - resolution: "@humanwhocodes/config-array@npm:0.13.0" +"@humanwhocodes/config-array@npm:^0.11.10": + version: 0.11.10 + resolution: "@humanwhocodes/config-array@npm:0.11.10" dependencies: - "@humanwhocodes/object-schema": "npm:^2.0.3" - debug: "npm:^4.3.1" + "@humanwhocodes/object-schema": "npm:^1.2.1" + debug: "npm:^4.1.1" minimatch: "npm:^3.0.5" - checksum: 205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e + checksum: 9e307a49a5baa28beb243d2c14c145f288fccd6885f4c92a9055707057ec40980242256b2a07c976cfa6c75f7081da111a40a9844d1ca8daeff2302f8b640e76 languageName: node linkType: hard @@ -5358,10 +4698,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.3": - version: 2.0.3 - resolution: "@humanwhocodes/object-schema@npm:2.0.3" - checksum: 80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c +"@humanwhocodes/object-schema@npm:^1.2.1": + version: 1.2.1 + resolution: "@humanwhocodes/object-schema@npm:1.2.1" + checksum: c3c35fdb70c04a569278351c75553e293ae339684ed75895edc79facc7276e351115786946658d78133130c0cca80e57e2203bc07f8fa7fe7980300e8deef7db languageName: node linkType: hard @@ -5784,7 +5124,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": +"@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -5973,9 +5313,9 @@ __metadata: languageName: node linkType: hard -"@metamask/sdk-communication-layer@npm:0.28.2": - version: 0.28.2 - resolution: "@metamask/sdk-communication-layer@npm:0.28.2" +"@metamask/sdk-communication-layer@npm:0.30.0": + version: 0.30.0 + resolution: "@metamask/sdk-communication-layer@npm:0.30.0" dependencies: bufferutil: "npm:^4.0.8" date-fns: "npm:^2.29.3" @@ -5988,13 +5328,13 @@ __metadata: eventemitter2: ^6.4.7 readable-stream: ^3.6.2 socket.io-client: ^4.5.1 - checksum: 7d51316eb313bd4464e8e5d787c4d88228e40673414883a693f5772908cb5c17903db0d3101bc04ee9db218728525a0ad3a8545c6e7d933b48f3ae6ce8a474bc + checksum: e3f2b1a05e474142c1c92c89b4347cbefe4503143cd9e27ff961a341afe2bc2d593b111db5a9425231ff1661a9219449fb50c47c3f4ccc39c81c97e925aac477 languageName: node linkType: hard -"@metamask/sdk-install-modal-web@npm:0.28.1": - version: 0.28.1 - resolution: "@metamask/sdk-install-modal-web@npm:0.28.1" +"@metamask/sdk-install-modal-web@npm:0.30.0": + version: 0.30.0 + resolution: "@metamask/sdk-install-modal-web@npm:0.30.0" dependencies: qr-code-styling: "npm:^1.6.0-rc.1" peerDependencies: @@ -6009,24 +5349,22 @@ __metadata: optional: true react-native: optional: true - checksum: e7bc9789d6499ff1f2ec2587b0604c4df445bc35e0914165289348fe9325ccff60ef094b5ebe39310af9c68ee8d6d71ed0a6a217e2d3947a2aa92a4c7063e4a1 + checksum: b515a356148179e74c80562d6127c59a21d25bce0a83bef3b190d02785d231936cc394fb87f6141a673ba0d8ba3f443f0572540aa6883b74ea17b9f6e771dc00 languageName: node linkType: hard -"@metamask/sdk@npm:0.28.2": - version: 0.28.2 - resolution: "@metamask/sdk@npm:0.28.2" +"@metamask/sdk@npm:0.30.1": + version: 0.30.1 + resolution: "@metamask/sdk@npm:0.30.1" dependencies: "@metamask/onboarding": "npm:^1.0.1" "@metamask/providers": "npm:16.1.0" - "@metamask/sdk-communication-layer": "npm:0.28.2" - "@metamask/sdk-install-modal-web": "npm:0.28.1" - "@types/dom-screen-wake-lock": "npm:^1.0.0" - "@types/uuid": "npm:^10.0.0" + "@metamask/sdk-communication-layer": "npm:0.30.0" + "@metamask/sdk-install-modal-web": "npm:0.30.0" bowser: "npm:^2.9.0" cross-fetch: "npm:^4.0.0" debug: "npm:^4.3.4" - eciesjs: "npm:^0.3.15" + eciesjs: "npm:^0.4.8" eth-rpc-errors: "npm:^4.0.3" eventemitter2: "npm:^6.4.7" i18next: "npm:23.11.5" @@ -6036,7 +5374,6 @@ __metadata: qrcode-terminal-nooctal: "npm:^0.12.1" react-native-webview: "npm:^11.26.0" readable-stream: "npm:^3.6.2" - rollup-plugin-visualizer: "npm:^5.9.2" socket.io-client: "npm:^4.5.1" util: "npm:^0.12.4" uuid: "npm:^8.3.2" @@ -6048,7 +5385,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 519240bd0729e9fbee6000d794c7071d4739917c40b3f4529f6315690b76221595110e4ede6ade10b595da8de4a152ac8a8571f6600ab16b132de61548536b37 + checksum: e42a98471adecc6c291e322ec6772fa8f8d9ae9949887e59c77f7d916ff44f5e69fc57b74449b2e04603e8ecb550e782815a6dc8f81f8afc291fc61d06fe6722 languageName: node linkType: hard @@ -6192,6 +5529,13 @@ __metadata: languageName: node linkType: hard +"@noble/ciphers@npm:^1.0.0": + version: 1.0.0 + resolution: "@noble/ciphers@npm:1.0.0" + checksum: 6c04d6e9d10a922fff170efc44622c95a25fb817f4b593e0f150dd27599576f3fe3c5b61eb02054b22d1507e3839879ddd5acb2d2acf8efbea4efab99bbcd333 + languageName: node + linkType: hard + "@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": version: 1.1.0 resolution: "@noble/curves@npm:1.1.0" @@ -6219,6 +5563,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.6.0, @noble/curves@npm:^1.6.0, @noble/curves@npm:~1.6.0": + version: 1.6.0 + resolution: "@noble/curves@npm:1.6.0" + dependencies: + "@noble/hashes": "npm:1.5.0" + checksum: f3262aa4d39148e627cd82b5ac1c93f88c5bb46dd2566b5e8e52ffac3a0fc381ad30c2111656fd2bd3b0d37d43d540543e0d93a5ff96a6cb184bc3bfe10d1cd9 + languageName: node + linkType: hard + "@noble/curves@npm:^1.4.0, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -6249,6 +5602,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:~1.5.0": + version: 1.5.0 + resolution: "@noble/hashes@npm:1.5.0" + checksum: 1b46539695fbfe4477c0822d90c881a04d4fa2921c08c552375b444a48cac9930cb1ee68de0a3c7859e676554d0f3771999716606dc4d8f826e414c11692cdd9 + languageName: node + linkType: hard + "@noble/hashes@npm:~1.3.1": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" @@ -6256,13 +5616,6 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:~1.5.0": - version: 1.5.0 - resolution: "@noble/hashes@npm:1.5.0" - checksum: 1b46539695fbfe4477c0822d90c881a04d4fa2921c08c552375b444a48cac9930cb1ee68de0a3c7859e676554d0f3771999716606dc4d8f826e414c11692cdd9 - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -6464,10 +5817,28 @@ __metadata: languageName: node linkType: hard -"@pkgr/core@npm:^0.1.0": - version: 0.1.1 - resolution: "@pkgr/core@npm:0.1.1" - checksum: 3f7536bc7f57320ab2cf96f8973664bef624710c403357429fbf680a5c3b4843c1dbd389bb43daa6b1f6f1f007bb082f5abcb76bb2b5dc9f421647743b71d3d8 +"@pkgr/utils@npm:^2.3.1": + version: 2.4.2 + resolution: "@pkgr/utils@npm:2.4.2" + dependencies: + cross-spawn: "npm:^7.0.3" + fast-glob: "npm:^3.3.0" + is-glob: "npm:^4.0.3" + open: "npm:^9.1.0" + picocolors: "npm:^1.0.0" + tslib: "npm:^2.6.0" + checksum: 7c3e68f6405a1d4c51f418d8d580e71d7bade2683d5db07e8413d8e57f7e389047eda44a2341f77a1b3085895fca7676a9d45e8812a58312524f8c4c65d501be + languageName: node + linkType: hard + +"@playwright/test@npm:^1.49.1": + version: 1.49.1 + resolution: "@playwright/test@npm:1.49.1" + dependencies: + playwright: "npm:1.49.1" + bin: + playwright: cli.js + checksum: 2fca0bb7b334f7a23c7c5dfa5dbe37b47794c56f39b747c8d74a2f95c339e7902a296f2f1dd32c47bdd723cfa92cee05219f1a5876725dc89a1871b9137a286d languageName: node linkType: hard @@ -7322,6 +6693,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi" dependencies: + "@reown/appkit-core-react-native": "npm:1.0.2" "@reown/appkit-wallet-react-native": "npm:1.0.2" peerDependencies: wagmi: ">=2" @@ -7349,6 +6721,9 @@ __metadata: "@reown/appkit-common-react-native@npm:1.0.2, @reown/appkit-common-react-native@workspace:packages/common": version: 0.0.0-use.local resolution: "@reown/appkit-common-react-native@workspace:packages/common" + dependencies: + bignumber.js: "npm:9.1.2" + dayjs: "npm:1.11.10" languageName: unknown linkType: soft @@ -7374,7 +6749,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.0.2" "@reown/appkit-scaffold-utils-react-native": "npm:1.0.2" "@reown/appkit-siwe-react-native": "npm:1.0.2" - "@walletconnect/ethereum-provider": "npm:2.16.1" + "@walletconnect/ethereum-provider": "npm:2.17.3" ethers: "npm:6.10.0" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -7395,7 +6770,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.0.2" "@reown/appkit-scaffold-utils-react-native": "npm:1.0.2" "@reown/appkit-siwe-react-native": "npm:1.0.2" - "@walletconnect/ethereum-provider": "npm:2.16.1" + "@walletconnect/ethereum-provider": "npm:2.17.3" ethers: "npm:5.7.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -7503,13 +6878,13 @@ __metadata: languageName: node linkType: hard -"@safe-global/safe-apps-provider@npm:0.18.3": - version: 0.18.3 - resolution: "@safe-global/safe-apps-provider@npm:0.18.3" +"@safe-global/safe-apps-provider@npm:0.18.4": + version: 0.18.4 + resolution: "@safe-global/safe-apps-provider@npm:0.18.4" dependencies: "@safe-global/safe-apps-sdk": "npm:^9.1.0" events: "npm:^3.3.0" - checksum: 7209d761919969c0859e8b9df90fd46d06c3f99424ecd5fd2e0b8080355a880504ee5c46cebcbaa94739f8be272f3f7102a9f40cf18e6c1a9e1d7dd29d77ee5e + checksum: 612c9816b75b86b73b95b5df35529f4d48da1a3a59b2b999f6ef836b28b10cda2142e159dbc97f0298fa8f5b76df82a1e08e34034fdf12f148e9fd4af2f72134 languageName: node linkType: hard @@ -7551,6 +6926,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.1.7": + version: 1.1.9 + resolution: "@scure/base@npm:1.1.9" + checksum: 77a06b9a2db8144d22d9bf198338893d77367c51b58c72b99df990c0a11f7cadd066d4102abb15e3ca6798d1529e3765f55c4355742465e49aed7a0c01fe76e8 + languageName: node + linkType: hard + "@scure/base@npm:~1.1.8": version: 1.1.8 resolution: "@scure/base@npm:1.1.8" @@ -7580,6 +6962,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.5.0, @scure/bip32@npm:^1.5.0": + version: 1.5.0 + resolution: "@scure/bip32@npm:1.5.0" + dependencies: + "@noble/curves": "npm:~1.6.0" + "@noble/hashes": "npm:~1.5.0" + "@scure/base": "npm:~1.1.7" + checksum: 3319beda59e7f129d770cbe49709a2d1742f2deb6989b12e37aa1a47cd128a8c943bdd9286c6a5513ef4539307c4bca8f89f9aa91f294cac4598cbf95fa0c01d + languageName: node + linkType: hard + "@scure/bip39@npm:1.2.1": version: 1.2.1 resolution: "@scure/bip39@npm:1.2.1" @@ -7600,7 +6993,7 @@ __metadata: languageName: node linkType: hard -"@scure/bip39@npm:1.4.0": +"@scure/bip39@npm:1.4.0, @scure/bip39@npm:^1.4.0": version: 1.4.0 resolution: "@scure/bip39@npm:1.4.0" dependencies: @@ -8654,13 +8047,6 @@ __metadata: languageName: node linkType: hard -"@types/dom-screen-wake-lock@npm:^1.0.0": - version: 1.0.3 - resolution: "@types/dom-screen-wake-lock@npm:1.0.3" - checksum: bab45f6a797de562f1bd3c095c49b7c0464ad05e571f38d00adaa35da2b02109bfe587206cc55f420377634cf0f7b07caa5acb3257e49dfd2d94dab74c617bf1 - languageName: node - linkType: hard - "@types/escodegen@npm:^0.0.6": version: 0.0.6 resolution: "@types/escodegen@npm:0.0.6" @@ -8668,37 +8054,24 @@ __metadata: languageName: node linkType: hard -"@types/eslint-scope@npm:^3.7.3": - version: 3.7.4 - resolution: "@types/eslint-scope@npm:3.7.4" - dependencies: - "@types/eslint": "npm:*" - "@types/estree": "npm:*" - checksum: f8a19cddf9d402f079bcc261958fff5ff2616465e4fb4cd423aa966a6a32bf5d3c65ca3ca0fbe824776b48c5cd525efbaf927b98b8eeef093aa68a1a2ba19359 - languageName: node - linkType: hard - -"@types/eslint@npm:*": - version: 8.44.2 - resolution: "@types/eslint@npm:8.44.2" - dependencies: - "@types/estree": "npm:*" - "@types/json-schema": "npm:*" - checksum: 3c402215f7f495f9267a51fecd6a6d056eb8b3b031a1c472286b7d23a397257327eb03712befa7da60614dd63d31235d27dbc5c586b6a408798dafb8ee0c5eb2 +"@types/estree@npm:^0.0.51": + version: 0.0.51 + resolution: "@types/estree@npm:0.0.51" + checksum: a70c60d5e634e752fcd45b58c9c046ef22ad59ede4bc93ad5193c7e3b736ebd6bcd788ade59d9c3b7da6eeb0939235f011d4c59bb4fc04d8c346b76035099dd1 languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0": +"@types/estree@npm:^1.0.0": version: 1.0.1 resolution: "@types/estree@npm:1.0.1" checksum: b4022067f834d86766f23074a1a7ac6c460e823b00cd8fe94c997bc491e7794615facd3e1520a934c42bd8c0689dbff81e5c643b01f1dee143fc758cac19669e languageName: node linkType: hard -"@types/estree@npm:^0.0.51": - version: 0.0.51 - resolution: "@types/estree@npm:0.0.51" - checksum: a70c60d5e634e752fcd45b58c9c046ef22ad59ede4bc93ad5193c7e3b736ebd6bcd788ade59d9c3b7da6eeb0939235f011d4c59bb4fc04d8c346b76035099dd1 +"@types/estree@npm:^1.0.5": + version: 1.0.6 + resolution: "@types/estree@npm:1.0.6" + checksum: cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a languageName: node linkType: hard @@ -8726,6 +8099,13 @@ __metadata: languageName: node linkType: hard +"@types/gh-pages@npm:^6": + version: 6.1.0 + resolution: "@types/gh-pages@npm:6.1.0" + checksum: d8bf644822df211accac9cff24fcc0a5155fd715d05bc1698175623f5cde1aff81c302e7e38f7105e0fa0fe7ab24d7009d8dbb875897af669f48e06c3c20484c + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.6 resolution: "@types/graceful-fs@npm:4.1.6" @@ -8793,7 +8173,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.12 resolution: "@types/json-schema@npm:7.0.12" checksum: 2c39946ae321fe42d085c61a85872a81bbee70f9b2054ad344e8811dfc478fdbaf1ebf5f2989bb87c895ba2dfc3b1dcba85db11e467bbcdc023708814207791c @@ -8883,6 +8263,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.10.1": + version: 22.10.1 + resolution: "@types/node@npm:22.10.1" + dependencies: + undici-types: "npm:~6.20.0" + checksum: 0fbb6d29fa35d807f0223a4db709c598ac08d66820240a2cd6a8a69b8f0bc921d65b339d850a666b43b4e779f967e6ed6cf6f0fca3575e08241e6b900364c234 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -8985,15 +8374,6 @@ __metadata: languageName: node linkType: hard -"@types/secp256k1@npm:^4.0.4": - version: 4.0.6 - resolution: "@types/secp256k1@npm:4.0.6" - dependencies: - "@types/node": "npm:*" - checksum: 0e391316ae30c218779583b626382a56546ddbefb65f1ff9cf5e078af8a7118f67f3e66e30914399cc6f8710c424d0d8c3f34262ffb1f429c6ad911fd0d0bc26 - languageName: node - linkType: hard - "@types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4": version: 7.5.0 resolution: "@types/semver@npm:7.5.0" @@ -9064,13 +8444,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^10.0.0": - version: 10.0.0 - resolution: "@types/uuid@npm:10.0.0" - checksum: 9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60 - languageName: node - linkType: hard - "@types/uuid@npm:^9.0.1": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -9284,7 +8657,7 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": +"@ungap/structured-clone@npm:^1.0.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" checksum: 8209c937cb39119f44eb63cf90c0b73e7c754209a6411c707be08e50e29ee81356dca1a848a405c8bdeebfe2f5e4f831ad310ae1689eeef65e7445c090c6657d @@ -9387,35 +8760,34 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.1.10": - version: 5.1.10 - resolution: "@wagmi/connectors@npm:5.1.10" +"@wagmi/connectors@npm:5.4.0": + version: 5.4.0 + resolution: "@wagmi/connectors@npm:5.4.0" dependencies: - "@coinbase/wallet-sdk": "npm:4.0.4" - "@metamask/sdk": "npm:0.28.2" - "@safe-global/safe-apps-provider": "npm:0.18.3" + "@coinbase/wallet-sdk": "npm:4.2.3" + "@metamask/sdk": "npm:0.30.1" + "@safe-global/safe-apps-provider": "npm:0.18.4" "@safe-global/safe-apps-sdk": "npm:9.1.0" - "@walletconnect/ethereum-provider": "npm:2.16.1" - "@walletconnect/modal": "npm:2.6.2" + "@walletconnect/ethereum-provider": "npm:2.17.0" cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.13.5 + "@wagmi/core": 2.14.6 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 9af1a06bd239f7c710ebc05a507f65d20a1860b8e05d4bf10b85cef94d8d9ae77cc7a4dbc402b9237fde0701071f46fa8e7a384fcb7873ea03493b11a6125d22 + checksum: 6cb88b23f44a57cad6c3ab992a31764f364e5ea1c38640d207eaa7f7955675738e99f2a9d2d74ea5295f617717fca1204903c82ec293d690a95a913a13451251 languageName: node linkType: hard -"@wagmi/core@npm:2.13.5": - version: 2.13.5 - resolution: "@wagmi/core@npm:2.13.5" +"@wagmi/core@npm:2.14.6": + version: 2.14.6 + resolution: "@wagmi/core@npm:2.14.6" dependencies: eventemitter3: "npm:5.0.1" mipd: "npm:0.0.7" - zustand: "npm:4.4.1" + zustand: "npm:5.0.0" peerDependencies: "@tanstack/query-core": ">=5.0.0" typescript: ">=5.0.4" @@ -9425,13 +8797,13 @@ __metadata: optional: true typescript: optional: true - checksum: e386de867acf92e6a29a6e22e2d612719a1c60cb126473d6675b8b02af2f92c13c9202b89287540872eeace15213fe309411466d5d93fe5308da3a68550aca9f + checksum: bc79ba678f00da5e769526875698e9dc1464fc650f3db27ecf9865b78f0690b7006bb36b0ea0acf6deb9ea5d5a84d343fc8ec6efaa9e9a73868ddca9a8eb046e languageName: node linkType: hard -"@walletconnect/core@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/core@npm:2.16.1" +"@walletconnect/core@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/core@npm:2.17.0" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -9444,12 +8816,37 @@ __metadata: "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + lodash.isequal: "npm:4.5.0" + uint8arrays: "npm:3.1.0" + checksum: 34ae5b9b68c08c1dd3ebb2a6ebff8697307e76fbfe4d6b51d5d090da5cd1613e1c66fa5ac3a87c914333458d7b5bf075bb664292f6b2c7d438c72f706d87416d + languageName: node + linkType: hard + +"@walletconnect/core@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/core@npm:2.17.3" + dependencies: + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/jsonrpc-ws-connection": "npm:1.0.16" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" + "@walletconnect/window-getters": "npm:1.0.1" events: "npm:3.3.0" lodash.isequal: "npm:4.5.0" uint8arrays: "npm:3.1.0" - checksum: fadb2db6afe8b3da79b3c84be4e885227efdb38ec5857c66211e5a4ca2cf7b7a0204a3e336b51586bc0ebc816a03da4b4f135269877dcd1119c36385776c1db4 + checksum: e6a841a0d5b27922b83fbb7a1dbcb519b825d70489f9bd6a909cf0b3c543ab3a6c209a0775a95c5dc452a875757f04c9ca27d02c6f002c39974d2ce2061e5887 languageName: node linkType: hard @@ -9462,21 +8859,40 @@ __metadata: languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/ethereum-provider@npm:2.16.1" +"@walletconnect/ethereum-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/ethereum-provider@npm:2.17.0" + dependencies: + "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/modal": "npm:2.7.0" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/universal-provider": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + checksum: b046a9c296e95b22841f0b2efd28a4ce1a38529a9ba412d3c8ffc482879d79c3d2a24b8c0ec712baecf781938b4321ab5c1ecad5573d078add7c47b0cfd08a25 + languageName: node + linkType: hard + +"@walletconnect/ethereum-provider@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/ethereum-provider@npm:2.17.3" dependencies: "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/modal": "npm:2.6.2" - "@walletconnect/sign-client": "npm:2.16.1" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/universal-provider": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/modal": "npm:2.7.0" + "@walletconnect/sign-client": "npm:2.17.3" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/universal-provider": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" events: "npm:3.3.0" - checksum: 985432b2f5c3da7648640e498d92ae2da05f0a18d43055d7a930c71185d9865fc38bd0a6c4fcb6e06007a8e61084f4e682f9054abee495713e7fe425cf02463e + checksum: 6ca5aaf5f72dfe0c8edd54f4bd30a55ee22e28cf766a6fe1052a22ad252f0aab4d41c9e105b97e1a4ce29f25fbb8aaed3081a447ecb1759664306b4725948774 languageName: node linkType: hard @@ -9567,6 +8983,18 @@ __metadata: languageName: node linkType: hard +"@walletconnect/jsonrpc-ws-connection@npm:1.0.16": + version: 1.0.16 + resolution: "@walletconnect/jsonrpc-ws-connection@npm:1.0.16" + dependencies: + "@walletconnect/jsonrpc-utils": "npm:^1.0.6" + "@walletconnect/safe-json": "npm:^1.0.2" + events: "npm:^3.3.0" + ws: "npm:^7.5.1" + checksum: 30a09d24ffb6b4b291e2d1263504c4ea6c6797c992f5e6eb8033e58bd24749c80fd4e5ba6ffaadb28f8ced0c6b131213195b616f8983bb9f56aa7c91e83e6218 + languageName: node + linkType: hard + "@walletconnect/keyvaluestorage@npm:1.1.1": version: 1.1.1 resolution: "@walletconnect/keyvaluestorage@npm:1.1.1" @@ -9593,40 +9021,60 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal-core@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal-core@npm:2.6.2" +"@walletconnect/modal-core@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal-core@npm:2.7.0" dependencies: valtio: "npm:1.11.2" - checksum: 5e3fb21a1fc923ec0d2a3e33cc360e3d56278a211609d5fd4cc4d6e3b4f1acb40b9783fcc771b259b78c7e731af3862def096aa1da2e210e7859729808304c94 + checksum: 84b11735c005e37e661aa0f08b2e8c8098db3b2cacd957c4a73f4d3de11b2d5e04dd97ab970f8d22fc3e8269fea3297b9487e177343bbab8dd69b3b917fb7f60 languageName: node linkType: hard -"@walletconnect/modal-ui@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal-ui@npm:2.6.2" +"@walletconnect/modal-ui@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal-ui@npm:2.7.0" dependencies: - "@walletconnect/modal-core": "npm:2.6.2" + "@walletconnect/modal-core": "npm:2.7.0" lit: "npm:2.8.0" motion: "npm:10.16.2" qrcode: "npm:1.5.3" - checksum: 5d8f0a2703b9757dfa48ad3e48a40e64608f6a28db31ec93a2f10e942dcc5ee986c03ffdab94018e905836d339131fc928bc14614a94943011868cdddc36a32a + checksum: b717f1fc9854b7d14a4364720fce2d44167f547533340704644ed2fdf9d861b3798ffd19a3b51062a366a8bc39f84b9a8bb3dd04e9e33da742192359be00b051 languageName: node linkType: hard -"@walletconnect/modal@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal@npm:2.6.2" +"@walletconnect/modal@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal@npm:2.7.0" + dependencies: + "@walletconnect/modal-core": "npm:2.7.0" + "@walletconnect/modal-ui": "npm:2.7.0" + checksum: 2f3074eebbca41a46e29680dc2565bc762133508774f05db0075a82b0b66ecc8defca40a94ad63669676090a7e3ef671804592b10e91636ab1cdeac014a1eb11 + languageName: node + linkType: hard + +"@walletconnect/react-native-compat@npm:2.17.1": + version: 2.17.1 + resolution: "@walletconnect/react-native-compat@npm:2.17.1" dependencies: - "@walletconnect/modal-core": "npm:2.6.2" - "@walletconnect/modal-ui": "npm:2.6.2" - checksum: 1cc309f63d061e49fdf7b10d28093d7ef1a47f4624f717f8fd3bf6097ac3b00cea4acc45c50e8bd386d4bcfdf10f4dcba960f7129c557b9dc42ef7d05b970807 + events: "npm:3.3.0" + fast-text-encoding: "npm:1.0.6" + react-native-url-polyfill: "npm:2.0.0" + peerDependencies: + "@react-native-async-storage/async-storage": "*" + "@react-native-community/netinfo": "*" + expo-application: "*" + react-native: "*" + react-native-get-random-values: "*" + peerDependenciesMeta: + expo-application: + optional: true + checksum: 55afa3b7de9cf71f208a10d30ac70bbef67d32013807ebfc2f99ab61cc7e27a51def1e4e795e88e9164452c0ab1d219820cf5b8e0e5e4c316a7501a508476bba languageName: node linkType: hard -"@walletconnect/react-native-compat@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/react-native-compat@npm:2.16.1" +"@walletconnect/react-native-compat@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/react-native-compat@npm:2.17.2" dependencies: events: "npm:3.3.0" fast-text-encoding: "npm:1.0.6" @@ -9635,11 +9083,12 @@ __metadata: "@react-native-async-storage/async-storage": "*" "@react-native-community/netinfo": "*" expo-application: "*" + react-native: "*" react-native-get-random-values: "*" peerDependenciesMeta: expo-application: optional: true - checksum: d73dd15cb83b35ac46420b184906c0ae42216fe69b8b90d631bdc8479952703dabee7f498cbf089ad23826b3f93370b8ebbc6c7c911cdcba7cf80f41f8614370 + checksum: ec6e15d6966f22268a8c8f94e19d1259dfed25097632d5ff1cf8134aaf0f87c0f6a8528b3b2d39754c7d6d9d5d402ece91a7e63c8c5302be29fbf724f0cd4b25 languageName: node linkType: hard @@ -9675,20 +9124,37 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/sign-client@npm:2.16.1" +"@walletconnect/sign-client@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/sign-client@npm:2.17.0" + dependencies: + "@walletconnect/core": "npm:2.17.0" + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + checksum: 48f7d13b3db49584a40dc2653f49fabadd100a324e2213476b8d9e4d6fe0808a08ae14103d2e5b609abff3115197003d8570d606275dbd0f6774d0d49da10c61 + languageName: node + linkType: hard + +"@walletconnect/sign-client@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/sign-client@npm:2.17.3" dependencies: - "@walletconnect/core": "npm:2.16.1" + "@walletconnect/core": "npm:2.17.3" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" events: "npm:3.3.0" - checksum: 88727aca13a4e4b5420bde6cb15567a5d07587e8f814025e8c11f69edf941112acf78b21487a8a1380afb42351f8057cb2fc9e92c625f5adee3d085d3efeb072 + checksum: 454afa3c933ec11f651c4cd275af88eef7da65b5d4bcf8987f768f340557492cf436d662ca42baa54ad8136e4b16f5269e0bc3e212580df09e0ee49873718b96 languageName: node linkType: hard @@ -9701,9 +9167,23 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/types@npm:2.16.1" +"@walletconnect/types@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/types@npm:2.17.0" + dependencies: + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + events: "npm:3.3.0" + checksum: bdc0c062da1edb4410882d9cfca1bb30eb0afd7caea90d5e7a66eaf15e28380e9ef97635cd5e5a017947f4c814c1f780622b4d8946b11a335d415ae066ec7ade + languageName: node + linkType: hard + +"@walletconnect/types@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/types@npm:2.17.3" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -9711,30 +9191,50 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: d796b934fe30771a281dd716c4a0a36992a96b201cebd1012ad2278f7aff224503af6bb18e39461498927d47368c0b7a8d0457bbb42b4fe9712f3307b5c131f7 + checksum: 6e50f1f3d64f32d0fa697bb61340191b153aa0a77b8a483cacaeb62aefa190524e10f78188260b591eaae877d6bfa5ea9ffab5ed905c286151300577f2e0101f + languageName: node + linkType: hard + +"@walletconnect/universal-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/universal-provider@npm:2.17.0" + dependencies: + "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + checksum: 7c1afc79054db5add4e937d7adaadb4fc26aecffb5d749d388418fa5d4eb153807ab4de301b642cd80669b4e5c6bcae917f18cf5ce8696d87da8b3705b60d1ec languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/universal-provider@npm:2.16.1" +"@walletconnect/universal-provider@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/universal-provider@npm:2.17.3" dependencies: + "@walletconnect/events": "npm:1.0.1" "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.16.1" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/sign-client": "npm:2.17.3" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" events: "npm:3.3.0" - checksum: cc128497dbf8555f65d383bd1c1962ee4d84a8bdb21680820766a96157799cf498d56836fb620d5c02f9ad7691c21d6173603795df5f7e2a558be47fab4133c1 + lodash: "npm:4.17.21" + checksum: a577099e5b40fc254df56f9fa3335ff064af24804ec7db9e213ef74261076b2e92194251f56f44de3a7d980deb7cef14f76ca961399e6f6671d1a7dccbdea8d9 languageName: node linkType: hard -"@walletconnect/utils@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/utils@npm:2.16.1" +"@walletconnect/utils@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/utils@npm:2.17.0" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" @@ -9745,14 +9245,42 @@ __metadata: "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.16.1" + "@walletconnect/types": "npm:2.17.0" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" elliptic: "npm:^6.5.7" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: 0bfbc9d5f8be999f4c3d315a5d64c59ad6fcaaa14898bcdfea3bae4cf7a79da40f26cba27ea25fea6320b608064ee42059d10ac70c87086be876f08d7ad73205 + checksum: d1da74b2cd7af35f16d735fe408cfc820c611b2709bd00899e4e91b0b0a6dcd8f344f97df34d0ef8cabc121619a40b62118ffa2aa233ddba9863d1ba23480a0c + languageName: node + linkType: hard + +"@walletconnect/utils@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/utils@npm:2.17.3" + dependencies: + "@ethersproject/hash": "npm:5.7.0" + "@ethersproject/transactions": "npm:5.7.0" + "@stablelib/chacha20poly1305": "npm:1.0.1" + "@stablelib/hkdf": "npm:1.0.1" + "@stablelib/random": "npm:1.0.2" + "@stablelib/sha256": "npm:1.0.1" + "@stablelib/x25519": "npm:1.0.3" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/window-getters": "npm:1.0.1" + "@walletconnect/window-metadata": "npm:1.0.1" + detect-browser: "npm:5.3.0" + elliptic: "npm:6.6.1" + query-string: "npm:7.1.3" + uint8arrays: "npm:3.1.0" + checksum: ab08f625786eb55e0ae41075a3ccee9804750b1f20745f2d7a81569a6741d022463b250958124925e6b5f51d3a5b3ec783a23233391d8d937c4bcd76e7a8cc8c languageName: node linkType: hard @@ -9775,13 +9303,13 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/ast@npm:1.11.6" +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: "@webassemblyjs/helper-numbers": "npm:1.11.6" "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - checksum: e28476a183c8a1787adcf0e5df1d36ec4589467ab712c674fe4f6769c7fb19d1217bfb5856b3edd0f3e0a148ebae9e4bbb84110cee96664966dfef204d9c31fb + checksum: ba7f2b96c6e67e249df6156d02c69eb5f1bd18d5005303cdc42accb053bebbbde673826e54db0437c9748e97abd218366a1d13fa46859b23cde611b6b409998c languageName: node linkType: hard @@ -9799,10 +9327,10 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-buffer@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-buffer@npm:1.11.6" - checksum: 55b5d67db95369cdb2a505ae7ebdf47194d49dfc1aecb0f5403277dcc899c7d3e1f07e8d279646adf8eafd89959272db62ca66fbe803321661ab184176ddfd3a +"@webassemblyjs/helper-buffer@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.12.1" + checksum: 0270724afb4601237410f7fd845ab58ccda1d5456a8783aadfb16eaaf3f2c9610c28e4a5bcb6ad880cde5183c82f7f116d5ccfc2310502439d33f14b6888b48a languageName: node linkType: hard @@ -9824,15 +9352,15 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-wasm-section@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.6" +"@webassemblyjs/helper-wasm-section@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.12.1" dependencies: - "@webassemblyjs/ast": "npm:1.11.6" - "@webassemblyjs/helper-buffer": "npm:1.11.6" + "@webassemblyjs/ast": "npm:1.12.1" + "@webassemblyjs/helper-buffer": "npm:1.12.1" "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - "@webassemblyjs/wasm-gen": "npm:1.11.6" - checksum: b79b19a63181f32e5ee0e786fa8264535ea5360276033911fae597d2de15e1776f028091d08c5a813a3901fd2228e74cd8c7e958fded064df734f00546bef8ce + "@webassemblyjs/wasm-gen": "npm:1.12.1" + checksum: 0546350724d285ae3c26e6fc444be4c3b5fb824f3be0ec8ceb474179dc3f4430336dd2e36a44b3e3a1a6815960e5eec98cd9b3a8ec66dc53d86daedd3296a6a2 languageName: node linkType: hard @@ -9861,68 +9389,68 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-edit@npm:1.11.6" +"@webassemblyjs/wasm-edit@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: - "@webassemblyjs/ast": "npm:1.11.6" - "@webassemblyjs/helper-buffer": "npm:1.11.6" + "@webassemblyjs/ast": "npm:1.12.1" + "@webassemblyjs/helper-buffer": "npm:1.12.1" "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - "@webassemblyjs/helper-wasm-section": "npm:1.11.6" - "@webassemblyjs/wasm-gen": "npm:1.11.6" - "@webassemblyjs/wasm-opt": "npm:1.11.6" - "@webassemblyjs/wasm-parser": "npm:1.11.6" - "@webassemblyjs/wast-printer": "npm:1.11.6" - checksum: 9a56b6bf635cf7aa5d6e926eaddf44c12fba050170e452a8e17ab4e1b937708678c03f5817120fb9de1e27167667ce693d16ce718d41e5a16393996a6017ab73 + "@webassemblyjs/helper-wasm-section": "npm:1.12.1" + "@webassemblyjs/wasm-gen": "npm:1.12.1" + "@webassemblyjs/wasm-opt": "npm:1.12.1" + "@webassemblyjs/wasm-parser": "npm:1.12.1" + "@webassemblyjs/wast-printer": "npm:1.12.1" + checksum: 972f5e6c522890743999e0ed45260aae728098801c6128856b310dd21f1ee63435fc7b518e30e0ba1cdafd0d1e38275829c1e4451c3536a1d9e726e07a5bba0b languageName: node linkType: hard -"@webassemblyjs/wasm-gen@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-gen@npm:1.11.6" +"@webassemblyjs/wasm-gen@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.12.1" dependencies: - "@webassemblyjs/ast": "npm:1.11.6" + "@webassemblyjs/ast": "npm:1.12.1" "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" "@webassemblyjs/ieee754": "npm:1.11.6" "@webassemblyjs/leb128": "npm:1.11.6" "@webassemblyjs/utf8": "npm:1.11.6" - checksum: ce9a39d3dab2eb4a5df991bc9f3609960daa4671d25d700f4617152f9f79da768547359f817bee10cd88532c3e0a8a1714d383438e0a54217eba53cb822bd5ad + checksum: 1e257288177af9fa34c69cab94f4d9036ebed611f77f3897c988874e75182eeeec759c79b89a7a49dd24624fc2d3d48d5580b62b67c4a1c9bfbdcd266b281c16 languageName: node linkType: hard -"@webassemblyjs/wasm-opt@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-opt@npm:1.11.6" +"@webassemblyjs/wasm-opt@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.12.1" dependencies: - "@webassemblyjs/ast": "npm:1.11.6" - "@webassemblyjs/helper-buffer": "npm:1.11.6" - "@webassemblyjs/wasm-gen": "npm:1.11.6" - "@webassemblyjs/wasm-parser": "npm:1.11.6" - checksum: 82788408054171688e9f12883b693777219366d6867003e34dccc21b4a0950ef53edc9d2b4d54cabdb6ee869cf37c8718401b4baa4f70a7f7dd3867c75637298 + "@webassemblyjs/ast": "npm:1.12.1" + "@webassemblyjs/helper-buffer": "npm:1.12.1" + "@webassemblyjs/wasm-gen": "npm:1.12.1" + "@webassemblyjs/wasm-parser": "npm:1.12.1" + checksum: 992a45e1f1871033c36987459436ab4e6430642ca49328e6e32a13de9106fe69ae6c0ac27d7050efd76851e502d11cd1ac0e06b55655dfa889ad82f11a2712fb languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.11.6, @webassemblyjs/wasm-parser@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-parser@npm:1.11.6" +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: - "@webassemblyjs/ast": "npm:1.11.6" + "@webassemblyjs/ast": "npm:1.12.1" "@webassemblyjs/helper-api-error": "npm:1.11.6" "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" "@webassemblyjs/ieee754": "npm:1.11.6" "@webassemblyjs/leb128": "npm:1.11.6" "@webassemblyjs/utf8": "npm:1.11.6" - checksum: 7a97a5f34f98bdcfd812157845a06d53f3d3f67dbd4ae5d6bf66e234e17dc4a76b2b5e74e5dd70b4cab9778fc130194d50bbd6f9a1d23e15ed1ed666233d6f5f + checksum: e85cec1acad07e5eb65b92d37c8e6ca09c6ca50d7ca58803a1532b452c7321050a0328c49810c337cc2dfd100c5326a54d5ebd1aa5c339ebe6ef10c250323a0e languageName: node linkType: hard -"@webassemblyjs/wast-printer@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wast-printer@npm:1.11.6" +"@webassemblyjs/wast-printer@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wast-printer@npm:1.12.1" dependencies: - "@webassemblyjs/ast": "npm:1.11.6" + "@webassemblyjs/ast": "npm:1.12.1" "@xtuc/long": "npm:4.2.2" - checksum: 916b90fa3a8aadd95ca41c21d4316d0a7582cf6d0dcf6d9db86ab0de823914df513919fba60ac1edd227ff00e93a66b927b15cbddd36b69d8a34c8815752633c + checksum: 39bf746eb7a79aa69953f194943bbc43bebae98bd7cadd4d8bc8c0df470ca6bf9d2b789effaa180e900fab4e2691983c1f7d41571458bd2a26267f2f0c73705a languageName: node linkType: hard @@ -9976,6 +9504,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.0.6, abitype@npm:^1.0.6": + version: 1.0.6 + resolution: "abitype@npm:1.0.6" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: 30ca97010bbf34b9aaed401858eeb6bc30419f7ff11eb34adcb243522dd56c9d8a9d3d406aa5d4f60a7c263902f5136043005698e3f073ea882a4922d43a2929 + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -9995,12 +9538,12 @@ __metadata: languageName: node linkType: hard -"acorn-import-assertions@npm:^1.9.0": - version: 1.9.0 - resolution: "acorn-import-assertions@npm:1.9.0" +"acorn-import-attributes@npm:^1.9.5": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" peerDependencies: acorn: ^8 - checksum: 3b4a194e128efdc9b86c2b1544f623aba4c1aa70d638f8ab7dc3971a5b4aa4c57bd62f99af6e5325bb5973c55863b4112e708a6f408bad7a138647ca72283afe + checksum: 5926eaaead2326d5a86f322ff1b617b0f698aa61dc719a5baa0e9d955c9885cc71febac3fb5bacff71bbf2c4f9c12db2056883c68c53eb962c048b952e1e013d languageName: node linkType: hard @@ -10291,11 +9834,12 @@ __metadata: "@types/jest": "npm:29.5.7" "@types/qrcode": "npm:1.5.5" "@types/react": "npm:^18.2.6" - "@walletconnect/react-native-compat": "npm:2.16.1" + "@walletconnect/react-native-compat": "npm:2.17.2" babel-jest: "npm:^29.7.0" eslint: "npm:^8.46.0" eslint-plugin-ft-flow: "npm:2.0.3" eslint-plugin-prettier: "npm:5.0.1" + eslint-plugin-valtio: "npm:^0.6.4" jest: "npm:29.7.0" prettier: "npm:3.0.1" react: "npm:18.2.0" @@ -10310,8 +9854,8 @@ __metadata: tsconfig: "npm:*" turbo: "npm:2.1.1" typescript: "npm:5.2.2" - viem: "npm:2.21.6" - wagmi: "npm:2.12.11" + viem: "npm:2.21.48" + wagmi: "npm:2.12.33" languageName: unknown linkType: soft @@ -10511,6 +10055,13 @@ __metadata: languageName: node linkType: hard +"async@npm:^3.2.4": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -10616,19 +10167,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.10": - version: 0.4.11 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.11" - dependencies: - "@babel/compat-data": "npm:^7.22.6" - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: b2217bc8d5976cf8142453ed44daabf0b2e0e75518f24eac83b54a8892e87a88f1bd9089daa92fd25df979ecd0acfd29b6bc28c4182c1c46344cee15ef9bce84 - languageName: node - linkType: hard - "babel-plugin-polyfill-corejs2@npm:^0.4.5": version: 0.4.5 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.5" @@ -10655,18 +10193,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.10.6": - version: 0.10.6 - resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - core-js-compat: "npm:^3.38.0" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 3a69220471b07722c2ae6537310bf26b772514e12b601398082965459c838be70a0ca70b0662f0737070654ff6207673391221d48599abb4a2b27765206d9f79 - languageName: node - linkType: hard - "babel-plugin-polyfill-corejs3@npm:^0.8.3": version: 0.8.3 resolution: "babel-plugin-polyfill-corejs3@npm:0.8.3" @@ -10713,17 +10239,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.6.1": - version: 0.6.2 - resolution: "babel-plugin-polyfill-regenerator@npm:0.6.2" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: bc541037cf7620bc84ddb75a1c0ce3288f90e7d2799c070a53f8a495c8c8ae0316447becb06f958dd25dcce2a2fce855d318ecfa48036a1ddb218d55aa38a744 - languageName: node - linkType: hard - "babel-plugin-react-compiler@npm:^0.0.0-experimental-592953e-20240517": version: 0.0.0 resolution: "babel-plugin-react-compiler@npm:0.0.0" @@ -10896,13 +10411,20 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:1.6.x": +"big-integer@npm:1.6.x, big-integer@npm:^1.6.44": version: 1.6.51 resolution: "big-integer@npm:1.6.51" checksum: c8139662d57f8833a44802f4b65be911679c569535ea73c5cfd3c1c8994eaead1b84b6f63e1db63833e4d4cacb6b6a9e5522178113dfdc8e4c81ed8436f1e8cc languageName: node linkType: hard +"bignumber.js@npm:9.1.2": + version: 9.1.2 + resolution: "bignumber.js@npm:9.1.2" + checksum: e17786545433f3110b868725c449fa9625366a6e675cd70eb39b60938d6adbd0158cb4b3ad4f306ce817165d37e63f4aa3098ba4110db1d9a3b9f66abfbaf10d + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -10996,6 +10518,15 @@ __metadata: languageName: node linkType: hard +"bplist-parser@npm:^0.2.0": + version: 0.2.0 + resolution: "bplist-parser@npm:0.2.0" + dependencies: + big-integer: "npm:^1.6.44" + checksum: ce79c69e0f6efe506281e7c84e3712f7d12978991675b6e3a58a295b16f13ca81aa9b845c335614a545e0af728c8311b6aa3142af76ba1cb616af9bbac5c4a9f + languageName: node + linkType: hard + "bplist-parser@npm:^0.3.1": version: 0.3.2 resolution: "bplist-parser@npm:0.3.2" @@ -11024,12 +10555,12 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" +"braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: "npm:^7.0.1" - checksum: 321b4d675791479293264019156ca322163f02dc06e3c4cab33bb15cd43d80b51efef69b0930cfde3acd63d126ebca24cd0544fa6f261e093a0fb41ab9dda381 + fill-range: "npm:^7.1.1" + checksum: 7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 languageName: node linkType: hard @@ -11047,20 +10578,6 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.14.5, browserslist@npm:^4.21.9": - version: 4.21.10 - resolution: "browserslist@npm:4.21.10" - dependencies: - caniuse-lite: "npm:^1.0.30001517" - electron-to-chromium: "npm:^1.4.477" - node-releases: "npm:^2.0.13" - update-browserslist-db: "npm:^1.0.11" - bin: - browserslist: cli.js - checksum: e8c98496e5f2a5128d0e2f1f186dc0416bfc49c811e568b19c9e07a56cccc1f7f415fa4f532488e6a13dfacbe3332a9b55b152082ff125402696a11a158a0894 - languageName: node - linkType: hard - "browserslist@npm:^4.20.4, browserslist@npm:^4.22.1": version: 4.22.1 resolution: "browserslist@npm:4.22.1" @@ -11075,6 +10592,34 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.21.10": + version: 4.24.0 + resolution: "browserslist@npm:4.24.0" + dependencies: + caniuse-lite: "npm:^1.0.30001663" + electron-to-chromium: "npm:^1.5.28" + node-releases: "npm:^2.0.18" + update-browserslist-db: "npm:^1.1.0" + bin: + browserslist: cli.js + checksum: 95e76ad522753c4c470427f6e3c8a4bb5478ff448841e22b3d3e53f89ecaf17b6984666d6c7e715c370f1e7fa0cf684f42e34e554236a8b2fab38ea76b9e4c52 + languageName: node + linkType: hard + +"browserslist@npm:^4.21.9": + version: 4.21.10 + resolution: "browserslist@npm:4.21.10" + dependencies: + caniuse-lite: "npm:^1.0.30001517" + electron-to-chromium: "npm:^1.4.477" + node-releases: "npm:^2.0.13" + update-browserslist-db: "npm:^1.0.11" + bin: + browserslist: cli.js + checksum: e8c98496e5f2a5128d0e2f1f186dc0416bfc49c811e568b19c9e07a56cccc1f7f415fa4f532488e6a13dfacbe3332a9b55b152082ff125402696a11a158a0894 + languageName: node + linkType: hard + "browserslist@npm:^4.22.2": version: 4.22.2 resolution: "browserslist@npm:4.22.2" @@ -11103,20 +10648,6 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.23.3": - version: 4.23.3 - resolution: "browserslist@npm:4.23.3" - dependencies: - caniuse-lite: "npm:^1.0.30001646" - electron-to-chromium: "npm:^1.5.4" - node-releases: "npm:^2.0.18" - update-browserslist-db: "npm:^1.1.0" - bin: - browserslist: cli.js - checksum: 3063bfdf812815346447f4796c8f04601bf5d62003374305fd323c2a463e42776475bcc5309264e39bcf9a8605851e53560695991a623be988138b3ff8c66642 - languageName: node - linkType: hard - "bs-logger@npm:0.x": version: 0.2.6 resolution: "bs-logger@npm:0.2.6" @@ -11203,6 +10734,15 @@ __metadata: languageName: node linkType: hard +"bundle-name@npm:^3.0.0": + version: 3.0.0 + resolution: "bundle-name@npm:3.0.0" + dependencies: + run-applescript: "npm:^5.0.0" + checksum: 57bc7f8b025d83961b04db2f1eff6a87f2363c2891f3542a4b82471ff8ebb5d484af48e9784fcdb28ef1d48bb01f03d891966dc3ef58758e46ea32d750ce40f8 + languageName: node + linkType: hard + "bytes@npm:3.0.0": version: 3.0.0 resolution: "bytes@npm:3.0.0" @@ -11350,10 +10890,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001646": - version: 1.0.30001660 - resolution: "caniuse-lite@npm:1.0.30001660" - checksum: d28900b56c597176d515c3175ca75c454f2d30cb2c09a44d7bdb009bb0c4d8a2557905adb77642889bbe9feb85fbfe9d974c8b8e53521fb4b50ee16ab246104e +"caniuse-lite@npm:^1.0.30001663": + version: 1.0.30001663 + resolution: "caniuse-lite@npm:1.0.30001663" + checksum: 6508e27bf7fdec657f26f318b1ab64ace6e1208ef9fedaf0975bc89046e0c683bfba837f108840ada1686ff09b8ffd01e05ac791dcf598b8f16eefb636875cf2 languageName: node linkType: hard @@ -11766,6 +11306,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^11.0.0": + version: 11.1.0 + resolution: "commander@npm:11.1.0" + checksum: 13cc6ac875e48780250f723fb81c1c1178d35c5decb1abb1b628b3177af08a8554e76b2c0f29de72d69eef7c864d12613272a71fabef8047922bc622ab75a179 + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -11948,15 +11495,6 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": - version: 3.38.1 - resolution: "core-js-compat@npm:3.38.1" - dependencies: - browserslist: "npm:^4.23.3" - checksum: d8bc8a35591fc5fbf3e376d793f298ec41eb452619c7ef9de4ea59b74be06e9fda799e0dcbf9ba59880dae87e3b41fb191d744ffc988315642a1272bb9442b31 - languageName: node - linkType: hard - "core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -12212,6 +11750,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:1.11.10": + version: 1.11.10 + resolution: "dayjs@npm:1.11.10" + checksum: 4de9af50639d47df87f2e15fa36bb07e0f9ed1e9c52c6caa1482788ee9a384d668f1dbd00c54f82aaab163db07d61d2899384b8254da3a9184fc6deca080e2fe + languageName: node + linkType: hard + "dayjs@npm:^1.8.15": version: 1.11.9 resolution: "dayjs@npm:1.11.9" @@ -12310,6 +11855,28 @@ __metadata: languageName: node linkType: hard +"default-browser-id@npm:^3.0.0": + version: 3.0.0 + resolution: "default-browser-id@npm:3.0.0" + dependencies: + bplist-parser: "npm:^0.2.0" + untildify: "npm:^4.0.0" + checksum: 8db3ab882eb3e1e8b59d84c8641320e6c66d8eeb17eb4bb848b7dd549b1e6fd313988e4a13542e95fbaeff03f6e9dedc5ad191ad4df7996187753eb0d45c00b7 + languageName: node + linkType: hard + +"default-browser@npm:^4.0.0": + version: 4.0.0 + resolution: "default-browser@npm:4.0.0" + dependencies: + bundle-name: "npm:^3.0.0" + default-browser-id: "npm:^3.0.0" + execa: "npm:^7.1.1" + titleize: "npm:^3.0.0" + checksum: 7c8848badc139ecf9d878e562bc4e7ab4301e51ba120b24d8dcb14739c30152115cc612065ac3ab73c02aace4afa29db5a044257b2f0cf234f16e3a58f6c925e + languageName: node + linkType: hard + "default-gateway@npm:^4.2.0": version: 4.2.0 resolution: "default-gateway@npm:4.2.0" @@ -12347,6 +11914,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": version: 1.2.0 resolution: "define-properties@npm:1.2.0" @@ -12669,14 +12243,15 @@ __metadata: languageName: node linkType: hard -"eciesjs@npm:^0.3.15": - version: 0.3.18 - resolution: "eciesjs@npm:0.3.18" +"eciesjs@npm:^0.4.8": + version: 0.4.10 + resolution: "eciesjs@npm:0.4.10" dependencies: - "@types/secp256k1": "npm:^4.0.4" - futoin-hkdf: "npm:^1.5.3" - secp256k1: "npm:^5.0.0" - checksum: 88e334b1fb8ae685eadf8023bc4a5c5247c1f7e6b873b8ba8ec84a3e7890352160ca7fcc1b78f75e67e04f2142310e89788a013fbb4f272f2130e76ada5050bc + "@ecies/ciphers": "npm:^0.2.0" + "@noble/ciphers": "npm:^1.0.0" + "@noble/curves": "npm:^1.6.0" + "@noble/hashes": "npm:^1.5.0" + checksum: f9e0603a839b763c1bb0a00a64686d553f2d8c10efcfab57461d9e052ebacc80dca0e28cbad8548015a99bee90c60cb085486be4c44261283535873d97aba59e languageName: node linkType: hard @@ -12715,10 +12290,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.4": - version: 1.5.22 - resolution: "electron-to-chromium@npm:1.5.22" - checksum: 3c1c640dfa77e9d8e16c112d79ddbe87b47b2df7fada2406f2974b22227dd4592a5215c3318baf570ffdc1479151589dbdb8c0eac61347e9c78e1710f3b7ee5d +"electron-to-chromium@npm:^1.5.28": + version: 1.5.28 + resolution: "electron-to-chromium@npm:1.5.28" + checksum: 6e2f4150ba03ce53ca128955c7d2da071d3774362a10c68848a85b71c29857915e2256cb53cd2de17fdbf0f56bf76ec174d24965abef7430d8c414ec733030b2 languageName: node linkType: hard @@ -12737,9 +12312,9 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.5.4": - version: 6.5.5 - resolution: "elliptic@npm:6.5.5" +"elliptic@npm:6.6.1": + version: 6.6.1 + resolution: "elliptic@npm:6.6.1" dependencies: bn.js: "npm:^4.11.9" brorand: "npm:^1.1.0" @@ -12748,7 +12323,7 @@ __metadata: inherits: "npm:^2.0.4" minimalistic-assert: "npm:^1.0.1" minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 3e591e93783a1b66f234ebf5bd3a8a9a8e063a75073a35a671e03e3b25253b6e33ac121f7efe9b8808890fffb17b40596cc19d01e6e8d1fa13b9a56ff65597c8 + checksum: 8b24ef782eec8b472053793ea1e91ae6bee41afffdfcb78a81c0a53b191e715cbe1292aa07165958a9bbe675bd0955142560b1a007ffce7d6c765bcaf951a867 languageName: node linkType: hard @@ -12767,6 +12342,13 @@ __metadata: languageName: node linkType: hard +"email-addresses@npm:^5.0.0": + version: 5.0.0 + resolution: "email-addresses@npm:5.0.0" + checksum: fc8a6f84e378bbe601ce39a3d8d86bc7e4584030ae9eb1938e12943f7fb5207e5fd7ae449cced3bea70968a519ade560d55ca170208c3f1413d7d25d8613a577 + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -12858,13 +12440,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.15.0": - version: 5.15.0 - resolution: "enhanced-resolve@npm:5.15.0" +"enhanced-resolve@npm:^5.17.1": + version: 5.17.1 + resolution: "enhanced-resolve@npm:5.17.1" dependencies: graceful-fs: "npm:^4.2.4" tapable: "npm:^2.2.0" - checksum: 69984a7990913948b4150855aed26a84afb4cb1c5a94fb8e3a65bd00729a73fc2eaff6871fb8e345377f294831afe349615c93560f2f54d61b43cdfdf668f19a + checksum: 81a0515675eca17efdba2cf5bad87abc91a528fc1191aad50e275e74f045b41506167d420099022da7181c8d787170ea41e4a11a0b10b7a16f6237daecb15370 languageName: node linkType: hard @@ -13187,7 +12769,7 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^1.0.5": +"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" checksum: a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 @@ -13360,6 +12942,13 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-valtio@npm:^0.6.4": + version: 0.6.4 + resolution: "eslint-plugin-valtio@npm:0.6.4" + checksum: e8147833bf0d0eecaf7e36add2ee5bfe4a3ce8c2a5793f06f1c7072814589b37b73cd44e3d725ce6c0e47f70f7b4965d66f6097c74c0b1298336ad1aca1259f4 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -13387,7 +12976,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0": +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.2": version: 3.4.2 resolution: "eslint-visitor-keys@npm:3.4.2" checksum: 4521d1d470490c89fb613aec6fb2f0814b496a4618619ec8dfcc985640fe33c9c64f3dab882f50ebb401b4613f35f2601a9ef9a72b57739af5b0150fecdaf1f1 @@ -13402,17 +12991,16 @@ __metadata: linkType: hard "eslint@npm:^8.46.0": - version: 8.57.1 - resolution: "eslint@npm:8.57.1" + version: 8.46.0 + resolution: "eslint@npm:8.46.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.6.1" - "@eslint/eslintrc": "npm:^2.1.4" - "@eslint/js": "npm:8.57.1" - "@humanwhocodes/config-array": "npm:^0.13.0" + "@eslint/eslintrc": "npm:^2.1.1" + "@eslint/js": "npm:^8.46.0" + "@humanwhocodes/config-array": "npm:^0.11.10" "@humanwhocodes/module-importer": "npm:^1.0.1" "@nodelib/fs.walk": "npm:^1.2.8" - "@ungap/structured-clone": "npm:^1.2.0" ajv: "npm:^6.12.4" chalk: "npm:^4.0.0" cross-spawn: "npm:^7.0.2" @@ -13420,7 +13008,7 @@ __metadata: doctrine: "npm:^3.0.0" escape-string-regexp: "npm:^4.0.0" eslint-scope: "npm:^7.2.2" - eslint-visitor-keys: "npm:^3.4.3" + eslint-visitor-keys: "npm:^3.4.2" espree: "npm:^9.6.1" esquery: "npm:^1.4.2" esutils: "npm:^2.0.2" @@ -13445,7 +13033,7 @@ __metadata: text-table: "npm:^0.2.0" bin: eslint: bin/eslint.js - checksum: 1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1 + checksum: 81abddb21e540dcd509ba08fdf524b494cbda69a62ffce2a61b5adfcdeb3cbf713f72c6cbb42932333decb4b067ae7a89e4cb5e908e0d42e4287d4f357576a72 languageName: node linkType: hard @@ -13471,11 +13059,11 @@ __metadata: linkType: hard "esquery@npm:^1.4.2": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" + version: 1.5.0 + resolution: "esquery@npm:1.5.0" dependencies: estraverse: "npm:^5.1.0" - checksum: cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + checksum: a084bd049d954cc88ac69df30534043fb2aee5555b56246493f42f27d1e168f00d9e5d4192e46f10290d312dc30dc7d58994d61a609c579c1219d636996f9213 languageName: node linkType: hard @@ -13719,6 +13307,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:^7.1.1": + version: 7.2.0 + resolution: "execa@npm:7.2.0" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.1" + human-signals: "npm:^4.3.0" + is-stream: "npm:^3.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^5.1.0" + onetime: "npm:^6.0.0" + signal-exit: "npm:^3.0.7" + strip-final-newline: "npm:^3.0.0" + checksum: 098cd6a1bc26d509e5402c43f4971736450b84d058391820c6f237aeec6436963e006fd8423c9722f148c53da86aa50045929c7278b5522197dff802d10f9885 + languageName: node + linkType: hard + "execa@npm:^8.0.1": version: 8.0.1 resolution: "execa@npm:8.0.1" @@ -14062,7 +13667,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.9": +"fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.1 resolution: "fast-glob@npm:3.3.1" dependencies: @@ -14110,9 +13715,9 @@ __metadata: linkType: hard "fast-loops@npm:^1.1.3": - version: 1.1.3 - resolution: "fast-loops@npm:1.1.3" - checksum: ba71c001704c44a617053ed34b1a8c0d2ed9723022eb7b93c98299d9862f93213609b32c9daec7d606625ab318769d11da8bb06e9ddd9c28e3bda1249fb6e36d + version: 1.1.4 + resolution: "fast-loops@npm:1.1.4" + checksum: 25e8a608fccc0d84c1d037efa715ab1e6f21576e1070931b3ed966657204c47ed2b1cba16e5c46ddde2d62aba0b4100d86616d995318b7367fa0a902a78ed885 languageName: node linkType: hard @@ -14137,25 +13742,14 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.0.12": - version: 4.2.7 - resolution: "fast-xml-parser@npm:4.2.7" - dependencies: - strnum: "npm:^1.0.5" - bin: - fxparser: src/cli/cli.js - checksum: 0681922d95713062ec6205fd41be503890c474a45831c39502e72fccf0b0bd88c49d2c2fa79c6d24d432573631d515967fd17938bcedf230cb134c291cbbbf5e - languageName: node - linkType: hard - -"fast-xml-parser@npm:^4.2.4": - version: 4.3.2 - resolution: "fast-xml-parser@npm:4.3.2" +"fast-xml-parser@npm:^4.0.12, fast-xml-parser@npm:^4.2.4": + version: 4.5.0 + resolution: "fast-xml-parser@npm:4.5.0" dependencies: strnum: "npm:^1.0.5" bin: fxparser: src/cli/cli.js - checksum: 7c1611349384656ec4faa9802fbc8cf8c01206a1b79193d5cd54586307801562509007f6cf16e5da7d43da4fa4639770f38959a285b9466aa98dab0a9b8ca171 + checksum: 71d206c9e137f5c26af88d27dde0108068a5d074401901d643c500c36e95dfd828333a98bda020846c41f5b9b364e2b0e9be5b19b0bdcab5cf31559c07b80a95 languageName: node linkType: hard @@ -14224,6 +13818,24 @@ __metadata: languageName: node linkType: hard +"filename-reserved-regex@npm:^2.0.0": + version: 2.0.0 + resolution: "filename-reserved-regex@npm:2.0.0" + checksum: 453740b7f9fd126e508da555b37e38c1f7ff19f5e9f3d297b2de1beb09854957baddd74c83235e87b16e9ce27a2368798896669edad5a81b5b7bd8cb57c942fc + languageName: node + linkType: hard + +"filenamify@npm:^4.3.0": + version: 4.3.0 + resolution: "filenamify@npm:4.3.0" + dependencies: + filename-reserved-regex: "npm:^2.0.0" + strip-outer: "npm:^1.0.1" + trim-repeated: "npm:^1.0.0" + checksum: dcfd2f116d66f78c9dd58bb0f0d9b6529d89c801a9f37a4f86e7adc0acecb6881c7fb7c3231dc9e6754b767edcfdca89cba3a492a58afd2b48479b30d14ccf8f + languageName: node + linkType: hard + "filesize@npm:^10.0.12": version: 10.1.6 resolution: "filesize@npm:10.1.6" @@ -14231,12 +13843,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: "npm:^5.0.1" - checksum: 7cdad7d426ffbaadf45aeb5d15ec675bbd77f7597ad5399e3d2766987ed20bda24d5fac64b3ee79d93276f5865608bb22344a26b9b1ae6c4d00bd94bf611623f + checksum: b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 languageName: node linkType: hard @@ -14530,6 +14142,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.1.1": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 + languageName: node + linkType: hard + "fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" @@ -14596,7 +14219,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -14606,7 +14229,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -14648,13 +14271,6 @@ __metadata: languageName: node linkType: hard -"futoin-hkdf@npm:^1.5.3": - version: 1.5.3 - resolution: "futoin-hkdf@npm:1.5.3" - checksum: fe87b50d2ac125ca2074e92588ca1df5016e9657267363cb77d8287080639dc31f90e7740f4737aa054c3e687b2ab3456f9b5c55950b94cd2c2010bc441aa5ae - languageName: node - linkType: hard - "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -14740,7 +14356,7 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^6.0.0": +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": version: 6.0.1 resolution: "get-stream@npm:6.0.1" checksum: 49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 @@ -14771,6 +14387,24 @@ __metadata: languageName: node linkType: hard +"gh-pages@npm:^6.2.0": + version: 6.2.0 + resolution: "gh-pages@npm:6.2.0" + dependencies: + async: "npm:^3.2.4" + commander: "npm:^11.0.0" + email-addresses: "npm:^5.0.0" + filenamify: "npm:^4.3.0" + find-cache-dir: "npm:^3.3.1" + fs-extra: "npm:^11.1.1" + globby: "npm:^11.1.0" + bin: + gh-pages: bin/gh-pages.js + gh-pages-clean: bin/gh-pages-clean.js + checksum: 30b996b3a9c3dc00d333b6fb15232b3ddc8628f9f458de871ad237b4e3414e68f5408d7525d82ae4a551e24bd7461f009908e8db7c7031dc7dc51e62e7c18ac0 + languageName: node + linkType: hard + "github-slugger@npm:^2.0.0": version: 2.0.0 resolution: "github-slugger@npm:2.0.0" @@ -14880,11 +14514,11 @@ __metadata: linkType: hard "globals@npm:^13.19.0": - version: 13.24.0 - resolution: "globals@npm:13.24.0" + version: 13.20.0 + resolution: "globals@npm:13.20.0" dependencies: type-fest: "npm:^0.20.2" - checksum: d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd + checksum: 9a028f136f1e7a3574689f430f7d57faa0d699c4c7e92ade00b02882a892be31c314d50dff07b48e607283013117bb8a997406d03a1f7ab4a33a005eb16efd6c languageName: node linkType: hard @@ -14920,7 +14554,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -15337,6 +14971,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^4.3.0": + version: 4.3.1 + resolution: "human-signals@npm:4.3.1" + checksum: 40498b33fe139f5cc4ef5d2f95eb1803d6318ac1b1c63eaf14eeed5484d26332c828de4a5a05676b6c83d7b9e57727c59addb4b1dea19cb8d71e83689e5b336c + languageName: node + linkType: hard + "human-signals@npm:^5.0.0": version: 5.0.0 resolution: "human-signals@npm:5.0.0" @@ -16107,6 +15748,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.6": + version: 1.0.6 + resolution: "isows@npm:1.0.6" + peerDependencies: + ws: "*" + checksum: f89338f63ce2f497d6cd0f86e42c634209328ebb43b3bdfdc85d8f1589ee75f02b7e6d9e1ba274101d0f6f513b1b8cbe6985e6542b4aaa1f0c5fd50d9c1be95c + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -17289,7 +16939,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.13, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": +"lodash@npm:4.17.21, lodash@npm:^4.17.13, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -18126,12 +17776,12 @@ __metadata: linkType: hard "micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": - version: 4.0.5 - resolution: "micromatch@npm:4.0.5" + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" dependencies: - braces: "npm:^3.0.2" + braces: "npm:^3.0.3" picomatch: "npm:^2.3.1" - checksum: 3d6505b20f9fa804af5d8c596cb1c5e475b9b0cd05f652c5b56141cf941bd72adaeb7a436fda344235cef93a7f29b7472efc779fcdb83b478eab0867b95cdeff + checksum: 166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 languageName: node linkType: hard @@ -18469,21 +18119,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.6": - version: 3.3.6 - resolution: "nanoid@npm:3.3.6" - bin: - nanoid: bin/nanoid.cjs - checksum: 606b355960d0fcbe3d27924c4c52ef7d47d3b57208808ece73279420d91469b01ec1dce10fae512b6d4a8c5a5432b352b228336a8b2202a6ea68e67fa348e2ee - languageName: node - linkType: hard - -"nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" +"nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" bin: nanoid: bin/nanoid.cjs - checksum: e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 + checksum: 4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120 languageName: node linkType: hard @@ -18571,15 +18212,6 @@ __metadata: languageName: node linkType: hard -"node-addon-api@npm:^5.0.0": - version: 5.1.0 - resolution: "node-addon-api@npm:5.1.0" - dependencies: - node-gyp: "npm:latest" - checksum: 0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d - languageName: node - linkType: hard - "node-addon-api@npm:^7.0.0": version: 7.0.0 resolution: "node-addon-api@npm:7.0.0" @@ -18992,7 +18624,7 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.4, open@npm:^8.3.0, open@npm:^8.4.0": +"open@npm:^8.0.4, open@npm:^8.3.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -19003,6 +18635,18 @@ __metadata: languageName: node linkType: hard +"open@npm:^9.1.0": + version: 9.1.0 + resolution: "open@npm:9.1.0" + dependencies: + default-browser: "npm:^4.0.0" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^2.2.0" + checksum: 8073ec0dd8994a7a7d9bac208bd17d093993a65ce10f2eb9b62b6d3a91c9366ae903938a237c275493c130171d339f6dcbdd2a2de7e32953452c0867b97825af + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -19079,6 +18723,26 @@ __metadata: languageName: node linkType: hard +"ox@npm:0.1.2": + version: 0.1.2 + resolution: "ox@npm:0.1.2" + dependencies: + "@adraffy/ens-normalize": "npm:^1.10.1" + "@noble/curves": "npm:^1.6.0" + "@noble/hashes": "npm:^1.5.0" + "@scure/bip32": "npm:^1.5.0" + "@scure/bip39": "npm:^1.4.0" + abitype: "npm:^1.0.6" + eventemitter3: "npm:5.0.1" + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 9d0615e9a95c316063587fe08dc268476e67429eea897598b2f69cb1509ac66739f888b0b9bc1cfd0b4bd2f1a3fd0af4d3e81d40ba0bf3abd53e36a6f5b21323 + languageName: node + linkType: hard + "p-filter@npm:^2.1.0": version: 2.1.0 resolution: "p-filter@npm:2.1.0" @@ -19507,6 +19171,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.49.1": + version: 1.49.1 + resolution: "playwright-core@npm:1.49.1" + bin: + playwright-core: cli.js + checksum: 990b619c75715cd98b2c10c1180a126e3a454b247063b8352bc67792fe01183ec07f31d30c8714c3768cefed12886d1d64ac06da701f2baafc2cad9b439e3919 + languageName: node + linkType: hard + +"playwright@npm:1.49.1": + version: 1.49.1 + resolution: "playwright@npm:1.49.1" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.49.1" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 2368762c898920d4a0a5788b153dead45f9c36c3f5cf4d2af5228d0b8ea65823e3bbe998877950a2b9bb23a211e4633996f854c6188769dc81a25543ac818ab5 + languageName: node + linkType: hard + "plist@npm:^3.0.5": version: 3.1.0 resolution: "plist@npm:3.1.0" @@ -19638,6 +19326,13 @@ __metadata: languageName: node linkType: hard +"preact@npm:^10.24.2": + version: 10.24.3 + resolution: "preact@npm:10.24.3" + checksum: c863df6d7be6a660480189762d8a8f2d4148733fc2bb9efbd9d2fd27315d2c7ede850a16077d716c91666c915c0349bd3c9699733e4f08457226a0519f408761 + languageName: node + linkType: hard + "preferred-pm@npm:^3.0.0": version: 3.1.2 resolution: "preferred-pm@npm:3.1.2" @@ -20288,6 +19983,16 @@ __metadata: languageName: node linkType: hard +"react-native-toast-message@npm:2.2.1": + version: 2.2.1 + resolution: "react-native-toast-message@npm:2.2.1" + peerDependencies: + react: "*" + react-native: "*" + checksum: 03418b03ae345f5fe1c1747a98ae9c420a9ea37402d849920a14bfc6eb01541ad934266ae2656433604448d2fba1266ab6f6be649a49c9b22445e86444c1f6de + languageName: node + linkType: hard + "react-native-url-polyfill@npm:2.0.0": version: 2.0.0 resolution: "react-native-url-polyfill@npm:2.0.0" @@ -21046,22 +20751,12 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-visualizer@npm:^5.9.2": - version: 5.12.0 - resolution: "rollup-plugin-visualizer@npm:5.12.0" +"run-applescript@npm:^5.0.0": + version: 5.0.0 + resolution: "run-applescript@npm:5.0.0" dependencies: - open: "npm:^8.4.0" - picomatch: "npm:^2.3.1" - source-map: "npm:^0.7.4" - yargs: "npm:^17.5.1" - peerDependencies: - rollup: 2.x || 3.x || 4.x - peerDependenciesMeta: - rollup: - optional: true - bin: - rollup-plugin-visualizer: dist/bin/cli.js - checksum: 0e44a641223377ebb472bb10f2b22efa773b5f6fbe8d54f197f07c68d7a432cbf00abad79a0aa1570f70c673c792f24700d926d663ed9a4d0ad8406ae5a0f4e4 + execa: "npm:^5.0.0" + checksum: f9977db5770929f3f0db434b8e6aa266498c70dec913c84320c0a06add510cf44e3a048c44da088abee312006f9cbf572fd065cdc8f15d7682afda8755f4114c languageName: node linkType: hard @@ -21196,18 +20891,6 @@ __metadata: languageName: node linkType: hard -"secp256k1@npm:^5.0.0": - version: 5.0.0 - resolution: "secp256k1@npm:5.0.0" - dependencies: - elliptic: "npm:^6.5.4" - node-addon-api: "npm:^5.0.0" - node-gyp: "npm:latest" - node-gyp-build: "npm:^4.2.0" - checksum: b9ab4c952babfe6103978b2f656265041ebe09b8a91b26a796cbcbe04d2252e28e12ec50d5ed3006bf2ca5feef6edcbd71c7c85122615f5ffbcd1acdd564f77f - languageName: node - linkType: hard - "selfsigned@npm:^2.4.1": version: 2.4.1 resolution: "selfsigned@npm:2.4.1" @@ -21256,9 +20939,9 @@ __metadata: languageName: node linkType: hard -"send@npm:0.18.0, send@npm:^0.18.0": - version: 0.18.0 - resolution: "send@npm:0.18.0" +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" dependencies: debug: "npm:2.6.9" depd: "npm:2.0.0" @@ -21273,13 +20956,13 @@ __metadata: on-finished: "npm:2.4.1" range-parser: "npm:~1.2.1" statuses: "npm:2.0.1" - checksum: 0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a + checksum: ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 languageName: node linkType: hard -"send@npm:0.19.0": - version: 0.19.0 - resolution: "send@npm:0.19.0" +"send@npm:^0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" dependencies: debug: "npm:2.6.9" depd: "npm:2.0.0" @@ -21294,7 +20977,7 @@ __metadata: on-finished: "npm:2.4.1" range-parser: "npm:~1.2.1" statuses: "npm:2.0.1" - checksum: ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + checksum: 0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a languageName: node linkType: hard @@ -21314,7 +20997,7 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.16.2": +"serve-static@npm:1.16.2, serve-static@npm:^1.13.1": version: 1.16.2 resolution: "serve-static@npm:1.16.2" dependencies: @@ -21326,18 +21009,6 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:^1.13.1": - version: 1.15.0 - resolution: "serve-static@npm:1.15.0" - dependencies: - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - parseurl: "npm:~1.3.3" - send: "npm:0.18.0" - checksum: fa9f0e21a540a28f301258dfe1e57bb4f81cd460d28f0e973860477dd4acef946a1f41748b5bd41c73b621bea2029569c935faa38578fd34cd42a9b4947088ba - languageName: node - linkType: hard - "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -21620,7 +21291,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.7.3, source-map@npm:^0.7.4": +"source-map@npm:^0.7.3": version: 0.7.4 resolution: "source-map@npm:0.7.4" checksum: dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc @@ -21970,6 +21641,15 @@ __metadata: languageName: node linkType: hard +"strip-outer@npm:^1.0.1": + version: 1.0.1 + resolution: "strip-outer@npm:1.0.1" + dependencies: + escape-string-regexp: "npm:^1.0.2" + checksum: c0f38e6f37563d878a221b1c76f0822f180ec5fc39be5ada30ee637a7d5b59d19418093bad2b4db1e69c40d7a7a7ac50828afce07276cf3d51ac8965cb140dfb + languageName: node + linkType: hard + "strnum@npm:^1.0.5": version: 1.0.5 resolution: "strnum@npm:1.0.5" @@ -22091,12 +21771,12 @@ __metadata: linkType: hard "synckit@npm:^0.8.5": - version: 0.8.8 - resolution: "synckit@npm:0.8.8" + version: 0.8.5 + resolution: "synckit@npm:0.8.5" dependencies: - "@pkgr/core": "npm:^0.1.0" - tslib: "npm:^2.6.2" - checksum: c3d3aa8e284f3f84f2f868b960c9f49239b364e35f6d20825a448449a3e9c8f49fe36cdd5196b30615682f007830d46f2ea354003954c7336723cb821e4b6519 + "@pkgr/utils": "npm:^2.3.1" + tslib: "npm:^2.5.0" + checksum: 9827f828cabc404b3a147c38f824c8d5b846eb6f65189d965aa0b71ea8ecda5048f8f50b4bdfd8813148844175233cff56c6bc8d87a7118cf10707df870519f4 languageName: node linkType: hard @@ -22114,23 +21794,9 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.0.5": - version: 6.1.15 - resolution: "tar@npm:6.1.15" - dependencies: - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - minipass: "npm:^5.0.0" - minizlib: "npm:^2.1.1" - mkdirp: "npm:^1.0.3" - yallist: "npm:^4.0.0" - checksum: bb2babe7b14442f690d83c2b2c571c9dd0bf802314773e05f4a3e4a241fdecd7fb560b8e4e7d6ea34533c8cd692e1b8418a3b8ba3b9687fe78a683dfbad7f82d - languageName: node - linkType: hard - -"tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.2.0 - resolution: "tar@npm:6.2.0" +"tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.2.1 + resolution: "tar@npm:6.2.1" dependencies: chownr: "npm:^2.0.0" fs-minipass: "npm:^2.0.0" @@ -22138,7 +21804,7 @@ __metadata: minizlib: "npm:^2.1.1" mkdirp: "npm:^1.0.3" yallist: "npm:^4.0.0" - checksum: 02ca064a1a6b4521fef88c07d389ac0936730091f8c02d30ea60d472e0378768e870769ab9e986d87807bfee5654359cf29ff4372746cc65e30cbddc352660d8 + checksum: a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 languageName: node linkType: hard @@ -22215,7 +21881,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.7": +"terser-webpack-plugin@npm:^5.3.1": version: 5.3.9 resolution: "terser-webpack-plugin@npm:5.3.9" dependencies: @@ -22237,6 +21903,28 @@ __metadata: languageName: node linkType: hard +"terser-webpack-plugin@npm:^5.3.10": + version: 5.3.10 + resolution: "terser-webpack-plugin@npm:5.3.10" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.20" + jest-worker: "npm:^27.4.5" + schema-utils: "npm:^3.1.1" + serialize-javascript: "npm:^6.0.1" + terser: "npm:^5.26.0" + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 66d1ed3174542560911cf96f4716aeea8d60e7caab212291705d50072b6ba844c7391442541b13c848684044042bea9ec87512b8506528c12854943da05faf91 + languageName: node + linkType: hard + "terser@npm:^5.10.0, terser@npm:^5.15.0, terser@npm:^5.16.8": version: 5.19.2 resolution: "terser@npm:5.19.2" @@ -22251,6 +21939,20 @@ __metadata: languageName: node linkType: hard +"terser@npm:^5.26.0": + version: 5.33.0 + resolution: "terser@npm:5.33.0" + dependencies: + "@jridgewell/source-map": "npm:^0.3.3" + acorn: "npm:^8.8.2" + commander: "npm:^2.20.0" + source-map-support: "npm:~0.5.20" + bin: + terser: bin/terser + checksum: 18a1cd33366dcd8fee7d6eef78c9c417cbe688e5153841e6a574f9d4937066dc40f67b1e96305f73f25bc6f2c458dbe442a056092c99619d4dbee8ad9fae4a3e + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -22348,6 +22050,13 @@ __metadata: languageName: node linkType: hard +"titleize@npm:^3.0.0": + version: 3.0.0 + resolution: "titleize@npm:3.0.0" + checksum: 5ae6084ba299b5782f95e3fe85ea9f0fa4d74b8ae722b6b3208157e975589fbb27733aeba4e5080fa9314a856044ef52caa61b87caea4b1baade951a55c06336 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -22401,6 +22110,15 @@ __metadata: languageName: node linkType: hard +"trim-repeated@npm:^1.0.0": + version: 1.0.0 + resolution: "trim-repeated@npm:1.0.0" + dependencies: + escape-string-regexp: "npm:^1.0.2" + checksum: 89acada0142ed0cdb113615a3e82fdb09e7fdb0e3504ded62762dd935bc27debfcc38edefa497dc7145d8dc8602d40dd9eec891e0ea6c28fa0cc384200b692db + languageName: node + linkType: hard + "ts-api-utils@npm:^1.3.0": version: 1.3.0 resolution: "ts-api-utils@npm:1.3.0" @@ -22532,7 +22250,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0": version: 2.6.1 resolution: "tslib@npm:2.6.1" checksum: a0382d386f5f1d6e3a39ab22bc56d1e08493da99ab3daf550e63bae6c08fdd6dd4fd20623ef387cad8262ce3fede98439257054fc025f2103cd4603b4509a052 @@ -22546,13 +22264,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.6.2": - version: 2.7.0 - resolution: "tslib@npm:2.7.0" - checksum: 469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 - languageName: node - linkType: hard - "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -22869,6 +22580,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: 68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf + languageName: node + linkType: hard + "unenv@npm:^1.8.0": version: 1.9.0 resolution: "unenv@npm:1.9.0" @@ -23076,6 +22794,13 @@ __metadata: languageName: node linkType: hard +"untildify@npm:^4.0.0": + version: 4.0.0 + resolution: "untildify@npm:4.0.0" + checksum: d758e624c707d49f76f7511d75d09a8eda7f2020d231ec52b67ff4896bcf7013be3f9522d8375f57e586e9a2e827f5641c7e06ee46ab9c435fc2b2b2e9de517a + languageName: node + linkType: hard + "untun@npm:^0.1.3": version: 0.1.3 resolution: "untun@npm:0.1.3" @@ -23321,25 +23046,25 @@ __metadata: languageName: node linkType: hard -"viem@npm:2.21.6": - version: 2.21.6 - resolution: "viem@npm:2.21.6" +"viem@npm:2.21.48": + version: 2.21.48 + resolution: "viem@npm:2.21.48" dependencies: - "@adraffy/ens-normalize": "npm:1.10.0" - "@noble/curves": "npm:1.4.0" - "@noble/hashes": "npm:1.4.0" - "@scure/bip32": "npm:1.4.0" + "@noble/curves": "npm:1.6.0" + "@noble/hashes": "npm:1.5.0" + "@scure/bip32": "npm:1.5.0" "@scure/bip39": "npm:1.4.0" - abitype: "npm:1.0.5" - isows: "npm:1.0.4" - webauthn-p256: "npm:0.0.5" - ws: "npm:8.17.1" + abitype: "npm:1.0.6" + isows: "npm:1.0.6" + ox: "npm:0.1.2" + webauthn-p256: "npm:0.0.10" + ws: "npm:8.18.0" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 6d039e0855567fb3793aee929ecb527e04ee000504aa73967bf4bd51d90c4a2dbc88f826790a06af7011432fde97ae7a22f9e24459dde462dc0d38c5157d2dc2 + checksum: e9b2799535263a859bddda25d962b13d2c76aec191e1849dd0f268c32a43eb65932a05cc5be270c92e19d79aafda73884690c0b0fbdb9311266a01ea3f659082 languageName: node linkType: hard @@ -23372,12 +23097,12 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:2.12.11": - version: 2.12.11 - resolution: "wagmi@npm:2.12.11" +"wagmi@npm:2.12.33": + version: 2.12.33 + resolution: "wagmi@npm:2.12.33" dependencies: - "@wagmi/connectors": "npm:5.1.10" - "@wagmi/core": "npm:2.13.5" + "@wagmi/connectors": "npm:5.4.0" + "@wagmi/core": "npm:2.14.6" use-sync-external-store: "npm:1.2.0" peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -23387,7 +23112,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: bdf424fc1521b41bc19c933d1221133f377ed2f853d85d1da33d43cd737180bf3734ba8482ea475055a480d7a4fc32626ec29097aad07249b114662827c64ca8 + checksum: dca024324acdc85f602c6755243dfa61ab1dee51fa844957139af7aba50217fad1e1547227a1bdc7a465fc1921710489c9bb8d5dd354c6ee01c85c73326cc279 languageName: node linkType: hard @@ -23407,13 +23132,13 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.4.0": - version: 2.4.0 - resolution: "watchpack@npm:2.4.0" +"watchpack@npm:^2.4.1": + version: 2.4.2 + resolution: "watchpack@npm:2.4.2" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: c5e35f9fb9338d31d2141d9835643c0f49b5f9c521440bb648181059e5940d93dd8ed856aa8a33fbcdd4e121dad63c7e8c15c063cf485429cd9d427be197fe62 + checksum: ec60a5f0e9efaeca0102fd9126346b3b2d523e01c34030d3fddf5813a7125765121ebdc2552981136dcd2c852deb1af0b39340f2fcc235f292db5399d0283577 languageName: node linkType: hard @@ -23426,6 +23151,16 @@ __metadata: languageName: node linkType: hard +"webauthn-p256@npm:0.0.10": + version: 0.0.10 + resolution: "webauthn-p256@npm:0.0.10" + dependencies: + "@noble/curves": "npm:^1.4.0" + "@noble/hashes": "npm:^1.4.0" + checksum: 27d836d81a1fec24a31d2d9b652f8ff6876b51940d1003bbd14dc5cfa57c58d84223b5a4eece229516522fd997bc0bc7be618ac42b129fb5fa42fa530060b16d + languageName: node + linkType: hard + "webauthn-p256@npm:0.0.5": version: 0.0.5 resolution: "webauthn-p256@npm:0.0.5" @@ -23515,39 +23250,38 @@ __metadata: linkType: hard "webpack@npm:5": - version: 5.88.2 - resolution: "webpack@npm:5.88.2" + version: 5.95.0 + resolution: "webpack@npm:5.95.0" dependencies: - "@types/eslint-scope": "npm:^3.7.3" - "@types/estree": "npm:^1.0.0" - "@webassemblyjs/ast": "npm:^1.11.5" - "@webassemblyjs/wasm-edit": "npm:^1.11.5" - "@webassemblyjs/wasm-parser": "npm:^1.11.5" + "@types/estree": "npm:^1.0.5" + "@webassemblyjs/ast": "npm:^1.12.1" + "@webassemblyjs/wasm-edit": "npm:^1.12.1" + "@webassemblyjs/wasm-parser": "npm:^1.12.1" acorn: "npm:^8.7.1" - acorn-import-assertions: "npm:^1.9.0" - browserslist: "npm:^4.14.5" + acorn-import-attributes: "npm:^1.9.5" + browserslist: "npm:^4.21.10" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.15.0" + enhanced-resolve: "npm:^5.17.1" es-module-lexer: "npm:^1.2.1" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.9" + graceful-fs: "npm:^4.2.11" json-parse-even-better-errors: "npm:^2.3.1" loader-runner: "npm:^4.2.0" mime-types: "npm:^2.1.27" neo-async: "npm:^2.6.2" schema-utils: "npm:^3.2.0" tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.7" - watchpack: "npm:^2.4.0" + terser-webpack-plugin: "npm:^5.3.10" + watchpack: "npm:^2.4.1" webpack-sources: "npm:^3.2.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 743acf04cdb7f73ec059761d3921798014139005c88e136ab99fe158f544695eee2caf4be775cc06e7f481d84725d443df2c1c8e00ec24a130e8b8fd514ff7b9 + checksum: b9e6d0f8ebcbf0632494ac0b90fe4acb8f4a9b83f7ace4a67a15545a36fe58599c912ab58e625e1bf58ab3b0916c75fe99da6196d412ee0cab0b5065edd84238 languageName: node linkType: hard @@ -23760,6 +23494,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard + "ws@npm:8.5.0": version: 8.5.0 resolution: "ws@npm:8.5.0" @@ -24012,15 +23761,14 @@ __metadata: languageName: node linkType: hard -"zustand@npm:4.4.1": - version: 4.4.1 - resolution: "zustand@npm:4.4.1" - dependencies: - use-sync-external-store: "npm:1.2.0" +"zustand@npm:5.0.0": + version: 5.0.0 + resolution: "zustand@npm:5.0.0" peerDependencies: - "@types/react": ">=16.8" - immer: ">=9.0" - react: ">=16.8" + "@types/react": ">=18.0.0" + immer: ">=9.0.6" + react: ">=18.0.0" + use-sync-external-store: ">=1.2.0" peerDependenciesMeta: "@types/react": optional: true @@ -24028,6 +23776,8 @@ __metadata: optional: true react: optional: true - checksum: c119273886e5cdbd7a9f80c9e0fee8a2c736bb6428e283b25c6dfd428789a95e10b6ed6b18553c955ce0d5dd62e2f4a84af3e2a41f31fdb34fd25462d2b19a8c + use-sync-external-store: + optional: true + checksum: 7546df78aa512f1d2271e238c44699c0ac4bc57f12ae46fcfe8ba1e8a97686fc690596e654101acfabcd706099aa5d3519fc3f22d32b3082baa60699bb333e9a languageName: node linkType: hard