diff --git a/.env.custom b/.env.custom
index b35cf9192..ede540940 100644
--- a/.env.custom
+++ b/.env.custom
@@ -1 +1 @@
-APPLICATION_VERSION=1.36.4
\ No newline at end of file
+APPLICATION_VERSION=1.44.3
\ No newline at end of file
diff --git a/.env.example b/.env.example
index ab53f2b04..b4c47be00 100644
--- a/.env.example
+++ b/.env.example
@@ -38,10 +38,15 @@ NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION=
NEXT_PUBLIC_FIREBASE_OPTIONS_STAGING=
NEXT_PUBLIC_FIREBASE_VAPID_KEY_STAGING=
-# Redefine
-NEXT_PUBLIC_REDEFINE_API=
+# Blockaid
+NEXT_PUBLIC_BLOCKAID_CLIENT_ID
# Social Login
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING=
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION=
+# Cypress wallet private keys
+CYPRESS_WALLET_CREDENTIALS=
+
+# Beamer keys for e2e tests
+BEAMER_DATA_E2E=
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
index d3c2987cb..a8dc812ac 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,11 +1,9 @@
{
- "extends": [
- "next",
- "prettier",
- "plugin:prettier/recommended",
- "plugin:storybook/recommended"
- ],
+ "extends": ["next", "prettier", "plugin:prettier/recommended", "plugin:storybook/recommended"],
"parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "project": ["./tsconfig.json"]
+ },
"rules": {
"@next/next/no-img-element": "off",
"@next/next/google-font-display": "off",
@@ -13,7 +11,9 @@
"@next/next/no-page-custom-font": "off",
"unused-imports/no-unused-imports-ts": "off",
"@typescript-eslint/consistent-type-imports": "error",
+ "@typescript-eslint/await-thenable": "error",
"no-constant-condition": "warn",
+ "no-unused-vars": ["off", { "varsIgnorePattern": "^_" }],
"react-hooks/exhaustive-deps": [
"warn",
{
@@ -25,14 +25,6 @@
"jsx-quotes": ["error", "prefer-double"],
"react/jsx-curly-brace-presence": ["off", { "props": "never", "children": "never" }]
},
- "ignorePatterns": [
- "node_modules/",
- ".next/",
- ".github/"
- ],
- "plugins": [
- "unused-imports",
- "@typescript-eslint",
- "no-only-tests"
- ]
+ "ignorePatterns": ["node_modules/", ".next/", ".github/", "cypress/", "src/types/contracts/"],
+ "plugins": ["unused-imports", "@typescript-eslint", "no-only-tests"]
}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..4bae3ef5f
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: 'npm'
+ directory: '/'
+ schedule:
+ interval: 'weekly'
+
+ - package-ecosystem: 'github-actions'
+ directory: '/'
+ schedule:
+ interval: 'weekly'
diff --git a/.github/workflows/build/action.yml b/.github/workflows/build/action.yml
index fd3d7998f..ba6ad94d7 100644
--- a/.github/workflows/build/action.yml
+++ b/.github/workflows/build/action.yml
@@ -51,7 +51,7 @@ runs:
NEXT_PUBLIC_SAFE_RELAY_SERVICE_URL_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SAFE_GELATO_RELAY_SERVICE_URL_PRODUCTION }}
NEXT_PUBLIC_SAFE_RELAY_SERVICE_URL_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SAFE_GELATO_RELAY_SERVICE_URL_STAGING }}
NEXT_PUBLIC_IS_OFFICIAL_HOST: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_IS_OFFICIAL_HOST }}
- NEXT_PUBLIC_REDEFINE_API: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_REDEFINE_API }}
+ NEXT_PUBLIC_BLOCKAID_CLIENT_ID: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_BLOCKAID_CLIENT_ID }}
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING }}
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION }}
NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION }}
@@ -59,3 +59,4 @@ runs:
NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION }}
NEXT_PUBLIC_FIREBASE_VAPID_KEY_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_FIREBASE_VAPID_KEY_STAGING }}
NEXT_PUBLIC_SPINDL_SDK_KEY: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SPINDL_SDK_KEY }}
+ NEXT_PUBLIC_ECOSYSTEM_ID_ADDRESS: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_ECOSYSTEM_ID_ADDRESS }}
diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml
index 26ffce42c..f578a7653 100644
--- a/.github/workflows/cla.yml
+++ b/.github/workflows/cla.yml
@@ -13,7 +13,7 @@ jobs:
- name: 'CLA Assistant'
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
- uses: contributor-assistant/github-action@v2.2.0
+ uses: contributor-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret
@@ -24,7 +24,7 @@ jobs:
# branch should not be protected
branch: 'main'
# user names of users allowed to contribute without CLA
- allowlist: lukasschor,rmeissner,germartinez,Uxio0,dasanra,francovenica,tschubotz,luarx,DaniSomoza,iamacook,yagopv,usame-algan,schmanu,DiogoSoaress,JagoFigueroa,fmrsabino,mike10ca,jmealy,compojoom,TanyaEfremova,bot*
+ allowlist: clovisdasilvaneto,lukasschor,rmeissner,germartinez,Uxio0,dasanra,francovenica,tschubotz,luarx,DaniSomoza,iamacook,yagopv,usame-algan,schmanu,DiogoSoaress,JagoFigueroa,fmrsabino,mike10ca,jmealy,compojoom,TanyaEfremova,bot*
# the followings are the optional inputs - If the optional inputs are not given, then default values will be taken
# enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository)
diff --git a/.github/workflows/cypress/action.yml b/.github/workflows/cypress/action.yml
index bcccd875b..c549e1c5f 100644
--- a/.github/workflows/cypress/action.yml
+++ b/.github/workflows/cypress/action.yml
@@ -23,6 +23,10 @@ inputs:
description: 'Cypress cloud record key'
required: false
+ tag:
+ description: 'Cypress cloud tag key'
+ required: false
+
runs:
using: 'composite'
steps:
@@ -34,6 +38,10 @@ runs:
curl -O 'https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb'
sudo apt-get install ./google-chrome-stable_current_amd64.deb
+ - name: Install Cypress 13.13.1
+ shell: bash
+ run: npm install cypress@13.13.1 --legacy-peer-deps
+
- uses: ./.github/workflows/build
with:
secrets: ${{ inputs.secrets }}
@@ -43,16 +51,18 @@ runs:
shell: bash
run: yarn serve &
- - uses: cypress-io/github-action@v4
+ - uses: cypress-io/github-action@v6
with:
spec: ${{ inputs.spec }}
group: ${{ inputs.group }}
parallel: true
browser: chrome
record: true
+ tag: ${{ inputs.tag }}
config: baseUrl=http://localhost:8080
env:
CYPRESS_RECORD_KEY: ${{ inputs.record_key || fromJSON(inputs.secrets).CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ fromJSON(inputs.secrets).GITHUB_TOKEN }}
CYPRESS_PROJECT_ID: ${{ inputs.project_id }}
CYPRESS_WALLET_CREDENTIALS: ${{ fromJSON(inputs.secrets).CYPRESS_WALLET_CREDENTIALS }}
+ BEAMER_DATA_E2E: ${{ fromJSON(inputs.secrets).BEAMER_DATA_E2E }}
diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml
index a56a220e1..e6ef646e0 100644
--- a/.github/workflows/deploy-dev.yml
+++ b/.github/workflows/deploy-dev.yml
@@ -43,10 +43,10 @@ jobs:
secrets: ${{ toJSON(secrets) }}
if: startsWith(github.ref, 'refs/heads/main')
- - uses: ./.github/workflows/build-storybook
+ #- uses: ./.github/workflows/build-storybook
- name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v3
+ uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE }}
aws-region: ${{ secrets.AWS_REGION }}
diff --git a/.github/workflows/deploy-dockerhub.yml b/.github/workflows/deploy-dockerhub.yml
index a565ca877..3559ae754 100644
--- a/.github/workflows/deploy-dockerhub.yml
+++ b/.github/workflows/deploy-dockerhub.yml
@@ -27,7 +27,7 @@ jobs:
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Deploy Main
if: github.ref == 'refs/heads/main'
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
push: true
tags: safeglobal/safe-wallet-web:staging
@@ -38,7 +38,7 @@ jobs:
cache-to: type=gha,mode=max
- name: Deploy Develop
if: github.ref == 'refs/heads/dev'
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
push: true
tags: safeglobal/safe-wallet-web:dev
@@ -49,7 +49,7 @@ jobs:
cache-to: type=gha,mode=max
- name: Deploy Tag
if: (github.event_name == 'release' && github.event.action == 'released')
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
push: true
tags: |
diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml
index 84a0e6ba6..32a2f2ff6 100644
--- a/.github/workflows/deploy-production.yml
+++ b/.github/workflows/deploy-production.yml
@@ -33,7 +33,7 @@ jobs:
run: sha256sum "$ARCHIVE_NAME".tar.gz > ${{ env.ARCHIVE_NAME }}-sha256-checksum.txt
- name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v3
+ uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE }}
aws-region: ${{ secrets.AWS_REGION }}
diff --git a/.github/workflows/e2e-hp-ondemand.yml b/.github/workflows/e2e-hp-ondemand.yml
index e898380e1..bd1f2e95a 100644
--- a/.github/workflows/e2e-hp-ondemand.yml
+++ b/.github/workflows/e2e-hp-ondemand.yml
@@ -12,6 +12,7 @@ concurrency:
jobs:
e2e:
runs-on: ubuntu-20.04
+ timeout-minutes: 60
name: Cypress Happy path on demand tests
strategy:
fail-fast: false
@@ -26,10 +27,11 @@ jobs:
spec: |
cypress/e2e/happypath/*.cy.js
group: 'Happy path on demand tests'
+ tag: 'happypath'
- name: Python setup
if: always()
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v5
with:
python-version: '3.x'
diff --git a/.github/workflows/e2e-ondemand.yml b/.github/workflows/e2e-ondemand.yml
index 906195d4a..704f42f86 100644
--- a/.github/workflows/e2e-ondemand.yml
+++ b/.github/workflows/e2e-ondemand.yml
@@ -12,6 +12,7 @@ concurrency:
jobs:
e2e:
runs-on: ubuntu-20.04
+ timeout-minutes: 40
name: Cypress Regression on demand tests
strategy:
fail-fast: false
@@ -27,11 +28,13 @@ jobs:
cypress/e2e/regression/*.cy.js
cypress/e2e/safe-apps/*.cy.js
cypress/e2e/smoke/*.cy.js
+ cypress/e2e/prodhealthcheck/*.cy.js
group: 'Regression on demand tests'
+ tag: 'regression'
- name: Python setup
if: always()
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v5
with:
python-version: '3.x'
@@ -49,7 +52,7 @@ jobs:
if: always()
run: |
pip install trcli
- trcli -y \
+ if ! trcli -y \
-h https://gno.testrail.io/ \
--project "Safe- Web App" \
--username ${{ secrets.TESTRAIL_USERNAME }} \
@@ -57,4 +60,6 @@ jobs:
parse_junit \
--title "Automated Tests, branch: ${GITHUB_REF_NAME}" \
--run-description ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} \
- -f "reports/junit-report.xml"
+ -f "reports/junit-report.xml"; then
+ echo -e "\e[41;32mTestRail upload failed. Pipeline will continue, please check the upload process.\e[0m"
+ fi
diff --git a/.github/workflows/e2e-prod-ondemand.yml b/.github/workflows/e2e-prod-ondemand.yml
new file mode 100644
index 000000000..76fd8c5db
--- /dev/null
+++ b/.github/workflows/e2e-prod-ondemand.yml
@@ -0,0 +1,59 @@
+name: Production health check tests
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 4 * * 1-5'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ e2e:
+ runs-on: ubuntu-20.04
+ name: Cypress production health check tests
+ strategy:
+ fail-fast: false
+ matrix:
+ containers: [1]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: ./.github/workflows/cypress
+ with:
+ secrets: ${{ toJSON(secrets) }}
+ spec: |
+ cypress/e2e/prodhealthcheck/*.cy.js
+ group: 'Production health check tests'
+ tag: 'production'
+
+ - name: Python setup
+ if: always()
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+
+ - name: Install junitparser
+ if: always()
+ run: |
+ pip install junitparser
+
+ - name: Merge JUnit reports for TestRail
+ if: always()
+ run: |
+ junitparser merge --suite-name "Root Suite" --glob "reports/junit-*" "reports/junit-report.xml"
+
+ - name: TestRail CLI upload results
+ if: always()
+ run: |
+ pip install trcli
+ trcli -y \
+ -h https://gno.testrail.io/ \
+ --project "Safe- Web App" \
+ --username ${{ secrets.TESTRAIL_USERNAME }} \
+ --password ${{ secrets.TESTRAIL_PASSWORD }} \
+ parse_junit \
+ --title "Automated Tests, branch: ${GITHUB_REF_NAME}" \
+ --run-description ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} \
+ -f "reports/junit-report.xml"
diff --git a/.github/workflows/e2e-regression.yml b/.github/workflows/e2e-regression.yml
index 104147064..e8dd34ffd 100644
--- a/.github/workflows/e2e-regression.yml
+++ b/.github/workflows/e2e-regression.yml
@@ -26,3 +26,4 @@ jobs:
secrets: ${{ toJSON(secrets) }}
spec: cypress/e2e/**/*.cy.js
group: 'Regression tests'
+ tag: 'regression'
diff --git a/.github/workflows/e2e-safe-apps.yml b/.github/workflows/e2e-safe-apps.yml
index 2843bdfb1..88c0d5d32 100644
--- a/.github/workflows/e2e-safe-apps.yml
+++ b/.github/workflows/e2e-safe-apps.yml
@@ -26,4 +26,4 @@ jobs:
group: 'Safe Apps tests'
project_id: okn21k
record_key: ${{ secrets.CYPRESS_SAFE_APPS_RECORD_KEY }}
-
+ tag: 'safeapps'
diff --git a/.github/workflows/e2e-smoke.yml b/.github/workflows/e2e-smoke.yml
index e9cb5a6ab..cb7abeabb 100644
--- a/.github/workflows/e2e-smoke.yml
+++ b/.github/workflows/e2e-smoke.yml
@@ -10,6 +10,7 @@ concurrency:
jobs:
e2e:
runs-on: ubuntu-20.04
+ timeout-minutes: 30
name: Cypress Smoke tests
strategy:
@@ -25,3 +26,4 @@ jobs:
secrets: ${{ toJSON(secrets) }}
spec: cypress/e2e/smoke/*.cy.js
group: 'Smoke tests'
+ tag: 'smoke'
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 1f43f7b41..bdbe5727f 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -7,12 +7,21 @@ concurrency:
jobs:
eslint:
+ permissions:
+ checks: write
+ pull-requests: read
+ statuses: write
+
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/workflows/yarn
- - uses: Maggi64/eslint-plus-action@master
+ - uses: CatChen/eslint-suggestion-action@v2
with:
- npmInstall: false
+ request-changes: true # optional
+ fail-check: true # optional
+ github-token: ${{ secrets.GITHUB_TOKEN }} # optional
+ directory: './' # optional
+ targets: 'src' # optional
diff --git a/.github/workflows/nextjs-bundle-analysis.yml b/.github/workflows/nextjs-bundle-analysis.yml
index 1e3ad27f2..49d08b009 100644
--- a/.github/workflows/nextjs-bundle-analysis.yml
+++ b/.github/workflows/nextjs-bundle-analysis.yml
@@ -32,7 +32,7 @@ jobs:
run: npx -p nextjs-bundle-analysis report
- name: Upload bundle
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: bundle
path: .next/analyze/__bundle_analysis.json
diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml
index 3821d33ed..d389a5286 100644
--- a/.github/workflows/tag-release.yml
+++ b/.github/workflows/tag-release.yml
@@ -28,7 +28,7 @@ jobs:
- name: GitHub release
if: steps.version.outputs.version
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index c05edb116..54aed4878 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -12,6 +12,11 @@ concurrency:
jobs:
test:
+ permissions:
+ contents: read
+ checks: write
+ pull-requests: write
+
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -19,7 +24,7 @@ jobs:
- uses: ./.github/workflows/yarn
- name: Annotations and coverage report
- uses: ArtiomTr/jest-coverage-report-action@v2
+ uses: ArtiomTr/jest-coverage-report-action@v2.3.1
with:
skip-step: install
annotations: failed-tests
diff --git a/README.md b/README.md
index 9a2b59261..97e85fa55 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,9 @@
![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/safe-global/safe-wallet-web)
[![GitPOAP Badge](https://public-api.gitpoap.io/v1/repo/safe-global/safe-wallet-web/badge)](https://www.gitpoap.io/gh/safe-global/safe-wallet-web)
-The default Safe web interface.
+Safe{Wallet} is a smart contract wallet for Ethereum and other EVM chains. Based on Gnosis Safe multisig contracts.
+
+This repository is the frontend of the Safe{Wallet} app.
## Contributing
@@ -19,31 +21,31 @@ Create a `.env` file with environment variables. You can use the `.env.example`
Here's the list of all the environment variables:
-| Env variable | Description
-| ------------------------------------------------------ | -----------
-| `NEXT_PUBLIC_INFURA_TOKEN` | [Infura](https://docs.infura.io/infura/networks/ethereum/how-to/secure-a-project/project-id) RPC API token
-| `NEXT_PUBLIC_SAFE_APPS_INFURA_TOKEN` | Infura token for Safe Apps, falls back to `NEXT_PUBLIC_INFURA_TOKEN`
-| `NEXT_PUBLIC_IS_PRODUCTION` | Set to `true` to build a minified production app
-| `NEXT_PUBLIC_GATEWAY_URL_PRODUCTION` | The base URL for the [Safe Client Gateway](https://github.com/safe-global/safe-client-gateway)
-| `NEXT_PUBLIC_GATEWAY_URL_STAGING` | The base CGW URL on staging
-| `NEXT_PUBLIC_SAFE_VERSION` | The latest version of the Safe contract, defaults to 1.3.0
-| `NEXT_PUBLIC_WC_PROJECT_ID` | [WalletConnect v2](https://docs.walletconnect.com/2.0/cloud/relay) project ID
-| `NEXT_PUBLIC_TENDERLY_ORG_NAME` | [Tenderly](https://tenderly.co) org name for Transaction Simulation
-| `NEXT_PUBLIC_TENDERLY_PROJECT_NAME` | Tenderly project name
-| `NEXT_PUBLIC_TENDERLY_SIMULATE_ENDPOINT_URL` | Tenderly simulation URL
-| `NEXT_PUBLIC_BEAMER_ID` | [Beamer](https://www.getbeamer.com) is a news feed for in-app announcements
-| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID` | [GTM](https://tagmanager.google.com) project id
-| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_DEVELOPMENT_AUTH` | Dev GTM key
-| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_LATEST_AUTH` | Preview GTM key
-| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_LIVE_AUTH` | Production GTM key
-| `NEXT_PUBLIC_SENTRY_DSN` | [Sentry](https://sentry.io) id for tracking runtime errors
-| `NEXT_PUBLIC_IS_OFFICIAL_HOST` | Whether it's the official distribution of the app, or a fork; has legal implications. Set to true only if you also update the legal pages like Imprint and Terms of use
-| `NEXT_PUBLIC_REDEFINE_API` | Redefine API base URL
-| `NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION` | Firebase Cloud Messaging (FCM) `initializeApp` options on production
-| `NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION` | FCM vapid key on production
-| `NEXT_PUBLIC_FIREBASE_OPTIONS_STAGING` | FCM `initializeApp` options on staging
-| `NEXT_PUBLIC_FIREBASE_VAPID_KEY_STAGING` | FCM vapid key on staging
-| `NEXT_PUBLIC_SPINDL_SDK_KEY` | [Spindl](http://spindl.xyz) SDK key
+| Env variable | Description |
+| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `NEXT_PUBLIC_INFURA_TOKEN` | [Infura](https://docs.infura.io/infura/networks/ethereum/how-to/secure-a-project/project-id) RPC API token |
+| `NEXT_PUBLIC_SAFE_APPS_INFURA_TOKEN` | Infura token for Safe Apps, falls back to `NEXT_PUBLIC_INFURA_TOKEN` |
+| `NEXT_PUBLIC_IS_PRODUCTION` | Set to `true` to build a minified production app |
+| `NEXT_PUBLIC_GATEWAY_URL_PRODUCTION` | The base URL for the [Safe Client Gateway](https://github.com/safe-global/safe-client-gateway) |
+| `NEXT_PUBLIC_GATEWAY_URL_STAGING` | The base CGW URL on staging |
+| `NEXT_PUBLIC_SAFE_VERSION` | The latest version of the Safe contract, defaults to 1.4.1 |
+| `NEXT_PUBLIC_WC_PROJECT_ID` | [WalletConnect v2](https://docs.walletconnect.com/2.0/cloud/relay) project ID |
+| `NEXT_PUBLIC_TENDERLY_ORG_NAME` | [Tenderly](https://tenderly.co) org name for Transaction Simulation |
+| `NEXT_PUBLIC_TENDERLY_PROJECT_NAME` | Tenderly project name |
+| `NEXT_PUBLIC_TENDERLY_SIMULATE_ENDPOINT_URL` | Tenderly simulation URL |
+| `NEXT_PUBLIC_BEAMER_ID` | [Beamer](https://www.getbeamer.com) is a news feed for in-app announcements |
+| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID` | [GTM](https://tagmanager.google.com) project id |
+| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_DEVELOPMENT_AUTH` | Dev GTM key |
+| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_LATEST_AUTH` | Preview GTM key |
+| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_LIVE_AUTH` | Production GTM key |
+| `NEXT_PUBLIC_SENTRY_DSN` | [Sentry](https://sentry.io) id for tracking runtime errors |
+| `NEXT_PUBLIC_IS_OFFICIAL_HOST` | Whether it's the official distribution of the app, or a fork; has legal implications. Set to true only if you also update the legal pages like Imprint and Terms of use |
+| `NEXT_PUBLIC_REDEFINE_API` | Redefine API base URL |
+| `NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION` | Firebase Cloud Messaging (FCM) `initializeApp` options on production |
+| `NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION` | FCM vapid key on production |
+| `NEXT_PUBLIC_FIREBASE_OPTIONS_STAGING` | FCM `initializeApp` options on staging |
+| `NEXT_PUBLIC_FIREBASE_VAPID_KEY_STAGING` | FCM vapid key on staging |
+| `NEXT_PUBLIC_SPINDL_SDK_KEY` | [Spindl](http://spindl.xyz) SDK key |
If you don't provide some of the variables, the corresponding features will be disabled in the UI.
@@ -56,6 +58,7 @@ yarn
```
Generate types:
+
```bash
yarn postinstall
```
@@ -111,6 +114,7 @@ yarn cypress:open
```
You can then choose which e2e tests to run.
+Some tests will require signer private keys, please include them in your .env file
## Component template
diff --git a/cypress.config.js b/cypress.config.js
index b03786f20..f04876345 100644
--- a/cypress.config.js
+++ b/cypress.config.js
@@ -1,6 +1,12 @@
import { defineConfig } from 'cypress'
import 'dotenv/config'
import * as fs from 'fs'
+import path, { dirname } from 'path'
+import { fileURLToPath } from 'url'
+import matter from 'gray-matter'
+import { configureVisualRegression } from 'cypress-visual-regression'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
export default defineConfig({
projectId: 'exhdra',
@@ -14,7 +20,30 @@ export default defineConfig({
openMode: 0,
},
e2e: {
+ screenshotsFolder: './cypress/snapshots/actual',
setupNodeEvents(on, config) {
+ // Read and parse the terms Markdown file
+ try {
+ const filePath = path.resolve(__dirname, './src/markdown/terms/terms.md')
+
+ const content = fs.readFileSync(filePath, 'utf8')
+ const parsed = matter(content)
+ const frontmatter = parsed.data
+
+ // Set Cookie term version on the cypress env - this way we can access it in the tests
+ config.env.CURRENT_COOKIE_TERMS_VERSION = frontmatter.version
+ } catch (error) {
+ console.error('Error reading or parsing terms.md file:', error)
+ }
+
+ configureVisualRegression(on),
+ on('task', {
+ log(message) {
+ console.log(message)
+ return null
+ },
+ })
+
on('after:spec', (spec, results) => {
if (results && results.video) {
const failures = results.tests.some((test) => test.attempts.some((attempt) => attempt.state === 'failed'))
@@ -23,9 +52,15 @@ export default defineConfig({
}
}
})
+
+ return config
},
env: {
...process.env,
+ visualRegressionType: 'regression',
+ visualRegressionBaseDirectory: 'cypress/snapshots/actual',
+ visualRegressionDiffDirectory: 'cypress/snapshots/diff',
+ visualRegressionGenerateDiff: 'fail',
},
baseUrl: 'http://localhost:3000',
testIsolation: false,
diff --git a/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js b/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js
index 532b72cce..f0236a53a 100644
--- a/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js
+++ b/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js
@@ -8,7 +8,7 @@ import * as nfts from '../pages/nfts.pages'
import * as ls from '../../support/localstorage_data.js'
import { ethers } from 'ethers'
import SafeApiKit from '@safe-global/api-kit'
-import { createEthersAdapter, createSigners } from '../../support/api/utils_ether'
+import { createSigners } from '../../support/api/utils_ether'
import { createSafes } from '../../support/api/utils_protocolkit'
import { contracts, abi_qtrust, abi_nft_pc2 } from '../../support/api/contracts'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
@@ -40,10 +40,6 @@ const tokenContract = new ethers.Contract(contractAddress, abi_qtrust, provider)
const nftContract = new ethers.Contract(nftContractAddress, abi_nft_pc2, provider)
const owner1Signer = signers[0]
-const owner2Signer = signers[1]
-
-const ethAdapterOwner1 = createEthersAdapter(owner1Signer)
-const ethAdapterOwner2 = createEthersAdapter(owner2Signer)
function visit(url) {
cy.visit(url)
@@ -51,12 +47,14 @@ function visit(url) {
describe('Send funds with connected signer happy path tests', { defaultCommandTimeout: 60000 }, () => {
before(async () => {
+ cy.clearLocalStorage().then(() => {
+ main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies)
+ main.addToLocalStorage(
+ constants.localStorageKeys.SAFE_v2__tokenlist_onboarding,
+ ls.cookies.acceptedTokenListOnboarding,
+ )
+ })
safesData = await getSafes(CATEGORIES.funds)
- main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__cookies, ls.cookies.acceptedCookies)
- main.addToLocalStorage(
- constants.localStorageKeys.SAFE_v2__tokenlist_onboarding,
- ls.cookies.acceptedTokenListOnboarding,
- )
apiKit = new SafeApiKit({
chainId: BigInt(1),
txServiceUrl: constants.stagingTxServiceUrl,
@@ -65,8 +63,8 @@ describe('Send funds with connected signer happy path tests', { defaultCommandTi
outgoingSafeAddress = safesData.SEP_FUNDS_SAFE_6.substring(4)
const safeConfigurations = [
- { ethAdapter: ethAdapterOwner1, safeAddress: outgoingSafeAddress },
- { ethAdapter: ethAdapterOwner2, safeAddress: outgoingSafeAddress },
+ { signer: walletCredentials.OWNER_1_PRIVATE_KEY, safeAddress: outgoingSafeAddress, provider },
+ { signer: walletCredentials.OWNER_2_PRIVATE_KEY, safeAddress: outgoingSafeAddress, provider },
]
safes = await createSafes(safeConfigurations)
@@ -104,6 +102,8 @@ describe('Send funds with connected signer happy path tests', { defaultCommandTi
})
await tx.wait()
main.verifyNonceChange(network_pref + originatingSafe, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
})
})
@@ -157,6 +157,8 @@ describe('Send funds with connected signer happy path tests', { defaultCommandTi
const safeTx = await apiKit.getTransaction(safeTxHashofExistingTx)
await protocolKitOwner2_S3.executeTransaction(safeTx)
main.verifyNonceChange(network_pref + targetSafe, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
})
@@ -190,6 +192,8 @@ describe('Send funds with connected signer happy path tests', { defaultCommandTi
await tx.wait()
main.verifyNonceChange(network_pref + originatingSafe, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
})
})
diff --git a/cypress/e2e/happypath/sendfunds_queue_1.cy.js b/cypress/e2e/happypath/sendfunds_queue_1.cy.js
index f0a6d5624..b1f6e1bd4 100644
--- a/cypress/e2e/happypath/sendfunds_queue_1.cy.js
+++ b/cypress/e2e/happypath/sendfunds_queue_1.cy.js
@@ -4,10 +4,12 @@ import * as assets from '../pages/assets.pages'
import * as tx from '../pages/transactions.page'
import { ethers } from 'ethers'
import SafeApiKit from '@safe-global/api-kit'
-import { createEthersAdapter, createSigners } from '../../support/api/utils_ether'
+import { createSigners } from '../../support/api/utils_ether'
import { createSafes } from '../../support/api/utils_protocolkit'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
import * as wallet from '../../support/utils/wallet.js'
+import * as ls from '../../support/localstorage_data.js'
+import * as navigation from '../pages/navigation.page.js'
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
const receiver = walletCredentials.OWNER_2_WALLET_ADDRESS
@@ -37,13 +39,8 @@ const signers = createSigners(privateKeys, provider)
const owner1Signer = signers[0]
const owner2Signer = signers[1]
-const ethAdapterOwner1 = createEthersAdapter(owner1Signer)
-const ethAdapterOwner2 = createEthersAdapter(owner2Signer)
-
function visit(url) {
cy.visit(url)
- cy.clearLocalStorage()
- main.acceptCookies()
}
function executeTransactionFlow(fromSafe) {
@@ -56,6 +53,14 @@ function executeTransactionFlow(fromSafe) {
describe('Send funds from queue happy path tests 1', () => {
before(async () => {
+ cy.clearLocalStorage().then(() => {
+ main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies)
+ main.addToLocalStorage(
+ constants.localStorageKeys.SAFE_v2__tokenlist_onboarding,
+ ls.cookies.acceptedTokenListOnboarding,
+ )
+ })
+
safesData = await getSafes(CATEGORIES.funds)
apiKit = new SafeApiKit({
chainId: BigInt(1),
@@ -67,10 +72,10 @@ describe('Send funds from queue happy path tests 1', () => {
existingSafeAddress3 = safesData.SEP_FUNDS_SAFE_5.substring(4)
const safeConfigurations = [
- { ethAdapter: ethAdapterOwner1, safeAddress: existingSafeAddress1 },
- { ethAdapter: ethAdapterOwner1, safeAddress: existingSafeAddress2 },
- { ethAdapter: ethAdapterOwner1, safeAddress: existingSafeAddress3 },
- { ethAdapter: ethAdapterOwner2, safeAddress: existingSafeAddress3 },
+ { signer: privateKeys[0], safeAddress: existingSafeAddress1, provider },
+ { signer: privateKeys[0], safeAddress: existingSafeAddress2, provider },
+ { signer: privateKeys[0], safeAddress: existingSafeAddress3, provider },
+ { signer: privateKeys[1], safeAddress: existingSafeAddress3, provider },
]
safes = await createSafes(safeConfigurations)
@@ -81,41 +86,39 @@ describe('Send funds from queue happy path tests 1', () => {
protocolKitOwner2_S3 = safes[3]
})
- it(
- 'Verify confirmation and execution of native token queued tx by second signer with connected wallet',
- { defaultCommandTimeout: 300000 },
- () => {
- cy.wrap(null)
- .then(() => {
- return main.fetchCurrentNonce(network_pref + existingSafeAddress1)
- })
- .then(async (currentNonce) => {
- const amount = ethers.parseUnits(tokenAmount, unit_eth).toString()
- const safeTransactionData = {
- to: receiver,
- data: '0x',
- value: amount.toString(),
- }
-
- const safeTransaction = await protocolKitOwnerS1.createTransaction({ transactions: [safeTransactionData] })
- const safeTxHash = await protocolKitOwnerS1.getTransactionHash(safeTransaction)
- const senderSignature = await protocolKitOwnerS1.signHash(safeTxHash)
- const safeAddress = existingSafeAddress1
-
- await apiKit.proposeTransaction({
- safeAddress,
- safeTransactionData: safeTransaction.data,
- safeTxHash,
- senderAddress: await owner1Signer.getAddress(),
- senderSignature: senderSignature.data,
- })
-
- executeTransactionFlow(safeAddress)
- cy.wait(5000)
- main.verifyNonceChange(network_pref + safeAddress, currentNonce + 1)
+ it('Verify confirmation and execution of native token queued tx by second signer with connected wallet', () => {
+ cy.wrap(null)
+ .then(() => {
+ return main.fetchCurrentNonce(network_pref + existingSafeAddress1)
+ })
+ .then(async (currentNonce) => {
+ const amount = ethers.parseUnits(tokenAmount, unit_eth).toString()
+ const safeTransactionData = {
+ to: receiver,
+ data: '0x',
+ value: amount.toString(),
+ }
+
+ const safeTransaction = await protocolKitOwnerS1.createTransaction({ transactions: [safeTransactionData] })
+ const safeTxHash = await protocolKitOwnerS1.getTransactionHash(safeTransaction)
+ const senderSignature = await protocolKitOwnerS1.signHash(safeTxHash)
+ const safeAddress = existingSafeAddress1
+
+ await apiKit.proposeTransaction({
+ safeAddress,
+ safeTransactionData: safeTransaction.data,
+ safeTxHash,
+ senderAddress: await owner1Signer.getAddress(),
+ senderSignature: senderSignature.data,
})
- },
- )
+
+ executeTransactionFlow(safeAddress)
+ cy.wait(5000)
+ main.verifyNonceChange(network_pref + safeAddress, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+ })
it.skip('Verify confirmation and execution of native token queued tx by second signer with relayer', () => {
function executeTransactionFlow(fromSafe) {
@@ -198,6 +201,8 @@ describe('Send funds from queue happy path tests 1', () => {
executeTransaction(safeAddress)
cy.wait(5000)
main.verifyNonceChange(network_pref + safeAddress, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
})
})
diff --git a/cypress/e2e/happypath/sendfunds_relay.cy.js b/cypress/e2e/happypath/sendfunds_relay.cy.js
index 1f4a65bb5..fcf34d798 100644
--- a/cypress/e2e/happypath/sendfunds_relay.cy.js
+++ b/cypress/e2e/happypath/sendfunds_relay.cy.js
@@ -8,11 +8,11 @@ import * as nfts from '../pages/nfts.pages'
import * as ls from '../../support/localstorage_data.js'
import { ethers } from 'ethers'
import SafeApiKit from '@safe-global/api-kit'
-import { createEthersAdapter, createSigners } from '../../support/api/utils_ether'
+import { createSigners } from '../../support/api/utils_ether'
import { createSafes } from '../../support/api/utils_protocolkit'
import { contracts, abi_qtrust, abi_nft_pc2 } from '../../support/api/contracts'
-import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
import * as wallet from '../../support/utils/wallet.js'
+import * as fundSafes from '../../fixtures/safes/funds.json'
const transferAmount = '1'
@@ -41,9 +41,6 @@ const nftContract = new ethers.Contract(nftContractAddress, abi_nft_pc2, provide
const owner1Signer = signers[0]
const owner2Signer = signers[1]
-const ethAdapterOwner1 = createEthersAdapter(owner1Signer)
-const ethAdapterOwner2 = createEthersAdapter(owner2Signer)
-
function visit(url) {
cy.visit(url)
}
@@ -51,12 +48,14 @@ function visit(url) {
// TODO: Relay only allows 5 txs per hour.
describe('Send funds with relay happy path tests', { defaultCommandTimeout: 300000 }, () => {
before(async () => {
- safesData = await getSafes(CATEGORIES.funds)
- main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__cookies, ls.cookies.acceptedCookies)
- main.addToLocalStorage(
- constants.localStorageKeys.SAFE_v2__tokenlist_onboarding,
- ls.cookies.acceptedTokenListOnboarding,
- )
+ cy.clearLocalStorage().then(() => {
+ main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies)
+ main.addToLocalStorage(
+ constants.localStorageKeys.SAFE_v2__tokenlist_onboarding,
+ ls.cookies.acceptedTokenListOnboarding,
+ )
+ })
+ safesData = fundSafes
apiKit = new SafeApiKit({
chainId: BigInt(1),
txServiceUrl: constants.stagingTxServiceUrl,
@@ -65,8 +64,8 @@ describe('Send funds with relay happy path tests', { defaultCommandTimeout: 3000
outgoingSafeAddress = safesData.SEP_FUNDS_SAFE_8.substring(4)
const safeConfigurations = [
- { ethAdapter: ethAdapterOwner1, safeAddress: outgoingSafeAddress },
- { ethAdapter: ethAdapterOwner2, safeAddress: outgoingSafeAddress },
+ { signer: privateKeys[0], safeAddress: outgoingSafeAddress, provider },
+ { signer: privateKeys[1], safeAddress: outgoingSafeAddress, provider },
]
safes = await createSafes(safeConfigurations)
@@ -107,6 +106,8 @@ describe('Send funds with relay happy path tests', { defaultCommandTimeout: 3000
})
await tx.wait()
main.verifyNonceChange(network_pref + originatingSafe, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
})
})
@@ -165,6 +166,8 @@ describe('Send funds with relay happy path tests', { defaultCommandTimeout: 3000
const safeTx = await apiKit.getTransaction(safeTxHashofExistingTx)
await protocolKitOwner2_S3.executeTransaction(safeTx)
main.verifyNonceChange(network_pref + targetSafe, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
})
})
@@ -205,6 +208,8 @@ describe('Send funds with relay happy path tests', { defaultCommandTimeout: 3000
await tx.wait()
main.verifyNonceChange(network_pref + originatingSafe, currentNonce + 1)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
})
})
diff --git a/cypress/e2e/happypath/tx_history_filter_hp_2.cy.js b/cypress/e2e/happypath/tx_history_filter_hp_2.cy.js
index 98e0f5d98..39c322cce 100644
--- a/cypress/e2e/happypath/tx_history_filter_hp_2.cy.js
+++ b/cypress/e2e/happypath/tx_history_filter_hp_2.cy.js
@@ -2,18 +2,24 @@ import * as constants from '../../support/constants.js'
import * as main from '../pages/main.page.js'
import * as createTx from '../pages/create_tx.pages.js'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as ls from '../../support/localstorage_data.js'
let staticSafes = []
describe('Tx history happy path tests 2', () => {
before(async () => {
+ cy.clearLocalStorage().then(() => {
+ main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies)
+ main.addToLocalStorage(
+ constants.localStorageKeys.SAFE_v2__tokenlist_onboarding,
+ ls.cookies.acceptedTokenListOnboarding,
+ )
+ })
staticSafes = await getSafes(CATEGORIES.static)
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_8)
- main.acceptCookies()
})
it('Verify a user can filter outgoing transactions by module', () => {
@@ -25,6 +31,6 @@ describe('Tx history happy path tests 2', () => {
createTx.fillFilterForm({ address: moduleAddress })
createTx.clickOnApplyBtn()
createTx.verifyNumberOfTransactions(1)
- createTx.checkTxItemDate(0, uiDate)
+ createTx.checkTxItemDate(1, uiDate)
})
})
diff --git a/cypress/e2e/pages/assets.pages.js b/cypress/e2e/pages/assets.pages.js
index b92af57eb..a19f57b9b 100644
--- a/cypress/e2e/pages/assets.pages.js
+++ b/cypress/e2e/pages/assets.pages.js
@@ -241,6 +241,8 @@ export function checkHiddenTokenBtnCounter(value) {
}
export function verifyEachRowHasCheckbox(state) {
+ const tokens = [currencyTestTokenB, currencyTestTokenA]
+ main.verifyTextVisibility(tokens)
cy.get(tokenListTable).within(() => {
cy.get('tbody').within(() => {
cy.get('tr').each(($row) => {
diff --git a/cypress/e2e/pages/batches.pages.js b/cypress/e2e/pages/batches.pages.js
index 85e98e635..c06fc5d91 100644
--- a/cypress/e2e/pages/batches.pages.js
+++ b/cypress/e2e/pages/batches.pages.js
@@ -3,12 +3,13 @@ import * as constants from '../../support/constants'
const tokenSelectorText = 'G(รถ|oe)rli Ether'
const noLaterString = 'No, later'
const yesExecuteString = 'Yes, execute'
-const newTransactionTitle = 'New transaction'
+export const newTransactionBtnStr = 'New transaction'
const sendTokensButn = 'Send tokens'
const nextBtn = 'Next'
const executeBtn = 'Execute'
-const addToBatchBtn = 'Add to batch'
+export const addToBatchBtn = 'Add to batch'
const confirmBatchBtn = 'Confirm batch'
+export const batchedTxs = 'Batched transactions'
export const closeModalBtnBtn = '[data-testid="CloseIcon"]'
export const deleteTransactionbtn = '[title="Delete transaction"]'
@@ -61,7 +62,7 @@ function executeTransaction() {
}
function addToBatchButton() {
- cy.contains(addToBatchBtn).should('be.visible').and('not.be.disabled').click()
+ cy.get('button').contains(addToBatchBtn).click()
}
export function openBatchtransactionsModal() {
@@ -106,3 +107,15 @@ export function verifyTransactionAdded() {
export function verifyBatchIconCount(count) {
cy.get(batchTxCounter).contains(count)
}
+
+export function verifyNewTxButtonStatus(param) {
+ cy.get('button').contains(newTransactionBtnStr).should(param)
+}
+
+export function isTxExpanded(index, option) {
+ cy.contains(batchedTxs)
+ .parent()
+ .within(() => {
+ cy.get('li').eq(index).find(`div[aria-expanded="${option}"]`)
+ })
+}
diff --git a/cypress/e2e/pages/create_tx.pages.js b/cypress/e2e/pages/create_tx.pages.js
index 221959716..315211344 100644
--- a/cypress/e2e/pages/create_tx.pages.js
+++ b/cypress/e2e/pages/create_tx.pages.js
@@ -1,23 +1,21 @@
import * as constants from '../../support/constants'
import * as main from '../pages/main.page'
import * as wallet from '../pages/create_wallet.pages'
+import * as modal from '../pages/modals.page'
export const delegateCallWarning = '[data-testid="delegate-call-warning"]'
export const policyChangeWarning = '[data-testid="threshold-warning"]'
const newTransactionBtnStr = 'New transaction'
const recepientInput = 'input[name="recipient"]'
-const sendTokensBtnStr = 'Send tokens'
const tokenAddressInput = 'input[name="tokenAddress"]'
const amountInput = 'input[name="amount"]'
const nonceInput = 'input[name="nonce"]'
-const nonceTxValue = '[data-testid="nonce"]'
const gasLimitInput = '[name="gasLimit"]'
const rotateLeftIcon = '[data-testid="RotateLeftIcon"]'
export const transactionItem = '[data-testid="transaction-item"]'
export const connectedWalletExecMethod = '[data-testid="connected-wallet-execution-method"]'
const addToBatchBtn = '[data-track="batching: Add to batch"]'
const accordionDetails = '[data-testid="accordion-details"]'
-const accordionMessageDetails = '[data-testid="accordion-msg-details"]'
const copyIcon = '[data-testid="copy-btn-icon"]'
const transactionSideList = '[data-testid="transaction-actions-list"]'
const confirmationVisibilityBtn = '[data-testid="confirmation-visibility-btn"]'
@@ -53,7 +51,7 @@ const transactionsPerHrStr = 'free transactions left this hour'
const maxAmountBtnStr = 'Max'
const nextBtnStr = 'Next'
-const nativeTokenTransferStr = 'Native token transfer'
+const nativeTokenTransferStr = 'ETH'
const yesStr = 'Yes, '
const estimatedFeeStr = 'Estimated fee'
const executeStr = 'Execute'
@@ -61,11 +59,24 @@ const editBtnStr = 'Edit'
const executionParamsStr = 'Execution parameters'
const noLaterStr = 'No, later'
const signBtnStr = 'Sign'
+const confirmBtnStr = 'Confirm'
const expandAllBtnStr = 'Expand all'
const collapseAllBtnStr = 'Collapse all'
export const messageNestedStr = `"nestedString": "Test message 3 off-chain"`
const noTxFoundStr = (type) => `0 ${type} transactions found`
const deleteFromQueueStr = 'Delete from the queue'
+const bulkExecuteBtn = (tx) => `Bulk execute ${tx} transactions`
+const bulkConfirmationText = (tx) =>
+ `This transaction batches a total of ${tx} transactions from your queue into a single Ethereum transaction`
+
+const disabledBultExecuteBtnTooltip =
+ 'Batch execution is only available for transactions that have been fully signed and are strictly sequential in Safe Account nonce'
+const enabledBulkExecuteBtnTooltip = 'All highlighted transactions will be included in the batch execution'
+
+const bulkExecuteBtnStr = 'Bulk execute'
+
+const batchModalTitle = 'Batch'
+const bulkTxStr = 'Bulk transactions'
export const filterTypes = {
incoming: 'Incoming',
@@ -77,6 +88,15 @@ function clickOnRejectBtn() {
cy.get(rejectTxBtn).click()
}
+export function verifyBulkExecuteBtnIsEnabled(txs) {
+ return cy.get('button').contains(bulkExecuteBtn(txs)).should('be.enabled')
+}
+
+export function verifyEnabledBulkExecuteBtnTooltip() {
+ cy.get('button').contains(bulkExecuteBtnStr).trigger('mouseover', { force: true })
+ cy.contains(enabledBulkExecuteBtnTooltip).should('exist')
+}
+
export function deleteTx() {
clickOnRejectBtn()
cy.get(wallet.choiceBtn).contains(deleteFromQueueStr).click()
@@ -274,6 +294,10 @@ export function expandAllActions(actions) {
main.checkTextsExistWithinElement(accordionDetails, actions)
}
+export function clickOnExpandAllActionsBtn() {
+ cy.get(expandAllBtn).click()
+}
+
export function collapseAllActions(data) {
cy.get(collapseAllBtn).click()
data.forEach((action) => {
@@ -461,7 +485,6 @@ export function openExecutionParamsModal() {
export function verifyAndSubmitExecutionParams() {
cy.contains(executionParamsStr).parents('form').as('Paramsform')
-
const arrayNames = ['Wallet nonce', 'Max priority fee (Gwei)', 'Max fee (Gwei)', 'Gas limit']
arrayNames.forEach((element) => {
cy.get('@Paramsform').find('label').contains(`${element}`).next().find('input').should('not.be.disabled')
@@ -482,6 +505,10 @@ export function clickOnSignTransactionBtn() {
cy.get('button').contains(signBtnStr).click()
}
+export function clickOnConfirmTransactionBtn() {
+ cy.get('button').contains(confirmBtnStr).click()
+}
+
export function waitForProposeRequest() {
cy.intercept('POST', constants.proposeEndpoint).as('ProposeTx')
cy.wait('@ProposeTx')
@@ -540,3 +567,35 @@ export function verifyTxDestinationAddress(receivedAddress) {
export function verifyReplacedSigner(newSignerName) {
cy.get(replacementNewSigner).should('exist').contains(newSignerName)
}
+
+function verifyBulkActions(actions) {
+ actions.forEach((action) => {
+ cy.contains(action).should('exist')
+ })
+}
+
+export function verifyBulkConfirmationScreen(tx, actions) {
+ cy.contains(bulkConfirmationText(tx))
+ verifyBulkActions(actions)
+ cy.get(modal.modalHeader).within(() => {
+ cy.contains(batchModalTitle).should('exist')
+ cy.get('svg').should('exist')
+ })
+}
+
+export function verifyBulkTxHistoryBlock(tx, actions) {
+ cy.contains(bulkTxStr)
+ .parent('div')
+ .parent()
+ .eq(0)
+ .within(() => {
+ cy.contains(tx)
+ verifyBulkActions(actions)
+ })
+}
+
+export function verifyBulkExecuteBtnIsDisabled() {
+ cy.get('button').contains(bulkExecuteBtnStr).should('be.disabled')
+ cy.get('button').contains(bulkExecuteBtnStr).trigger('mouseover', { force: true })
+ cy.contains(disabledBultExecuteBtnTooltip).should('exist')
+}
diff --git a/cypress/e2e/pages/create_wallet.pages.js b/cypress/e2e/pages/create_wallet.pages.js
index 813ba6d61..1c79e430c 100644
--- a/cypress/e2e/pages/create_wallet.pages.js
+++ b/cypress/e2e/pages/create_wallet.pages.js
@@ -32,7 +32,7 @@ export const choiceBtn = '[data-testid="choice-btn"]'
const addFundsBtn = '[data-testid="add-funds-btn"]'
const createTxBtn = '[data-testid="create-tx-btn"]'
const qrCodeSwitch = '[data-testid="qr-code-switch"]'
-export const activateAccountBtn = '[data-testid="activate-account-btn"]'
+export const activateAccountBtn = '[data-testid="activate-account-btn-cf"]'
const notificationsSwitch = '[data-testid="notifications-switch"]'
export const addFundsSection = '[data-testid="add-funds-section"]'
export const noTokensAlert = '[data-testid="no-tokens-alert"]'
@@ -49,8 +49,14 @@ const initialSteps = '0 of 2 steps completed'
export const addSignerStr = 'Add signer'
export const accountRecoveryStr = 'Account recovery'
export const sendTokensStr = 'Send tokens'
+const noWalletConnectedMsg = 'No wallet connected'
+export const deployWalletStr = 'about to deploy this Safe Account'
const connectWalletBtn = '[data-testid="connect-wallet-btn"]'
+
+export function waitForConnectionMsgDisappear() {
+ cy.contains(noWalletConnectedMsg).should('not.exist')
+}
export function checkNotificationsSwitchIs(status) {
cy.get(notificationsSwitch).find('input').should(`be.${status}`)
}
@@ -175,7 +181,7 @@ export function selectNetwork(network) {
cy.get(expandMoreIcon).parents('div').eq(1).click()
cy.wait(1000)
let regex = new RegExp(`^${network}$`)
- cy.get('li').contains(regex).click()
+ cy.get('li').parents('ul').contains(regex).click()
}
export function clickOnNextBtn() {
diff --git a/cypress/e2e/pages/dashboard.pages.js b/cypress/e2e/pages/dashboard.pages.js
index 746106e15..ce1675e7c 100644
--- a/cypress/e2e/pages/dashboard.pages.js
+++ b/cypress/e2e/pages/dashboard.pages.js
@@ -4,20 +4,16 @@ import * as main from './main.page.js'
import * as createtx from './create_tx.pages.js'
import staticSafes from '../../fixtures/safes/static.json'
-const connectAndTransactStr = 'Connect & transact'
const transactionQueueStr = 'Pending transactions'
const noTransactionStr = 'This Safe has no queued transactions'
const overviewStr = 'Total asset value'
const sendStr = 'Send'
const receiveStr = 'Receive'
const viewAllStr = 'View all'
-const transactionBuilderStr = 'Use Transaction Builder'
const safeAppStr = 'Safe Apps'
const exploreSafeApps = 'Explore Safe Apps'
export const copiedAppUrl = 'share/safe-app?appUrl'
-const txBuilder = 'a[href*="tx-builder"]'
-const safeSpecificLink = 'a[href*="&appUrl=http"]'
const copyShareBtn = '[data-testid="copy-btn-icon"]'
const exploreAppsBtn = '[data-testid="explore-apps-btn"]'
const viewAllLink = '[data-testid="view-all-link"][href^="/transactions/queue"]'
@@ -108,10 +104,6 @@ export function verifyShareBtnWorks(index, data) {
)
}
-export function verifyConnectTransactStrIsVisible() {
- cy.contains(connectAndTransactStr).should('be.visible')
-}
-
export function verifyOverviewWidgetData() {
// Alias for the Overview section
cy.contains('div', overviewStr).parents('section').as('overviewSection')
@@ -144,21 +136,6 @@ export function verifyTxQueueWidget() {
})
}
-export function verifyFeaturedAppsSection() {
- // Alias for the featured Safe Apps section
- cy.contains('h2', connectAndTransactStr).parents('section').as('featuredSafeAppsSection')
-
- // Tx Builder app
- cy.get('@featuredSafeAppsSection').within(() => {
- // Transaction Builder
- cy.contains(transactionBuilderStr)
- cy.get(txBuilder).should('exist')
-
- // Featured apps have a Safe-specific link
- cy.get(safeSpecificLink).should('have.length', 1)
- })
-}
-
export function verifySafeAppsSection() {
cy.contains('h2', safeAppStr).parents('section').as('safeAppsSection')
cy.get('@safeAppsSection').contains(exploreSafeApps)
diff --git a/cypress/e2e/pages/main.page.js b/cypress/e2e/pages/main.page.js
index 5643720ef..0288673d5 100644
--- a/cypress/e2e/pages/main.page.js
+++ b/cypress/e2e/pages/main.page.js
@@ -19,7 +19,7 @@ export function clickOnSideMenuItem(item) {
export function waitForHistoryCallToComplete() {
cy.intercept('GET', constants.transactionHistoryEndpoint).as('History')
- cy.wait('@History')
+ cy.wait('@History', { timeout: 20000 })
}
export const fetchSafeData = (safeAddress) => {
@@ -333,3 +333,12 @@ export function getIframeBody(iframe) {
export const checkButtonByTextExists = (buttonText) => {
cy.get('button').contains(buttonText).should('exist')
}
+
+export function getAddedSafeAddressFromLocalStorage(chainId, index) {
+ return cy.window().then((win) => {
+ const addedSafes = win.localStorage.getItem(constants.localStorageKeys.SAFE_v2__addedSafes)
+ const addedSafesObj = JSON.parse(addedSafes)
+ const safeAddress = Object.keys(addedSafesObj[chainId])[index]
+ return safeAddress
+ })
+}
diff --git a/cypress/e2e/pages/modals.page.js b/cypress/e2e/pages/modals.page.js
index d2b99a68e..6a4fe0b8e 100644
--- a/cypress/e2e/pages/modals.page.js
+++ b/cypress/e2e/pages/modals.page.js
@@ -1,5 +1,6 @@
export const modalTitle = '[data-testid="modal-title"]'
export const modal = '[data-testid="modal-view"]'
+export const modalHeader = '[data-testid="modal-header"]'
export const modalTitiles = {
editEntry: 'Edit entry',
diff --git a/cypress/e2e/pages/modules.page.js b/cypress/e2e/pages/modules.page.js
new file mode 100644
index 000000000..d886f5987
--- /dev/null
+++ b/cypress/e2e/pages/modules.page.js
@@ -0,0 +1 @@
+export const moduleRemoveIcon = '[data-testid="module-remove-btn"]'
diff --git a/cypress/e2e/pages/navigation.page.js b/cypress/e2e/pages/navigation.page.js
index ff672497e..18971fd50 100644
--- a/cypress/e2e/pages/navigation.page.js
+++ b/cypress/e2e/pages/navigation.page.js
@@ -3,7 +3,7 @@ export const setupSection = '[data-testid="setup-section"]'
export const modalBackBtn = '[data-testid="modal-back-btn"]'
export const newTxBtn = '[data-testid="new-tx-btn"]'
const modalCloseIcon = '[data-testid="CloseIcon"]'
-const expandMoreIcon = 'svg[data-testid="ExpandMoreIcon"]'
+export const expandMoreIcon = 'svg[data-testid="ExpandMoreIcon"]'
const sentinelStart = 'div[data-testid="sentinelStart"]'
const disconnectBtnStr = 'Disconnect'
@@ -16,8 +16,8 @@ export function clickOnSideNavigation(option) {
cy.get(option).should('exist').click()
}
-export function clickOnModalCloseBtn() {
- cy.get(modalCloseIcon).eq(0).trigger('click')
+export function clickOnModalCloseBtn(index) {
+ cy.get(modalCloseIcon).eq(index).trigger('click')
}
export function clickOnNewTxBtn() {
diff --git a/cypress/e2e/pages/owners.pages.js b/cypress/e2e/pages/owners.pages.js
index afde4caa6..039cecaee 100644
--- a/cypress/e2e/pages/owners.pages.js
+++ b/cypress/e2e/pages/owners.pages.js
@@ -125,8 +125,8 @@ export function getAddressToBeRemoved() {
return removedAddress
}
-export function openReplaceOwnerWindow() {
- cy.get(replaceOwnerBtn).click({ force: true })
+export function openReplaceOwnerWindow(index) {
+ cy.get(replaceOwnerBtn).eq(index).click({ force: true })
cy.get(newOwnerName).should('be.visible')
cy.get(newOwnerAddress).should('be.visible')
}
@@ -191,7 +191,7 @@ export function verifyNonceInputValue(value) {
}
export function verifyErrorMsgInvalidAddress(errorMsg) {
- cy.get('label').contains(errorMsg).should('be.visible')
+ cy.get('label').contains(errorMsg).should('exist')
}
export function verifyValidWalletName(errorMsg) {
diff --git a/cypress/e2e/pages/recovery.pages.js b/cypress/e2e/pages/recovery.pages.js
index 60537a806..9c84ea3fc 100644
--- a/cypress/e2e/pages/recovery.pages.js
+++ b/cypress/e2e/pages/recovery.pages.js
@@ -4,23 +4,51 @@ import * as safe from '../pages/load_safe.pages'
import * as tx from '../pages/transactions.page'
import { tableContainer } from '../pages/address_book.page'
import { txDate } from '../pages/create_tx.pages'
+import { modalHeader } from '../pages/modals.page'
-const setupRecoveryBtn = '[data-testid="setup-recovery-btn"]'
-const setupRecoveryModalBtn = '[data-testid="setup-btn"]'
+export const setupRecoveryBtn = '[data-testid="setup-recovery-btn"]'
+export const setupRecoveryModalBtn = '[data-testid="setup-btn"]'
const recoveryNextBtn = '[data-testid="next-btn"]'
const warningSection = '[data-testid="warning-section"]'
const termsCheckbox = 'input[type="checkbox"]'
-const removeRecovererBtn = '[data-testid="remove-recoverer-btn"]'
+export const removeRecovererBtn = '[data-testid="remove-recoverer-btn"]'
+export const editRecovererBtn = '[data-testid="edit-recoverer-btn"]'
const removeRecovererSection = '[data-testid="remove-recoverer-section"]'
const startRecoveryBtn = '[data-testid="start-recovery-btn"]'
const recoveryDelaySelect = '[data-testid="recovery-delay-select"]'
+const recoveryExpirySelect = '[data-testid="recovery-expiry-select"]'
const postponeRecoveryBtn = '[data-testid="postpone-recovery-btn"]'
const goToQueueBtn = '[data-testid="queue-btn"]'
const executeBtn = '[data-testid="execute-btn"]'
const cancelRecoveryBtn = '[data-testid="cancel-recovery-btn"]'
const cancelProposalBtn = '[data-testid="cancel-proposal-btn"]'
const executeFormBtn = '[data-testid="execute-form-btn"]'
+const advancedBtn = '[data-testid="advanced-btn"]'
+const recoveryProposalModal = '[data-testid="recovery-proposal"]'
+const recoveryProposalHorizontal = '[data-testid="recovery-proposal-hr"]'
+export const recoveryOptions = {
+ fiveMin: '5 minutes',
+ oneHr: '1 hour',
+ fiveSixDays: '56 days',
+ never: 'never',
+}
+export function clickOnEditRecoverer() {
+ cy.get(editRecovererBtn).click()
+}
+export function verifyRecovererSettings(data) {
+ main.checkTextsExistWithinElement(tableContainer, data)
+}
+
+export function verifyRecovererConfirmationData(data) {
+ data.forEach((item) => {
+ cy.get(modalHeader).next('div').contains(item)
+ })
+}
+
+export function verifyRecoveryTableDisplayed() {
+ cy.get(tableContainer).should('be.visible')
+}
export function clickOnExecuteRecoveryCancelBtn() {
cy.get(executeFormBtn).click()
}
@@ -46,6 +74,27 @@ export function setRecoveryDelay(option) {
cy.contains(option).click()
}
+export function verifyRecoveryDelayOptions(options) {
+ cy.get(recoveryDelaySelect).click()
+ options.forEach((item) => {
+ cy.contains(item)
+ })
+}
+
+export function setRecoveryExpiry(option) {
+ cy.get(advancedBtn).click()
+ cy.get(recoveryExpirySelect).click()
+ cy.contains(option).click()
+}
+
+export function verifyRecoveryExpiryOptions(options) {
+ cy.get(advancedBtn).click()
+ cy.get(recoveryExpirySelect).click()
+ options.forEach((item) => {
+ cy.contains(item)
+ })
+}
+
export function getSetupRecoveryBtn() {
return cy.get(setupRecoveryBtn)
}
@@ -116,3 +165,21 @@ export function postponeRecovery() {
cy.get(postponeRecoveryBtn).should('not.exist')
})
}
+
+export function clickOnRecoverLaterBtn() {
+ cy.get(postponeRecoveryBtn).click()
+ cy.get(postponeRecoveryBtn).should('not.exist')
+}
+
+export function verifyNonceState(state) {
+ if (state === constants.elementExistanceStates.exist) {
+ cy.get(nonceFld).should(constants.elementExistanceStates.exist)
+ }
+ cy.get(nonceFld).should(constants.elementExistanceStates.not_exist)
+}
+
+export function verifyRecoveryProposalModalState(option, horizontal = false) {
+ let modal = recoveryProposalModal
+ if (horizontal) modal = recoveryProposalHorizontal
+ cy.get(modal).should(option)
+}
diff --git a/cypress/e2e/pages/safeapps.pages.js b/cypress/e2e/pages/safeapps.pages.js
index 1810656cb..a673a864c 100644
--- a/cypress/e2e/pages/safeapps.pages.js
+++ b/cypress/e2e/pages/safeapps.pages.js
@@ -27,7 +27,7 @@ const acceptBtnStr = /accept/i
const clearAllBtnStr = /clear all/i
const allowAllPermissions = /allow all/i
export const enterAddressStr = /enter address or ens name/i
-export const addTransactionStr = /add transaction/i
+export const addTransactionStr = /add new transaction/i
export const createBatchStr = /create batch/i
export const sendBatchStr = /send batch/i
export const transactionDetailsStr = /transaction details/i
@@ -218,12 +218,10 @@ export function verifyAppDescription(descr) {
export function clickOnOpenSafeAppBtn() {
cy.get(openSafeAppBtn).click()
- cy.wait(2000)
}
export function verifyDisclaimerIsDisplayed() {
verifyDisclaimerIsVisible()
- cy.wait(500)
}
function verifyDisclaimerIsVisible() {
@@ -249,20 +247,17 @@ export function verifyMicrofoneCheckBoxExists() {
return cy.findByRole('checkbox', { name: microfoneCheckBoxStr }).should('exist')
}
-export function storeAndVerifyPermissions() {
+export function verifyInfoModalAcceptance() {
cy.waitForSelector(() => {
return cy
.findByRole('button', { name: continueBtnStr })
.click()
- .wait(500)
+ .wait(2000)
.should(() => {
- const storedBrowserPermissions = JSON.parse(localStorage.getItem(constants.BROWSER_PERMISSIONS_KEY))
- const browserPermissions = Object.values(storedBrowserPermissions)[0][0]
- const storedInfoModal = JSON.parse(localStorage.getItem(constants.INFO_MODAL_KEY))
-
- expect(browserPermissions.feature).to.eq('camera')
- expect(browserPermissions.status).to.eq('granted')
- expect(storedInfoModal['11155111'].consentsAccepted).to.eq(true)
+ const storedInfoModal = JSON.parse(
+ localStorage.getItem(constants.localStorageKeys.SAFE_v2__SafeApps__infoModal),
+ )
+ expect(storedInfoModal[constants.networkKeys.sepolia].consentsAccepted).to.eq(true)
})
})
}
diff --git a/cypress/e2e/pages/sidebar.pages.js b/cypress/e2e/pages/sidebar.pages.js
index 854394159..4729e6f97 100644
--- a/cypress/e2e/pages/sidebar.pages.js
+++ b/cypress/e2e/pages/sidebar.pages.js
@@ -57,8 +57,13 @@ const emptyWatchListStr = 'Watch any Safe Account to keep an eye on its activity
const emptySafeListStr = "You don't have any Safe Accounts yet"
const myAccountsStr = 'My accounts'
const confirmTxStr = (number) => `${number} to confirm`
+const pedningTxStr = (n) => `${n} pending transaction`
export const confirmGenStr = 'to confirm'
+export function verifyNumberOfPendingTxTag(tx) {
+ cy.contains(pedningTxStr(tx))
+}
+
export function getImportBtn() {
return cy.get(importBtn).scrollIntoView().should('be.visible')
}
diff --git a/cypress/e2e/pages/spending_limits.pages.js b/cypress/e2e/pages/spending_limits.pages.js
index d6178686b..a7b844282 100644
--- a/cypress/e2e/pages/spending_limits.pages.js
+++ b/cypress/e2e/pages/spending_limits.pages.js
@@ -33,6 +33,9 @@ const oldTokenAmount = '[data-testid="old-token-amount"]'
const oldResetTime = '[data-testid="old-reset-time"]'
const slimitReplacementWarning = '[data-testid="limit-replacement-warning"]'
const addressItem = '[data-testid="address-item"]'
+const allActionsSection = '[data-testid="all-actions"]'
+const actionItem = '[data-testid="action-item"]'
+const decodedTxSummary = '[data-testid="decoded-tx-summary"]'
const actionSectionItem = () => {
return cy.get('[data-testid="CodeIcon"]').parent()
@@ -48,7 +51,12 @@ export const timePeriodOptions = {
const getBeneficiaryInput = () => cy.get(beneficiarySection).find('input').should('be.enabled')
const automationOwner = ls.addressBookData.sepoliaAddress2[11155111]['0xC16Db0251654C0a72E91B190d81eAD367d2C6fED']
-const expectedSpendOptions = ['0 of 0.17 ETH', '0.00001 of 0.05 ETH', '0 of 0.01 ETH']
+export const actionNames = {
+ resetAllowance: 'resetAllowance',
+ setAllowance: 'setAllowance',
+}
+
+const expectedSpendOptions = ['0.02 of 0.17 ETH', '0.00001 of 0.05 ETH', '0 of 0.01 ETH']
const expectedResetOptions = new Array(3).fill('One-time')
const newTransactionStr = 'New transaction'
@@ -205,3 +213,25 @@ export function verifyCharErrorValidation() {
export function verifyNumberAmountEntered(amount) {
cy.get(tokenAmountFld).find('input').should('have.value', amount)
}
+
+export function verifyActionCount(count) {
+ main.verifyElementsCount(actionItem, count)
+}
+
+export function verifyActionNames(names) {
+ cy.get(allActionsSection)
+ .parent()
+ .within(() => {
+ names.forEach((item) => {
+ cy.contains(item)
+ })
+ })
+}
+
+export function verifyDecodedTxSummary(names) {
+ cy.get(decodedTxSummary).within(() => {
+ names.forEach((item) => {
+ cy.contains(item)
+ })
+ })
+}
diff --git a/cypress/e2e/pages/swaps.pages.js b/cypress/e2e/pages/swaps.pages.js
index efb734aca..75cbc3d5d 100644
--- a/cypress/e2e/pages/swaps.pages.js
+++ b/cypress/e2e/pages/swaps.pages.js
@@ -3,7 +3,7 @@ import * as main from '../pages/main.page.js'
import * as create_tx from '../pages/create_tx.pages.js'
export const inputCurrencyInput = '[id="input-currency-input"]'
-export const outputurrencyInput = '[id="output-currency-input"]'
+export const outputCurrencyInput = '[id="output-currency-input"]'
const tokenList = '[id="tokens-list"]'
export const swapBtn = '[id="swap-button"]'
const exceedFeesChkbox = 'input[id="fees-exceed-checkbox"]'
@@ -173,18 +173,38 @@ export function verifySelectedInputCurrancy(option) {
cy.get('span').contains(option).should('be.visible')
})
}
-export function selectInputCurrency(option) {
- cy.get(inputCurrencyInput).within(() => {
- cy.get('button').eq(0).trigger('mouseover').trigger('click')
+
+function selectCurrency(inputSelector, option) {
+ cy.get(inputSelector).within(() => {
+ cy.get('button')
+ .eq(0)
+ .invoke('text')
+ .then(($value) => {
+ cy.log('*** Currency value ' + $value)
+ if (!$value.includes(option)) {
+ cy.log('*** Currency value is different from specified')
+ cy.get('button').eq(0).trigger('mouseover').trigger('click')
+ cy.wrap(true).as('isAction')
+ } else {
+ cy.wrap(false).as('isAction')
+ }
+ })
})
- cy.get(tokenList).find('span').contains(option).click()
+
+ cy.get('@isAction').then((isAction) => {
+ if (isAction) {
+ cy.log('*** Clicking on token option')
+ cy.get(tokenList).find('span').contains(option).click()
+ }
+ })
+}
+
+export function selectInputCurrency(option) {
+ selectCurrency(inputCurrencyInput, option)
}
export function selectOutputCurrency(option) {
- cy.get(outputurrencyInput).within(() => {
- cy.get('button').trigger('mouseover').trigger('click')
- })
- cy.get(tokenList).find('span').contains(option).click()
+ selectCurrency(outputCurrencyInput, option)
}
export function setInputValue(value) {
@@ -194,7 +214,7 @@ export function setInputValue(value) {
}
export function setOutputValue(value) {
- cy.get(outputurrencyInput).within(() => {
+ cy.get(outputCurrencyInput).within(() => {
cy.get('input').type(value)
})
}
@@ -254,18 +274,19 @@ export function verifyOrderIDUrl() {
cy.get(create_tx.txRowTitle)
.contains(orderIdStr)
.parent()
+ .parent()
.within(() => {
cy.get(explorerBtn).should('have.attr', 'href').and('include', cowOrdersUrl)
})
}
export function verifyOrderDetails(limitPrice, expiry, slippage, interactWith, oderID, widgetFee) {
- cy.get(limitPriceFld).contains(limitPrice)
- cy.get(expiryFld).contains(expiry)
- cy.get(slippageFld).contains(slippage)
- cy.get(orderIDFld).contains(oderID)
- cy.get(widgetFeeFld).contains(widgetFee)
- cy.get(interactWithFld).contains(interactWith)
+ cy.contains(limitPrice)
+ cy.contains(expiry)
+ cy.contains(slippage)
+ cy.contains(oderID)
+ cy.contains(widgetFee)
+ cy.contains(interactWith)
}
export function verifyRecipientAlertIsDisplayed() {
diff --git a/cypress/e2e/pages/tables.page.js b/cypress/e2e/pages/tables.page.js
new file mode 100644
index 000000000..de71b5632
--- /dev/null
+++ b/cypress/e2e/pages/tables.page.js
@@ -0,0 +1 @@
+export const dataRow = '[data-testid="tx-data-row"]'
diff --git a/cypress/e2e/prodhealthcheck/add_owner.cy.js b/cypress/e2e/prodhealthcheck/add_owner.cy.js
new file mode 100644
index 000000000..539ecc8ce
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/add_owner.cy.js
@@ -0,0 +1,31 @@
+import * as constants from '../../support/constants'
+import * as main from '../../e2e/pages/main.page'
+import * as owner from '../pages/owners.pages'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+
+let staticSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+describe('[PROD] Add Owners tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4)
+ cy.contains(owner.safeAccountNonceStr, { timeout: 10000 })
+ })
+
+ // TODO: Added to prod
+ it('Verify add owner button is disabled for disconnected user', () => {
+ owner.verifyAddOwnerBtnIsDisabled()
+ })
+
+ // TODO: Added to prod
+ it.skip('Verify the Add New Owner Form can be opened', () => {
+ wallet.connectSigner(signer)
+ owner.openAddOwnerWindow()
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/create_tx.cy.js b/cypress/e2e/prodhealthcheck/create_tx.cy.js
new file mode 100644
index 000000000..9f3df5161
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/create_tx.cy.js
@@ -0,0 +1,46 @@
+import * as constants from '../../support/constants'
+import * as main from '../../e2e/pages/main.page'
+import * as createtx from '../../e2e/pages/create_tx.pages'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+
+let staticSafes = []
+
+const sendValue = 0.00002
+
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+function happyPathToStepTwo() {
+ createtx.typeRecipientAddress(constants.EOA)
+ createtx.clickOnTokenselectorAndSelectSepoliaEth()
+ createtx.setSendValue(sendValue)
+ createtx.clickOnNextBtn()
+}
+
+describe.skip('[PROD] Create transactions tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_6)
+ wallet.connectSigner(signer)
+ createtx.clickOnNewtransactionBtn()
+ createtx.clickOnSendTokensBtn()
+ })
+
+ // TODO: Added to prod
+ it('Verify submitting a tx and that clicking on notification shows the transaction in queue', () => {
+ happyPathToStepTwo()
+ createtx.verifySubmitBtnIsEnabled()
+ createtx.changeNonce(14)
+ cy.wait(1000)
+ createtx.clickOnSignTransactionBtn()
+ createtx.waitForProposeRequest()
+ createtx.clickViewTransaction()
+ createtx.verifySingleTxPage()
+ createtx.verifyQueueLabel()
+ createtx.verifyTransactionSummary(sendValue)
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/load_safe.cy.js b/cypress/e2e/prodhealthcheck/load_safe.cy.js
new file mode 100644
index 000000000..8d1e630c3
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/load_safe.cy.js
@@ -0,0 +1,43 @@
+import 'cypress-file-upload'
+import * as constants from '../../support/constants'
+import * as main from '../pages/main.page'
+import * as safe from '../pages/load_safe.pages'
+import * as createwallet from '../pages/create_wallet.pages'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+
+let staticSafes = []
+
+const testSafeName = 'Test safe name'
+const testOwnerName = 'Test Owner Name'
+// TODO
+const SAFE_ENS_NAME = 'test20.eth'
+const SAFE_ENS_NAME_TRANSLATED = constants.EOA
+
+const EOA_ADDRESS = constants.EOA
+
+const INVALID_ADDRESS_ERROR_MSG = 'Address given is not a valid Safe address'
+
+// TODO
+const OWNER_ENS_DEFAULT_NAME = 'test20.eth'
+const OWNER_ADDRESS = constants.EOA
+
+describe('[PROD] Load Safe tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.loadNewSafeSepoliaUrl)
+ cy.wait(2000)
+ })
+
+ // TODO: Added to prod
+ it('Verify Safe and owner names are displayed in the Review step', () => {
+ safe.inputNameAndAddress(testSafeName, staticSafes.SEP_STATIC_SAFE_4)
+ safe.clickOnNextBtn()
+ createwallet.typeOwnerName(testOwnerName, 0)
+ safe.clickOnNextBtn()
+ safe.verifyDataInReviewSection(testSafeName, testOwnerName)
+ safe.clickOnAddBtn()
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/messages_onchain.cy.js b/cypress/e2e/prodhealthcheck/messages_onchain.cy.js
new file mode 100644
index 000000000..5b4f0928c
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/messages_onchain.cy.js
@@ -0,0 +1,28 @@
+import * as constants from '../../support/constants.js'
+import * as main from '../pages/main.page.js'
+import * as createTx from '../pages/create_tx.pages.js'
+import * as msg_data from '../../fixtures/txmessages_data.json'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+
+let staticSafes = []
+
+const typeMessagesOnchain = msg_data.type.onChain
+
+describe('[PROD] Onchain Messages tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_10)
+ })
+
+ // TODO: Added to prod
+ it('Verify summary for signed on-chain message', () => {
+ createTx.verifySummaryByName(
+ typeMessagesOnchain.contractName,
+ [typeMessagesOnchain.success, typeMessagesOnchain.signMessage],
+ typeMessagesOnchain.altTmage,
+ )
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/nfts.cy.js b/cypress/e2e/prodhealthcheck/nfts.cy.js
new file mode 100644
index 000000000..dcc484f39
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/nfts.cy.js
@@ -0,0 +1,87 @@
+import * as constants from '../../support/constants'
+import * as main from '../pages/main.page'
+import * as nfts from '../pages/nfts.pages'
+import * as createTx from '../pages/create_tx.pages'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+
+const singleNFT = ['safeTransferFrom']
+const multipleNFT = ['multiSend']
+const multipleNFTAction = 'safeTransferFrom'
+const NFTSentName = 'GTT #22'
+
+let nftsSafes,
+ staticSafes = []
+
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+describe.skip('[PROD] NFTs tests', () => {
+ before(() => {
+ getSafes(CATEGORIES.nfts)
+ .then((nfts) => {
+ nftsSafes = nfts
+ return getSafes(CATEGORIES.static)
+ })
+ .then((statics) => {
+ staticSafes = statics
+ })
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.balanceNftsUrl + staticSafes.SEP_STATIC_SAFE_2)
+ wallet.connectSigner(signer)
+ nfts.waitForNftItems(2)
+ })
+
+ // TODO: Added to prod
+ // TODO: Add Sign action
+ it('Verify multipls NFTs can be selected and reviewed', () => {
+ nfts.verifyInitialNFTData()
+ nfts.selectNFTs(3)
+ nfts.deselectNFTs([2], 3)
+ nfts.sendNFT()
+ nfts.verifyNFTModalData()
+ nfts.typeRecipientAddress(staticSafes.SEP_STATIC_SAFE_1)
+ nfts.clikOnNextBtn()
+ nfts.verifyReviewModalData(2)
+ })
+
+ // TODO: Added to prod
+ it('Verify that when 2 NFTs are selected, actions and tx details are correct in Review step', () => {
+ nfts.verifyInitialNFTData()
+ nfts.selectNFTs(2)
+ nfts.sendNFT()
+ nfts.typeRecipientAddress(staticSafes.SEP_STATIC_SAFE_1)
+ nfts.clikOnNextBtn()
+ nfts.verifyTxDetails(multipleNFT)
+ nfts.verifyCountOfActions(2)
+ nfts.verifyActionName(0, multipleNFTAction)
+ nfts.verifyActionName(1, multipleNFTAction)
+ })
+
+ // TODO: Added to prod
+ it('Verify Send button is disabled for non-owner', () => {
+ cy.visit(constants.balanceNftsUrl + nftsSafes.SEP_NFT_SAFE_2)
+ nfts.verifyInitialNFTData()
+ nfts.selectNFTs(1)
+ nfts.verifySendNFTBtnDisabled()
+ })
+
+ // TODO: Added to prod
+ it('Verify Send NFT transaction has been created', () => {
+ cy.visit(constants.balanceNftsUrl + nftsSafes.SEP_NFT_SAFE_1)
+ nfts.verifyInitialNFTData()
+ nfts.selectNFTs(1)
+ nfts.sendNFT()
+ nfts.typeRecipientAddress(staticSafes.SEP_STATIC_SAFE_1)
+ createTx.changeNonce(2)
+ nfts.clikOnNextBtn()
+ createTx.clickOnSignTransactionBtn()
+ createTx.waitForProposeRequest()
+ createTx.clickViewTransaction()
+ createTx.verifySingleTxPage()
+ createTx.verifyQueueLabel()
+ createTx.verifyTransactionStrExists(NFTSentName)
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/recovery.cy.js b/cypress/e2e/prodhealthcheck/recovery.cy.js
new file mode 100644
index 000000000..e201a66a3
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/recovery.cy.js
@@ -0,0 +1,49 @@
+import * as constants from '../../support/constants.js'
+import * as main from '../pages/main.page.js'
+import * as recovery from '../pages/recovery.pages.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+
+let recoverySafes,
+ staticSafes = []
+
+describe('[PROD] Production recovery health check tests', { defaultCommandTimeout: 50000 }, () => {
+ before(() => {
+ getSafes(CATEGORIES.recovery)
+ .then((recoveries) => {
+ recoverySafes = recoveries
+ return getSafes(CATEGORIES.static)
+ })
+ .then((statics) => {
+ staticSafes = statics
+ })
+ })
+
+ it('Verify that the Security section contains Account recovery block on supported netwroks', () => {
+ const safes = [
+ staticSafes.ETH_STATIC_SAFE_15,
+ staticSafes.GNO_STATIC_SAFE_16,
+ staticSafes.MATIC_STATIC_SAFE_17,
+ staticSafes.SEP_STATIC_SAFE_13,
+ ]
+
+ safes.forEach((safe) => {
+ cy.visit(constants.prodbaseUrl + constants.securityUrl + safe)
+ recovery.getSetupRecoveryBtn()
+ })
+ })
+
+ it('Verify that the Security and Login section does not contain Account recovery block on unsupported networks', () => {
+ const safes = [
+ staticSafes.BNB_STATIC_SAFE_18,
+ staticSafes.AURORA_STATIC_SAFE_19,
+ staticSafes.AVAX_STATIC_SAFE_20,
+ staticSafes.LINEA_STATIC_SAFE_21,
+ staticSafes.ZKSYNC_STATIC_SAFE_22,
+ ]
+
+ safes.forEach((safe) => {
+ cy.visit(constants.prodbaseUrl + constants.securityUrl + safe)
+ main.verifyElementsCount(recovery.setupRecoveryBtn, 0)
+ })
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/remove_owner.cy.js b/cypress/e2e/prodhealthcheck/remove_owner.cy.js
new file mode 100644
index 000000000..755f320db
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/remove_owner.cy.js
@@ -0,0 +1,41 @@
+import * as constants from '../../support/constants'
+import * as main from '../../e2e/pages/main.page'
+import * as owner from '../pages/owners.pages'
+import * as createwallet from '../pages/create_wallet.pages'
+import * as createTx from '../pages/create_tx.pages.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+
+let staticSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+describe('[PROD] Remove Owners tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.setupUrl + staticSafes.SEP_STATIC_SAFE_13)
+ main.waitForHistoryCallToComplete()
+ cy.contains(owner.safeAccountNonceStr, { timeout: 10000 })
+ })
+
+ // TODO: Added to prod
+ it.skip('Verify owner deletion transaction has been created', () => {
+ wallet.connectSigner(signer)
+ owner.waitForConnectionStatus()
+ owner.openRemoveOwnerWindow(1)
+ cy.wait(3000)
+ createwallet.clickOnNextBtn()
+ //This method creates the @removedAddress alias
+ owner.getAddressToBeRemoved()
+ owner.verifyOwnerDeletionWindowDisplayed()
+ createTx.changeNonce(10)
+ createTx.clickOnSignTransactionBtn()
+ createTx.waitForProposeRequest()
+ createTx.clickViewTransaction()
+ createTx.clickOnTransactionItemByName('removeOwner')
+ createTx.verifyTxDestinationAddress('@removedAddress')
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/sidebar.cy.js b/cypress/e2e/prodhealthcheck/sidebar.cy.js
new file mode 100644
index 000000000..edb534286
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/sidebar.cy.js
@@ -0,0 +1,43 @@
+import * as constants from '../../support/constants'
+import * as main from '../pages/main.page'
+import * as sideBar from '../pages/sidebar.pages'
+import * as navigation from '../pages/navigation.page'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+
+let staticSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+describe('[PROD] Sidebar tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.homeUrl + staticSafes.SEP_STATIC_SAFE_9)
+ })
+
+ // TODO: Added to prod
+ it('Verify current safe details', () => {
+ sideBar.verifySafeHeaderDetails(sideBar.testSafeHeaderDetails)
+ })
+
+ // TODO: Added to prod
+ it.skip('Verify New transaction button enabled for owners', () => {
+ wallet.connectSigner(signer)
+ sideBar.verifyNewTxBtnStatus(constants.enabledStates.enabled)
+ })
+
+ // TODO: Added to prod
+ it.skip('Verify New transaction button enabled for beneficiaries who are non-owners', () => {
+ cy.visit(constants.prodbaseUrl + constants.homeUrl + staticSafes.SEP_STATIC_SAFE_11)
+ wallet.connectSigner(signer)
+ sideBar.verifyNewTxBtnStatus(constants.enabledStates.enabled)
+ })
+
+ // TODO: Added to prod
+ it('Verify New Transaction button disabled for non-owners', () => {
+ main.verifyElementsCount(navigation.newTxBtn, 0)
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/sidebar_3.cy.js b/cypress/e2e/prodhealthcheck/sidebar_3.cy.js
new file mode 100644
index 000000000..218d5f747
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/sidebar_3.cy.js
@@ -0,0 +1,66 @@
+import * as constants from '../../support/constants.js'
+import * as main from '../pages/main.page.js'
+import * as sideBar from '../pages/sidebar.pages.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+import * as navigation from '../pages/navigation.page.js'
+import * as owner from '../pages/owners.pages.js'
+
+let staticSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signer1 = walletCredentials.OWNER_1_PRIVATE_KEY
+const signer2 = walletCredentials.OWNER_3_PRIVATE_KEY
+
+describe.skip('[PROD] Sidebar tests 3', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ // TODO: Added to prod
+ it('Verify the "My accounts" counter at the top is counting all safes the user owns', () => {
+ cy.visit(constants.prodbaseUrl + constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_9)
+ cy.intercept('GET', constants.safeListEndpoint, {
+ 11155111: [sideBar.sideBarSafes.safe1, sideBar.sideBarSafes.safe2],
+ })
+ wallet.connectSigner(signer)
+ sideBar.openSidebar()
+ sideBar.checkMyAccountCounter(2)
+ })
+
+ // TODO: Added to prod
+ it('Verify pending signature is displayed in sidebar for unsigned tx', () => {
+ cy.visit(constants.prodbaseUrl + constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_7)
+ wallet.connectSigner(signer)
+ cy.intercept('GET', constants.safeListEndpoint, {
+ 11155111: [sideBar.sideBarSafesPendingActions.safe1],
+ })
+ sideBar.openSidebar()
+ sideBar.verifyTxToConfirmDoesNotExist()
+ cy.get('body').click()
+ owner.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ cy.intercept('GET', constants.safeListEndpoint, {
+ 11155111: [sideBar.sideBarSafesPendingActions.safe1],
+ })
+ wallet.connectSigner(signer2)
+ sideBar.openSidebar()
+ sideBar.verifyAddedSafesExist([sideBar.sideBarSafesPendingActions.safe1short])
+ sideBar.checkTxToConfirm(1)
+ })
+
+ // TODO: Added to prod
+ it('Verify balance exists in a tx in sidebar', () => {
+ cy.visit(constants.prodbaseUrl + constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_7)
+ wallet.connectSigner(signer)
+ owner.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer)
+ cy.intercept('GET', constants.safeListEndpoint, {
+ 11155111: [sideBar.sideBarSafesPendingActions.safe1],
+ })
+ sideBar.openSidebar()
+ sideBar.verifyTxToConfirmDoesNotExist()
+ sideBar.checkBalanceExists()
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/spending_limits.cy.js b/cypress/e2e/prodhealthcheck/spending_limits.cy.js
new file mode 100644
index 000000000..e1f663f6d
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/spending_limits.cy.js
@@ -0,0 +1,55 @@
+import * as constants from '../../support/constants'
+import * as main from '../pages/main.page'
+import * as spendinglimit from '../pages/spending_limits.pages'
+import * as navigation from '../pages/navigation.page'
+import * as tx from '../pages/create_tx.pages'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+
+let staticSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signerAddress = walletCredentials.OWNER_4_WALLET_ADDRESS
+
+const tokenAmount = 0.1
+const newTokenAmount = 0.001
+const spendingLimitBalance = '(0.15 ETH)'
+
+describe('[PROD] Spending limits tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.setupUrl + staticSafes.SEP_STATIC_SAFE_8)
+ cy.get(spendinglimit.spendingLimitsSection).should('be.visible')
+ })
+
+ // TODO: Added to prod
+ it.skip('Verify that the Review step shows beneficiary, amount allowed, reset time', () => {
+ //Assume that default reset time is set to One time
+ wallet.connectSigner(signer)
+ spendinglimit.clickOnNewSpendingLimitBtn()
+ spendinglimit.enterBeneficiaryAddress(staticSafes.SEP_STATIC_SAFE_6)
+ spendinglimit.enterSpendingLimitAmount(0.1)
+ spendinglimit.clickOnNextBtn()
+ spendinglimit.checkReviewData(
+ tokenAmount,
+ staticSafes.SEP_STATIC_SAFE_6,
+ spendinglimit.timePeriodOptions.oneTime.split(' ').join('-'),
+ )
+ })
+
+ // TODO: Added to prod
+ it('Verify values and trash icons are displayed in Beneficiary table', () => {
+ spendinglimit.verifyBeneficiaryTable()
+ })
+
+ // TODO: Added to prod
+ it.skip('Verify Spending limit option is available when selecting the corresponding token', () => {
+ wallet.connectSigner(signer)
+ navigation.clickOnNewTxBtn()
+ tx.clickOnSendTokensBtn()
+ spendinglimit.verifyTxOptionExist([spendinglimit.spendingLimitTxOption])
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/swaps_history_2.cy.js b/cypress/e2e/prodhealthcheck/swaps_history_2.cy.js
new file mode 100644
index 000000000..96f76e9f4
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/swaps_history_2.cy.js
@@ -0,0 +1,65 @@
+import * as constants from '../../support/constants.js'
+import * as main from '../pages/main.page.js'
+import * as create_tx from '../pages/create_tx.pages.js'
+import * as swaps_data from '../../fixtures/swaps_data.json'
+import * as swaps from '../pages/swaps.pages.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+
+let staticSafes = []
+
+const swapsHistory = swaps_data.type.history
+
+describe('[PROD] Swaps history tests 2', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ // TODO: Added to prod
+ it('Verify swap buy operation with 2 actions: approve & swap', { defaultCommandTimeout: 30000 }, () => {
+ cy.visit(
+ constants.prodbaseUrl + constants.transactionUrl + staticSafes.SEP_STATIC_SAFE_1 + swaps.swapTxs.buy2actions,
+ )
+ const eq = swaps.createRegex(swapsHistory.oneGNOFull, 'COW')
+ const atMost = swaps.createRegex(swapsHistory.forAtMostCow, 'COW')
+
+ create_tx.verifyExpandedDetails([
+ swapsHistory.buyOrder,
+ swapsHistory.buy,
+ eq,
+ atMost,
+ swapsHistory.cow,
+ swapsHistory.expired,
+ swapsHistory.actionApprove,
+ swapsHistory.actionPreSignature,
+ ])
+ })
+
+ // TODO: Added to prod
+ // TODO: Unskip after next release due to changes in design tx
+ it.skip(
+ 'Verify no decoding if tx was created using CowSwap safe-app in the history',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ cy.visit(
+ constants.prodbaseUrl +
+ constants.transactionUrl +
+ staticSafes.SEP_STATIC_SAFE_1 +
+ swaps.swapTxs.safeAppSwapOrder,
+ )
+ main.verifyValuesDoNotExist('div', [
+ swapsHistory.actionApproveG,
+ swapsHistory.actionDepositG,
+ swapsHistory.amount,
+ swapsHistory.executionPrice,
+ swapsHistory.surplus,
+ swapsHistory.expiry,
+ swapsHistory.oderId,
+ swapsHistory.status,
+ swapsHistory.forAtLeast,
+ swapsHistory.forAtMost,
+ ])
+ main.verifyValuesDoNotExist(create_tx.transactionItem, [swapsHistory.title, swapsHistory.cow, swapsHistory.dai])
+ main.verifyValuesExist(create_tx.transactionItem, [swapsHistory.actionPreSignatureG, swapsHistory.safeAppTitile])
+ },
+ )
+})
diff --git a/cypress/e2e/prodhealthcheck/swaps_tokens.cy.js b/cypress/e2e/prodhealthcheck/swaps_tokens.cy.js
new file mode 100644
index 000000000..acc2f9a5f
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/swaps_tokens.cy.js
@@ -0,0 +1,49 @@
+import * as constants from '../../support/constants.js'
+import * as main from '../pages/main.page.js'
+import * as swaps from '../pages/swaps.pages.js'
+import * as assets from '../pages/assets.pages.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+
+let staticSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+let iframeSelector = `iframe[src*="${constants.swapWidget}"]`
+
+describe('[PROD] Swaps token tests', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_1)
+ })
+
+ // TODO: Added to prod
+ it.skip(
+ 'Verify that clicking the swap from assets tab, autofills that token automatically in the form',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ wallet.connectSigner(signer)
+ assets.selectTokenList(assets.tokenListOptions.allTokens)
+
+ swaps.clickOnAssetSwapBtn(0)
+ swaps.acceptLegalDisclaimer()
+ cy.wait(2000)
+ main.getIframeBody(iframeSelector).within(() => {
+ swaps.verifySelectedInputCurrancy(swaps.swapTokens.eth)
+ })
+ },
+ )
+
+ // TODO: Added to prod
+ // TODO: Check why expected number of buttons not displayed sometimes
+ it.skip('Verify swap button are displayed in assets table and dashboard', () => {
+ assets.selectTokenList(assets.tokenListOptions.allTokens)
+ main.verifyElementsCount(swaps.assetsSwapBtn, 4)
+ cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_1)
+ main.verifyElementsCount(swaps.assetsSwapBtn, 4)
+ main.verifyElementsCount(swaps.dashboardSwapBtn, 1)
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/tokens.cy.js b/cypress/e2e/prodhealthcheck/tokens.cy.js
new file mode 100644
index 000000000..14b18ab68
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/tokens.cy.js
@@ -0,0 +1,95 @@
+import * as constants from '../../support/constants'
+import * as main from '../pages/main.page'
+import * as assets from '../pages/assets.pages'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+
+const ASSET_NAME_COLUMN = 0
+const TOKEN_AMOUNT_COLUMN = 1
+const FIAT_AMOUNT_COLUMN = 2
+
+let staticSafes = []
+
+describe('[PROD] Prod tokens tests', () => {
+ const fiatRegex = assets.fiatRegex
+
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+ beforeEach(() => {
+ cy.visit(constants.prodbaseUrl + constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_2)
+ })
+
+ // TODO: Added to prod
+ it('Verify that non-native tokens are present and have balance', () => {
+ assets.selectTokenList(assets.tokenListOptions.allTokens)
+ assets.verifyBalance(assets.currencyDaiCap, TOKEN_AMOUNT_COLUMN, assets.currencyDaiAlttext)
+ assets.verifyTokenBalanceFormat(
+ assets.currencyDaiCap,
+ assets.currencyDaiFormat_2,
+ TOKEN_AMOUNT_COLUMN,
+ FIAT_AMOUNT_COLUMN,
+ fiatRegex,
+ )
+
+ assets.verifyBalance(assets.currencyAave, TOKEN_AMOUNT_COLUMN, assets.currencyAaveAlttext)
+ assets.verifyTokenBalanceFormat(
+ assets.currencyAave,
+ assets.currentcyAaveFormat,
+ TOKEN_AMOUNT_COLUMN,
+ FIAT_AMOUNT_COLUMN,
+ fiatRegex,
+ )
+
+ assets.verifyBalance(assets.currencyLink, TOKEN_AMOUNT_COLUMN, assets.currencyLinkAlttext)
+ assets.verifyTokenBalanceFormat(
+ assets.currencyLink,
+ assets.currentcyLinkFormat,
+ TOKEN_AMOUNT_COLUMN,
+ FIAT_AMOUNT_COLUMN,
+ fiatRegex,
+ )
+
+ assets.verifyBalance(assets.currencyTestTokenA, TOKEN_AMOUNT_COLUMN, assets.currencyTestTokenAAlttext)
+ assets.verifyTokenBalanceFormat(
+ assets.currencyTestTokenA,
+ assets.currentcyTestTokenAFormat,
+ TOKEN_AMOUNT_COLUMN,
+ FIAT_AMOUNT_COLUMN,
+ fiatRegex,
+ )
+
+ assets.verifyBalance(assets.currencyTestTokenB, TOKEN_AMOUNT_COLUMN, assets.currencyTestTokenBAlttext)
+ assets.verifyTokenBalanceFormat(
+ assets.currencyTestTokenB,
+ assets.currentcyTestTokenBFormat,
+ TOKEN_AMOUNT_COLUMN,
+ FIAT_AMOUNT_COLUMN,
+ fiatRegex,
+ )
+
+ assets.verifyBalance(assets.currencyUSDC, TOKEN_AMOUNT_COLUMN, assets.currencyTestUSDCAlttext)
+ assets.verifyTokenBalanceFormat(
+ assets.currencyUSDC,
+ assets.currentcyTestUSDCFormat,
+ TOKEN_AMOUNT_COLUMN,
+ FIAT_AMOUNT_COLUMN,
+ fiatRegex,
+ )
+ })
+
+ // TODO: Added to prod
+ //Include in smoke.
+ it('Verify that when owner is disconnected, Send button is disabled', () => {
+ assets.selectTokenList(assets.tokenListOptions.allTokens)
+ assets.showSendBtn(0)
+ assets.VerifySendButtonIsDisabled()
+ })
+
+ // TODO: Added to prod
+ it('Verify that when connected user is not owner, Send button is disabled', () => {
+ cy.visit(constants.prodbaseUrl + constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_3)
+ assets.selectTokenList(assets.tokenListOptions.allTokens)
+ assets.showSendBtn(0)
+ assets.VerifySendButtonIsDisabled()
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/tx_history.cy.js b/cypress/e2e/prodhealthcheck/tx_history.cy.js
new file mode 100644
index 000000000..25bef3d1c
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/tx_history.cy.js
@@ -0,0 +1,134 @@
+import * as constants from '../../support/constants'
+import * as main from '../pages/main.page'
+import * as createTx from '../pages/create_tx.pages'
+import * as data from '../../fixtures/txhistory_data_data.json'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+
+let staticSafes = []
+
+const typeCreateAccount = data.type.accountCreation
+const typeReceive = data.type.receive
+const typeSend = data.type.send
+const typeSpendingLimits = data.type.spendingLimits
+const typeDeleteAllowance = data.type.deleteSpendingLimit
+const typeSideActions = data.type.sideActions
+const typeGeneral = data.type.general
+
+describe('[PROD] Tx history tests 1', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.intercept(
+ 'GET',
+ `**${constants.stagingCGWChains}${constants.networkKeys.sepolia}/${
+ constants.stagingCGWSafes
+ }${staticSafes.SEP_STATIC_SAFE_7.substring(4)}/transactions/history**`,
+ (req) => {
+ req.url = `https://safe-client.safe.global/v1/chains/11155111/safes/0x5912f6616c84024cD1aff0D5b55bb36F5180fFdb/transactions/history?timezone=Europe/Berlin&trusted=false&cursor=limit=100&offset=1`
+ req.continue()
+ },
+ ).as('allTransactions')
+
+ cy.visit(constants.prodbaseUrl + constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_7)
+ cy.wait('@allTransactions')
+ })
+
+ // TODO: Added to prod
+ // Account creation
+ it('Verify summary for account creation', () => {
+ createTx.verifySummaryByName(
+ typeCreateAccount.title,
+ [typeCreateAccount.actionsSummary, typeGeneral.statusOk],
+ typeCreateAccount.altTmage,
+ )
+ })
+
+ // TODO: Added to prod
+ it('Verify exapanded details for account creation', () => {
+ createTx.clickOnTransactionItemByName(typeCreateAccount.title)
+ createTx.verifyExpandedDetails([
+ typeCreateAccount.creator.actionTitle,
+ typeCreateAccount.creator.address,
+ typeCreateAccount.factory.actionTitle,
+ typeCreateAccount.factory.name,
+ typeCreateAccount.factory.address,
+ typeCreateAccount.masterCopy.actionTitle,
+ typeCreateAccount.masterCopy.name,
+ typeCreateAccount.masterCopy.address,
+ typeCreateAccount.transactionHash,
+ ])
+ })
+
+ // TODO: Added to prod
+ // Token send
+ it('Verify exapanded details for token send', () => {
+ createTx.clickOnTransactionItemByName(typeSend.title, typeSend.summaryTxInfo)
+ createTx.verifyExpandedDetails([typeSend.sentTo, typeSend.recipientAddress, typeSend.transactionHash])
+ createTx.verifyActionListExists([
+ typeSideActions.created,
+ typeSideActions.confirmations,
+ typeSideActions.executedBy,
+ ])
+ })
+
+ // TODO: Added to prod
+ // Spending limits
+ // TODO: Unskip after next release due to design tx
+ it.skip('Verify summary for setting spend limits', () => {
+ createTx.verifySummaryByName(
+ typeSpendingLimits.title,
+ typeSpendingLimits.summaryTxInfo,
+ [typeGeneral.statusOk],
+ typeSpendingLimits.altTmage,
+ )
+ })
+
+ // TODO: Added to prod
+ // TODO: Unskip after next release due to design tx
+ it.skip('Verify exapanded details for initial spending limits setup', () => {
+ createTx.clickOnTransactionItemByName(typeSpendingLimits.title, typeSpendingLimits.summaryTxInfo)
+ createTx.verifyExpandedDetails(
+ [
+ typeSpendingLimits.contractTitle,
+ typeSpendingLimits.call_multiSend,
+ typeSpendingLimits.transactionHash,
+ typeSpendingLimits.safeTxHash,
+ ],
+ createTx.delegateCallWarning,
+ )
+ })
+
+ // TODO: Added to prod
+ // TODO: Unskip after next release due to design tx
+ it.skip('Verify that 3 actions exist in initial spending limits setup', () => {
+ createTx.clickOnTransactionItemByName(typeSpendingLimits.title, typeSpendingLimits.summaryTxInfo)
+ createTx.verifyActions([
+ typeSpendingLimits.enableModule.title,
+ typeSpendingLimits.addDelegate.title,
+ typeSpendingLimits.setAllowance.title,
+ ])
+ })
+
+ // Spending limit deletion
+ it('Verify exapanded details for allowance deletion', () => {
+ createTx.clickOnTransactionItemByName(typeDeleteAllowance.title, typeDeleteAllowance.summaryTxInfo)
+ createTx.verifyExpandedDetails([
+ typeDeleteAllowance.description,
+ typeDeleteAllowance.beneficiary,
+ typeDeleteAllowance.beneficiaryAddress,
+ typeDeleteAllowance.transactionHash,
+ typeDeleteAllowance.safeTxHash,
+ typeDeleteAllowance.token,
+ typeDeleteAllowance.tokenName,
+ ])
+ })
+
+ // TODO: Added to prod
+ it('Verify advanced details displayed in exapanded details for allowance deletion', () => {
+ createTx.clickOnTransactionItemByName(typeDeleteAllowance.title, typeDeleteAllowance.summaryTxInfo)
+ createTx.expandAdvancedDetails([typeDeleteAllowance.baseGas])
+ createTx.collapseAdvancedDetails([typeDeleteAllowance.baseGas])
+ })
+})
diff --git a/cypress/e2e/prodhealthcheck/tx_history_2.cy.js b/cypress/e2e/prodhealthcheck/tx_history_2.cy.js
new file mode 100644
index 000000000..2532190e3
--- /dev/null
+++ b/cypress/e2e/prodhealthcheck/tx_history_2.cy.js
@@ -0,0 +1,140 @@
+import * as constants from '../../support/constants'
+import * as main from '../pages/main.page'
+import * as createTx from '../pages/create_tx.pages'
+import * as data from '../../fixtures/txhistory_data_data.json'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+
+let staticSafes = []
+
+const typeOnchainRejection = data.type.onchainRejection
+const typeBatch = data.type.batchNativeTransfer
+const typeAddOwner = data.type.addOwner
+const typeChangeOwner = data.type.swapOwner
+const typeRemoveOwner = data.type.removeOwner
+const typeDisableOwner = data.type.disableModule
+const typeChangeThreshold = data.type.changeThreshold
+const typeSideActions = data.type.sideActions
+const typeGeneral = data.type.general
+const typeUntrustedToken = data.type.untrustedReceivedToken
+
+describe('[PROD] Tx history tests 2', () => {
+ before(async () => {
+ staticSafes = await getSafes(CATEGORIES.static)
+ })
+
+ beforeEach(() => {
+ cy.intercept(
+ 'GET',
+ `**${constants.stagingCGWChains}${constants.networkKeys.sepolia}/${
+ constants.stagingCGWSafes
+ }${staticSafes.SEP_STATIC_SAFE_7.substring(4)}/transactions/history**`,
+ (req) => {
+ req.url = `https://safe-client.safe.global/v1/chains/11155111/safes/0x5912f6616c84024cD1aff0D5b55bb36F5180fFdb/transactions/history?timezone=Europe/Berlin&trusted=false&cursor=limit=100&offset=1`
+ req.continue()
+ },
+ ).as('allTransactions')
+
+ cy.visit(constants.prodbaseUrl + constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_7)
+ })
+
+ it('Verify number of transactions is correct', () => {
+ createTx.verifyNumberOfTransactions(20)
+ })
+
+ // TODO: Added to prod
+ // On-chain rejection
+ it('Verify exapanded details for on-chain rejection', () => {
+ createTx.clickOnTransactionItemByName(typeOnchainRejection.title)
+ createTx.verifyExpandedDetails([
+ typeOnchainRejection.description,
+ typeOnchainRejection.transactionHash,
+ typeOnchainRejection.safeTxHash,
+ ])
+ createTx.verifyActionListExists([
+ typeSideActions.rejectionCreated,
+ typeSideActions.confirmations,
+ typeSideActions.executedBy,
+ ])
+ })
+
+ // TODO: Added to prod
+ // Batch transaction
+ // TODO: Unskip after next release due to design tx
+ it.skip('Verify exapanded details for batch', () => {
+ createTx.clickOnTransactionItemByName(typeBatch.title, typeBatch.summaryTxInfo)
+ createTx.verifyExpandedDetails(
+ [typeBatch.contractTitle, typeBatch.transactionHash, typeBatch.safeTxHash],
+ createTx.delegateCallWarning,
+ )
+ createTx.verifyActions([typeBatch.nativeTransfer.title])
+ })
+
+ // TODO: Added to prod
+ // Add owner
+ it('Verify summary for adding owner', () => {
+ createTx.verifySummaryByName(typeAddOwner.title, [typeGeneral.statusOk], typeAddOwner.altImage)
+ })
+
+ // TODO: Added to prod
+ // Change owner
+ it('Verify summary for changing owner', () => {
+ createTx.verifySummaryByName(typeChangeOwner.title, [typeGeneral.statusOk], typeChangeOwner.altImage)
+ })
+
+ // TODO: Added to prod
+ it('Verify exapanded details for changing owner', () => {
+ createTx.clickOnTransactionItemByName(typeChangeOwner.title)
+ createTx.verifyExpandedDetails([
+ typeChangeOwner.description,
+ typeChangeOwner.newOwner.actionTitile,
+ typeChangeOwner.newOwner.ownerAddress,
+ typeChangeOwner.oldOwner.actionTitile,
+ typeChangeOwner.oldOwner.ownerAddress,
+
+ typeChangeOwner.transactionHash,
+ typeChangeOwner.safeTxHash,
+ ])
+ })
+
+ // TODO: Added to prod
+ // Remove owner
+ it('Verify summary for removing owner', () => {
+ createTx.verifySummaryByName(typeRemoveOwner.title, [typeGeneral.statusOk], typeRemoveOwner.altImage)
+ })
+
+ // TODO: Added to prod
+ // Disbale module
+ it('Verify summary for disable module', () => {
+ createTx.verifySummaryByName(typeDisableOwner.title, [typeGeneral.statusOk], typeDisableOwner.altImage)
+ })
+
+ // TODO: Added to prod
+ // Change threshold
+ it('Verify summary for changing threshold', () => {
+ createTx.verifySummaryByName(
+ typeChangeThreshold.title,
+ [typeChangeThreshold.summaryTxInfo, typeGeneral.statusOk],
+ typeChangeThreshold.altImage,
+ )
+ })
+
+ // TODO: Added to prod
+ it('Verify exapanded details for changing threshold', () => {
+ createTx.clickOnTransactionItemByName(typeChangeThreshold.title)
+ createTx.verifyExpandedDetails(
+ [
+ typeChangeThreshold.requiredConfirmationsTitle,
+ typeChangeThreshold.transactionHash,
+ typeChangeThreshold.safeTxHash,
+ ],
+ createTx.policyChangeWarning,
+ )
+ createTx.checkRequiredThreshold(2)
+ })
+
+ // TODO: Added to prod
+ it('Verify that sender address of untrusted token will not be copied until agreed in warning popup', () => {
+ createTx.clickOnTransactionItemByName(typeUntrustedToken.summaryTitle, typeUntrustedToken.summaryTxInfo)
+ createTx.verifyAddressNotCopied(0, typeUntrustedToken.senderAddress)
+ })
+})
diff --git a/cypress/e2e/regression/add_owner.cy.js b/cypress/e2e/regression/add_owner.cy.js
index 6129ab561..3bd525cfc 100644
--- a/cypress/e2e/regression/add_owner.cy.js
+++ b/cypress/e2e/regression/add_owner.cy.js
@@ -4,10 +4,14 @@ import * as owner from '../pages/owners.pages'
import * as addressBook from '../pages/address_book.page'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
import * as wallet from '../../support/utils/wallet.js'
+import * as createTx from '../pages/create_tx.pages.js'
+import * as navigation from '../pages/navigation.page'
+import { getEvents, events, checkDataLayerEvents } from '../../support/utils/gtag.js'
let staticSafes = []
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signer2 = walletCredentials.OWNER_1_PRIVATE_KEY
describe('Add Owners tests', () => {
before(async () => {
@@ -16,15 +20,15 @@ describe('Add Owners tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4)
- cy.clearLocalStorage()
- main.acceptCookies()
cy.contains(owner.safeAccountNonceStr, { timeout: 10000 })
})
+ // TODO: Added to prod
it('Verify add owner button is disabled for disconnected user', () => {
owner.verifyAddOwnerBtnIsDisabled()
})
+ // TODO: Added to prod
it('Verify the Add New Owner Form can be opened', () => {
wallet.connectSigner(signer)
owner.openAddOwnerWindow()
@@ -58,4 +62,45 @@ describe('Add Owners tests', () => {
owner.clickOnNextBtn()
owner.verifyConfirmTransactionWindowDisplayed()
})
+
+ it(
+ 'Verify creation, confirmation and deletion of Add owner tx. GA tx_confirm',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ const tx_confirmed = [
+ {
+ eventLabel: events.txConfirmedAddOwner.eventLabel,
+ eventCategory: events.txConfirmedAddOwner.category,
+ eventType: events.txConfirmedAddOwner.eventType,
+ safeAddress: staticSafes.SEP_STATIC_SAFE_24.slice(6),
+ },
+ ]
+ cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_24)
+ wallet.connectSigner(signer2)
+ owner.waitForConnectionStatus()
+ owner.openAddOwnerWindow()
+ owner.typeOwnerAddress(constants.SEPOLIA_OWNER_2)
+ createTx.changeNonce(1)
+ owner.clickOnNextBtn()
+ createTx.clickOnSignTransactionBtn()
+ createTx.clickViewTransaction()
+
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer)
+
+ createTx.clickOnConfirmTransactionBtn()
+ createTx.clickOnNoLaterOption()
+ createTx.clickOnSignTransactionBtn()
+
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer2)
+
+ createTx.deleteTx()
+
+ getEvents()
+ checkDataLayerEvents(tx_confirmed)
+ },
+ )
})
diff --git a/cypress/e2e/regression/address_book.cy.js b/cypress/e2e/regression/address_book.cy.js
index 1413b95a0..16d37bcb3 100644
--- a/cypress/e2e/regression/address_book.cy.js
+++ b/cypress/e2e/regression/address_book.cy.js
@@ -24,8 +24,6 @@ describe('Address book tests', () => {
beforeEach(() => {
cy.visit(constants.addressBookUrl + staticSafes.SEP_STATIC_SAFE_4)
- cy.clearLocalStorage()
- main.acceptCookies()
})
it('Verify owners name can be edited', () => {
@@ -45,25 +43,6 @@ describe('Address book tests', () => {
})
})
- //TODO: Rework to use Polygon. Replace Verify csv file can be imported (Goerli) with this test
- it.skip('Verify that Sepolia and Polygon addresses can be imported', () => {
- // Go to a Safe on Gnosis Chain
- cy.get('header')
- .contains(/^G(รถ|oe)rli$/)
- .click()
- cy.contains('Gnosis Chain').click()
-
- // Navigate to the Address Book page
- cy.visit(`/address-book?safe=${constants.GNO_TEST_SAFE}`)
-
- // Waits for the Address Book table to be in the page
- cy.contains('p', 'Address book').should('be.visible')
-
- // Finds the imported Gnosis Chain address
- cy.contains(constants.GNO_CSV_ENTRY.name).should('exist')
- cy.contains(constants.GNO_CSV_ENTRY.address).should('exist')
- })
-
it('Verify the address book file can be exported', () => {
cy.wrap(null)
.then(() => main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.dataSet))
@@ -79,8 +58,38 @@ describe('Address book tests', () => {
addressBook.verifyExportMessage(12)
addressBook.confirmExport()
const downloadsFolder = Cypress.config('downloadsFolder')
- //File reading is failing in the CI. Can be tested locally
- cy.readFile(path.join(downloadsFolder, fileName)).should('exist')
+
+ cy.readFile(path.join(downloadsFolder, fileName), 'utf-8').then((content) => {
+ const lines = content
+ .replace(/^\uFEFF/, '')
+ .trim()
+ .split('\r\n')
+
+ const [header, ...dataLines] = lines
+ const actualData = dataLines.reduce((acc, line) => {
+ const [address, name, chainId] = line.split(',')
+ acc[chainId] = acc[chainId] || {}
+ acc[chainId][address] = name
+ return acc
+ }, {})
+
+ Object.keys(ls.addressBookData.dataSet).forEach((chainId) => {
+ cy.log(`Checking chainId: ${chainId}`)
+
+ const actualChainData = actualData[chainId] || {}
+ const expectedChainData = ls.addressBookData.dataSet[chainId]
+
+ Object.keys(expectedChainData).forEach((address) => {
+ const actualName = actualChainData[address]
+ const expectedName = expectedChainData[address]
+
+ cy.log(
+ `ChainId: ${chainId}, Address: ${address}, Actual Name: ${actualName}, Expected Name: ${expectedName}`,
+ )
+ expect(actualName).to.equal(expectedName)
+ })
+ })
+ })
})
})
diff --git a/cypress/e2e/regression/address_book_2.cy.js b/cypress/e2e/regression/address_book_2.cy.js
index 9f49706fa..39859b85f 100644
--- a/cypress/e2e/regression/address_book_2.cy.js
+++ b/cypress/e2e/regression/address_book_2.cy.js
@@ -21,9 +21,6 @@ describe('Address book tests - 2', () => {
beforeEach(() => {
cy.visit(constants.addressBookUrl + staticSafes.SEP_STATIC_SAFE_4)
- cy.clearLocalStorage()
- cy.wait(1000)
- main.acceptCookies()
})
it('Verify Name and Address columns sorting works', () => {
@@ -67,14 +64,6 @@ describe('Address book tests - 2', () => {
addressBook.verifyNameWasChanged(owner1, onwer3)
})
- it.skip('Verify copy to clipboard/Etherscan work as expected', () => {
- main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sepoliaAddress1)
- cy.wait(1000)
- cy.reload()
- createtx.verifyCopyIconWorks(0, constants.RECIPIENT_ADDRESS)
- createtx.verifyNumberOfExternalLinks(1)
- })
-
it('Verify by default there 25 rows shown per page', () => {
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.pagination)
cy.wait(1000)
diff --git a/cypress/e2e/regression/balances_pagination.cy.js b/cypress/e2e/regression/balances_pagination.cy.js
index b110310b9..8b81050e7 100644
--- a/cypress/e2e/regression/balances_pagination.cy.js
+++ b/cypress/e2e/regression/balances_pagination.cy.js
@@ -6,9 +6,7 @@ const ASSETS_LENGTH = 8
describe('Balance pagination tests', () => {
before(() => {
- cy.clearLocalStorage()
cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_6)
- main.acceptCookies()
assets.selectTokenList(assets.tokenListOptions.allTokens)
})
diff --git a/cypress/e2e/regression/batch_tx.cy.js b/cypress/e2e/regression/batch_tx.cy.js
index b9ecb1374..b8f1c5d3f 100644
--- a/cypress/e2e/regression/batch_tx.cy.js
+++ b/cypress/e2e/regression/batch_tx.cy.js
@@ -4,6 +4,8 @@ import * as main from '../../e2e/pages/main.page'
import * as owner from '../../e2e/pages/owners.pages.js'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
import * as wallet from '../../support/utils/wallet.js'
+import * as ls from '../../support/localstorage_data.js'
+import * as navigation from '../pages/navigation.page.js'
const currentNonce = 3
const funds_first_tx = '0.001'
@@ -12,6 +14,7 @@ const funds_second_tx = '0.002'
let staticSafes = []
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signer2 = walletCredentials.OWNER_3_PRIVATE_KEY
describe('Batch transaction tests', () => {
before(async () => {
@@ -19,15 +22,11 @@ describe('Batch transaction tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_2)
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
- main.acceptCookies()
})
- // TODO: Check if localstorage can be used to add batches
- // Rework test
it('Verify the Add batch button is present in a transaction form', () => {
//The "true" is to validate that the add to batch button is not visible if "Yes, execute" is selected
batch.addNewTransactionToBatch(constants.EOA, currentNonce, funds_first_tx)
@@ -41,4 +40,51 @@ describe('Batch transaction tests', () => {
batch.clickOnBatchCounter()
batch.verifyAmountTransactionsInBatch(2)
})
+
+ it('Verify that clicking on "Confirm batch" button opens confirm batch modal with listed transactions', () => {
+ main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__batch, ls.batchData.entry0)
+ cy.reload()
+ batch.clickOnBatchCounter()
+ batch.clickOnConfirmBatchBtn()
+ cy.contains(funds_first_tx).parents('ul').as('TransactionList')
+ cy.get('@TransactionList').find('li').eq(0).contains(funds_first_tx)
+ cy.get('@TransactionList').find('li').eq(1).contains(funds_second_tx)
+ cy.contains(batch.addToBatchBtn).should('have.length', 0)
+ })
+
+ it('Verify the "New transaction" button in Add batch modal is enabled/disabled for different users types', () => {
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer)
+ batch.openBatchtransactionsModal()
+ batch.verifyNewTxButtonStatus(constants.enabledStates.enabled)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer2)
+ owner.waitForConnectionStatus()
+ batch.verifyNewTxButtonStatus(constants.enabledStates.disabled)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ batch.verifyNewTxButtonStatus(constants.enabledStates.disabled)
+ })
+
+ it('Verify a batched tx can be expanded and collapsed', () => {
+ main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__batch, ls.batchData.entry0)
+ cy.reload()
+ batch.clickOnBatchCounter()
+ cy.contains(funds_first_tx).parents('ul').as('TransactionList')
+ cy.get('@TransactionList').find('li').eq(0).contains(funds_first_tx).click()
+ batch.isTxExpanded(0, true)
+ cy.get('@TransactionList').find('li').eq(0).contains(funds_first_tx).click()
+ batch.isTxExpanded(0, false)
+ })
+
+ it('Verify that the Add batch button is not present on non-safe pages', () => {
+ const urls = [constants.welcomeUrl, constants.appSettingsUrl, constants.appsUrl]
+
+ urls.forEach((url) => {
+ cy.visit(url)
+ cy.get(batch.batchTxTopBar).should('not.exist')
+ })
+ })
})
diff --git a/cypress/e2e/regression/beamer.cy.js b/cypress/e2e/regression/beamer.cy.js
deleted file mode 100644
index 7dd046e3d..000000000
--- a/cypress/e2e/regression/beamer.cy.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as constants from '../../support/constants'
-import * as addressbook from '../pages/address_book.page'
-import * as main from '../../e2e/pages/main.page'
-import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
-
-let staticSafes = []
-
-describe('Beamer tests', () => {
- before(async () => {
- staticSafes = await getSafes(CATEGORIES.static)
- cy.clearLocalStorage()
- cy.visit(constants.addressBookUrl + staticSafes.SEP_STATIC_SAFE_4)
- main.acceptCookies()
- })
-
- it.skip('Verify "Updates" cookie acceptance is required before displaying Beamer', () => {
- addressbook.clickOnWhatsNewBtn()
- addressbook.acceptBeamerCookies()
- addressbook.verifyBeamerIsChecked()
- main.acceptCookies()
- // wait for Beamer cookies to be set
- cy.wait(1000)
- addressbook.clickOnWhatsNewBtn(true) // clicks through the "lastPostTitle"
- addressbook.verifyBeameriFrameExists()
- })
-})
diff --git a/cypress/e2e/regression/bulk_execution.cy.js b/cypress/e2e/regression/bulk_execution.cy.js
new file mode 100644
index 000000000..c48467aab
--- /dev/null
+++ b/cypress/e2e/regression/bulk_execution.cy.js
@@ -0,0 +1,86 @@
+import * as constants from '../../support/constants.js'
+import * as main from '../pages/main.page.js'
+import * as create_tx from '../pages/create_tx.pages.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+import * as data from '../../fixtures/txhistory_data_data.json'
+
+let staticSafes,
+ fundsSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+const typeBulkTx = data.type.bulkTransaction
+
+describe('Bulk execution', () => {
+ before(() => {
+ getSafes(CATEGORIES.funds)
+ .then((funds) => {
+ fundsSafes = funds
+ return getSafes(CATEGORIES.static)
+ })
+ .then((statics) => {
+ staticSafes = statics
+ })
+ })
+
+ it('Verify that Bulk Execution is available for a few fully signed txs located one by one', () => {
+ cy.visit(constants.transactionQueueUrl + fundsSafes.SEP_FUNDS_SAFE_14)
+ main.acceptCookies()
+ wallet.connectSigner(signer)
+ create_tx.verifyBulkExecuteBtnIsEnabled(2)
+ create_tx.verifyEnabledBulkExecuteBtnTooltip()
+ })
+
+ it(
+ 'Verify that "Confirm bulk execution" screen contains only available for execution txs in the actions list',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ const actions = ['1transfer', '2removeOwner']
+
+ cy.visit(constants.transactionQueueUrl + fundsSafes.SEP_FUNDS_SAFE_14)
+ wallet.connectSigner(signer)
+ main.acceptCookies()
+ create_tx.verifyBulkExecuteBtnIsEnabled(2).click()
+ create_tx.verifyBulkConfirmationScreen(2, actions)
+ },
+ )
+
+ it(
+ 'Verify bulk view for the txs with the same tx hash in the History (tx executed via bulk feature)',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ const actions = ['Wrapped Ether', 'addOwnerWithThreshold', 'Sent']
+ const tx = '3 transactions'
+
+ cy.visit(constants.transactionsHistoryUrl + fundsSafes.SEP_FUNDS_SAFE_14)
+ wallet.connectSigner(signer)
+ main.acceptCookies()
+ create_tx.verifyBulkTxHistoryBlock(tx, actions)
+ },
+ )
+
+ it(
+ 'Verify bulk view for the outgoing and incoming txs in the History after swap',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ const data = [typeBulkTx.receive, typeBulkTx.send, typeBulkTx.COW, typeBulkTx.DAI]
+ const tx = typeBulkTx.twoTx
+
+ cy.visit(constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_1)
+ main.acceptCookies()
+
+ create_tx.verifyBulkTxHistoryBlock(tx, data)
+ },
+ )
+
+ it(
+ 'Verify that Bulk Execution button is disabled if the tx in Next is not fully signed',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ cy.visit(constants.transactionQueueUrl + fundsSafes.SEP_FUNDS_SAFE_15)
+ main.acceptCookies()
+ create_tx.verifyBulkExecuteBtnIsDisabled()
+ },
+ )
+})
diff --git a/cypress/e2e/regression/create_safe_cf.cy.js b/cypress/e2e/regression/create_safe_cf.cy.js
index 4b936701d..c1acac991 100644
--- a/cypress/e2e/regression/create_safe_cf.cy.js
+++ b/cypress/e2e/regression/create_safe_cf.cy.js
@@ -10,7 +10,8 @@ import * as wallet from '../../support/utils/wallet.js'
let staticSafes = []
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
-const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+// DO NOT use OWNER_2_PRIVATE_KEY for safe creation. Used for CF safes.
+const signer = walletCredentials.OWNER_2_PRIVATE_KEY
const txOrder = [
'Activate Safe now',
@@ -27,9 +28,7 @@ describe('CF Safe regression tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
- cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_14)
- main.acceptCookies()
+ cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_0)
})
it('Verify Add native assets and Create tx modals can be opened', () => {
@@ -39,10 +38,10 @@ describe('CF Safe regression tests', () => {
owner.waitForConnectionStatus()
createwallet.clickOnAddFundsBtn()
main.verifyElementsIsVisible([createwallet.qrCode])
- navigation.clickOnModalCloseBtn()
+ navigation.clickOnModalCloseBtn(0)
createwallet.clickOnCreateTxBtn()
- navigation.clickOnModalCloseBtn()
+ navigation.clickOnModalCloseBtn(0)
})
it('Verify "0 out of 2 step completed" is shown in the dashboard', () => {
@@ -87,7 +86,7 @@ describe('CF Safe regression tests', () => {
owner.waitForConnectionStatus()
createwallet.clickOnCreateTxBtn()
createwallet.clickOnTxType(txOrder[0])
- main.verifyElementsExist([createwallet.activateAccountBtn])
+ cy.contains(createwallet.deployWalletStr)
})
it('Verify "Add another Owner" takes to a tx Add owner', () => {
@@ -123,10 +122,6 @@ describe('CF Safe regression tests', () => {
it('Verify "Custom transaction" takes to the tx builder app ', () => {
const iframeSelector = `iframe[id="iframe-${constants.TX_Builder_url}"]`
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__undeployedSafes, ls.undeployedSafe.safe1)
- main.addToLocalStorage(
- constants.localStorageKeys.SAFE_v2__SafeApps__infoModal,
- ls.appPermissions(constants.safeTestAppurl).infoModalAccepted,
- )
cy.reload()
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
@@ -140,21 +135,22 @@ describe('CF Safe regression tests', () => {
it('Verify "Notifications" in the settings are disabled', () => {
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__undeployedSafes, ls.undeployedSafe.safe1)
cy.reload()
- cy.visit(constants.notificationsUrl + staticSafes.SEP_STATIC_SAFE_14)
+ cy.visit(constants.notificationsUrl + staticSafes.SEP_STATIC_SAFE_0)
createwallet.checkNotificationsSwitchIs(constants.enabledStates.disabled)
})
it('Verify in assets, that a "Add funds" block is present', () => {
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__undeployedSafes, ls.undeployedSafe.safe1)
cy.reload()
- cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_14)
+ cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_0)
main.verifyElementsIsVisible([createwallet.addFundsSection, createwallet.noTokensAlert])
})
it('Verify clicking on "Activate now" button opens safe activation flow', () => {
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__undeployedSafes, ls.undeployedSafe.safe1)
- cy.reload()
- cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_14)
+ cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_0)
+ wallet.connectSigner(signer)
+ owner.waitForConnectionStatus()
createwallet.clickOnActivateAccountBtn()
main.verifyElementsIsVisible([createwallet.activateAccountBtn])
})
diff --git a/cypress/e2e/regression/create_safe_simple.cy.js b/cypress/e2e/regression/create_safe_simple.cy.js
index 2580029ba..620ad6e31 100644
--- a/cypress/e2e/regression/create_safe_simple.cy.js
+++ b/cypress/e2e/regression/create_safe_simple.cy.js
@@ -11,8 +11,6 @@ const signer = walletCredentials.OWNER_4_PRIVATE_KEY
describe('Safe creation tests', () => {
beforeEach(() => {
cy.visit(constants.welcomeUrl + '?chain=sep')
- cy.clearLocalStorage()
- main.acceptCookies()
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
})
@@ -134,9 +132,7 @@ describe('Safe creation tests', () => {
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sameOwnerName),
)
.then(() => {
- wallet.connectSigner(signer)
- createwallet.clickOnContinueWithWalletBtn()
- createwallet.clickOnCreateNewSafeBtn()
+ createwallet.waitForConnectionMsgDisappear()
createwallet.clickOnNextBtn()
createwallet.clickOnAddNewOwnerBtn()
createwallet.clickOnSignerAddressInput(1)
diff --git a/cypress/e2e/regression/create_safe_simple_2.cy.js b/cypress/e2e/regression/create_safe_simple_2.cy.js
index f0ad5bf2b..0a10dda33 100644
--- a/cypress/e2e/regression/create_safe_simple_2.cy.js
+++ b/cypress/e2e/regression/create_safe_simple_2.cy.js
@@ -15,8 +15,6 @@ const signer = walletCredentials.OWNER_4_PRIVATE_KEY
describe('Safe creation tests 2', () => {
beforeEach(() => {
cy.visit(constants.welcomeUrl + '?chain=sep')
- cy.clearLocalStorage()
- main.acceptCookies()
})
it('Cancel button cancels safe creation', () => {
diff --git a/cypress/e2e/regression/create_tx.cy.js b/cypress/e2e/regression/create_tx.cy.js
index 914828302..f7dfd03f7 100644
--- a/cypress/e2e/regression/create_tx.cy.js
+++ b/cypress/e2e/regression/create_tx.cy.js
@@ -24,21 +24,19 @@ describe('Create transactions tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_6)
- main.acceptCookies()
wallet.connectSigner(signer)
createtx.clickOnNewtransactionBtn()
createtx.clickOnSendTokensBtn()
})
+ // TODO: Added to prod
it('Verify submitting a tx and that clicking on notification shows the transaction in queue', () => {
happyPathToStepTwo()
createtx.verifySubmitBtnIsEnabled()
createtx.changeNonce(14)
cy.wait(1000)
createtx.clickOnSignTransactionBtn()
- createtx.waitForProposeRequest()
createtx.clickViewTransaction()
createtx.verifySingleTxPage()
createtx.verifyQueueLabel()
diff --git a/cypress/e2e/regression/load_safe.cy.js b/cypress/e2e/regression/load_safe.cy.js
index 87533108d..83f3f80d0 100644
--- a/cypress/e2e/regression/load_safe.cy.js
+++ b/cypress/e2e/regression/load_safe.cy.js
@@ -27,9 +27,7 @@ describe('Load Safe tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.loadNewSafeSepoliaUrl)
- main.acceptCookies()
cy.wait(2000)
})
@@ -40,6 +38,7 @@ describe('Load Safe tests', () => {
safe.clickOnNextBtn()
})
+ // TODO: Added to prod
it('Verify Safe and owner names are displayed in the Review step', () => {
safe.inputNameAndAddress(testSafeName, staticSafes.SEP_STATIC_SAFE_4)
safe.clickOnNextBtn()
diff --git a/cypress/e2e/regression/load_safe_2.cy.js b/cypress/e2e/regression/load_safe_2.cy.js
index 0cd807c43..e5750ec3d 100644
--- a/cypress/e2e/regression/load_safe_2.cy.js
+++ b/cypress/e2e/regression/load_safe_2.cy.js
@@ -26,9 +26,7 @@ describe('Load Safe tests 2', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.loadNewSafeSepoliaUrl)
- main.acceptCookies()
cy.wait(2000)
})
diff --git a/cypress/e2e/regression/messages_onchain.cy.js b/cypress/e2e/regression/messages_onchain.cy.js
index 492e38fba..88039368f 100644
--- a/cypress/e2e/regression/messages_onchain.cy.js
+++ b/cypress/e2e/regression/messages_onchain.cy.js
@@ -14,28 +14,18 @@ describe('Onchain Messages tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_10)
- main.acceptCookies()
})
it('Verify exapanded details for signed on-chain message', () => {
createTx.clickOnTransactionItemByName(typeMessagesOnchain.contractName)
- createTx.verifyExpandedDetails([
- typeMessagesOnchain.contractName,
- typeMessagesOnchain.contractAddress,
- typeMessagesOnchain.delegateCall,
- ])
+ createTx.verifyExpandedDetails([typeMessagesOnchain.contractName, typeMessagesOnchain.delegateCall])
})
it('Verify exapanded details for unsigned on-chain message', () => {
cy.visit(constants.transactionQueueUrl + staticSafes.SEP_STATIC_SAFE_10)
createTx.clickOnTransactionItemByName(typeMessagesOnchain.contractName)
- createTx.verifyExpandedDetails([
- typeMessagesOnchain.contractName,
- typeMessagesOnchain.contractAddress,
- typeMessagesOnchain.delegateCall,
- ])
+ createTx.verifyExpandedDetails([typeMessagesOnchain.contractName, typeMessagesOnchain.delegateCall])
})
it('Verify summary for unsigned on-chain message', () => {
@@ -47,6 +37,7 @@ describe('Onchain Messages tests', () => {
)
})
+ // TODO: Added to prod
it('Verify summary for signed on-chain message', () => {
createTx.verifySummaryByName(
typeMessagesOnchain.contractName,
diff --git a/cypress/e2e/regression/messages_popup.cy.js b/cypress/e2e/regression/messages_popup.cy.js
index 441debb12..a679e6cc9 100644
--- a/cypress/e2e/regression/messages_popup.cy.js
+++ b/cypress/e2e/regression/messages_popup.cy.js
@@ -18,9 +18,7 @@ describe('Messages popup window tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.appsCustomUrl + staticSafes.SEP_STATIC_SAFE_10)
- main.acceptCookies()
iframeSelector = `iframe[id="iframe-${constants.safeTestAppurl}"]`
})
@@ -33,10 +31,6 @@ describe('Messages popup window tests', () => {
constants.localStorageKeys.SAFE_v2__SafeApps__browserPermissions,
ls.appPermissions(constants.safeTestAppurl).grantedPermissions,
)
- main.addToLocalStorage(
- constants.localStorageKeys.SAFE_v2__SafeApps__infoModal,
- ls.appPermissions(constants.safeTestAppurl).infoModalAccepted,
- )
cy.reload()
apps.clickOnApp(safeApp)
apps.clickOnOpenSafeAppBtn()
@@ -56,10 +50,7 @@ describe('Messages popup window tests', () => {
constants.localStorageKeys.SAFE_v2__SafeApps__browserPermissions,
ls.appPermissions(constants.safeTestAppurl).grantedPermissions,
)
- main.addToLocalStorage(
- constants.localStorageKeys.SAFE_v2__SafeApps__infoModal,
- ls.appPermissions(constants.safeTestAppurl).infoModalAccepted,
- )
+
cy.reload()
apps.clickOnApp(safeApp)
apps.clickOnOpenSafeAppBtn()
diff --git a/cypress/e2e/regression/nfts.cy.js b/cypress/e2e/regression/nfts.cy.js
index 386af6267..429ae3a23 100644
--- a/cypress/e2e/regression/nfts.cy.js
+++ b/cypress/e2e/regression/nfts.cy.js
@@ -30,13 +30,12 @@ describe('NFTs tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.balanceNftsUrl + staticSafes.SEP_STATIC_SAFE_2)
- main.acceptCookies()
wallet.connectSigner(signer)
nfts.waitForNftItems(2)
})
+ // TODO: Added to prod
// TODO: Add Sign action
it('Verify multipls NFTs can be selected and reviewed', () => {
nfts.verifyInitialNFTData()
@@ -59,6 +58,7 @@ describe('NFTs tests', () => {
nfts.verifyCountOfActions(0)
})
+ // TODO: Added to prod
it('Verify that when 2 NFTs are selected, actions and tx details are correct in Review step', () => {
nfts.verifyInitialNFTData()
nfts.selectNFTs(2)
@@ -71,6 +71,7 @@ describe('NFTs tests', () => {
nfts.verifyActionName(1, multipleNFTAction)
})
+ // TODO: Added to prod
it('Verify Send button is disabled for non-owner', () => {
cy.visit(constants.balanceNftsUrl + nftsSafes.SEP_NFT_SAFE_2)
nfts.verifyInitialNFTData()
@@ -85,9 +86,9 @@ describe('NFTs tests', () => {
nfts.verifySendNFTBtnDisabled()
})
+ // TODO: Added to prod
it('Verify Send NFT transaction has been created', () => {
cy.visit(constants.balanceNftsUrl + nftsSafes.SEP_NFT_SAFE_1)
- wallet.connectSigner(signer)
nfts.verifyInitialNFTData()
nfts.selectNFTs(1)
nfts.sendNFT()
diff --git a/cypress/e2e/regression/recovery.cy.js b/cypress/e2e/regression/recovery.cy.js
new file mode 100644
index 000000000..37d585cc9
--- /dev/null
+++ b/cypress/e2e/regression/recovery.cy.js
@@ -0,0 +1,242 @@
+import * as constants from '../../support/constants.js'
+import * as main from '../pages/main.page.js'
+import * as owner from '../pages/owners.pages.js'
+import * as recovery from '../pages/recovery.pages.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as wallet from '../../support/utils/wallet.js'
+import * as modules from '../pages/modules.page.js'
+import * as navigation from '../pages/navigation.page.js'
+
+let recoverySafes,
+ staticSafes = []
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const guardian = walletCredentials.OWNER_2_PRIVATE_KEY
+
+describe('Recovery regression tests', { defaultCommandTimeout: 50000 }, () => {
+ before(() => {
+ getSafes(CATEGORIES.recovery)
+ .then((recoveries) => {
+ recoverySafes = recoveries
+ return getSafes(CATEGORIES.static)
+ })
+ .then((statics) => {
+ staticSafes = statics
+ })
+ })
+
+ it('Verify there is no account recovery section in the global settings', () => {
+ cy.visit(constants.setupUrl + recoverySafes.SEP_RECOVERY_SAFE_1)
+ cy.clearLocalStorage()
+ main.acceptCookies()
+ main.verifyElementsCount(recovery.setupRecoveryModalBtn, 0)
+ })
+
+ it('Verify that non-owner can not edit and delete recovery set up on Security and Login', () => {
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ main.acceptCookies()
+ recovery.verifyRecoveryTableDisplayed()
+ main.verifyElementsCount(recovery.removeRecovererBtn, 0)
+ main.verifyElementsCount(recovery.editRecovererBtn, 0)
+ })
+
+ it('Verify that non-owner can not delete recovery set up on Modules', () => {
+ cy.visit(constants.modulesUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ main.acceptCookies()
+ main.verifyElementsStatus([modules.moduleRemoveIcon], constants.enabledStates.disabled)
+ })
+
+ it('Verify that guardian can not delete or edit recovery set up on Security and Login', () => {
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(guardian)
+ main.acceptCookies()
+ recovery.postponeRecovery()
+ recovery.verifyRecoveryTableDisplayed()
+ main.verifyElementsCount(recovery.removeRecovererBtn, 0)
+ main.verifyElementsCount(recovery.editRecovererBtn, 0)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that during the first connection to the safe "Proposal to recover account" modal is displayed for the guardian', () => {
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(guardian)
+ main.acceptCookies()
+ recovery.verifyRecoveryProposalModalState(constants.elementExistanceStates.exist)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that "Account recovery" widget is displayed in the header for the Guardian', () => {
+ cy.visit(constants.homeUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(guardian)
+ main.acceptCookies()
+ recovery.clickOnRecoverLaterBtn()
+ recovery.verifyRecoveryProposalModalState(constants.elementExistanceStates.exist, true)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that recover later option is cached and "Proposal to account recovery" modal is not displayed on next safe opening', () => {
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(guardian)
+ main.acceptCookies()
+ recovery.clickOnRecoverLaterBtn()
+ cy.reload()
+ recovery.verifyRecoveryProposalModalState(constants.elementExistanceStates.not_exist)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that "Proposal to account recovery" modal is not displayed if the user is not guardian', () => {
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(signer)
+ main.acceptCookies()
+ recovery.verifyRecoveryProposalModalState(constants.elementExistanceStates.not_exist)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that the guardian can not delete recovery set up on Modules', () => {
+ cy.visit(constants.modulesUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(guardian)
+ main.acceptCookies()
+ recovery.postponeRecovery()
+ main.verifyElementsStatus([modules.moduleRemoveIcon], constants.enabledStates.disabled)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify initial and edited recovery settings', () => {
+ const address = '0x9445...F1BA'
+ const settings = [address, recovery.recoveryOptions.fiveSixDays, recovery.recoveryOptions.never]
+ const confirmationData = [recovery.recoveryOptions.fiveMin, recovery.recoveryOptions.oneHr]
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(signer)
+ main.acceptCookies()
+ recovery.verifyRecoveryTableDisplayed()
+ recovery.verifyRecovererSettings(settings)
+ recovery.clickOnEditRecoverer()
+ recovery.clickOnNextBtn()
+ recovery.setRecoveryDelay(recovery.recoveryOptions.fiveMin)
+ recovery.setRecoveryExpiry(recovery.recoveryOptions.oneHr)
+ recovery.agreeToTerms()
+ recovery.clickOnNextBtn()
+ recovery.verifyRecovererConfirmationData(confirmationData)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that set up recovery flow can be canceled before submitting tx', () => {
+ cy.visit(constants.securityUrl + staticSafes.SEP_STATIC_SAFE_13)
+ cy.clearLocalStorage()
+ wallet.connectSigner(signer)
+ main.acceptCookies()
+ recovery.clickOnSetupRecoveryBtn()
+ recovery.clickOnSetupRecoveryModalBtn()
+ recovery.clickOnNextBtn()
+ recovery.enterRecovererAddress(constants.SEPOLIA_OWNER_2)
+ recovery.agreeToTerms()
+ recovery.clickOnNextBtn()
+ navigation.clickOnModalCloseBtn(0)
+ recovery.getSetupRecoveryBtn()
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify Recovery delay and Expiry options are present during recovery setup', () => {
+ const options = [
+ recovery.recoveryOptions.fiveMin,
+ recovery.recoveryOptions.fiveSixDays,
+ recovery.recoveryOptions.oneHr,
+ ]
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(signer)
+ main.acceptCookies()
+ recovery.verifyRecoveryTableDisplayed()
+ recovery.clickOnEditRecoverer()
+ recovery.clickOnNextBtn()
+ recovery.verifyRecoveryDelayOptions(options)
+ cy.get('body').click()
+ recovery.verifyRecoveryExpiryOptions(options)
+ cy.get('body').click()
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that there is validation for the Guardian address field', () => {
+ cy.visit(constants.securityUrl + staticSafes.SEP_STATIC_SAFE_13)
+ cy.clearLocalStorage()
+ wallet.connectSigner(signer)
+ main.acceptCookies()
+ recovery.clickOnSetupRecoveryBtn()
+ recovery.clickOnSetupRecoveryModalBtn()
+ recovery.clickOnNextBtn()
+
+ recovery.enterRecovererAddress(main.generateRandomString(10), 1)
+ owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.invalidFormat)
+
+ recovery.enterRecovererAddress(constants.DEFAULT_OWNER_ADDRESS.toUpperCase(), 1)
+ owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.invalidChecksum)
+
+ recovery.enterRecovererAddress(constants.ENS_TEST_SEPOLIA_INVALID, 1)
+ owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.failedResolve)
+
+ recovery.enterRecovererAddress(staticSafes.SEP_STATIC_SAFE_13, 1)
+ owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.ownSafeGuardian)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that recovery tx is opened after clicking on "Start recovery" button in the widget', () => {
+ cy.visit(constants.securityUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ cy.clearLocalStorage()
+ wallet.connectSigner(guardian)
+ main.acceptCookies()
+ recovery.clickOnRecoverLaterBtn()
+ cy.visit(constants.homeUrl + recoverySafes.SEP_RECOVERY_SAFE_4)
+ recovery.clickOnStartRecoveryBtn()
+ recovery.enterRecovererAddress(constants.SEPOLIA_OWNER_2)
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ })
+
+ it('Verify that the Security section contains Account recovery block on supported netwroks', () => {
+ const safes = [
+ staticSafes.ETH_STATIC_SAFE_15,
+ staticSafes.GNO_STATIC_SAFE_16,
+ staticSafes.MATIC_STATIC_SAFE_17,
+ staticSafes.SEP_STATIC_SAFE_13,
+ ]
+
+ safes.forEach((safe) => {
+ cy.visit(constants.prodbaseUrl + constants.securityUrl + safe)
+ recovery.getSetupRecoveryBtn()
+ })
+ })
+
+ it('Verify that the Security and Login section does not contain Account recovery block on unsupported networks', () => {
+ const safes = [
+ staticSafes.BNB_STATIC_SAFE_18,
+ staticSafes.AURORA_STATIC_SAFE_19,
+ staticSafes.AVAX_STATIC_SAFE_20,
+ staticSafes.LINEA_STATIC_SAFE_21,
+ staticSafes.ZKSYNC_STATIC_SAFE_22,
+ ]
+
+ safes.forEach((safe) => {
+ cy.visit(constants.prodbaseUrl + constants.securityUrl + safe)
+ main.verifyElementsCount(recovery.setupRecoveryBtn, 0)
+ })
+ })
+})
diff --git a/cypress/e2e/regression/remove_owner.cy.js b/cypress/e2e/regression/remove_owner.cy.js
index 15b0e6963..41e0b7371 100644
--- a/cypress/e2e/regression/remove_owner.cy.js
+++ b/cypress/e2e/regression/remove_owner.cy.js
@@ -18,8 +18,6 @@ describe('Remove Owners tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_13)
main.waitForHistoryCallToComplete()
- cy.clearLocalStorage()
- main.acceptCookies()
cy.contains(owner.safeAccountNonceStr, { timeout: 10000 })
})
@@ -49,6 +47,7 @@ describe('Remove Owners tests', () => {
owner.getThresholdOptions().should('have.length', 1)
})
+ // TODO: Added to prod
it('Verify owner deletion transaction has been created', () => {
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
diff --git a/cypress/e2e/regression/replace_owner.cy.js b/cypress/e2e/regression/replace_owner.cy.js
index f332faded..4f7db44a7 100644
--- a/cypress/e2e/regression/replace_owner.cy.js
+++ b/cypress/e2e/regression/replace_owner.cy.js
@@ -1,14 +1,16 @@
import * as constants from '../../support/constants'
import * as main from '../../e2e/pages/main.page'
import * as owner from '../pages/owners.pages'
-import * as addressBook from '../pages/address_book.page'
import * as createTx from '../pages/create_tx.pages.js'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
import * as wallet from '../../support/utils/wallet.js'
+import * as ls from '../../support/localstorage_data.js'
+import { getEvents, events, checkDataLayerEvents } from '../../support/utils/gtag.js'
let staticSafes = []
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signer2 = walletCredentials.OWNER_1_PRIVATE_KEY
const ownerName = 'Replacement Signer Name'
@@ -19,8 +21,6 @@ describe('Replace Owners tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4)
- cy.clearLocalStorage()
- main.acceptCookies()
cy.contains(owner.safeAccountNonceStr, { timeout: 10000 })
})
@@ -32,23 +32,17 @@ describe('Replace Owners tests', () => {
it('Verify max characters in name field', () => {
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
- owner.openReplaceOwnerWindow()
+ owner.openReplaceOwnerWindow(0)
owner.typeOwnerName(main.generateRandomString(51))
owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.exceedChars)
})
- // TODO: Rework with localstorage
it('Verify that Address input auto-fills with related value', () => {
- cy.visit(constants.addressBookUrl + staticSafes.SEP_STATIC_SAFE_4)
- addressBook.clickOnCreateEntryBtn()
- addressBook.typeInName(constants.addresBookContacts.user1.name)
- addressBook.typeInAddress(constants.addresBookContacts.user1.address)
- addressBook.clickOnSaveEntryBtn()
- addressBook.verifyNewEntryAdded(constants.addresBookContacts.user1.name, constants.addresBookContacts.user1.address)
+ main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.autofillData)
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4)
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
- owner.openReplaceOwnerWindow()
+ owner.openReplaceOwnerWindow(0)
owner.typeOwnerAddress(constants.addresBookContacts.user1.address)
owner.verifyNewOwnerName(constants.addresBookContacts.user1.name)
})
@@ -56,7 +50,7 @@ describe('Replace Owners tests', () => {
it('Verify that Name field not mandatory. Verify confirmation for owner replacement is displayed', () => {
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
- owner.openReplaceOwnerWindow()
+ owner.openReplaceOwnerWindow(0)
owner.typeOwnerAddress(constants.SEPOLIA_OWNER_2)
owner.clickOnNextBtn()
owner.verifyConfirmTransactionWindowDisplayed()
@@ -65,7 +59,7 @@ describe('Replace Owners tests', () => {
it('Verify relevant error messages are displayed in Address input', () => {
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
- owner.openReplaceOwnerWindow()
+ owner.openReplaceOwnerWindow(0)
owner.typeOwnerAddress(main.generateRandomString(10))
owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.invalidFormat)
@@ -82,19 +76,29 @@ describe('Replace Owners tests', () => {
owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.alreadyAdded)
})
- it("Verify 'Replace' tx is created", () => {
- cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4)
+ it("Verify 'Replace' tx is created. GA tx_created", () => {
+ const tx_created = [
+ {
+ eventLabel: events.txCreatedSwapOwner.eventLabel,
+ eventCategory: events.txCreatedSwapOwner.category,
+ eventAction: events.txCreatedSwapOwner.action,
+ event: events.txCreatedSwapOwner.eventName,
+ safeAddress: staticSafes.SEP_STATIC_SAFE_25.slice(6),
+ },
+ ]
+ cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_25)
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
- owner.openReplaceOwnerWindow()
+ owner.openReplaceOwnerWindow(1)
cy.wait(1000)
owner.typeOwnerName(ownerName)
owner.typeOwnerAddress(constants.SEPOLIA_OWNER_2)
- createTx.changeNonce(2)
+ createTx.changeNonce(0)
owner.clickOnNextBtn()
createTx.clickOnSignTransactionBtn()
- createTx.waitForProposeRequest()
createTx.clickViewTransaction()
createTx.verifyReplacedSigner(ownerName)
+ getEvents()
+ checkDataLayerEvents(tx_created)
})
})
diff --git a/cypress/e2e/regression/sidebar.cy.js b/cypress/e2e/regression/sidebar.cy.js
index 597b8e381..668c9ed88 100644
--- a/cypress/e2e/regression/sidebar.cy.js
+++ b/cypress/e2e/regression/sidebar.cy.js
@@ -16,14 +16,13 @@ describe('Sidebar tests', () => {
beforeEach(() => {
cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_9)
- cy.clearLocalStorage()
- main.acceptCookies()
})
it('Verify Current network is displayed at the top', () => {
sideBar.verifyNetworkIsDisplayed(constants.networks.sepolia)
})
+ // TODO: Added to prod
it('Verify current safe details', () => {
sideBar.verifySafeHeaderDetails(sideBar.testSafeHeaderDetails)
})
@@ -33,25 +32,24 @@ describe('Sidebar tests', () => {
sideBar.verifyQRModalDisplayed()
})
- it.skip('Verify Copy button copies the address', () => {
- sideBar.verifyCopyAddressBtn(staticSafes.SEP_STATIC_SAFE_9.substring(4))
- })
-
it('Verify Open blockexplorer button contain etherscan link', () => {
sideBar.verifyEtherscanLinkExists()
})
+ // TODO: Added to prod
it('Verify New transaction button enabled for owners', () => {
wallet.connectSigner(signer)
sideBar.verifyNewTxBtnStatus(constants.enabledStates.enabled)
})
+ // TODO: Added to prod
it('Verify New transaction button enabled for beneficiaries who are non-owners', () => {
cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_11)
wallet.connectSigner(signer)
sideBar.verifyNewTxBtnStatus(constants.enabledStates.enabled)
})
+ // TODO: Added to prod
it('Verify New Transaction button disabled for non-owners', () => {
main.verifyElementsCount(navigation.newTxBtn, 0)
})
diff --git a/cypress/e2e/regression/sidebar_2.cy.js b/cypress/e2e/regression/sidebar_2.cy.js
index a24156d4e..b0e56b43b 100644
--- a/cypress/e2e/regression/sidebar_2.cy.js
+++ b/cypress/e2e/regression/sidebar_2.cy.js
@@ -19,8 +19,6 @@ describe('Sidebar added sidebar tests', () => {
beforeEach(() => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_9)
cy.wait(2000)
- cy.clearLocalStorage()
- main.acceptCookies()
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addedSafes, ls.addedSafes.set2)
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.addedSafes)
})
@@ -48,15 +46,8 @@ describe('Sidebar added sidebar tests', () => {
sideBar.checkCurrencyInHeader(assets.currency$)
})
- // Waiting for endpoint from CGW
- it.skip('Verify "wallet" tag counter if the safe has tx ready for execution', () => {
+ it('Verify "wallet" tag counter if the safe has tx ready for execution', () => {
sideBar.openSidebar()
- sideBar.verifyMissingSignature(staticSafe200)
- })
-
- // Waiting for endpoint from CGW
- it.skip('Verify "Wallet" tag counter only shows for owners', () => {
- sideBar.openSidebar()
- sideBar.verifyQueuedTx(staticSafe200)
+ sideBar.verifyNumberOfPendingTxTag(1)
})
})
diff --git a/cypress/e2e/regression/sidebar_3.cy.js b/cypress/e2e/regression/sidebar_3.cy.js
index b1ff34d2c..a760e5ba0 100644
--- a/cypress/e2e/regression/sidebar_3.cy.js
+++ b/cypress/e2e/regression/sidebar_3.cy.js
@@ -19,10 +19,6 @@ describe('Sidebar tests 3', () => {
staticSafes = await getSafes(CATEGORIES.static)
})
- beforeEach(() => {
- cy.clearLocalStorage()
- })
-
it('Verify that users with no accounts see the empty state in "My accounts" block', () => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_9)
main.acceptCookies()
@@ -89,6 +85,7 @@ describe('Sidebar tests 3', () => {
main.checkButtonByTextExists(sideBar.exportBtnStr)
})
+ // TODO: Added to prod
it('Verify the "My accounts" counter at the top is counting all safes the user owns', () => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_9)
main.acceptCookies()
@@ -118,6 +115,7 @@ describe('Sidebar tests 3', () => {
sideBar.verifyAddedSafesExist([sideBar.sideBarSafes.safe3short])
})
+ // TODO: Added to prod
it('Verify pending signature is displayed in sidebar for unsigned tx', () => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_7)
main.acceptCookies()
@@ -141,10 +139,14 @@ describe('Sidebar tests 3', () => {
sideBar.checkTxToConfirm(1)
})
+ // TODO: Added to prod
it('Verify balance exists in a tx in sidebar', () => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_7)
main.acceptCookies()
wallet.connectSigner(signer)
+ owner.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer)
cy.intercept('GET', constants.safeListEndpoint, {
11155111: [sideBar.sideBarSafesPendingActions.safe1],
})
diff --git a/cypress/e2e/regression/sidebar_nonowner.cy.js b/cypress/e2e/regression/sidebar_nonowner.cy.js
index 629bbbea4..3104c1174 100644
--- a/cypress/e2e/regression/sidebar_nonowner.cy.js
+++ b/cypress/e2e/regression/sidebar_nonowner.cy.js
@@ -21,8 +21,6 @@ describe('Sidebar non-owner tests', () => {
beforeEach(() => {
cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_11)
cy.wait(2000)
- cy.clearLocalStorage()
- main.acceptCookies()
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addedSafes, ls.addedSafes.set3)
main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.addedSafes)
})
diff --git a/cypress/e2e/regression/spending_limits.cy.js b/cypress/e2e/regression/spending_limits.cy.js
index 91513b135..527a83604 100644
--- a/cypress/e2e/regression/spending_limits.cy.js
+++ b/cypress/e2e/regression/spending_limits.cy.js
@@ -10,10 +10,11 @@ import * as wallet from '../../support/utils/wallet.js'
let staticSafes = []
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signerAddress = walletCredentials.OWNER_4_WALLET_ADDRESS
const tokenAmount = 0.1
const newTokenAmount = 0.001
-const spendingLimitBalance = '(0.17 ETH)'
+const spendingLimitBalance = '(0.15 ETH)'
describe('Spending limits tests', () => {
before(async () => {
@@ -22,11 +23,31 @@ describe('Spending limits tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_8)
- cy.clearLocalStorage()
- main.acceptCookies()
cy.get(spendinglimit.spendingLimitsSection).should('be.visible')
})
+ it('Verify resetAllowance and setAllowance actions are shown if a part of allowance was used', () => {
+ wallet.connectSigner(signer)
+ spendinglimit.clickOnNewSpendingLimitBtn()
+ spendinglimit.enterBeneficiaryAddress(signerAddress)
+ spendinglimit.enterSpendingLimitAmount(0.1)
+ spendinglimit.clickOnNextBtn()
+ spendinglimit.verifyActionCount(2)
+ spendinglimit.verifyActionNames([spendinglimit.actionNames.resetAllowance, spendinglimit.actionNames.setAllowance])
+ })
+
+ it('Verify only setAllowance action is shown if allowance was not used', () => {
+ cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_23)
+ wallet.connectSigner(signer)
+ spendinglimit.clickOnNewSpendingLimitBtn()
+ spendinglimit.enterBeneficiaryAddress(signerAddress)
+ spendinglimit.enterSpendingLimitAmount(0.1)
+ spendinglimit.clickOnNextBtn()
+ spendinglimit.verifyActionCount(0)
+ spendinglimit.verifyDecodedTxSummary([spendinglimit.actionNames.setAllowance])
+ })
+
+ // TODO: Added to prod
it('Verify that the Review step shows beneficiary, amount allowed, reset time', () => {
//Assume that default reset time is set to One time
wallet.connectSigner(signer)
@@ -41,10 +62,12 @@ describe('Spending limits tests', () => {
)
})
+ // TODO: Added to prod
it('Verify values and trash icons are displayed in Beneficiary table', () => {
spendinglimit.verifyBeneficiaryTable()
})
+ // TODO: Added to prod
it('Verify Spending limit option is available when selecting the corresponding token', () => {
wallet.connectSigner(signer)
navigation.clickOnNewTxBtn()
@@ -147,11 +170,6 @@ describe('Spending limits tests', () => {
})
})
- it.skip('Verify that clicking on copy icon of a beneficiary works', () => {
- tx.verifyNumberOfCopyIcons(3)
- tx.verifyCopyIconWorks(0, constants.DEFAULT_OWNER_ADDRESS)
- })
-
it('Verify explorer links contain Sepolia link', () => {
tx.verifyNumberOfExternalLinks(3)
})
diff --git a/cypress/e2e/regression/spending_limits_nonowner.cy.js b/cypress/e2e/regression/spending_limits_nonowner.cy.js
index e64bda33f..d6fe2ede2 100644
--- a/cypress/e2e/regression/spending_limits_nonowner.cy.js
+++ b/cypress/e2e/regression/spending_limits_nonowner.cy.js
@@ -12,8 +12,6 @@ describe('Spending limits non-owner tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_3)
- cy.clearLocalStorage()
- main.acceptCookies()
cy.get(spendinglimit.spendingLimitsSection).should('be.visible')
})
diff --git a/cypress/e2e/regression/swaps.cy.js b/cypress/e2e/regression/swaps.cy.js
index c8994fdc4..e2251c7c9 100644
--- a/cypress/e2e/regression/swaps.cy.js
+++ b/cypress/e2e/regression/swaps.cy.js
@@ -7,10 +7,14 @@ import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
import * as owner from '../pages/owners.pages'
import * as wallet from '../../support/utils/wallet.js'
import * as swaps_data from '../../fixtures/swaps_data.json'
+import * as navigation from '../pages/navigation.page'
+import { getEvents, events, checkDataLayerEvents } from '../../support/utils/gtag.js'
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
const signer = walletCredentials.OWNER_4_PRIVATE_KEY
const signer2 = walletCredentials.OWNER_3_WALLET_ADDRESS
+const signer3 = walletCredentials.OWNER_1_PRIVATE_KEY
+
let staticSafes = []
let iframeSelector
@@ -23,11 +27,9 @@ describe('Swaps tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.swapUrl + staticSafes.SEP_STATIC_SAFE_1)
main.waitForHistoryCallToComplete()
wallet.connectSigner(signer)
- main.acceptCookies()
iframeSelector = `iframe[src*="${constants.swapWidget}"]`
})
@@ -42,10 +44,10 @@ describe('Swaps tests', () => {
swaps.clickOnSettingsBtn()
swaps.selectInputCurrency(swaps.swapTokens.cow)
swaps.checkTokenBalance(staticSafes.SEP_STATIC_SAFE_1.substring(4), swaps.swapTokens.cow)
- swaps.setInputValue(4)
+ swaps.setInputValue(20)
swaps.selectOutputCurrency(swaps.swapTokens.dai)
swaps.checkSwapBtnIsVisible()
- swaps.isInputGreaterZero(swaps.outputurrencyInput).then((isGreaterThanZero) => {
+ swaps.isInputGreaterZero(swaps.outputCurrencyInput).then((isGreaterThanZero) => {
cy.wrap(isGreaterThanZero).should('be.true')
})
swaps.clickOnExceeFeeChkbox()
@@ -88,6 +90,7 @@ describe('Swaps tests', () => {
}
})
.within(() => {
+ swaps.selectInputCurrency(swaps.swapTokens.cow)
swaps.clickOnSettingsBtn()
swaps.enableCustomRecipient(isCustomRecipientFound(swaps.customRecipient))
swaps.clickOnSettingsBtn()
@@ -108,6 +111,7 @@ describe('Swaps tests', () => {
main.getIframeBody(iframeSelector).then(($frame) => {
cy.wrap($frame).within(() => {
+ swaps.selectInputCurrency(swaps.swapTokens.cow)
swaps.clickOnSettingsBtn()
if (isCustomRecipientFound($frame, swaps.customRecipient)) {
@@ -134,13 +138,15 @@ describe('Swaps tests', () => {
swaps.acceptLegalDisclaimer()
cy.wait(4000)
main.getIframeBody(iframeSelector).within(() => {
+ swaps.selectInputCurrency(swaps.swapTokens.cow)
swaps.clickOnSettingsBtn()
swaps.setSlippage('0.30')
swaps.setExpiry('2')
swaps.clickOnSettingsBtn()
- swaps.setInputValue(4)
+ swaps.setInputValue(200)
+ swaps.selectOutputCurrency(swaps.swapTokens.dai)
swaps.checkSwapBtnIsVisible()
- swaps.isInputGreaterZero(swaps.outputurrencyInput).then((isGreaterThanZero) => {
+ swaps.isInputGreaterZero(swaps.outputCurrencyInput).then((isGreaterThanZero) => {
cy.wrap(isGreaterThanZero).should('be.true')
})
swaps.clickOnExceeFeeChkbox()
@@ -159,17 +165,93 @@ describe('Swaps tests', () => {
const widgetFee = swaps.getWidgetFee()
const orderID = swaps.getOrderID()
+ const isCustomRecipientFound = ($frame, customRecipient) => {
+ const element = $frame.find(customRecipient)
+ return element.length > 0
+ }
+
+ swaps.acceptLegalDisclaimer()
+ cy.wait(4000)
+ main.getIframeBody(iframeSelector).then(($frame) => {
+ cy.wrap($frame).within(() => {
+ swaps.selectInputCurrency(swaps.swapTokens.cow)
+ swaps.setInputValue(200)
+ swaps.selectOutputCurrency(swaps.swapTokens.dai)
+ swaps.checkSwapBtnIsVisible()
+ swaps.clickOnSettingsBtn()
+
+ if (isCustomRecipientFound($frame, swaps.customRecipient)) {
+ swaps.disableCustomRecipient(true)
+ cy.wait(1000)
+ swaps.enableCustomRecipient(!isCustomRecipientFound($frame, swaps.customRecipient))
+ } else {
+ swaps.enableCustomRecipient(isCustomRecipientFound($frame, swaps.customRecipient))
+ cy.wait(1000)
+ }
+
+ swaps.clickOnSettingsBtn()
+ swaps.enterRecipient(signer2)
+ swaps.clickOnExceeFeeChkbox()
+ swaps.clickOnSwapBtn()
+ swaps.clickOnSwapBtn()
+ })
+ swaps.verifyRecipientAlertIsDisplayed()
+ })
+ },
+ )
+
+ it(
+ 'Verify an order can be created, signed by second signer and deleted. GA tx_confirm, tx_created',
+ { defaultCommandTimeout: 30000 },
+ () => {
+ const tx_created = [
+ {
+ eventLabel: events.txCreatedSwap.eventLabel,
+ eventCategory: events.txCreatedSwap.category,
+ eventType: events.txCreatedSwap.eventType,
+ safeAddress: staticSafes.SEP_STATIC_SAFE_1.slice(6),
+ },
+ ]
+ const tx_confirmed = [
+ {
+ eventLabel: events.txConfirmedSwap.eventLabel,
+ eventCategory: events.txConfirmedSwap.category,
+ eventType: events.txConfirmedSwap.eventType,
+ safeAddress: staticSafes.SEP_STATIC_SAFE_1.slice(6),
+ },
+ ]
swaps.acceptLegalDisclaimer()
cy.wait(4000)
main.getIframeBody(iframeSelector).within(() => {
- swaps.setInputValue(4)
- swaps.checkSwapBtnIsVisible()
- swaps.enterRecipient(signer2)
+ swaps.clickOnSettingsBtn()
+ swaps.setSlippage('0.30')
+ swaps.setExpiry('2')
+ swaps.clickOnSettingsBtn()
+ swaps.selectInputCurrency(swaps.swapTokens.cow)
+ swaps.checkTokenBalance(staticSafes.SEP_STATIC_SAFE_1.substring(4), swaps.swapTokens.cow)
+ swaps.setInputValue(100)
+ swaps.selectOutputCurrency(swaps.swapTokens.dai)
swaps.clickOnExceeFeeChkbox()
swaps.clickOnSwapBtn()
swaps.clickOnSwapBtn()
})
- swaps.verifyRecipientAlertIsDisplayed()
+ create_tx.changeNonce(22)
+ create_tx.clickOnSignTransactionBtn()
+ create_tx.clickViewTransaction()
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer3)
+ create_tx.clickOnConfirmTransactionBtn()
+ create_tx.clickOnNoLaterOption()
+ create_tx.clickOnSignTransactionBtn()
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
+ wallet.connectSigner(signer)
+ create_tx.deleteTx()
+
+ getEvents()
+ checkDataLayerEvents(tx_created)
+ checkDataLayerEvents(tx_confirmed)
},
)
})
diff --git a/cypress/e2e/regression/swaps_history.cy.js b/cypress/e2e/regression/swaps_history.cy.js
index 95d4ffb52..9d743e254 100644
--- a/cypress/e2e/regression/swaps_history.cy.js
+++ b/cypress/e2e/regression/swaps_history.cy.js
@@ -17,9 +17,7 @@ describe('Swaps history tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_1)
- main.acceptCookies()
})
it('Verify swap selling operation with one action', { defaultCommandTimeout: 30000 }, () => {
diff --git a/cypress/e2e/regression/swaps_history_2.cy.js b/cypress/e2e/regression/swaps_history_2.cy.js
index 1f97be155..4239df212 100644
--- a/cypress/e2e/regression/swaps_history_2.cy.js
+++ b/cypress/e2e/regression/swaps_history_2.cy.js
@@ -14,10 +14,6 @@ describe('Swaps history tests 2', () => {
staticSafes = await getSafes(CATEGORIES.static)
})
- beforeEach(() => {
- cy.clearLocalStorage()
- })
-
it('Verify swap sell order with one action', { defaultCommandTimeout: 30000 }, () => {
cy.visit(constants.transactionUrl + staticSafes.SEP_STATIC_SAFE_1 + swaps.swapTxs.sell1Action)
main.acceptCookies()
@@ -25,16 +21,10 @@ describe('Swaps history tests 2', () => {
const dai = swaps.createRegex(swapsHistory.forAtLeastFullDai, 'DAI')
const eq = swaps.createRegex(swapsHistory.DAIeqCOW, 'COW')
- create_tx.verifyExpandedDetails([
- swapsHistory.sellFull,
- dai,
- eq,
- swapsHistory.dai,
- swapsHistory.filled,
- swapsHistory.gGpV2,
- ])
+ create_tx.verifyExpandedDetails([swapsHistory.sellFull, dai, eq, swapsHistory.dai, swapsHistory.filled])
})
+ // TODO: Added to prod
it('Verify swap buy operation with 2 actions: approve & swap', { defaultCommandTimeout: 30000 }, () => {
cy.visit(constants.transactionUrl + staticSafes.SEP_STATIC_SAFE_1 + swaps.swapTxs.buy2actions)
main.acceptCookies()
@@ -69,7 +59,6 @@ describe('Swaps history tests 2', () => {
eq,
swapsHistory.cow,
swapsHistory.cancelled,
- swapsHistory.gGpV2,
])
})
@@ -112,6 +101,7 @@ describe('Swaps history tests 2', () => {
create_tx.verifyExpandedDetails([swapsHistory.sellOrder, swapsHistory.sell, usdc, eq, swapsHistory.filled])
})
+ // TODO: Added to prod
it(
'Verify no decoding if tx was created using CowSwap safe-app in the history',
{ defaultCommandTimeout: 30000 },
@@ -131,7 +121,7 @@ describe('Swaps history tests 2', () => {
swapsHistory.forAtMost,
])
main.verifyValuesDoNotExist(create_tx.transactionItem, [swapsHistory.title, swapsHistory.cow, swapsHistory.dai])
- main.verifyValuesExist(create_tx.transactionItem, [swapsHistory.actionPreSignatureG, swapsHistory.safeAppTitile])
+ main.verifyValuesExist(create_tx.transactionItem, [swapsHistory.actionPreSignatureG, swapsHistory.gGpV2])
},
)
diff --git a/cypress/e2e/regression/swaps_tokens.cy.js b/cypress/e2e/regression/swaps_tokens.cy.js
index b642b8f15..effe894ea 100644
--- a/cypress/e2e/regression/swaps_tokens.cy.js
+++ b/cypress/e2e/regression/swaps_tokens.cy.js
@@ -17,11 +17,10 @@ describe('[SMOKE] Swaps token tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_1)
- main.acceptCookies()
})
+ // TODO: Added to prod
it(
'Verify that clicking the swap from assets tab, autofills that token automatically in the form',
{ defaultCommandTimeout: 30000 },
@@ -38,6 +37,7 @@ describe('[SMOKE] Swaps token tests', () => {
},
)
+ // TODO: Added to prod
it('Verify swap button are displayed in assets table and dashboard', () => {
assets.selectTokenList(assets.tokenListOptions.allTokens)
main.verifyElementsCount(swaps.assetsSwapBtn, 4)
diff --git a/cypress/e2e/regression/tokens.cy.js b/cypress/e2e/regression/tokens.cy.js
index e941adeca..3f696c1d1 100644
--- a/cypress/e2e/regression/tokens.cy.js
+++ b/cypress/e2e/regression/tokens.cy.js
@@ -2,6 +2,7 @@ import * as constants from '../../support/constants'
import * as main from '../pages/main.page'
import * as assets from '../pages/assets.pages'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as ls from '../../support/localstorage_data.js'
const ASSET_NAME_COLUMN = 0
const TOKEN_AMOUNT_COLUMN = 1
@@ -16,11 +17,14 @@ describe('Tokens tests', () => {
staticSafes = await getSafes(CATEGORIES.static)
})
beforeEach(() => {
+ main.addToLocalStorage(
+ constants.localStorageKeys.SAFE_v2__tokenlist_onboarding,
+ ls.cookies.acceptedTokenListOnboarding,
+ )
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_2)
- cy.clearLocalStorage()
- main.acceptCookies()
})
+ // TODO: Added to prod
it('Verify that non-native tokens are present and have balance', () => {
assets.selectTokenList(assets.tokenListOptions.allTokens)
assets.verifyBalance(assets.currencyDaiCap, TOKEN_AMOUNT_COLUMN, assets.currencyDaiAlttext)
@@ -170,6 +174,7 @@ describe('Tokens tests', () => {
assets.verifyTokenBalanceOrder('descending')
})
+ // TODO: Added to prod
//Include in smoke.
it('Verify that when owner is disconnected, Send button is disabled', () => {
assets.selectTokenList(assets.tokenListOptions.allTokens)
@@ -177,6 +182,7 @@ describe('Tokens tests', () => {
assets.VerifySendButtonIsDisabled()
})
+ // TODO: Added to prod
it('Verify that when connected user is not owner, Send button is disabled', () => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_3)
assets.selectTokenList(assets.tokenListOptions.allTokens)
diff --git a/cypress/e2e/regression/tx_decoding.cy.js b/cypress/e2e/regression/tx_decoding.cy.js
new file mode 100644
index 000000000..9bf12f199
--- /dev/null
+++ b/cypress/e2e/regression/tx_decoding.cy.js
@@ -0,0 +1,17 @@
+import * as main from '../pages/main.page.js'
+import * as createTx from '../pages/create_tx.pages.js'
+import * as constants from '../../support/constants.js'
+
+const safe = 'sep:0x2a73e61bd15b25B6958b4DA3bfc759ca4db249b9'
+const decodedTx =
+ '&id=multisig_0x2a73e61bd15b25B6958b4DA3bfc759ca4db249b9_0xa3e73a212d7025c08048a05dcd829a88d1bf8a7c0d9eaf453b3b6039ad6156f3'
+
+//TODO: Check file error
+describe('Tx decoding tests', () => {
+ it.skip('Check visual tx', () => {
+ cy.visit(constants.transactionUrl + safe + decodedTx)
+ createTx.clickOnExpandAllActionsBtn()
+ cy.wait(1000)
+ cy.compareSnapshot('tx_decoding', { errorThreshold: 0, failSilently: false })
+ })
+})
diff --git a/cypress/e2e/regression/tx_history.cy.js b/cypress/e2e/regression/tx_history.cy.js
index d18372001..f5e39f5d4 100644
--- a/cypress/e2e/regression/tx_history.cy.js
+++ b/cypress/e2e/regression/tx_history.cy.js
@@ -20,11 +20,22 @@ describe('Tx history tests 1', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
+ cy.intercept(
+ 'GET',
+ `**${constants.stagingCGWChains}${constants.networkKeys.sepolia}/${
+ constants.stagingCGWSafes
+ }${staticSafes.SEP_STATIC_SAFE_7.substring(4)}/transactions/history**`,
+ (req) => {
+ req.url = `https://safe-client.staging.5afe.dev/v1/chains/11155111/safes/0x5912f6616c84024cD1aff0D5b55bb36F5180fFdb/transactions/history?timezone=Europe/Berlin&trusted=false&cursor=limit=100&offset=1`
+ req.continue()
+ },
+ ).as('allTransactions')
+
cy.visit(constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_7)
- main.acceptCookies()
+ cy.wait('@allTransactions')
})
+ // TODO: Added to prod
// Account creation
it('Verify summary for account creation', () => {
createTx.verifySummaryByName(
@@ -34,6 +45,7 @@ describe('Tx history tests 1', () => {
)
})
+ // TODO: Added to prod
it('Verify exapanded details for account creation', () => {
createTx.clickOnTransactionItemByName(typeCreateAccount.title)
createTx.verifyExpandedDetails([
@@ -49,24 +61,12 @@ describe('Tx history tests 1', () => {
])
})
- it.skip('Verify copy bottons work as expected for account creation', () => {
- createTx.clickOnTransactionItemByName(typeCreateAccount.title)
- createTx.verifyNumberOfCopyIcons(4)
- createTx.verifyCopyIconWorks(0, typeCreateAccount.creator.address)
- })
-
it('Verify external links exist for account creation', () => {
createTx.clickOnTransactionItemByName(typeCreateAccount.title)
createTx.verifyNumberOfExternalLinks(4)
})
- // Token receipt
- it.skip('Verify copy button copies tx hash', () => {
- createTx.clickOnTransactionItemByName(typeReceive.summaryTitle, typeReceive.summaryTxInfo)
- createTx.verifyNumberOfCopyIcons(2)
- createTx.verifyCopyIconWorks(1, typeReceive.transactionHashCopied)
- })
-
+ // TODO: Added to prod
// Token send
it('Verify exapanded details for token send', () => {
createTx.clickOnTransactionItemByName(typeSend.title, typeSend.summaryTxInfo)
@@ -78,6 +78,7 @@ describe('Tx history tests 1', () => {
])
})
+ // TODO: Added to prod
// Spending limits
it('Verify summary for setting spend limits', () => {
createTx.verifySummaryByName(
@@ -88,12 +89,13 @@ describe('Tx history tests 1', () => {
)
})
+ // TODO: Added to prod
it('Verify exapanded details for initial spending limits setup', () => {
createTx.clickOnTransactionItemByName(typeSpendingLimits.title, typeSpendingLimits.summaryTxInfo)
createTx.verifyExpandedDetails(
[
- typeSpendingLimits.title,
- typeSpendingLimits.description,
+ typeSpendingLimits.contractTitle,
+ typeSpendingLimits.call_multiSend,
typeSpendingLimits.transactionHash,
typeSpendingLimits.safeTxHash,
],
@@ -101,6 +103,7 @@ describe('Tx history tests 1', () => {
)
})
+ // TODO: Added to prod
it('Verify that 3 actions exist in initial spending limits setup', () => {
createTx.clickOnTransactionItemByName(typeSpendingLimits.title, typeSpendingLimits.summaryTxInfo)
createTx.verifyActions([
@@ -145,6 +148,7 @@ describe('Tx history tests 1', () => {
])
})
+ // TODO: Added to prod
it('Verify advanced details displayed in exapanded details for allowance deletion', () => {
createTx.clickOnTransactionItemByName(typeDeleteAllowance.title, typeDeleteAllowance.summaryTxInfo)
createTx.expandAdvancedDetails([typeDeleteAllowance.baseGas])
diff --git a/cypress/e2e/regression/tx_history_2.cy.js b/cypress/e2e/regression/tx_history_2.cy.js
index db98c415c..5b4be913b 100644
--- a/cypress/e2e/regression/tx_history_2.cy.js
+++ b/cypress/e2e/regression/tx_history_2.cy.js
@@ -23,15 +23,25 @@ describe('Tx history tests 2', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
+ cy.intercept(
+ 'GET',
+ `**${constants.stagingCGWChains}${constants.networkKeys.sepolia}/${
+ constants.stagingCGWSafes
+ }${staticSafes.SEP_STATIC_SAFE_7.substring(4)}/transactions/history**`,
+ (req) => {
+ req.url = `https://safe-client.staging.5afe.dev/v1/chains/11155111/safes/0x5912f6616c84024cD1aff0D5b55bb36F5180fFdb/transactions/history?timezone=Europe/Berlin&trusted=false&cursor=limit=100&offset=1`
+ req.continue()
+ },
+ ).as('allTransactions')
+
cy.visit(constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_7)
- main.acceptCookies()
})
it('Verify number of transactions is correct', () => {
createTx.verifyNumberOfTransactions(20)
})
+ // TODO: Added to prod
// On-chain rejection
it('Verify exapanded details for on-chain rejection', () => {
createTx.clickOnTransactionItemByName(typeOnchainRejection.title)
@@ -47,22 +57,18 @@ describe('Tx history tests 2', () => {
])
})
+ // TODO: Added to prod
// Batch transaction
it('Verify exapanded details for batch', () => {
createTx.clickOnTransactionItemByName(typeBatch.title, typeBatch.summaryTxInfo)
createTx.verifyExpandedDetails(
- [
- typeBatch.description,
- typeBatch.contractTitle,
- typeBatch.contractAddress,
- typeBatch.transactionHash,
- typeBatch.safeTxHash,
- ],
+ [typeBatch.contractTitle, typeBatch.transactionHash, typeBatch.safeTxHash],
createTx.delegateCallWarning,
)
createTx.verifyActions([typeBatch.nativeTransfer.title])
})
+ // TODO: Added to prod
// Add owner
it('Verify summary for adding owner', () => {
createTx.verifySummaryByName(typeAddOwner.title, [typeGeneral.statusOk], typeAddOwner.altImage)
@@ -82,11 +88,13 @@ describe('Tx history tests 2', () => {
)
})
+ // TODO: Added to prod
// Change owner
it('Verify summary for changing owner', () => {
createTx.verifySummaryByName(typeChangeOwner.title, [typeGeneral.statusOk], typeChangeOwner.altImage)
})
+ // TODO: Added to prod
it('Verify exapanded details for changing owner', () => {
createTx.clickOnTransactionItemByName(typeChangeOwner.title)
createTx.verifyExpandedDetails([
@@ -101,6 +109,7 @@ describe('Tx history tests 2', () => {
])
})
+ // TODO: Added to prod
// Remove owner
it('Verify summary for removing owner', () => {
createTx.verifySummaryByName(typeRemoveOwner.title, [typeGeneral.statusOk], typeRemoveOwner.altImage)
@@ -121,6 +130,7 @@ describe('Tx history tests 2', () => {
createTx.checkRequiredThreshold(1)
})
+ // TODO: Added to prod
// Disbale module
it('Verify summary for disable module', () => {
createTx.verifySummaryByName(typeDisableOwner.title, [typeGeneral.statusOk], typeDisableOwner.altImage)
@@ -136,6 +146,7 @@ describe('Tx history tests 2', () => {
])
})
+ // TODO: Added to prod
// Change threshold
it('Verify summary for changing threshold', () => {
createTx.verifySummaryByName(
@@ -145,6 +156,7 @@ describe('Tx history tests 2', () => {
)
})
+ // TODO: Added to prod
it('Verify exapanded details for changing threshold', () => {
createTx.clickOnTransactionItemByName(typeChangeThreshold.title)
createTx.verifyExpandedDetails(
@@ -158,6 +170,7 @@ describe('Tx history tests 2', () => {
createTx.checkRequiredThreshold(2)
})
+ // TODO: Added to prod
it('Verify that sender address of untrusted token will not be copied until agreed in warning popup', () => {
createTx.clickOnTransactionItemByName(typeUntrustedToken.summaryTitle, typeUntrustedToken.summaryTxInfo)
createTx.verifyAddressNotCopied(0, typeUntrustedToken.senderAddress)
diff --git a/cypress/e2e/safe-apps/apps_list.cy.js b/cypress/e2e/safe-apps/apps_list.cy.js
index 7d3e4750e..cf9682c42 100644
--- a/cypress/e2e/safe-apps/apps_list.cy.js
+++ b/cypress/e2e/safe-apps/apps_list.cy.js
@@ -2,6 +2,7 @@ import * as constants from '../../support/constants'
import * as main from '../pages/main.page'
import * as safeapps from '../pages/safeapps.pages'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as ls from '../../support/localstorage_data.js'
const myCustomAppTitle = 'Cypress Test App'
const myCustomAppDescrAdded = 'Cypress Test App Description'
@@ -14,11 +15,9 @@ describe('Safe Apps list tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(`${constants.appsUrl}?safe=${staticSafes.SEP_STATIC_SAFE_1}`, {
failOnStatusCode: false,
})
- main.acceptCookies()
})
it('Verify app list can be filtered by app name', () => {
diff --git a/cypress/e2e/safe-apps/browser_permissions.cy.js b/cypress/e2e/safe-apps/browser_permissions.cy.js
index fc08642fd..f42f30af6 100644
--- a/cypress/e2e/safe-apps/browser_permissions.cy.js
+++ b/cypress/e2e/safe-apps/browser_permissions.cy.js
@@ -4,7 +4,6 @@ import * as safeapps from '../pages/safeapps.pages'
describe('Browser permissions tests', () => {
beforeEach(() => {
- cy.clearLocalStorage()
cy.fixture('safe-app').then((html) => {
cy.intercept('GET', `${constants.testAppUrl}/*`, html)
cy.intercept('GET', `*/manifest.json`, {
@@ -15,7 +14,6 @@ describe('Browser permissions tests', () => {
})
})
cy.visitSafeApp(`${constants.testAppUrl}/app`)
- main.acceptCookies()
})
// @TODO: unknown apps don't have permissions
diff --git a/cypress/e2e/safe-apps/drain_account.spec.cy.js b/cypress/e2e/safe-apps/drain_account.spec.cy.js
index 032e75157..a5a01c84e 100644
--- a/cypress/e2e/safe-apps/drain_account.spec.cy.js
+++ b/cypress/e2e/safe-apps/drain_account.spec.cy.js
@@ -4,11 +4,16 @@ import * as main from '../pages/main.page'
import * as safeapps from '../pages/safeapps.pages'
import * as navigation from '../pages/navigation.page'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as ls from '../../support/localstorage_data.js'
+import * as wallet from '../../support/utils/wallet.js'
let safeAppSafes = []
let iframeSelector
-describe('Drain Account tests', { defaultCommandTimeout: 12000 }, () => {
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+
+describe('Drain Account tests', () => {
before(async () => {
safeAppSafes = await getSafes(CATEGORIES.safeapps)
})
@@ -17,27 +22,26 @@ describe('Drain Account tests', { defaultCommandTimeout: 12000 }, () => {
const appUrl = constants.drainAccount_url
iframeSelector = `iframe[id="iframe-${appUrl}"]`
const visitUrl = `/apps/open?safe=${safeAppSafes.SEP_SAFEAPP_SAFE_1}&appUrl=${encodeURIComponent(appUrl)}`
-
cy.intercept(`**//v1/chains/11155111/safes/${safeAppSafes.SEP_SAFEAPP_SAFE_1.substring(4)}/balances/**`, {
fixture: 'balances.json',
})
-
- cy.clearLocalStorage()
cy.visit(visitUrl)
- main.acceptCookies()
- safeapps.clickOnContinueBtn()
})
it('Verify drain can be created', () => {
+ wallet.connectSigner(signer)
cy.enter(iframeSelector).then((getBody) => {
getBody().findByLabelText(safeapps.recipientStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
getBody().findAllByText(safeapps.transferEverythingStr).click()
})
cy.findByRole('button', { name: safeapps.testTransfer1 })
cy.findByRole('button', { name: safeapps.nativeTransfer2 })
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
it('Verify partial drain can be created', () => {
+ wallet.connectSigner(signer)
cy.enter(iframeSelector).then((getBody) => {
getBody().findByLabelText(safeapps.selectAllRowsChbxStr).click()
getBody().findAllByLabelText(safeapps.selectRowChbxStr).eq(1).click()
@@ -47,6 +51,8 @@ describe('Drain Account tests', { defaultCommandTimeout: 12000 }, () => {
})
cy.findByRole('button', { name: safeapps.testTransfer2 })
cy.findByRole('button', { name: safeapps.nativeTransfer1 })
+ navigation.clickOnWalletExpandMoreIcon()
+ navigation.clickOnDisconnectBtn()
})
// TODO: ENS does not resolve
@@ -64,7 +70,7 @@ describe('Drain Account tests', { defaultCommandTimeout: 12000 }, () => {
getBody().findByLabelText(safeapps.recipientStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
getBody().findAllByText(safeapps.transferEverythingStr).click()
})
- navigation.clickOnModalCloseBtn()
+ navigation.clickOnModalCloseBtn(1)
cy.enter(iframeSelector).then((getBody) => {
getBody().findAllByText(safeapps.transferEverythingStr).should('be.visible')
})
diff --git a/cypress/e2e/safe-apps/info_modal.cy.js b/cypress/e2e/safe-apps/info_modal.cy.js
index 3a361ebbf..30b912de9 100644
--- a/cypress/e2e/safe-apps/info_modal.cy.js
+++ b/cypress/e2e/safe-apps/info_modal.cy.js
@@ -11,28 +11,27 @@ describe('Info modal tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(`${constants.appsUrl}?safe=${staticSafes.SEP_STATIC_SAFE_2}`, {
failOnStatusCode: false,
})
- main.acceptCookies()
})
it('Verify the disclaimer is displayed when a Safe App is opened', () => {
+ // Required to show disclaimer
+ cy.clearLocalStorage()
+ main.acceptCookies()
safeapps.clickOnApp(safeapps.transactionBuilderStr)
safeapps.clickOnOpenSafeAppBtn()
safeapps.verifyDisclaimerIsDisplayed()
})
- // Skip tests due to changed logic
- // TODO: Discuss furthers
- it.skip('Verify the permissions slide is shown if the app require permissions', () => {
- safeapps.clickOnContinueBtn()
- cy.wait(500) // wait for the animation to finish
- safeapps.verifyCameraCheckBoxExists()
- })
-
- it.skip('Verify the permissions and consents decision are stored when accepted', () => {
- safeapps.storeAndVerifyPermissions()
+ it('Verify info modal consent is stored when accepted', { defaultCommandTimeout: 20000 }, () => {
+ // Required to show disclaimer
+ cy.clearLocalStorage()
+ main.acceptCookies()
+ safeapps.clickOnApp(safeapps.transactionBuilderStr)
+ safeapps.clickOnOpenSafeAppBtn()
+ safeapps.verifyDisclaimerIsDisplayed()
+ safeapps.verifyInfoModalAcceptance()
})
})
diff --git a/cypress/e2e/safe-apps/permissions_settings.cy.js b/cypress/e2e/safe-apps/permissions_settings.cy.js
index 53df8b6ce..ee1d65e32 100644
--- a/cypress/e2e/safe-apps/permissions_settings.cy.js
+++ b/cypress/e2e/safe-apps/permissions_settings.cy.js
@@ -13,7 +13,6 @@ describe.skip('Permissions settings tests', () => {
before(() => {
getSafes(CATEGORIES.static).then((statics) => {
staticSafes = statics
- cy.clearLocalStorage()
cy.on('window:before:load', (window) => {
window.localStorage.setItem(
constants.BROWSER_PERMISSIONS_KEY,
@@ -52,7 +51,6 @@ describe.skip('Permissions settings tests', () => {
cy.visit(`${constants.appSettingsUrl}?safe=${staticSafes.SEP_STATIC_SAFE_2}`, {
failOnStatusCode: false,
})
- main.acceptCookies()
})
})
diff --git a/cypress/e2e/safe-apps/preview_drawer.cy.js b/cypress/e2e/safe-apps/preview_drawer.cy.js
index e24c590fa..d20ee5fca 100644
--- a/cypress/e2e/safe-apps/preview_drawer.cy.js
+++ b/cypress/e2e/safe-apps/preview_drawer.cy.js
@@ -2,6 +2,7 @@ import * as constants from '../../support/constants'
import * as main from '../pages/main.page'
import * as safeapps from '../pages/safeapps.pages'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as ls from '../../support/localstorage_data.js'
let staticSafes = []
@@ -11,11 +12,9 @@ describe('Preview drawer tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(`${constants.appsUrl}?safe=${staticSafes.SEP_STATIC_SAFE_2}`, {
failOnStatusCode: false,
})
- main.acceptCookies()
})
it('Verify the preview drawer is displayed when opening a Safe App from the app list', () => {
diff --git a/cypress/e2e/safe-apps/safe_permissions.cy.js b/cypress/e2e/safe-apps/safe_permissions.cy.js
index 0b64e41ad..d91082cbf 100644
--- a/cypress/e2e/safe-apps/safe_permissions.cy.js
+++ b/cypress/e2e/safe-apps/safe_permissions.cy.js
@@ -1,10 +1,10 @@
import * as constants from '../../support/constants'
import * as safeapps from '../pages/safeapps.pages'
import * as main from '../pages/main.page'
+import * as ls from '../../support/localstorage_data.js'
describe('Safe permissions system tests', () => {
beforeEach(() => {
- cy.clearLocalStorage()
cy.fixture('safe-app').then((html) => {
cy.intercept('GET', `${constants.testAppUrl}/*`, html)
cy.intercept('GET', `*/manifest.json`, {
@@ -17,11 +17,6 @@ describe('Safe permissions system tests', () => {
it('Verify that requesting permissions with wallet_requestPermissions shows the permissions prompt and return the permissions on accept', () => {
cy.visitSafeApp(constants.testAppUrl + constants.requestPermissionsUrl)
- main.acceptCookies()
- safeapps.clickOnContinueBtn()
- safeapps.verifyWarningDefaultAppMsgIsDisplayed()
- safeapps.clickOnContinueBtn()
-
safeapps.verifyPermissionsRequestExists()
safeapps.verifyAccessToAddressBookExists()
safeapps.clickOnAcceptBtn()
@@ -56,11 +51,6 @@ describe('Safe permissions system tests', () => {
})
cy.visitSafeApp(constants.testAppUrl + constants.getPermissionsUrl)
- main.acceptCookies()
- safeapps.clickOnContinueBtn()
- safeapps.verifyWarningDefaultAppMsgIsDisplayed()
- safeapps.clickOnContinueBtn()
-
cy.get('@safeAppsMessage').should('have.been.calledWithMatch', {
data: [
{
diff --git a/cypress/e2e/safe-apps/tx-builder.2spec.cy.js b/cypress/e2e/safe-apps/tx-builder.2spec.cy.js
new file mode 100644
index 000000000..7f059a65a
--- /dev/null
+++ b/cypress/e2e/safe-apps/tx-builder.2spec.cy.js
@@ -0,0 +1,172 @@
+import 'cypress-file-upload'
+import * as constants from '../../support/constants.js'
+import * as safeapps from '../pages/safeapps.pages.js'
+import * as createtx from '../pages/create_tx.pages.js'
+import * as navigation from '../pages/navigation.page.js'
+import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as ls from '../../support/localstorage_data.js'
+import { getEvents, events, checkDataLayerEvents } from '../../support/utils/gtag.js'
+import * as wallet from '../../support/utils/wallet.js'
+import * as utils from '../../support/utils/checkers.js'
+
+let safeAppSafes = []
+let iframeSelector
+
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signer2 = walletCredentials.OWNER_1_PRIVATE_KEY
+
+describe('Transaction Builder 2 tests', { defaultCommandTimeout: 20000 }, () => {
+ before(async () => {
+ safeAppSafes = await getSafes(CATEGORIES.safeapps)
+ })
+
+ beforeEach(() => {
+ const appUrl = constants.TX_Builder_url
+ iframeSelector = `iframe[id="iframe-${appUrl}"]`
+ const visitUrl = `/apps/open?safe=${safeAppSafes.SEP_SAFEAPP_SAFE_1}&appUrl=${encodeURIComponent(appUrl)}`
+ cy.visit(visitUrl)
+ })
+
+ it('Verify a batch cannot be created without method data', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS)
+ getBody().findByText(safeapps.addTransactionStr).click()
+ getBody()
+ .findAllByText(safeapps.requiredStr)
+ .then(($element) => {
+ const color = $element.css('color')
+ expect(utils.isInRedRange(color), 'Element color is ').to.be.true
+ })
+ })
+ })
+
+ it('Verify a batch can be uploaded, saved to library, downloaded and removed', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody().findAllByText('choose a file').attachFile('test-working-batch.json', { subjectType: 'drag-n-drop' })
+ getBody().findAllByText('uploaded').wait(300)
+ getBody().find(safeapps.saveToLibraryBtn).click()
+ getBody().findByLabelText(safeapps.batchNameStr).type(safeapps.e3eTestStr)
+ getBody().findAllByText(safeapps.createBtnStr).should('not.be.disabled').click()
+ getBody().findByText(safeapps.transactionLibraryStr).click()
+ getBody().find(safeapps.downloadBatchBtn).click()
+ getBody().find(safeapps.deleteBatchBtn).click()
+ getBody().findAllByText(safeapps.confirmDeleteBtnStr).should('not.be.disabled').click()
+ getBody().findByText(safeapps.noSavedBatchesStr).should('be.visible')
+ getBody().findByText(safeapps.backToTransactionStr).should('be.visible')
+ })
+ cy.readFile('cypress/downloads/E2E test.json').should('exist')
+ })
+
+ it('Verify there is notification if uploaded batch is from a different chain', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody().findAllByText('choose a file').attachFile('test-mainnet-batch.json', { subjectType: 'drag-n-drop' })
+ getBody().findAllByText(safeapps.warningStr).should('be.visible')
+ getBody().findAllByText(safeapps.anotherChainStr).should('be.visible')
+ })
+ })
+
+ it('Verify there is error message when a modified batch is uploaded', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody().findAllByText('choose a file').attachFile('test-modified-batch.json', { subjectType: 'drag-n-drop' })
+ getBody().findAllByText(safeapps.changedPropertiesStr)
+ getBody().findAllByText('choose a file').should('be.visible')
+ })
+ })
+
+ it('Verify an invalid batch cannot be uploaded', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody()
+ .findAllByText('choose a file')
+ .attachFile('test-invalid-batch.json', { subjectType: 'drag-n-drop' })
+ .findAllByText('choose a file')
+ .should('be.visible')
+ })
+ })
+
+ it('Verify an empty batch cannot be uploaded', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody()
+ .findAllByText('choose a file')
+ .attachFile('test-empty-batch.json', { subjectType: 'drag-n-drop' })
+ .findAllByText('choose a file')
+ .should('be.visible')
+ })
+ })
+
+ it('Verify a valid batch as successful can be simulated', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody().findByLabelText(safeapps.enterAddressStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
+ getBody().findByText(safeapps.keepProxiABIStr).click()
+ getBody().findByLabelText(safeapps.tokenAmount).type('0')
+ getBody().findByText(safeapps.addTransactionStr).click()
+ getBody().findByText(safeapps.createBatchStr).click()
+ getBody().findByText(safeapps.simulateBtnStr).click()
+ getBody().findByText(safeapps.transferStr).should('be.visible')
+ getBody().findByText(safeapps.successStr).should('be.visible')
+ })
+ })
+
+ it('Verify an invalid batch as failed can be simulated', () => {
+ cy.enter(iframeSelector).then((getBody) => {
+ getBody().findByLabelText(safeapps.enterAddressStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
+ getBody().findByText(safeapps.keepProxiABIStr).click()
+ getBody().findByLabelText(safeapps.tokenAmount).type('100')
+ getBody().findByText(safeapps.addTransactionStr).click()
+ getBody().findByText(safeapps.createBatchStr).click()
+ getBody().findByText(safeapps.simulateBtnStr).click()
+ getBody().findByText(safeapps.failedStr).should('be.visible')
+ })
+ })
+
+ // TODO: Fix visibility element
+ it('Verify a simple batch can be created, signed by second signer and deleted. GA tx_confirm, tx_created', () => {
+ const tx_created = [
+ {
+ eventLabel: events.txCreatedTxBuilder.eventLabel,
+ eventCategory: events.txCreatedTxBuilder.category,
+ eventType: events.txCreatedTxBuilder.eventType,
+ event: events.txCreatedTxBuilder.event,
+ safeAddress: safeAppSafes.SEP_SAFEAPP_SAFE_1.slice(6),
+ },
+ ]
+ const tx_confirmed = [
+ {
+ eventLabel: events.txConfirmedTxBuilder.eventLabel,
+ eventCategory: events.txConfirmedTxBuilder.category,
+ eventType: events.txConfirmedTxBuilder.eventType,
+ safeAddress: safeAppSafes.SEP_SAFEAPP_SAFE_1.slice(6),
+ },
+ ]
+ // wallet.connectSigner(signer)
+ // cy.enter(iframeSelector).then((getBody) => {
+ // getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS)
+ // getBody().find(safeapps.contractMethodIndex).parent().click()
+ // getBody().findByRole('option', { name: safeapps.testAddressValue2 }).click()
+ // getBody().findByLabelText(safeapps.newAddressValueStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
+ // getBody().findByText(safeapps.addTransactionStr).click()
+ // getBody().findAllByText(constants.SEPOLIA_CONTRACT_SHORT).should('have.length', 1)
+ // getBody().findByText(safeapps.testAddressValueStr).should('exist')
+ // getBody().findByText(safeapps.createBatchStr).click()
+ // getBody().findByText(safeapps.sendBatchStr).click()
+ // })
+
+ // createtx.clickOnSignTransactionBtn()
+ // createtx.clickViewTransaction()
+ // navigation.clickOnWalletExpandMoreIcon()
+ // navigation.clickOnDisconnectBtn()
+ // wallet.connectSigner(signer2)
+
+ // createtx.clickOnConfirmTransactionBtn()
+ // createtx.clickOnNoLaterOption()
+ // createtx.clickOnSignTransactionBtn()
+ // navigation.clickOnWalletExpandMoreIcon()
+ // navigation.clickOnDisconnectBtn()
+ // wallet.connectSigner(signer)
+ // createtx.deleteTx()
+ // createtx.verifyNumberOfTransactions(0)
+ // getEvents()
+ // checkDataLayerEvents(tx_created)
+ // checkDataLayerEvents(tx_confirmed)
+ })
+})
diff --git a/cypress/e2e/safe-apps/tx-builder.spec.cy.js b/cypress/e2e/safe-apps/tx-builder.spec.cy.js
index 7dd84ba19..8bf6f67ce 100644
--- a/cypress/e2e/safe-apps/tx-builder.spec.cy.js
+++ b/cypress/e2e/safe-apps/tx-builder.spec.cy.js
@@ -1,14 +1,21 @@
import 'cypress-file-upload'
import * as constants from '../../support/constants'
-import * as main from '../pages/main.page'
import * as safeapps from '../pages/safeapps.pages'
import * as createtx from '../../e2e/pages/create_tx.pages'
import * as navigation from '../pages/navigation.page'
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
+import * as ls from '../../support/localstorage_data.js'
+import { getEvents, events, checkDataLayerEvents } from '../../support/utils/gtag.js'
+import * as wallet from '../../support/utils/wallet.js'
+import * as utils from '../../support/utils/checkers.js'
let safeAppSafes = []
let iframeSelector
+const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
+const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+const signer2 = walletCredentials.OWNER_1_PRIVATE_KEY
+
describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
before(async () => {
safeAppSafes = await getSafes(CATEGORIES.safeapps)
@@ -18,14 +25,11 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
const appUrl = constants.TX_Builder_url
iframeSelector = `iframe[id="iframe-${appUrl}"]`
const visitUrl = `/apps/open?safe=${safeAppSafes.SEP_SAFEAPP_SAFE_1}&appUrl=${encodeURIComponent(appUrl)}`
-
- cy.clearLocalStorage()
cy.visit(visitUrl)
- main.acceptCookies()
- safeapps.clickOnContinueBtn()
})
- it('Verify a simple batch can be created', () => {
+ // TODO: Check if we still need this test as we now create complete flow of creating, signing and deleting a tx
+ it.skip('Verify a simple batch can be created', () => {
cy.enter(iframeSelector).then((getBody) => {
getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS)
getBody().find(safeapps.contractMethodIndex).parent().click()
@@ -69,8 +73,8 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
getBody().findByText(safeapps.sendBatchStr).click()
})
cy.get('h4').contains(safeapps.transactionBuilderStr).should('be.visible')
- cy.findAllByText(safeapps.testBooleanValue).should('have.length', 3)
- navigation.clickOnModalCloseBtn()
+ cy.findAllByText(safeapps.testBooleanValue).should('have.length', 6)
+ navigation.clickOnModalCloseBtn(0)
cy.enter(iframeSelector).then((getBody) => {
getBody().findAllByText(constants.SEPOLIA_CONTRACT_SHORT).should('have.length', 3)
getBody().findAllByText(safeapps.testBooleanValue).should('have.length', 3)
@@ -96,7 +100,7 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
cy.findByText(safeapps.thresholdStr2).should('exist')
})
- it('Verify a batch can be created from an ABI', () => {
+ it.skip('Verify a batch can be created from an ABI', () => {
cy.enter(iframeSelector).then((getBody) => {
getBody().findByLabelText(safeapps.enterABIStr).type(safeapps.abi)
getBody().findByLabelText(safeapps.toAddressStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
@@ -111,7 +115,7 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
getBody().findByText(safeapps.sendBatchStr).click()
})
cy.get('h4').contains(safeapps.transactionBuilderStr).should('be.visible')
- navigation.clickOnModalCloseBtn()
+ navigation.clickOnModalCloseBtn(0)
cy.enter(iframeSelector).then((getBody) => {
getBody().findAllByText(constants.SEPOLIA_RECIPIENT_ADDR_SHORT).should('have.length', 1)
getBody().findAllByText(safeapps.testFallback).should('have.length', 1)
@@ -133,7 +137,7 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
getBody().findByText(safeapps.sendBatchStr).click()
})
cy.get('h4').contains(safeapps.transactionBuilderStr).should('be.visible')
- navigation.clickOnModalCloseBtn()
+ navigation.clickOnModalCloseBtn(0)
cy.enter(iframeSelector).then((getBody) => {
getBody().findAllByText(constants.SEPOLIA_CONTRACT_SHORT).should('have.length', 1)
getBody().findAllByText(safeapps.customData).should('have.length', 1)
@@ -188,8 +192,8 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
getBody().findByText(safeapps.sendBatchStr).click()
})
cy.get('h4').contains(safeapps.transactionBuilderStr).should('be.visible')
- cy.findAllByText(safeapps.testAddressValueStr).should('have.length', 2)
- navigation.clickOnModalCloseBtn()
+ cy.findAllByText(safeapps.testAddressValueStr).should('have.length', 4)
+ navigation.clickOnModalCloseBtn(0)
cy.enter(iframeSelector).then((getBody) => {
getBody().findAllByText(constants.SEPOLIA_CONTRACT_SHORT).should('have.length', 2)
getBody().findAllByText(safeapps.testAddressValueStr).should('have.length', 2)
@@ -201,103 +205,24 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => {
getBody().findByLabelText(safeapps.enterAddressStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2.substring(5))
getBody()
.findAllByText(safeapps.addressNotValidStr)
- .should('have.css', 'color', 'rgb(244, 67, 54)')
- .and('be.visible')
+ .then(($element) => {
+ const color = $element.css('color')
+ expect(utils.isInRedRange(color), 'Element color is ').to.be.true
+ })
})
})
- it.skip('Verify a batch cannot be created without asset amount', () => {
+ it('Verify a batch cannot be created without asset amount', () => {
cy.enter(iframeSelector).then((getBody) => {
getBody().findByLabelText(safeapps.enterAddressStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
getBody().findByText(safeapps.keepProxiABIStr).click()
getBody().findByText(safeapps.addTransactionStr).click()
- getBody().findAllByText(safeapps.requiredStr).should('have.css', 'color', 'rgb(244, 67, 54)')
- })
- })
-
- it('Verify a batch cannot be created without method data', () => {
- cy.enter(iframeSelector).then((getBody) => {
- getBody().findByLabelText(safeapps.enterAddressStr).type(constants.SAFE_APP_ADDRESS)
- getBody().findByText(safeapps.addTransactionStr).click()
- getBody().findAllByText(safeapps.requiredStr).should('have.css', 'color', 'rgb(244, 67, 54)')
- })
- })
-
- it('Verify a batch can be uploaded, saved to library, downloaded and removed', () => {
- cy.enter(iframeSelector).then((getBody) => {
- getBody().findAllByText('choose a file').attachFile('test-working-batch.json', { subjectType: 'drag-n-drop' })
- getBody().findAllByText('uploaded').wait(300)
- getBody().find(safeapps.saveToLibraryBtn).click()
- getBody().findByLabelText(safeapps.batchNameStr).type(safeapps.e3eTestStr)
- getBody().findAllByText(safeapps.createBtnStr).should('not.be.disabled').click()
- getBody().findByText(safeapps.transactionLibraryStr).click()
- getBody().find(safeapps.downloadBatchBtn).click()
- getBody().find(safeapps.deleteBatchBtn).click()
- getBody().findAllByText(safeapps.confirmDeleteBtnStr).should('not.be.disabled').click()
- getBody().findByText(safeapps.noSavedBatchesStr).should('be.visible')
- getBody().findByText(safeapps.backToTransactionStr).should('be.visible')
- })
- cy.readFile('cypress/downloads/E2E test.json').should('exist')
- })
-
- it('Verify there is notification if uploaded batch is from a different chain', () => {
- cy.enter(iframeSelector).then((getBody) => {
- getBody().findAllByText('choose a file').attachFile('test-mainnet-batch.json', { subjectType: 'drag-n-drop' })
- getBody().findAllByText(safeapps.warningStr).should('be.visible')
- getBody().findAllByText(safeapps.anotherChainStr).should('be.visible')
- })
- })
-
- it('Verify there is error message when a modified batch is uploaded', () => {
- cy.enter(iframeSelector).then((getBody) => {
- getBody().findAllByText('choose a file').attachFile('test-modified-batch.json', { subjectType: 'drag-n-drop' })
- getBody().findAllByText(safeapps.changedPropertiesStr)
- getBody().findAllByText('choose a file').should('be.visible')
- })
- })
-
- it('Verify an invalid batch cannot be uploaded', () => {
- cy.enter(iframeSelector).then((getBody) => {
- getBody()
- .findAllByText('choose a file')
- .attachFile('test-invalid-batch.json', { subjectType: 'drag-n-drop' })
- .findAllByText('choose a file')
- .should('be.visible')
- })
- })
-
- it('Verify an empty batch cannot be uploaded', () => {
- cy.enter(iframeSelector).then((getBody) => {
getBody()
- .findAllByText('choose a file')
- .attachFile('test-empty-batch.json', { subjectType: 'drag-n-drop' })
- .findAllByText('choose a file')
- .should('be.visible')
- })
- })
-
- it.skip('Verify a valid batch as successful can be simulated', () => {
- cy.enter(iframeSelector).then((getBody) => {
- getBody().findByLabelText(safeapps.enterAddressStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
- getBody().findByText(safeapps.keepProxiABIStr).click()
- getBody().findByLabelText(safeapps.tokenAmount).type('0')
- getBody().findByText(safeapps.addTransactionStr).click()
- getBody().findByText(safeapps.createBatchStr).click()
- getBody().findByText(safeapps.simulateBtnStr).click()
- getBody().findByText(safeapps.transferStr).should('be.visible')
- getBody().findByText(safeapps.successStr).should('be.visible')
- })
- })
-
- it.skip('Verify an invalid batch as failed can be simulated', () => {
- cy.enter(iframeSelector).then((getBody) => {
- getBody().findByLabelText(safeapps.enterAddressStr).type(safeAppSafes.SEP_SAFEAPP_SAFE_2)
- getBody().findByText(safeapps.keepProxiABIStr).click()
- getBody().findByLabelText(safeapps.tokenAmount).type('100')
- getBody().findByText(safeapps.addTransactionStr).click()
- getBody().findByText(safeapps.createBatchStr).click()
- getBody().findByText(safeapps.simulateBtnStr).click()
- getBody().findByText(safeapps.failedStr).should('be.visible')
+ .findAllByText(safeapps.requiredStr)
+ .then(($element) => {
+ const color = $element.css('color')
+ expect(utils.isInRedRange(color), 'Element color is ').to.be.true
+ })
})
})
})
diff --git a/cypress/e2e/safe-apps/tx_modal.cy.js b/cypress/e2e/safe-apps/tx_modal.cy.js
index 2549e3aa6..3b33dc219 100644
--- a/cypress/e2e/safe-apps/tx_modal.cy.js
+++ b/cypress/e2e/safe-apps/tx_modal.cy.js
@@ -1,6 +1,6 @@
import * as constants from '../../support/constants'
import * as main from '../pages/main.page'
-import * as safeapps from '../pages/safeapps.pages'
+import * as ls from '../../support/localstorage_data.js'
const testAppName = 'Cypress Test App'
const testAppDescr = 'Cypress Test App Description'
@@ -9,7 +9,6 @@ const confirmTx = 'Confirm transaction'
describe('Transaction modal tests', () => {
beforeEach(() => {
- cy.clearLocalStorage()
cy.fixture('safe-app').then((html) => {
cy.intercept('GET', `${constants.testAppUrl}/*`, html)
cy.intercept('GET', `*/manifest.json`, {
@@ -25,10 +24,6 @@ describe('Transaction modal tests', () => {
{ defaultCommandTimeout: 12000 },
() => {
cy.visitSafeApp(`${constants.testAppUrl}/dummy`)
- main.acceptCookies()
- safeapps.clickOnContinueBtn()
- safeapps.verifyWarningDefaultAppMsgIsDisplayed()
- safeapps.clickOnContinueBtn()
cy.findByRole('dialog').within(() => {
cy.findByText(confirmTx)
cy.findByText(unknownApp)
diff --git a/cypress/e2e/smoke/add_owner.cy.js b/cypress/e2e/smoke/add_owner.cy.js
index 4539f4cb4..0d5429298 100644
--- a/cypress/e2e/smoke/add_owner.cy.js
+++ b/cypress/e2e/smoke/add_owner.cy.js
@@ -16,9 +16,7 @@ describe('[SMOKE] Add Owners tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4)
- cy.clearLocalStorage()
main.waitForHistoryCallToComplete()
- main.acceptCookies()
main.verifyElementsExist([navigation.setupSection])
})
diff --git a/cypress/e2e/smoke/address_book.cy.js b/cypress/e2e/smoke/address_book.cy.js
index 842369c38..326a33ea9 100644
--- a/cypress/e2e/smoke/address_book.cy.js
+++ b/cypress/e2e/smoke/address_book.cy.js
@@ -25,9 +25,7 @@ describe('[SMOKE] Address book tests', () => {
beforeEach(() => {
cy.visit(constants.addressBookUrl + staticSafes.SEP_STATIC_SAFE_4)
- cy.clearLocalStorage()
main.waitForHistoryCallToComplete()
- main.acceptCookies()
})
it('[SMOKE] Verify entry can be added', () => {
diff --git a/cypress/e2e/smoke/assets.cy.js b/cypress/e2e/smoke/assets.cy.js
index 368b266f3..c81721bc2 100644
--- a/cypress/e2e/smoke/assets.cy.js
+++ b/cypress/e2e/smoke/assets.cy.js
@@ -18,8 +18,6 @@ describe('[SMOKE] Assets tests', () => {
beforeEach(() => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_2)
- cy.clearLocalStorage()
- main.acceptCookies()
})
it('[SMOKE] Verify that the native token is visible', () => {
diff --git a/cypress/e2e/smoke/batch_tx.cy.js b/cypress/e2e/smoke/batch_tx.cy.js
index 93f459c7e..c0432a042 100644
--- a/cypress/e2e/smoke/batch_tx.cy.js
+++ b/cypress/e2e/smoke/batch_tx.cy.js
@@ -20,9 +20,7 @@ describe('[SMOKE] Batch transaction tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_2)
- main.acceptCookies()
})
it('[SMOKE] Verify empty batch list can be opened', () => {
@@ -46,14 +44,15 @@ describe('[SMOKE] Batch transaction tests', () => {
.then(() => main.isItemInLocalstorage(constants.localStorageKeys.SAFE_v2__batch, ls.batchData.entry0))
.then(() => {
cy.reload()
- batch.clickOnBatchCounter()
wallet.connectSigner(signer)
+ batch.clickOnBatchCounter()
+
batch.clickOnConfirmBatchBtn()
batch.verifyBatchTransactionsCount(2)
batch.clickOnBatchCounter()
cy.contains(funds_first_tx).parents('ul').as('TransactionList')
cy.get('@TransactionList').find('li').eq(0).contains(funds_first_tx)
- cy.get('@TransactionList').find('li').eq(1).contains(funds_first_tx)
+ cy.get('@TransactionList').find('li').eq(1).contains(funds_second_tx)
})
})
@@ -63,12 +62,12 @@ describe('[SMOKE] Batch transaction tests', () => {
.then(() => main.isItemInLocalstorage(constants.localStorageKeys.SAFE_v2__batch, ls.batchData.entry0))
.then(() => {
cy.reload()
- batch.clickOnBatchCounter()
wallet.connectSigner(signer)
+ batch.clickOnBatchCounter()
cy.contains(batch.batchedTransactionsStr).should('be.visible').parents('aside').find('ul > li').as('BatchList')
cy.get('@BatchList').find(batch.deleteTransactionbtn).eq(0).click()
cy.get('@BatchList').should('have.length', 1)
- cy.get('@BatchList').contains(funds_second_tx).should('not.exist')
+ cy.get('@BatchList').contains(funds_first_tx).should('not.exist')
})
})
})
diff --git a/cypress/e2e/smoke/create_safe_cf.cy.js b/cypress/e2e/smoke/create_safe_cf.cy.js
index 6eba4ed3e..3a9205383 100644
--- a/cypress/e2e/smoke/create_safe_cf.cy.js
+++ b/cypress/e2e/smoke/create_safe_cf.cy.js
@@ -3,17 +3,22 @@ import * as main from '../pages/main.page'
import * as createwallet from '../pages/create_wallet.pages'
import * as owner from '../pages/owners.pages'
import * as wallet from '../../support/utils/wallet.js'
+import { getEvents, events, checkDataLayerEvents } from '../../support/utils/gtag.js'
const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS'))
-const signer = walletCredentials.OWNER_4_PRIVATE_KEY
+// DO NOT use OWNER_2_PRIVATE_KEY for safe creation. Used for CF safes.
+const signer = walletCredentials.OWNER_2_PRIVATE_KEY
describe('[SMOKE] CF Safe creation tests', () => {
beforeEach(() => {
cy.visit(constants.welcomeUrl + '?chain=sep')
+ // Required for data layer
cy.clearLocalStorage()
main.acceptCookies()
+ getEvents()
})
- it('[SMOKE] CF creation happy path', () => {
+
+ it('[SMOKE] CF creation happy path. GA safe_created', () => {
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
createwallet.clickOnContinueWithWalletBtn()
@@ -22,6 +27,20 @@ describe('[SMOKE] CF Safe creation tests', () => {
createwallet.clickOnNextBtn()
createwallet.selectPayLaterOption()
createwallet.clickOnReviewStepNextBtn()
- createwallet.verifyCFSafeCreated()
+ cy.wait(1000)
+ main.getAddedSafeAddressFromLocalStorage(constants.networkKeys.sepolia, 0).then((address) => {
+ const safe_created = [
+ {
+ eventLabel: events.safeCreatedCF.eventLabel,
+ eventCategory: events.safeCreatedCF.category,
+ eventAction: events.safeCreatedCF.action,
+ eventType: events.safeCreatedCF.eventType,
+ event: events.safeCreatedCF.eventName,
+ safeAddress: address.slice(2),
+ },
+ ]
+ checkDataLayerEvents(safe_created)
+ createwallet.verifyCFSafeCreated()
+ })
})
})
diff --git a/cypress/e2e/smoke/create_safe_simple.cy.js b/cypress/e2e/smoke/create_safe_simple.cy.js
index 3b749b1e8..2ba907a99 100644
--- a/cypress/e2e/smoke/create_safe_simple.cy.js
+++ b/cypress/e2e/smoke/create_safe_simple.cy.js
@@ -10,8 +10,6 @@ const signer = walletCredentials.OWNER_4_PRIVATE_KEY
describe('[SMOKE] Safe creation tests', () => {
beforeEach(() => {
cy.visit(constants.welcomeUrl + '?chain=sep')
- cy.clearLocalStorage()
- main.acceptCookies()
})
it('[SMOKE] Verify a Wallet can be connected', () => {
wallet.connectSigner(signer)
diff --git a/cypress/e2e/smoke/create_tx.cy.js b/cypress/e2e/smoke/create_tx.cy.js
index 0a7dd1c6f..af0b9ba05 100644
--- a/cypress/e2e/smoke/create_tx.cy.js
+++ b/cypress/e2e/smoke/create_tx.cy.js
@@ -17,9 +17,7 @@ describe('[SMOKE] Create transactions tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_10)
- main.acceptCookies()
wallet.connectSigner(signer)
createtx.clickOnNewtransactionBtn()
createtx.clickOnSendTokensBtn()
diff --git a/cypress/e2e/smoke/create_tx_2.cy.js b/cypress/e2e/smoke/create_tx_2.cy.js
index 7030235f6..330cabe92 100644
--- a/cypress/e2e/smoke/create_tx_2.cy.js
+++ b/cypress/e2e/smoke/create_tx_2.cy.js
@@ -24,9 +24,7 @@ describe('[SMOKE] Create transactions tests 2', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_6)
- main.acceptCookies()
wallet.connectSigner(signer)
createtx.clickOnNewtransactionBtn()
createtx.clickOnSendTokensBtn()
diff --git a/cypress/e2e/smoke/dashboard.cy.js b/cypress/e2e/smoke/dashboard.cy.js
index 626625d70..cfdb9b9c1 100644
--- a/cypress/e2e/smoke/dashboard.cy.js
+++ b/cypress/e2e/smoke/dashboard.cy.js
@@ -9,8 +9,8 @@ let staticSafes = []
const txData = ['14', 'Send', '-0.00002 ETH', '1 out of 1']
const txaddOwner = ['5', 'addOwnerWithThreshold', '1 out of 2']
-const txMultiSendCall3 = ['4', 'Safe: MultiSendCallOnly 1.3.0', '3 actions', '1 out of 2']
-const txMultiSendCall2 = ['6', 'Safe: MultiSendCallOnly 1.3.0', '2 actions', '1 out of 2']
+const txMultiSendCall3 = ['4', 'Batch', '3 actions', '1 out of 2']
+const txMultiSendCall2 = ['6', 'Batch', '2 actions', '1 out of 2']
describe('[SMOKE] Dashboard tests', { defaultCommandTimeout: 20000 }, () => {
before(async () => {
@@ -18,10 +18,7 @@ describe('[SMOKE] Dashboard tests', { defaultCommandTimeout: 20000 }, () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_2)
- main.acceptCookies()
- dashboard.verifyConnectTransactStrIsVisible()
})
it('[SMOKE] Verify the overview widget is displayed', () => {
@@ -32,18 +29,10 @@ describe('[SMOKE] Dashboard tests', { defaultCommandTimeout: 20000 }, () => {
dashboard.verifyTxQueueWidget()
})
- it('[SMOKE] Verify the featured Safe Apps are displayed', () => {
- dashboard.verifyFeaturedAppsSection()
- })
-
it('[SMOKE] Verify the Safe Apps Section is displayed', () => {
dashboard.verifySafeAppsSection()
})
- it.skip('[SMOKE] Verify clicking on the share icon copies the app URL to the clipboard', () => {
- dashboard.verifyShareBtnWorks(0, dashboard.copiedAppUrl)
- })
-
it('[SMOKE] Verify clicking on Explore Safe apps button opens list of all apps', () => {
dashboard.clickOnExploreAppsBtn()
})
diff --git a/cypress/e2e/smoke/import_export_data.cy.js b/cypress/e2e/smoke/import_export_data.cy.js
index 6ecc9d6dd..b862c91ad 100644
--- a/cypress/e2e/smoke/import_export_data.cy.js
+++ b/cypress/e2e/smoke/import_export_data.cy.js
@@ -15,9 +15,7 @@ describe('[SMOKE] Import Export Data tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.dataSettingsUrl).then(() => {
- main.acceptCookies()
createwallet.selectNetwork(constants.networks.sepolia)
})
})
diff --git a/cypress/e2e/smoke/import_export_data_2.cy.js b/cypress/e2e/smoke/import_export_data_2.cy.js
index 7aa5ee3dc..7d52747f6 100644
--- a/cypress/e2e/smoke/import_export_data_2.cy.js
+++ b/cypress/e2e/smoke/import_export_data_2.cy.js
@@ -25,8 +25,6 @@ describe('[SMOKE] Import Export Data tests 2', () => {
beforeEach(() => {
cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_13)
- cy.clearLocalStorage()
- main.acceptCookies()
})
it('[SMOKE] Verify that the Sidebar Import button opens an import modal', () => {
diff --git a/cypress/e2e/smoke/landing.cy.js b/cypress/e2e/smoke/landing.cy.js
index f56883364..fe61ffea7 100644
--- a/cypress/e2e/smoke/landing.cy.js
+++ b/cypress/e2e/smoke/landing.cy.js
@@ -1,7 +1,6 @@
import * as constants from '../../support/constants'
describe('[SMOKE] Landing page tests', () => {
it('[SMOKE] Verify a user will be redirected to welcome page', () => {
- cy.clearLocalStorage()
cy.visit('/')
cy.url().should('include', constants.welcomeUrl)
})
diff --git a/cypress/e2e/smoke/load_safe.cy.js b/cypress/e2e/smoke/load_safe.cy.js
index 1b7c7d569..ff05d98de 100644
--- a/cypress/e2e/smoke/load_safe.cy.js
+++ b/cypress/e2e/smoke/load_safe.cy.js
@@ -16,9 +16,7 @@ describe('[SMOKE] Load Safe tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.loadNewSafeSepoliaUrl)
- main.acceptCookies()
cy.wait(2000)
})
diff --git a/cypress/e2e/smoke/messages_offchain.cy.js b/cypress/e2e/smoke/messages_offchain.cy.js
index 78839b83b..408d66f46 100644
--- a/cypress/e2e/smoke/messages_offchain.cy.js
+++ b/cypress/e2e/smoke/messages_offchain.cy.js
@@ -23,12 +23,11 @@ describe('[SMOKE] Offchain Messages tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.transactionsMessagesUrl + staticSafes.SEP_STATIC_SAFE_10)
- main.acceptCookies()
})
- it('[SMOKE] Verify summary for off-chain unsigned messages', () => {
+ // TODO: Clarify changes
+ it.skip('[SMOKE] Verify summary for off-chain unsigned messages', () => {
createTx.verifySummaryByIndex(0, [
typeMessagesGeneral.sign,
typeMessagesGeneral.oneOftwo,
@@ -41,7 +40,8 @@ describe('[SMOKE] Offchain Messages tests', () => {
])
})
- it('[SMOKE] Verify summary for off-chain signed messages', () => {
+ // TODO: Clarify changes
+ it.skip('[SMOKE] Verify summary for off-chain signed messages', () => {
createTx.verifySummaryByIndex(1, [
typeMessagesGeneral.confirmed,
typeMessagesGeneral.twoOftwo,
@@ -80,7 +80,8 @@ describe('[SMOKE] Offchain Messages tests', () => {
main.verifyTextVisibility(values)
})
- it('[SMOKE] Verify confirmation window is displayed for unsigned message', () => {
+ // TODO: Clarify changes
+ it.skip('[SMOKE] Verify confirmation window is displayed for unsigned message', () => {
wallet.connectSigner(signer)
messages.clickOnMessageSignBtn(2)
msg_confirmation_modal.verifyConfirmationWindowTitle(modal.modalTitiles.confirmMsg)
diff --git a/cypress/e2e/smoke/nfts.cy.js b/cypress/e2e/smoke/nfts.cy.js
index 30fa5c8d5..1ad6a72e8 100644
--- a/cypress/e2e/smoke/nfts.cy.js
+++ b/cypress/e2e/smoke/nfts.cy.js
@@ -15,9 +15,7 @@ describe('[SMOKE] NFTs tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.balanceNftsUrl + staticSafes.SEP_STATIC_SAFE_2)
- main.acceptCookies()
nfts.waitForNftItems(2)
})
diff --git a/cypress/e2e/smoke/replace_owner.cy.js b/cypress/e2e/smoke/replace_owner.cy.js
index ca931b895..b05e400da 100644
--- a/cypress/e2e/smoke/replace_owner.cy.js
+++ b/cypress/e2e/smoke/replace_owner.cy.js
@@ -15,8 +15,6 @@ describe('[SMOKE] Replace Owners tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4)
- cy.clearLocalStorage()
- main.acceptCookies()
cy.contains(owner.safeAccountNonceStr, { timeout: 10000 })
})
@@ -33,6 +31,6 @@ describe('[SMOKE] Replace Owners tests', () => {
it('[SMOKE] Verify that the owner replacement form is opened', () => {
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
- owner.openReplaceOwnerWindow()
+ owner.openReplaceOwnerWindow(0)
})
})
diff --git a/cypress/e2e/smoke/spending_limits.cy.js b/cypress/e2e/smoke/spending_limits.cy.js
index 63451967e..ac080d65d 100644
--- a/cypress/e2e/smoke/spending_limits.cy.js
+++ b/cypress/e2e/smoke/spending_limits.cy.js
@@ -16,8 +16,6 @@ describe('[SMOKE] Spending limits tests', () => {
beforeEach(() => {
cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_8)
- cy.clearLocalStorage()
- main.acceptCookies()
wallet.connectSigner(signer)
owner.waitForConnectionStatus()
cy.get(spendinglimit.spendingLimitsSection).should('be.visible')
diff --git a/cypress/e2e/smoke/tx_history.cy.js b/cypress/e2e/smoke/tx_history.cy.js
index 7559ed74a..32911d795 100644
--- a/cypress/e2e/smoke/tx_history.cy.js
+++ b/cypress/e2e/smoke/tx_history.cy.js
@@ -20,9 +20,7 @@ describe('[SMOKE] Tx history tests', () => {
})
beforeEach(() => {
- cy.clearLocalStorage()
cy.visit(constants.transactionsHistoryUrl + staticSafes.SEP_STATIC_SAFE_7)
- main.acceptCookies()
})
// Token receipt
diff --git a/cypress/fixtures/safes/funds.json b/cypress/fixtures/safes/funds.json
index 66cda0772..2d51a4d13 100644
--- a/cypress/fixtures/safes/funds.json
+++ b/cypress/fixtures/safes/funds.json
@@ -11,5 +11,7 @@
"SEP_FUNDS_SAFE_10": "sep:0xE72d4D7E87672c14Df3d449C6b79f20151c18fC1",
"SEP_FUNDS_SAFE_11": "sep:0x74D5228112a9652a9825a6A285Fb39e290269172",
"SEP_FUNDS_SAFE_12": "sep:0xe5DC58EfDA6ebe93014AaE7A5a673C5F80118171",
- "ETH_FUNDS_SAFE_13": "eth:0x8675B754342754A30A2AeF474D114d8460bca19b"
+ "ETH_FUNDS_SAFE_13": "eth:0x8675B754342754A30A2AeF474D114d8460bca19b",
+ "SEP_FUNDS_SAFE_14": "sep:0xF9e21491A1FccD40c9B658b8cA5e25018BA9105b",
+ "SEP_FUNDS_SAFE_15": "sep:0x1b412E4E47e3199c96d4544FD15875eA6886D4F0"
}
diff --git a/cypress/fixtures/safes/recovery.json b/cypress/fixtures/safes/recovery.json
index f04d8c5d7..5c39e72e4 100644
--- a/cypress/fixtures/safes/recovery.json
+++ b/cypress/fixtures/safes/recovery.json
@@ -1,5 +1,6 @@
{
"SEP_RECOVERY_SAFE_1": "sep:0x702E067A0015F1b835d9c631Cb28A9F617314F27",
"SEP_RECOVERY_SAFE_2": "sep:0xb791302040DB5Ab4Ade0b5295cecCaeF07AF07a1",
- "SEP_RECOVERY_SAFE_3": "sep:0xAE1E3f93fda95eEbb857Ee06325f6F1e45EF3CBE"
+ "SEP_RECOVERY_SAFE_3": "sep:0xAE1E3f93fda95eEbb857Ee06325f6F1e45EF3CBE",
+ "SEP_RECOVERY_SAFE_4": "sep:0xe41D568F5040FD9adeE8B64200c6B7C363C68c41"
}
diff --git a/cypress/fixtures/safes/static.json b/cypress/fixtures/safes/static.json
index 3a470ab92..6e02c7541 100644
--- a/cypress/fixtures/safes/static.json
+++ b/cypress/fixtures/safes/static.json
@@ -1,4 +1,5 @@
{
+ "SEP_STATIC_SAFE_0": "sep:0x926186108f74dB20BFeb2b6c888E523C78cb7E00",
"SEP_STATIC_SAFE_1": "sep:0x03042B890b99552b60A073F808100517fb148F60",
"SEP_STATIC_SAFE_2": "sep:0xBd69b0a9DC90eB6F9bAc3E4a5875f437348b6415",
"SEP_STATIC_SAFE_3": "sep:0x33C4AA5729D91FfB3B87AEf8a324bb6304Fb905c",
@@ -13,5 +14,16 @@
"SEP_STATIC_SAFE_11": "sep:0x10B45a24640E2170B6AA63ea3A289D723a0C9cba",
"SEP_STATIC_SAFE_12": "sep:0xFFfaC243A24EecE6553f0Da278322aCF1Fb6CeF1",
"SEP_STATIC_SAFE_13": "sep:0x027bBe128174F0e5e5d22ECe9623698E01cd3970",
- "SEP_STATIC_SAFE_14": "sep:0xe41D568F5040FD9adeE8B64200c6B7C363C68c41"
+ "SEP_STATIC_SAFE_14": "sep:0xe41D568F5040FD9adeE8B64200c6B7C363C68c41",
+ "ETH_STATIC_SAFE_15": "eth:0xfF501B324DC6d78dC9F983f140B9211c3EdB4dc7",
+ "GNO_STATIC_SAFE_16": "gno:0xB8d760a90a5ed54D3c2b3EFC231277e99188642A",
+ "MATIC_STATIC_SAFE_17": "matic:0x6D04edC44F7C88faa670683036edC2F6FC10b86e",
+ "BNB_STATIC_SAFE_18": "bnb:0x1D28a316431bAFf410Fe53398c6C5BD566032Eec",
+ "AURORA_STATIC_SAFE_19": "aurora:0xCEA454dD3d76Da856E72C3CBaDa8ee6A789aD167",
+ "AVAX_STATIC_SAFE_20": "avax:0x480e5A3E90a3fF4a16AECCB5d638fAba96a15c28",
+ "LINEA_STATIC_SAFE_21": "linea:0x95934e67299E0B3DD277907acABB512802f3536E",
+ "ZKSYNC_STATIC_SAFE_22": "zksync:0x49136c0270c5682FFbb38Cb29Ecf0563b2E1F9f6",
+ "SEP_STATIC_SAFE_23": "sep:0x589d862CE2d519d5A862066bB923da0564c3D2EA",
+ "SEP_STATIC_SAFE_24": "sep:0x49DC5764961DA4864DC5469f16BC68a0F765f2F2",
+ "SEP_STATIC_SAFE_25": "sep:0x4ECFAa2E8cb4697bCD27bdC9Ce3E16f03F73124F"
}
diff --git a/cypress/fixtures/txhistory_data_data.json b/cypress/fixtures/txhistory_data_data.json
index 8a276ccf8..e80972788 100644
--- a/cypress/fixtures/txhistory_data_data.json
+++ b/cypress/fixtures/txhistory_data_data.json
@@ -10,7 +10,7 @@
"executedBy": "Executed"
},
"accountCreation": {
- "actionsSummary": "Safe Account created by 0xC16D...6fED",
+ "actionsSummary": "Created by 0xC16D...6fED",
"transactionSafehash": {},
"summaryTime": "10:30 AM",
"title": "Safe Account created",
@@ -62,11 +62,11 @@
"safeTxHash": "0x1303...0fe2"
},
"batchNativeTransfer": {
- "title": "Safe: MultiSendCallOnly 1.3.0",
+ "title": "Batch",
"summaryTxInfo": "2 actions",
"summaryTime": "11:24 AM",
- "description": "MultiSend contract",
- "altImage": "Safe: MultiSendCallOnly 1.3.0",
+ "description": "Batch transaction with 2 actions",
+ "altImage": "Batch",
"contractTitle": "Safe: MultiSendCallOnly 1.3.0",
"contractAddress": "sep:0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B",
"transactionHash": "0xa5dd...b064",
@@ -82,9 +82,9 @@
"description": "Add signer",
"altImage": "addOwnerWithThreshold",
"requiredConfirmationsTitle": "Required confirmations for new transactions",
- "ownerAddress": "sep:0x01A9F68e339da12565cfBc47fe7D6EdEcB11C46f",
- "transactionHash": "0x51d5...da62",
- "safeTxHash": "0xdcc5...e1b2"
+ "ownerAddress": "sep:0x4fe7164d7cA511Ab35520bb14065F1693240dC90",
+ "transactionHash": "0xfcad...ab35",
+ "safeTxHash": "0x8583...3d2d"
},
"removeOwner": {
"title": "removeOwner",
@@ -145,28 +145,29 @@
"baseGas": "baseGas"
},
"spendingLimits": {
- "title": "Safe: MultiSendCallOnly 1.3.0",
+ "title": "Batch",
"summaryTxInfo": "3 actions",
"summaryTime": "11:06 AM",
- "description": "MultiSend contract",
- "altImage": "Safe: MultiSendCallOnly 1.3.0",
+ "description": "Batch transaction with 3 actions",
+ "altImage": "Batch",
"contractTitle": "Safe: MultiSendCallOnly 1.3.0",
"contractAddress": "sep:0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B",
"transactionHash": "0x69c3...bc37",
"safeTxHash": "0xf81c...243e",
+ "call_multiSend": "CallmultiSend",
"enableModule": {
"title": "enableModule",
"description": "Interact with",
"interactionAddress": "sep:0x5912f6616c84024cD1aff0D5b55bb36F5180fFdb",
"moduleAddress": "sep:0xCFbF...3134",
- "moduleAddressTitle": "module(address)"
+ "moduleAddressTitle": "module address"
},
"addDelegate": {
"title": "addDelegate",
"description": "Interact with",
"interactionAddress": "sep:0xCFbFaC74C26F8647cBDb8c5caf80BB5b32E43134",
"delegateAddress": "sep:0xC16D...6fED",
- "delegateAddressTitle": "delegate(address)"
+ "delegateAddressTitle": "delegate address"
},
"setAllowance": {
"title": "setAllowance",
@@ -175,7 +176,7 @@
"allowanceAmount": "100000000000",
"resetTimeMin": "0",
"resetBaseMin": "0",
- "delegateAddressTitle": "delegate(address)"
+ "delegateAddressTitle": "delegate address"
}
},
"untrustedReceivedToken": {
@@ -188,6 +189,18 @@
"transactionHashCopied": "0x54e7e766b08d4210bc1cfcc84d84ca4782a0cc1efe9e7d9c032d305060ed2a7c",
"altImage": "Received",
"altToken": ""
+ },
+ "bulkTransaction": {
+ "send": "Sent",
+ "receive": "Received",
+ "twoTx": "2 transactions",
+ "threeTx": "3 transactions",
+ "wrappedEther": "Wrapped Ether",
+ "addOwnerWithThreshold": "addOwnerWithThreshold",
+ "1transfer": "1transfer",
+ "2removeOwner": "2removeOwner",
+ "COW": "-10 COW",
+ "DAI": "363.19846 DAI"
}
}
}
diff --git a/cypress/fixtures/txmessages_data.json b/cypress/fixtures/txmessages_data.json
index fdd609947..71816eddf 100644
--- a/cypress/fixtures/txmessages_data.json
+++ b/cypress/fixtures/txmessages_data.json
@@ -3,6 +3,7 @@
"general": {
"confirmed": "Confirmed",
"sign": "Sign",
+ "zeroOftwo": "0 out of 2",
"oneOftwo": "1 out of 2",
"twoOftwo": "2 out of 2"
},
diff --git a/cypress/snapshots/actual/cypress/e2e/regression/tx_decoding.cy.js/tx_decoding.png b/cypress/snapshots/actual/cypress/e2e/regression/tx_decoding.cy.js/tx_decoding.png
new file mode 100644
index 000000000..08188e23a
Binary files /dev/null and b/cypress/snapshots/actual/cypress/e2e/regression/tx_decoding.cy.js/tx_decoding.png differ
diff --git a/cypress/support/api/utils_ether.js b/cypress/support/api/utils_ether.js
index 4a065ffd0..367b675fb 100644
--- a/cypress/support/api/utils_ether.js
+++ b/cypress/support/api/utils_ether.js
@@ -1,13 +1,5 @@
import { ethers } from 'ethers'
-import { EthersAdapter } from '@safe-global/protocol-kit'
export function createSigners(privateKeys, provider) {
return privateKeys.map((privateKey) => new ethers.Wallet(privateKey, provider))
}
-
-export function createEthersAdapter(signer) {
- return new EthersAdapter({
- ethers,
- signerOrProvider: signer,
- })
-}
diff --git a/cypress/support/api/utils_protocolkit.js b/cypress/support/api/utils_protocolkit.js
index 500b0d5a0..61b56e5ab 100644
--- a/cypress/support/api/utils_protocolkit.js
+++ b/cypress/support/api/utils_protocolkit.js
@@ -3,8 +3,11 @@ import Safe from '@safe-global/protocol-kit'
export async function createSafes(safeConfigurations) {
const safes = []
for (const config of safeConfigurations) {
- const safe = await Safe.create({
- ethAdapter: config.ethAdapter,
+ const providerUrl = config.provider._getConnection().url
+
+ const safe = await Safe.init({
+ provider: providerUrl,
+ signer: config.signer,
safeAddress: config.safeAddress,
})
safes.push(safe)
diff --git a/cypress/support/constants.js b/cypress/support/constants.js
index 0bba9e43a..b49298c6b 100644
--- a/cypress/support/constants.js
+++ b/cypress/support/constants.js
@@ -37,6 +37,7 @@ export const goerlySafeName = /g(รถ|oe)rli-safe/
export const sepoliaSafeName = 'sepolia-safe'
export const goerliToken = /G(รถ|oe)rli Ether/
+export const prodbaseUrl = 'https://app.safe.global'
export const swapWidget = 'https://swap.cow.fi/#/11155111/widget/swap/'
export const safeTestAppurl = 'https://safe-apps-test-app.pages.dev'
export const TX_Builder_url = 'https://safe-apps.dev.5afe.dev/tx-builder'
@@ -65,6 +66,7 @@ export const appSettingsUrl = '/settings/safe-apps'
export const setupUrl = '/settings/setup?safe='
export const dataSettingsUrl = '/settings/data?safe='
export const securityUrl = '/settings/security?safe='
+export const modulesUrl = '/settings/modules?safe='
export const notificationsUrl = '/settings/notifications?safe='
export const invalidAppUrl = 'https://my-invalid-custom-app.com/manifest.json'
export const validAppUrlJson = 'https://my-valid-custom-app.com/manifest.json'
@@ -196,6 +198,7 @@ export const addressBookErrrMsg = {
emptyAddress: 'Owner',
safeAlreadyAdded: 'Safe Account is already added',
prefixMismatch: "doesn't match the current chain",
+ ownSafeGuardian: 'The Safe Account cannot be a Recoverer of itself',
invalidPrefix(prefix) {
return `"${prefix}" doesn't match the current chain`
},
@@ -225,16 +228,19 @@ export const addresBookContacts = {
},
}
+export const CURRENT_COOKIE_TERMS_VERSION = Cypress.env('CURRENT_COOKIE_TERMS_VERSION')
+
export const localStorageKeys = {
SAFE_v2__addressBook: 'SAFE_v2__addressBook',
SAFE_v2__batch: 'SAFE_v2__batch',
SAFE_v2__settings: 'SAFE_v2__settings',
SAFE_v2__addedSafes: 'SAFE_v2__addedSafes',
SAFE_v2__safeApps: 'SAFE_v2__safeApps',
- SAFE_v2__cookies: 'SAFE_v2__cookies',
+ SAFE_v2_cookies: 'SAFE_v2__cookies_terms',
SAFE_v2__tokenlist_onboarding: 'SAFE_v2__tokenlist_onboarding',
SAFE_v2__customSafeApps_11155111: 'SAFE_v2__customSafeApps-11155111',
SAFE_v2__SafeApps__browserPermissions: 'SAFE_v2__SafeApps__browserPermissions',
SAFE_v2__SafeApps__infoModal: 'SAFE_v2__SafeApps__infoModal',
SAFE_v2__undeployedSafes: 'SAFE_v2__undeployedSafes',
+ SAFE_v2__batch: 'SAFE_v2__batch',
}
diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js
index fb98bb629..14693ec95 100644
--- a/cypress/support/e2e.js
+++ b/cypress/support/e2e.js
@@ -17,6 +17,8 @@
import '@testing-library/cypress/add-commands'
import './commands'
import './safe-apps-commands'
+import * as constants from './constants'
+import * as ls from './localstorage_data'
// Alternatively you can use CommonJS syntax:
// require('./commands')
@@ -25,7 +27,16 @@ import './safe-apps-commands'
However, in cypress the cookie banner state is evaluated after the banner has been dismissed not before
which displays the terms banner even though it shouldn't so we need to globally hide it in our tests.
*/
+const { addCompareSnapshotCommand } = require('cypress-visual-regression/dist/command')
+addCompareSnapshotCommand()
+
+const beamer = JSON.parse(Cypress.env('BEAMER_DATA_E2E'))
+const productID = beamer.PRODUCT_ID
+
before(() => {
+ Cypress.on('uncaught:exception', (err, runnable) => {
+ return false
+ })
cy.on('log:added', (ev) => {
if (Cypress.config('hideXHR')) {
const app = window.top
@@ -41,4 +52,20 @@ before(() => {
beforeEach(() => {
cy.setupInterceptors()
+ cy.clearAllSessionStorage()
+ cy.clearLocalStorage()
+ cy.clearCookies()
+ cy.window().then((window) => {
+ const getDate = () => new Date().toISOString()
+ const beamerKey1 = `_BEAMER_FIRST_VISIT_${productID}`
+ const beamerKey2 = `_BEAMER_BOOSTED_ANNOUNCEMENT_DATE_${productID}`
+ const cookiesKey = 'SAFE_v2__cookies_terms'
+ window.localStorage.setItem(beamerKey1, getDate())
+ window.localStorage.setItem(beamerKey2, getDate())
+ window.localStorage.setItem(cookiesKey, ls.cookies.acceptedCookies)
+ window.localStorage.setItem(
+ constants.localStorageKeys.SAFE_v2__SafeApps__infoModal,
+ ls.appPermissions(constants.safeTestAppurl).infoModalAccepted,
+ )
+ })
})
diff --git a/cypress/support/localstorage_data.js b/cypress/support/localstorage_data.js
index 65add7561..986afb30c 100644
--- a/cypress/support/localstorage_data.js
+++ b/cypress/support/localstorage_data.js
@@ -1,4 +1,15 @@
/* eslint-disable */
+
+import { CURRENT_COOKIE_TERMS_VERSION } from './constants.js'
+
+const cookieState = {
+ necessary: true,
+ updates: true,
+ analytics: true,
+ terms: true,
+ termsVersion: CURRENT_COOKIE_TERMS_VERSION,
+}
+
export const batchData = {
entry0: {
11155111: {
@@ -111,7 +122,7 @@ export const batchData = {
logoUri: null,
},
direction: 'OUTGOING',
- transferInfo: { type: 'NATIVE_COIN', value: '1000000000000000' },
+ transferInfo: { type: 'NATIVE_COIN', value: '2000000000000000' },
},
txData: {
hexData: null,
@@ -335,6 +346,11 @@ export const addressBookData = {
'0x6a5602335a878ADDCa4BF63a050E34946B56B5bC': 'BB Safe',
},
},
+ autofillData: {
+ 11155111: {
+ '0x01A9F68e339da12565cfBc47fe7D6EdEcB11C46f': 'David',
+ },
+ },
sameOwnerName: {
11155111: {
'0xC16Db0251654C0a72E91B190d81eAD367d2C6fED': 'Automation owner Sepolia',
@@ -374,7 +390,7 @@ export const addressBookData = {
'0xc2F3645bfd395516d1a18CA6ad9298299d328C01': 'Safe 27',
},
},
- cookies: { necessary: true, updates: true, analytics: true },
+ cookies: cookieState,
}
export const safeSettings = {
@@ -657,6 +673,13 @@ export const customApps = (url) => ({
},
})
+const infoModalAccepted = {
+ 11155111: {
+ consentsAccepted: true,
+ warningCheckedCustomApps: [],
+ },
+}
+
export const appPermissions = (url) => ({
grantedPermissions: {
[url]: [
@@ -664,27 +687,33 @@ export const appPermissions = (url) => ({
{ feature: 'microphone', status: 'granted' },
],
},
- infoModalAccepted: { 11155111: { consentsAccepted: true, warningCheckedCustomApps: [] } },
+ infoModalAccepted: JSON.stringify(infoModalAccepted),
})
export const cookies = {
- acceptedCookies: { necessary: true, updates: true, analytics: true },
+ acceptedCookies: JSON.stringify(cookieState),
acceptedTokenListOnboarding: true,
}
export const undeployedSafe = {
safe1: {
11155111: {
- '0xe41D568F5040FD9adeE8B64200c6B7C363C68c41': {
+ '0x926186108f74dB20BFeb2b6c888E523C78cb7E00': {
props: {
safeAccountConfig: {
threshold: 1,
- owners: ['0xC16Db0251654C0a72E91B190d81eAD367d2C6fED'],
+ owners: ['0x9445deb174C1eCbbfce8d31D33F438B8e7a0F1BA'],
fallbackHandler: '0x017062a1dE2FE6b99BE3d9d37841FeD19F573804',
},
- safeDeploymentConfig: { saltNonce: '20', safeVersion: '1.3.0' },
+ safeDeploymentConfig: {
+ saltNonce: '21',
+ safeVersion: '1.3.0',
+ },
+ },
+ status: {
+ status: 'AWAITING_EXECUTION',
+ type: 'PayLater',
},
- status: { status: 'AWAITING_EXECUTION' },
},
},
},
diff --git a/cypress/support/safe-apps-commands.js b/cypress/support/safe-apps-commands.js
index ddbf3d826..798eb50d3 100644
--- a/cypress/support/safe-apps-commands.js
+++ b/cypress/support/safe-apps-commands.js
@@ -8,7 +8,7 @@ Cypress.Commands.add('visitSafeApp', (appUrl) => {
window.localStorage.setItem(
INFO_MODAL_KEY,
JSON.stringify({
- 5: { consentsAccepted: true, warningCheckedCustomApps: allowedApps },
+ 11155111: { consentsAccepted: true, warningCheckedCustomApps: allowedApps },
}),
)
})
diff --git a/cypress/support/utils/checkers.js b/cypress/support/utils/checkers.js
index 112e11b09..492071bb6 100644
--- a/cypress/support/utils/checkers.js
+++ b/cypress/support/utils/checkers.js
@@ -2,3 +2,8 @@ export function startsWith0x(str) {
const pattern = /^0x/
return pattern.test(str)
}
+
+export const isInRedRange = (rgbColor) => {
+ const [r, g, b] = rgbColor.match(/\d+/g).map(Number)
+ return r >= 200 && r <= 255 && g >= 0 && g <= 95 && b >= 0 && b <= 120
+}
diff --git a/cypress/support/utils/gtag.js b/cypress/support/utils/gtag.js
new file mode 100644
index 000000000..fe6e9fd07
--- /dev/null
+++ b/cypress/support/utils/gtag.js
@@ -0,0 +1,72 @@
+export function getEvents() {
+ cy.window().then((win) => {
+ cy.wrap(win.dataLayer).as('dataLayer')
+ })
+}
+
+export const checkDataLayerEvents = (expectedEvents) => {
+ cy.get('@dataLayer').should((dataLayer) => {
+ expectedEvents.forEach((expectedEvent) => {
+ const eventExists = dataLayer.some((event) => {
+ return Object.keys(expectedEvent).every((key) => {
+ return event[key] === expectedEvent[key]
+ })
+ })
+ expect(eventExists, `Expected event matching fields: ${JSON.stringify(expectedEvent)} not found`).to.be.true
+ })
+ })
+}
+
+export const events = {
+ safeCreatedCF: {
+ category: 'create-safe',
+ action: 'Created Safe',
+ eventName: 'safe_created',
+ eventLabel: 'counterfactual',
+ eventType: 'safe_created',
+ },
+
+ txCreatedSwapOwner: {
+ category: 'transactions',
+ action: 'Create transaction',
+ eventName: 'tx_created',
+ eventLabel: 'owner_swap',
+ },
+
+ txConfirmedAddOwner: {
+ category: 'transactions',
+ action: 'Confirm transaction',
+ eventLabel: 'owner_add',
+ eventType: 'tx_confirmed',
+ event: 'tx_confirmed',
+ },
+ txCreatedSwap: {
+ category: 'transactions',
+ action: 'Confirm transaction',
+ eventLabel: 'native_swap',
+ eventType: 'tx_created',
+ },
+
+ txConfirmedSwap: {
+ category: 'transactions',
+ action: 'Confirm transaction',
+ eventLabel: 'native_swap',
+ eventType: 'tx_confirmed',
+ },
+
+ txCreatedTxBuilder: {
+ category: 'transactions',
+ action: 'Confirm transaction',
+ eventLabel: 'https://safe-apps.dev.5afe.dev/tx-builder',
+ eventType: 'tx_created',
+ event: 'tx_created',
+ },
+
+ txConfirmedTxBuilder: {
+ category: 'transactions',
+ action: 'Confirm transaction',
+ eventLabel: 'https://safe-apps.dev.5afe.dev/tx-builder',
+ eventType: 'tx_confirmed',
+ event: 'tx_confirmed',
+ },
+}
diff --git a/cypress/support/utils/txquery.js b/cypress/support/utils/txquery.js
index c4845de5f..844501610 100644
--- a/cypress/support/utils/txquery.js
+++ b/cypress/support/utils/txquery.js
@@ -9,7 +9,7 @@ function buildQueryUrl({ chainId, safeAddress, transactionType, ...params }) {
const defaultParams = {
safe: `sep:${safeAddress}`,
- timezone_offset: '7200000',
+ timezone: 'Europe/Berlin',
trusted: 'false',
}
diff --git a/cypress/support/utils/wallet.js b/cypress/support/utils/wallet.js
index a9ea82fd8..957b229f1 100644
--- a/cypress/support/utils/wallet.js
+++ b/cypress/support/utils/wallet.js
@@ -23,7 +23,7 @@ export function connectSigner(signer) {
function handlePkConnect() {
cy.get('body').then(($body) => {
if ($body.find(pkConnectBtn).length > 0) {
- cy.get(pkInput).find('input').clear().type(signer)
+ cy.get(pkInput).find('input').clear().type(signer, { log: false, force: true })
cy.get(pkConnectBtn).click()
}
})
@@ -32,6 +32,7 @@ export function connectSigner(signer) {
function enterPrivateKey() {
cy.wait(1000)
cy.get(connectWalletBtn)
+ .eq(0)
.should('be.enabled')
.and('be.visible')
.click()
diff --git a/docs/update-terms.md b/docs/update-terms.md
new file mode 100644
index 000000000..5e69fac01
--- /dev/null
+++ b/docs/update-terms.md
@@ -0,0 +1,28 @@
+# How to update Terms & Conditions
+
+To update the terms and conditions, follow these steps:
+
+1. Export the terms and conditions from Google Docs as a Markdown file.
+2. Replace the content of the src/markdown/terms/terms.md file with the exported content.
+3. Update the frontmatter of the file with the new version number and date.
+
+Thatโs it!
+
+The updated terms and conditions will be displayed in the app with the correct version number and date. A popup banner
+will automatically appear for users who havenโt accepted the new terms.
+
+## How does this work?
+
+We rely on the version number from the frontmatter. When the Redux store is rehydrated, we check the version stored in
+the store against the version in the frontmatter. If they differ, we reset the accepted terms, forcing the user to
+accept the new version.
+
+The Markdown file is automatically converted to HTML and displayed in the app. Note that because the Markdown was
+generated
+from Google Docs, we require the remark-heading-id plugin. Additionally, since Google Docs uses {# ...} syntax, it will
+fail in an MDX file.
+
+For Cypress, we follow a similar process. We read the version from the frontmatter and pass it as an environment
+variable.
+
+For Jest tests, we mock the file and read the version from the mock.
diff --git a/jest.config.cjs b/jest.config.cjs
index 8eb1dc0ed..ebf77f7fc 100644
--- a/jest.config.cjs
+++ b/jest.config.cjs
@@ -13,6 +13,7 @@ const customJestConfig = {
// Handle module aliases (this will be automatically configured for you soon)
'^@/(.*)$': '/src/$1',
'^.+\\.(svg)$': '/mocks/svg.js',
+ '^.+/markdown/terms/terms\\.md$': '/mocks/terms.md.js',
isows: '/node_modules/isows/_cjs/index.js',
},
testEnvironment: 'jest-environment-jsdom',
diff --git a/jest.setup.js b/jest.setup.js
index 702bee613..3a44ca67c 100644
--- a/jest.setup.js
+++ b/jest.setup.js
@@ -43,7 +43,7 @@ const NumberFormat = Intl.NumberFormat
const englishTestLocale = 'en'
// `viem` used by the `safe-apps-sdk` uses `TextEncoder` and `TextDecoder` which are not available in jsdom for some reason
-Object.assign(global, { TextDecoder, TextEncoder })
+Object.assign(global, { TextDecoder, TextEncoder, fetch: jest.fn() })
jest.spyOn(Intl, 'NumberFormat').mockImplementation((locale, ...rest) => new NumberFormat([englishTestLocale], ...rest))
diff --git a/mocks/terms.md.js b/mocks/terms.md.js
new file mode 100644
index 000000000..cadf08493
--- /dev/null
+++ b/mocks/terms.md.js
@@ -0,0 +1,6 @@
+export const metadata = {
+ version: 'test-version',
+ last_update_date: 'test-date',
+}
+
+export default metadata
diff --git a/next-env.d.ts b/next-env.d.ts
index 4f11a03dc..a4a7b3f5c 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -2,4 +2,4 @@
///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/basic-features/typescript for more information.
+// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
diff --git a/next.config.mjs b/next.config.mjs
index f3fc7f9e8..b867dc004 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,6 +1,11 @@
import path from 'path'
import withBundleAnalyzer from '@next/bundle-analyzer'
import withPWAInit from '@ducanh2912/next-pwa'
+import remarkGfm from 'remark-gfm'
+import remarkHeadingId from 'remark-heading-id'
+import createMDX from '@next/mdx'
+import remarkFrontmatter from 'remark-frontmatter'
+import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
const SERVICE_WORKERS_PATH = './src/service-workers'
@@ -26,13 +31,21 @@ const nextConfig = {
unoptimized: true,
},
+ pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
reactStrictMode: false,
productionBrowserSourceMaps: true,
eslint: {
dirs: ['src', 'cypress'],
},
experimental: {
- optimizePackageImports: ['@mui/material', '@mui/icons-material', 'lodash', 'date-fns', '@sentry/react', '@gnosis.pm/zodiac'],
+ optimizePackageImports: [
+ '@mui/material',
+ '@mui/icons-material',
+ 'lodash',
+ 'date-fns',
+ '@sentry/react',
+ '@gnosis.pm/zodiac',
+ ],
},
webpack(config) {
config.module.rules.push({
@@ -69,7 +82,19 @@ const nextConfig = {
return config
},
}
+const withMDX = createMDX({
+ extension: /\.(md|mdx)?$/,
+ jsx: true,
+ options: {
+ remarkPlugins: [
+ remarkFrontmatter,
+ [remarkMdxFrontmatter, { name: 'metadata' }],
+ remarkHeadingId, remarkGfm],
+ rehypePlugins: [],
+ },
+})
+
export default withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
-})(withPWA(nextConfig))
+})(withPWA(withMDX(nextConfig)))
diff --git a/package.json b/package.json
index bdc61714f..788afb52c 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "safe-wallet-web",
"homepage": "https://github.com/safe-global/safe-wallet-web",
"license": "GPL-3.0",
- "version": "1.39.2",
+ "version": "1.44.3",
"type": "module",
"scripts": {
"dev": "next dev",
@@ -31,7 +31,7 @@
"update-wc": "yarn add @walletconnect/web3wallet@latest @walletconnect/utils@latest; yarn add -D @walletconnect/types@latest",
"prepare": "husky",
"storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build"
+ "build-storybook": "storybook build --quiet"
},
"engines": {
"node": ">=16"
@@ -45,64 +45,67 @@
"dependencies": {
"@cowprotocol/widget-react": "^0.9.3",
"@ducanh2912/next-pwa": "^9.7.1",
- "@emotion/cache": "^11.11.0",
+ "@emotion/cache": "^11.13.1",
"@emotion/react": "^11.11.0",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
- "@gnosis.pm/zodiac": "^4.0.1",
+ "@gnosis.pm/zodiac": "^4.0.3",
"@mui/icons-material": "^5.14.20",
"@mui/material": "^5.14.20",
"@mui/x-date-pickers": "^5.0.20",
- "@reduxjs/toolkit": "^1.9.5",
- "@safe-global/api-kit": "^2.3.2",
- "@safe-global/protocol-kit": "^3.1.1",
+ "@reduxjs/toolkit": "^2.2.6",
+ "@safe-global/api-kit": "^2.4.6",
+ "@safe-global/protocol-kit": "^4.1.1",
"@safe-global/safe-apps-sdk": "^9.1.0",
- "@safe-global/safe-deployments": "^1.36.0",
- "@safe-global/safe-gateway-typescript-sdk": "3.21.8",
- "@safe-global/safe-modules-deployments": "^1.2.0",
+ "@safe-global/safe-deployments": "^1.37.8",
+ "@safe-global/safe-gateway-typescript-sdk": "3.22.3-beta.15",
+ "@safe-global/safe-modules-deployments": "^2.2.1",
"@sentry/react": "^7.91.0",
"@spindl-xyz/attribution-lite": "^1.4.0",
- "@walletconnect/utils": "^2.13.1",
- "@walletconnect/web3wallet": "^1.12.1",
+ "@walletconnect/utils": "^2.16.1",
+ "@walletconnect/web3wallet": "^1.15.1",
"@web3-onboard/coinbase": "^2.2.6",
"@web3-onboard/core": "^2.21.4",
- "@web3-onboard/injected-wallets": "^2.10.14",
+ "@web3-onboard/injected-wallets": "^2.11.2",
"@web3-onboard/keystone": "^2.3.7",
"@web3-onboard/ledger": "2.3.2",
"@web3-onboard/trezor": "^2.4.2",
"@web3-onboard/walletconnect": "^2.5.4",
"blo": "^1.1.1",
- "classnames": "^2.3.1",
+ "classnames": "^2.5.1",
"date-fns": "^2.30.0",
"ethers": "^6.11.1",
"exponential-backoff": "^3.1.0",
"firebase": "^10.3.1",
- "fuse.js": "^6.6.2",
+ "fuse.js": "^7.0.0",
"idb-keyval": "^6.2.1",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
- "next": "^14.1.1",
+ "next": "^14.2.13",
"papaparse": "^5.3.2",
"qrcode.react": "^3.1.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
"react-gtm-module": "^2.0.11",
"react-hook-form": "7.41.1",
"react-papaparse": "^4.0.2",
- "react-redux": "^8.0.5",
- "semver": "^7.5.2",
- "zodiac-roles-deployments": "^2.2.2"
+ "react-redux": "^9.1.2",
+ "semver": "^7.6.3",
+ "zodiac-roles-deployments": "^2.2.5"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.3.1",
"@cowprotocol/app-data": "^2.1.0",
"@faker-js/faker": "^8.1.0",
+ "@mdx-js/loader": "^3.0.1",
+ "@mdx-js/react": "^3.0.1",
"@next/bundle-analyzer": "^13.5.6",
- "@openzeppelin/contracts": "^4.9.2",
- "@safe-global/safe-core-sdk-types": "^4.1.1",
+ "@next/mdx": "^14.2.11",
+ "@openzeppelin/contracts": "^4.9.6",
+ "@safe-global/safe-core-sdk-types": "^5.0.1",
"@sentry/types": "^7.74.0",
- "@storybook/addon-designs": "^8.0.0",
+ "@storybook/addon-designs": "^8.0.3",
"@storybook/addon-essentials": "^8.0.6",
"@storybook/addon-interactions": "^8.0.6",
"@storybook/addon-links": "^8.0.6",
@@ -119,19 +122,21 @@
"@testing-library/user-event": "^14.4.2",
"@typechain/ethers-v6": "^0.5.1",
"@types/jest": "^29.5.4",
- "@types/js-cookie": "^3.0.2",
+ "@types/js-cookie": "^3.0.6",
"@types/lodash": "^4.14.182",
+ "@types/mdx": "^2.0.13",
"@types/node": "18.11.18",
"@types/qrcode": "^1.5.5",
- "@types/react": "^18.2.75",
- "@types/react-dom": "^18.2.24",
+ "@types/react": "^18.3.10",
+ "@types/react-dom": "^18.3.0",
"@types/react-gtm-module": "^2.0.3",
"@types/semver": "^7.3.10",
"@typescript-eslint/eslint-plugin": "^7.6.0",
- "@walletconnect/types": "^2.13.1",
+ "@walletconnect/types": "^2.16.1",
"cross-env": "^7.0.3",
"cypress": "^12.15.0",
"cypress-file-upload": "^5.0.8",
+ "cypress-visual-regression": "^5.0.2",
"eslint": "^8.57.0",
"eslint-config-next": "^14.1.0",
"eslint-config-prettier": "^8.5.0",
@@ -140,6 +145,7 @@
"eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-unused-imports": "^2.0.0",
"fake-indexeddb": "^4.0.2",
+ "gray-matter": "^4.0.3",
"husky": "^9.0.11",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
@@ -147,12 +153,16 @@
"postinstall-postinstall": "^2.1.0",
"mockdate": "^3.0.5",
"prettier": "^2.7.0",
- "storybook": "^8.0.6",
+ "remark-frontmatter": "^5.0.0",
+ "remark-gfm": "^4.0.0",
+ "remark-heading-id": "^1.0.1",
+ "remark-mdx-frontmatter": "^5.0.0",
+ "storybook": "^8.3.4",
"ts-prune": "^0.10.3",
"typechain": "^8.3.2",
"typescript": "^5.4.5",
"typescript-plugin-css-modules": "^4.2.2",
- "webpack": "^5.88.2"
+ "webpack": "^5.94.0"
},
"nextBundleAnalysis": {
"budget": null,
diff --git a/patches/@safe-global+safe-modules-deployments+1.2.0.patch b/patches/@safe-global+safe-modules-deployments+2.2.1.patch
similarity index 96%
rename from patches/@safe-global+safe-modules-deployments+1.2.0.patch
rename to patches/@safe-global+safe-modules-deployments+2.2.1.patch
index ce7a8e398..758793ce1 100644
--- a/patches/@safe-global+safe-modules-deployments+1.2.0.patch
+++ b/patches/@safe-global+safe-modules-deployments+2.2.1.patch
@@ -1,5 +1,5 @@
diff --git a/node_modules/@safe-global/safe-modules-deployments/dist/assets/allowance-module/v0.1.0/allowance-module.json b/node_modules/@safe-global/safe-modules-deployments/dist/assets/allowance-module/v0.1.0/allowance-module.json
-index 1324cfa..5c8a9ba 100644
+index 25a409d..4d53ae7 100644
--- a/node_modules/@safe-global/safe-modules-deployments/dist/assets/allowance-module/v0.1.0/allowance-module.json
+++ b/node_modules/@safe-global/safe-modules-deployments/dist/assets/allowance-module/v0.1.0/allowance-module.json
@@ -6,6 +6,8 @@
@@ -12,7 +12,7 @@ index 1324cfa..5c8a9ba 100644
"56": "0xCFbFaC74C26F8647cBDb8c5caf80BB5b32E43134",
"100": "0xCFbFaC74C26F8647cBDb8c5caf80BB5b32E43134",
diff --git a/node_modules/@safe-global/safe-modules-deployments/src/assets/allowance-module/v0.1.0/allowance-module.json b/node_modules/@safe-global/safe-modules-deployments/src/assets/allowance-module/v0.1.0/allowance-module.json
-index b873a40..5e49d1c 100644
+index 70e4643..6fc893a 100644
--- a/node_modules/@safe-global/safe-modules-deployments/src/assets/allowance-module/v0.1.0/allowance-module.json
+++ b/node_modules/@safe-global/safe-modules-deployments/src/assets/allowance-module/v0.1.0/allowance-module.json
@@ -6,6 +6,8 @@
diff --git a/public/images/common/download-cloud.svg b/public/images/common/download-cloud.svg
new file mode 100644
index 000000000..b6dd6403c
--- /dev/null
+++ b/public/images/common/download-cloud.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/common/multisend.svg b/public/images/common/multisend.svg
new file mode 100644
index 000000000..17a4cd2b8
--- /dev/null
+++ b/public/images/common/multisend.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/common/recovery_coincover.svg b/public/images/common/recovery_coincover.svg
deleted file mode 100644
index 94303918f..000000000
--- a/public/images/common/recovery_coincover.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/common/recovery_sygnum.svg b/public/images/common/recovery_sygnum.svg
index b13483a96..2644602e5 100644
--- a/public/images/common/recovery_sygnum.svg
+++ b/public/images/common/recovery_sygnum.svg
@@ -1,11 +1 @@
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/public/images/common/safe-pass-star.svg b/public/images/common/safe-pass-star.svg
new file mode 100644
index 000000000..2cff53958
--- /dev/null
+++ b/public/images/common/safe-pass-star.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/common/stake.svg b/public/images/common/stake.svg
new file mode 100644
index 000000000..41469d1e8
--- /dev/null
+++ b/public/images/common/stake.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/images/protofire-logo.svg b/public/images/protofire-logo.svg
new file mode 100644
index 000000000..928d8dbaa
--- /dev/null
+++ b/public/images/protofire-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/sidebar/lightbulb_icon.svg b/public/images/sidebar/lightbulb_icon.svg
new file mode 100644
index 000000000..717dd8c3e
--- /dev/null
+++ b/public/images/sidebar/lightbulb_icon.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/public/images/transactions/blockaid-icon.svg b/public/images/transactions/blockaid-icon.svg
new file mode 100644
index 000000000..7c66a098f
--- /dev/null
+++ b/public/images/transactions/blockaid-icon.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/images/transactions/zodiac-roles.svg b/public/images/transactions/zodiac-roles.svg
new file mode 100644
index 000000000..5eeb888df
--- /dev/null
+++ b/public/images/transactions/zodiac-roles.svg
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/address-book/ImportDialog/index.tsx b/src/components/address-book/ImportDialog/index.tsx
index d031893ed..9c5b047ab 100644
--- a/src/components/address-book/ImportDialog/index.tsx
+++ b/src/components/address-book/ImportDialog/index.tsx
@@ -114,7 +114,7 @@ const ImportDialog = ({ handleClose }: { handleClose: () => void }): ReactElemen
}}
>
{/* https://github.com/Bunlong/react-papaparse/blob/master/src/useCSVReader.tsx */}
- {({ getRootProps, acceptedFile, ProgressBar, getRemoveFileProps, Remove }: any) => {
+ {({ getRootProps, acceptedFile, getRemoveFileProps }: any) => {
const { onClick } = getRemoveFileProps()
const onRemove = (e: MouseEvent) => {
diff --git a/src/components/balances/AssetsTable/index.tsx b/src/components/balances/AssetsTable/index.tsx
index df1f069b5..077bfed57 100644
--- a/src/components/balances/AssetsTable/index.tsx
+++ b/src/components/balances/AssetsTable/index.tsx
@@ -1,6 +1,6 @@
import CheckBalance from '@/features/counterfactual/CheckBalance'
import { type ReactElement } from 'react'
-import { Tooltip, Typography, SvgIcon, IconButton, Box, Checkbox, Skeleton } from '@mui/material'
+import { Box, IconButton, Checkbox, Skeleton, SvgIcon, Tooltip, Typography } from '@mui/material'
import type { TokenInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { TokenType } from '@safe-global/safe-gateway-typescript-sdk'
import css from './styles.module.css'
@@ -21,6 +21,9 @@ import SwapButton from '@/features/swap/components/SwapButton'
import { SWAP_LABELS } from '@/services/analytics/events/swaps'
import SendButton from './SendButton'
import useIsSwapFeatureEnabled from '@/features/swap/hooks/useIsSwapFeatureEnabled'
+import useIsStakingFeatureEnabled from '@/features/stake/hooks/useIsSwapFeatureEnabled'
+import { STAKE_LABELS } from '@/services/analytics/events/stake'
+import StakeButton from '@/features/stake/components/StakeButton'
const skeletonCells: EnhancedTableProps['rows'][0]['cells'] = {
asset: {
@@ -97,6 +100,7 @@ const AssetsTable = ({
}): ReactElement => {
const { balances, loading } = useBalances()
const isSwapFeatureEnabled = useIsSwapFeatureEnabled()
+ const isStakingFeatureEnabled = useIsStakingFeatureEnabled()
const { isAssetSelected, toggleAsset, hidingAsset, hideAsset, cancel, deselectAll, saveChanges } = useHideAssets(() =>
setShowHiddenAssets(false),
@@ -130,6 +134,10 @@ const AssetsTable = ({
{item.tokenInfo.name}
+ {isStakingFeatureEnabled && item.tokenInfo.type === TokenType.NATIVE_TOKEN && (
+
+ )}
+
{!isNative && }
),
@@ -164,7 +172,7 @@ const AssetsTable = ({
inheritViewBox
color="error"
fontSize="small"
- sx={{ verticalAlign: 'middle', marginLeft: 0.5 }}
+ sx={{ verticalAlign: 'middle', ml: 0.5, mr: [0, '-20px'] }}
/>
diff --git a/src/components/common/AddressBookInput/index.test.tsx b/src/components/common/AddressBookInput/index.test.tsx
index 449e0eead..ee910e6dc 100644
--- a/src/components/common/AddressBookInput/index.test.tsx
+++ b/src/components/common/AddressBookInput/index.test.tsx
@@ -1,4 +1,5 @@
-import { act, fireEvent, render, waitFor } from '@/tests/test-utils'
+import { act } from 'react'
+import { fireEvent, render, waitFor } from '@/tests/test-utils'
import { FormProvider, useForm } from 'react-hook-form'
import AddressBookInput from '.'
import type { AddressInputProps } from '../AddressInput'
@@ -147,12 +148,12 @@ describe('AddressBookInput', () => {
expect(input).toHaveAttribute('aria-expanded', 'false')
- await act(() => {
+ act(() => {
fireEvent.mouseDown(input)
fireEvent.mouseUp(input)
})
- await act(() => {
+ act(() => {
fireEvent.change(input, { target: { value: invalidAddress } })
jest.advanceTimersByTime(1000)
})
@@ -160,7 +161,8 @@ describe('AddressBookInput', () => {
await waitFor(() => expect(utils.getByLabelText(validationError, { exact: false })).toBeDefined())
const address = checksumAddress(faker.finance.ethereumAddress())
- await act(() => {
+
+ act(() => {
fireEvent.change(input, { target: { value: address } })
jest.advanceTimersByTime(1000)
})
@@ -187,14 +189,14 @@ describe('AddressBookInput', () => {
expect(input).toHaveAttribute('aria-expanded', 'false')
- await act(() => {
+ act(() => {
fireEvent.mouseDown(input)
fireEvent.mouseUp(input)
})
expect(input).toHaveAttribute('aria-expanded', 'true')
- await act(() => {
+ act(() => {
fireEvent.click(utils.getByText('InvalidAddress'))
fireEvent.blur(input)
jest.advanceTimersByTime(1000)
@@ -206,7 +208,7 @@ describe('AddressBookInput', () => {
})
// Clear the input by clicking on the readonly input
- await act(() => {
+ act(() => {
// first click clears input
fireEvent.click(utils.getByLabelText(validationError, { exact: false }))
})
@@ -215,13 +217,13 @@ describe('AddressBookInput', () => {
const newInput = utils.getByLabelText(validationError, { exact: false })
expect(newInput).toBeVisible()
- await act(() => {
+ act(() => {
// mousedown opens autocompletion again
fireEvent.mouseDown(newInput)
fireEvent.mouseUp(newInput)
})
- await act(() => {
+ act(() => {
fireEvent.click(utils.getByText('ValidAddress'))
fireEvent.blur(newInput)
@@ -239,7 +241,7 @@ describe('AddressBookInput', () => {
const { input, utils } = setup('', {}, undefined, true)
const newAddress = checksumAddress(faker.finance.ethereumAddress())
- await act(() => {
+ act(() => {
fireEvent.change(input, { target: { value: newAddress } })
jest.advanceTimersByTime(1000)
})
@@ -253,7 +255,7 @@ describe('AddressBookInput', () => {
})
const nameInput = utils.getByLabelText('Name', { exact: false })
- await act(() => {
+ act(() => {
fireEvent.change(nameInput, { target: { value: 'Tim Testermann' } })
fireEvent.submit(nameInput)
})
@@ -265,7 +267,7 @@ describe('AddressBookInput', () => {
const { input, utils } = setup('', {}, undefined, false)
const newAddress = checksumAddress(faker.finance.ethereumAddress())
- await act(() => {
+ act(() => {
fireEvent.change(input, { target: { value: newAddress } })
jest.advanceTimersByTime(1000)
})
diff --git a/src/components/common/AddressBookInput/index.tsx b/src/components/common/AddressBookInput/index.tsx
index c49de00ef..77be19a66 100644
--- a/src/components/common/AddressBookInput/index.tsx
+++ b/src/components/common/AddressBookInput/index.tsx
@@ -62,7 +62,8 @@ const AddressBookInput = ({ name, canAdd, ...props }: AddressInputProps & { canA
(
+ // eslint-disable-next-line
+ render={({ field: { ref, ...field } }) => (
)}
/>
diff --git a/src/components/common/BlockedAddress/index.tsx b/src/components/common/BlockedAddress/index.tsx
index 4e2dc6d8e..3d271d422 100644
--- a/src/components/common/BlockedAddress/index.tsx
+++ b/src/components/common/BlockedAddress/index.tsx
@@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
import Disclaimer from '@/components/common/Disclaimer'
import { AppRoutes } from '@/config/routes'
-export const BlockedAddress = ({ address }: { address?: string }): ReactElement => {
+export const BlockedAddress = ({ address, featureTitle }: { address: string; featureTitle: string }): ReactElement => {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
const displayAddress = address && isMobile ? shortenAddress(address) : address
@@ -19,7 +19,7 @@ export const BlockedAddress = ({ address }: { address?: string }): ReactElement
)
diff --git a/src/components/common/ChainIndicator/styles.module.css b/src/components/common/ChainIndicator/styles.module.css
index c80681292..e1ed054b6 100644
--- a/src/components/common/ChainIndicator/styles.module.css
+++ b/src/components/common/ChainIndicator/styles.module.css
@@ -30,6 +30,12 @@
.indicator {
min-width: 35px;
}
+ .responsive {
+ min-width: 0;
+ }
+ .responsive .name {
+ display: none;
+ }
}
@container my-accounts-container (max-width: 500px) {
diff --git a/src/components/common/ChainSwitcher/index.tsx b/src/components/common/ChainSwitcher/index.tsx
index 8c70c4f4a..f0955d13c 100644
--- a/src/components/common/ChainSwitcher/index.tsx
+++ b/src/components/common/ChainSwitcher/index.tsx
@@ -1,30 +1,57 @@
import type { ReactElement } from 'react'
-import { useCallback } from 'react'
-import { Box, Button } from '@mui/material'
+import { useCallback, useState } from 'react'
+import { Button, CircularProgress, Typography } from '@mui/material'
import { useCurrentChain } from '@/hooks/useChains'
import useOnboard from '@/hooks/wallets/useOnboard'
import useIsWrongChain from '@/hooks/useIsWrongChain'
-import css from './styles.module.css'
import { switchWalletChain } from '@/services/tx/tx-sender/sdk'
-const ChainSwitcher = ({ fullWidth }: { fullWidth?: boolean }): ReactElement | null => {
+const ChainSwitcher = ({
+ fullWidth,
+ primaryCta = false,
+}: {
+ fullWidth?: boolean
+ primaryCta?: boolean
+}): ReactElement | null => {
const chain = useCurrentChain()
const onboard = useOnboard()
const isWrongChain = useIsWrongChain()
+ const [loading, setIsLoading] = useState(false)
const handleChainSwitch = useCallback(async () => {
if (!onboard || !chain) return
-
+ setIsLoading(true)
await switchWalletChain(onboard, chain.chainId)
+ setIsLoading(false)
}, [chain, onboard])
if (!isWrongChain) return null
return (
-
- Switch to
-
- {chain?.chainName}
+
+ {loading ? (
+
+ ) : (
+ <>
+ Switch to
+
+ {chain?.chainName}
+ >
+ )}
)
}
diff --git a/src/components/common/CheckWallet/index.test.tsx b/src/components/common/CheckWallet/index.test.tsx
index 7172f1e92..6d2806617 100644
--- a/src/components/common/CheckWallet/index.test.tsx
+++ b/src/components/common/CheckWallet/index.test.tsx
@@ -2,6 +2,7 @@ import { render } from '@/tests/test-utils'
import CheckWallet from '.'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
+import useIsWrongChain from '@/hooks/useIsWrongChain'
import useWallet from '@/hooks/wallets/useWallet'
import { chainBuilder } from '@/tests/builders/chains'
@@ -31,7 +32,14 @@ jest.mock('@/hooks/useChains', () => ({
useCurrentChain: jest.fn(() => chainBuilder().build()),
}))
-const renderButton = () => render({(isOk) => Continue } )
+// mock useIsWrongChain
+jest.mock('@/hooks/useIsWrongChain', () => ({
+ __esModule: true,
+ default: jest.fn(() => false),
+}))
+
+const renderButton = () =>
+ render({(isOk) => Continue } )
describe('CheckWallet', () => {
beforeEach(() => {
@@ -69,6 +77,18 @@ describe('CheckWallet', () => {
)
})
+ it('should be disabled when connected to the wrong network', () => {
+ ;(useIsWrongChain as jest.MockedFunction).mockReturnValue(true)
+ ;(useIsSafeOwner as jest.MockedFunction).mockReturnValueOnce(true)
+
+ const renderButtonWithNetworkCheck = () =>
+ render({(isOk) => } )
+
+ const { container } = renderButtonWithNetworkCheck()
+
+ expect(container.querySelector('button')).toBeDisabled()
+ })
+
it('should not disable the button for non-owner spending limit benificiaries', () => {
;(useIsSafeOwner as jest.MockedFunction).mockReturnValueOnce(false)
;(
diff --git a/src/components/common/CheckWallet/index.tsx b/src/components/common/CheckWallet/index.tsx
index 60ad9fc52..b354fb8f5 100644
--- a/src/components/common/CheckWallet/index.tsx
+++ b/src/components/common/CheckWallet/index.tsx
@@ -1,37 +1,58 @@
+import { useIsWalletDelegate } from '@/hooks/useDelegates'
import { type ReactElement } from 'react'
-import { Tooltip } from '@mui/material'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import useWallet from '@/hooks/wallets/useWallet'
import useConnectWallet from '../ConnectWallet/useConnectWallet'
+import useIsWrongChain from '@/hooks/useIsWrongChain'
+import { Tooltip } from '@mui/material'
+import useSafeInfo from '@/hooks/useSafeInfo'
type CheckWalletProps = {
children: (ok: boolean) => ReactElement
allowSpendingLimit?: boolean
allowNonOwner?: boolean
noTooltip?: boolean
+ checkNetwork?: boolean
}
enum Message {
WalletNotConnected = 'Please connect your wallet',
NotSafeOwner = 'Your connected wallet is not a signer of this Safe Account',
+ CounterfactualMultisig = 'You need to activate the Safe before transacting',
}
-const CheckWallet = ({ children, allowSpendingLimit, allowNonOwner, noTooltip }: CheckWalletProps): ReactElement => {
+const CheckWallet = ({
+ children,
+ allowSpendingLimit,
+ allowNonOwner,
+ noTooltip,
+ checkNetwork = false,
+}: CheckWalletProps): ReactElement => {
const wallet = useWallet()
const isSafeOwner = useIsSafeOwner()
const isSpendingLimit = useIsOnlySpendingLimitBeneficiary()
const connectWallet = useConnectWallet()
+ const isWrongChain = useIsWrongChain()
+ const isDelegate = useIsWalletDelegate()
+
+ const { safe } = useSafeInfo()
+
+ const isCounterfactualMultiSig = !allowNonOwner && !safe.deployed && safe.threshold > 1
const message =
- wallet && (isSafeOwner || allowNonOwner || (isSpendingLimit && allowSpendingLimit))
+ wallet &&
+ (isSafeOwner || allowNonOwner || (isSpendingLimit && allowSpendingLimit) || isDelegate) &&
+ !isCounterfactualMultiSig
? ''
: !wallet
? Message.WalletNotConnected
+ : isCounterfactualMultiSig
+ ? Message.CounterfactualMultisig
: Message.NotSafeOwner
+ if (checkNetwork && isWrongChain) return children(false)
if (!message) return children(true)
-
if (noTooltip) return children(false)
return (
diff --git a/src/components/common/Chip/index.tsx b/src/components/common/Chip/index.tsx
index 81bb62368..f87237f24 100644
--- a/src/components/common/Chip/index.tsx
+++ b/src/components/common/Chip/index.tsx
@@ -1,8 +1,32 @@
-import { Chip as MuiChip } from '@mui/material'
-import type { ChipProps } from '@mui/material'
-import type { ReactElement } from 'react'
-import React from 'react'
+import { Typography, Chip as MuiChip, type ChipProps } from '@mui/material'
-export function Chip(props: ChipProps): ReactElement {
- return
+type Props = {
+ label?: string
+ sx?: ChipProps['sx']
+}
+
+export function Chip({ sx, label = 'New' }: Props) {
+ return (
+
+ {label}
+
+ }
+ />
+ )
}
diff --git a/src/components/common/ConnectWallet/WalletDetails.tsx b/src/components/common/ConnectWallet/WalletDetails.tsx
deleted file mode 100644
index 8bab108de..000000000
--- a/src/components/common/ConnectWallet/WalletDetails.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Box, Divider, SvgIcon, Typography } from '@mui/material'
-import type { ReactElement } from 'react'
-
-import LockIcon from '@/public/images/common/lock.svg'
-
-import WalletLogin from '@/components/welcome/WelcomeLogin/WalletLogin'
-
-const WalletDetails = ({ onConnect }: { onConnect: () => void }): ReactElement => {
- return (
- <>
-
-
-
-
-
-
-
-
-
-
- or
-
-
- >
- )
-}
-
-export default WalletDetails
diff --git a/src/components/common/CookieAndTermBanner/index.tsx b/src/components/common/CookieAndTermBanner/index.tsx
index be809a48b..5c80c3005 100644
--- a/src/components/common/CookieAndTermBanner/index.tsx
+++ b/src/components/common/CookieAndTermBanner/index.tsx
@@ -4,9 +4,15 @@ import type { CheckboxProps } from '@mui/material'
import { Grid, Button, Checkbox, FormControlLabel, Typography, Paper, SvgIcon, Box } from '@mui/material'
import WarningIcon from '@/public/images/notifications/warning.svg'
import { useForm } from 'react-hook-form'
+import { metadata } from '@/markdown/terms/terms.md'
import { useAppDispatch, useAppSelector } from '@/store'
-import { selectCookies, CookieAndTermType, saveCookieAndTermConsent } from '@/store/cookiesAndTermsSlice'
+import {
+ selectCookies,
+ CookieAndTermType,
+ saveCookieAndTermConsent,
+ hasAcceptedTerms,
+} from '@/store/cookiesAndTermsSlice'
import { selectCookieBanner, openCookieBanner, closeCookieBanner } from '@/store/popupSlice'
import css from './styles.module.css'
@@ -52,7 +58,13 @@ export const CookieAndTermBanner = ({
})
const handleAccept = () => {
- dispatch(saveCookieAndTermConsent(getValues()))
+ const values = getValues()
+ dispatch(
+ saveCookieAndTermConsent({
+ ...values,
+ termsVersion: metadata.version,
+ }),
+ )
dispatch(closeCookieBanner())
}
@@ -74,9 +86,11 @@ export const CookieAndTermBanner = ({
- By browsing this page, you accept our Terms & Conditions (last updated July 2024) and the use of necessary
- cookies. By clicking "Accept all" you additionally agree to the use of Beamer and Analytics
- cookies as listed below. Cookie policy
+ By browsing this page, you accept our{' '}
+ Terms & Conditions (last updated{' '}
+ {metadata.last_update_date}) and the use of necessary cookies. By clicking "Accept all" you
+ additionally agree to the use of Beamer and Analytics cookies as listed below.{' '}
+ Cookie policy
@@ -135,11 +149,10 @@ export const CookieAndTermBanner = ({
const CookieBannerPopup = (): ReactElement | null => {
const cookiePopup = useAppSelector(selectCookieBanner)
- const cookies = useAppSelector(selectCookies)
const dispatch = useAppDispatch()
- // Open the banner if cookie preferences haven't been set
- const shouldOpen = cookies[CookieAndTermType.NECESSARY] === undefined
+ const hasAccepted = useAppSelector(hasAcceptedTerms)
+ const shouldOpen = !hasAccepted
useEffect(() => {
if (shouldOpen) {
@@ -155,5 +168,4 @@ const CookieBannerPopup = (): ReactElement | null => {
) : null
}
-
export default CookieBannerPopup
diff --git a/src/components/common/CopyTooltip/ConfirmCopyModal.tsx b/src/components/common/CopyTooltip/ConfirmCopyModal.tsx
index b2c039898..d659346f0 100644
--- a/src/components/common/CopyTooltip/ConfirmCopyModal.tsx
+++ b/src/components/common/CopyTooltip/ConfirmCopyModal.tsx
@@ -16,6 +16,8 @@ import { type ReactElement, useEffect, type SyntheticEvent } from 'react'
import { trackEvent, TX_LIST_EVENTS } from '@/services/analytics'
import Track from '../Track'
+import css from './styles.module.css'
+
export type ConfirmCopyModalProps = {
open: boolean
onClose: () => void
@@ -47,11 +49,18 @@ const ConfirmCopyModal = ({ open, onClose, onCopy, children }: ConfirmCopyModalP
{children}
-
-
- Proceed and copy
-
-
+
+
+
+ Proceed and copy
+
+
+
+
+ Do not copy
+
+
+
)
diff --git a/src/components/common/CopyTooltip/styles.module.css b/src/components/common/CopyTooltip/styles.module.css
new file mode 100644
index 000000000..6edc2a146
--- /dev/null
+++ b/src/components/common/CopyTooltip/styles.module.css
@@ -0,0 +1,15 @@
+.dialogActions {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+@media (max-width: 599.95px) {
+ .dialogActions {
+ flex-direction: column;
+ width: 100%;
+ }
+ .dialogActions > span {
+ width: 100%;
+ }
+}
diff --git a/src/components/common/DatePickerInput/index.tsx b/src/components/common/DatePickerInput/index.tsx
index ed760e712..1d8ca6137 100644
--- a/src/components/common/DatePickerInput/index.tsx
+++ b/src/components/common/DatePickerInput/index.tsx
@@ -53,7 +53,7 @@ const DatePickerInput = ({
inputFormat="dd/MM/yyyy"
{...field}
disableFuture={disableFuture}
- renderInput={({ label, error: _, ...params }) => (
+ renderInput={({ label, ...params }) => (
)}
PaperProps={{
diff --git a/src/components/common/DateTime/DateTime.tsx b/src/components/common/DateTime/DateTime.tsx
index 6ce5776e2..b29017a6a 100644
--- a/src/components/common/DateTime/DateTime.tsx
+++ b/src/components/common/DateTime/DateTime.tsx
@@ -9,8 +9,10 @@ type DateTimeProps = {
}
export const DateTime = ({ value, showDateTime, showTime }: DateTimeProps): ReactElement => {
+ const showTooltip = !showDateTime || showTime
+
return (
-
+
{showTime ? formatTime(value) : showDateTime ? formatDateTime(value) : formatTimeInWords(value)}
)
diff --git a/src/components/common/DateTime/index.test.tsx b/src/components/common/DateTime/index.test.tsx
index a749c21d7..d0e07ad03 100644
--- a/src/components/common/DateTime/index.test.tsx
+++ b/src/components/common/DateTime/index.test.tsx
@@ -70,13 +70,11 @@ describe('DateTime', () => {
date.setDate(date.getDate() - days)
- const { queryByText } = render( , {
+ const { getByText } = render( , {
routerProps: { pathname: '/transactions/history' },
})
- const expected = formatDateTime(date.getTime())
-
- expect(queryByText('3 days ago')).toBeInTheDocument()
+ expect(getByText('3 days ago')).toBeInTheDocument()
})
it('should render the full date and time after threshold on the filter', () => {
diff --git a/src/components/common/EnhancedTable/index.tsx b/src/components/common/EnhancedTable/index.tsx
index 0874f4beb..1be254118 100644
--- a/src/components/common/EnhancedTable/index.tsx
+++ b/src/components/common/EnhancedTable/index.tsx
@@ -88,6 +88,7 @@ function EnhancedTableHead(props: EnhancedTableHeadProps) {
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={createSortHandler(headCell.id)}
+ sx={{ mr: [0, '-26px'] }}
>
{headCell.label}
{orderBy === headCell.id ? (
diff --git a/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx b/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx
index d21b9a830..06701edc7 100644
--- a/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx
+++ b/src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx
@@ -83,7 +83,7 @@ const SrcEthHashInfo = ({
)}
-
+
{name && (
@@ -104,7 +104,12 @@ const SrcEthHashInfo = ({
{(!onlyName || !name) && (
{copyAddress ? (
-
+
{addressElement}
) : (
diff --git a/src/components/common/EthHashInfo/SrcEthHashInfo/styles.module.css b/src/components/common/EthHashInfo/SrcEthHashInfo/styles.module.css
index 9dc2b92c1..1f755478a 100644
--- a/src/components/common/EthHashInfo/SrcEthHashInfo/styles.module.css
+++ b/src/components/common/EthHashInfo/SrcEthHashInfo/styles.module.css
@@ -19,7 +19,6 @@
.addressContainer {
display: flex;
align-items: center;
- gap: 0.25em;
white-space: nowrap;
}
diff --git a/src/components/common/EthHashInfo/index.test.tsx b/src/components/common/EthHashInfo/index.test.tsx
index 0facb02d3..3ae9e9282 100644
--- a/src/components/common/EthHashInfo/index.test.tsx
+++ b/src/components/common/EthHashInfo/index.test.tsx
@@ -1,7 +1,8 @@
import { blo } from 'blo'
+import { act } from 'react'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import { act, fireEvent, render, waitFor } from '@/tests/test-utils'
+import { fireEvent, render, waitFor } from '@/tests/test-utils'
import * as useAllAddressBooks from '@/hooks/useAllAddressBooks'
import * as useChainId from '@/hooks/useChainId'
import * as store from '@/store'
@@ -258,7 +259,7 @@ describe('EthHashInfo', () => {
const button = container.querySelector('button')
- await act(() => {
+ act(() => {
fireEvent.click(button!)
})
@@ -288,7 +289,7 @@ describe('EthHashInfo', () => {
const button = container.querySelector('button')
- await act(() => {
+ act(() => {
fireEvent.click(button!)
})
@@ -320,7 +321,7 @@ describe('EthHashInfo', () => {
const button = container.querySelector('button')
- await act(() => {
+ act(() => {
fireEvent.click(button!)
})
@@ -349,7 +350,7 @@ describe('EthHashInfo', () => {
const button = container.querySelector('button')
- await act(() => {
+ act(() => {
fireEvent.click(button!)
})
@@ -375,7 +376,7 @@ describe('EthHashInfo', () => {
const button = container.querySelector('button')
- await act(() => {
+ act(() => {
fireEvent.click(button!)
})
diff --git a/src/components/common/FiatValue/FiatValue.test.tsx b/src/components/common/FiatValue/FiatValue.test.tsx
new file mode 100644
index 000000000..c4d6e8ce2
--- /dev/null
+++ b/src/components/common/FiatValue/FiatValue.test.tsx
@@ -0,0 +1,37 @@
+import { render } from '@/tests/test-utils'
+
+const normalizer = (text: string) => text.replace(/\u200A/g, ' ')
+
+describe('FiatValue', () => {
+ beforeEach(() => {
+ Object.defineProperty(window, 'navigator', {
+ value: {
+ language: 'en-US',
+ },
+ writable: true,
+ })
+ })
+
+ it('should render fiat value', () => {
+ const FiatValue = require('.').default
+ const { getByText } = render( )
+ const span = getByText((content) => normalizer(content) === '$ 100', { normalizer })
+ expect(span).toBeInTheDocument()
+ expect(span).toHaveAttribute('aria-label', '$โ100.00')
+ })
+
+ it('should render a big fiat value', () => {
+ const FiatValue = require('.').default
+ const { getByText } = render( )
+ const span = getByText((content) => normalizer(content) === '$ 100.29M', { normalizer })
+ expect(span).toBeInTheDocument()
+ expect(span).toHaveAttribute('aria-label', '$โ100,285,367.00')
+ })
+
+ it('should render fiat value with precise=true', () => {
+ const FiatValue = require('.').default
+ const { getByText } = render( )
+ expect(getByText((content) => normalizer(content) === '$ 100', { normalizer })).toBeInTheDocument()
+ expect(getByText('.35')).toBeInTheDocument()
+ })
+})
diff --git a/src/components/common/FiatValue/index.tsx b/src/components/common/FiatValue/index.tsx
index 9cc09f1ea..d934a7b28 100644
--- a/src/components/common/FiatValue/index.tsx
+++ b/src/components/common/FiatValue/index.tsx
@@ -1,22 +1,54 @@
import type { CSSProperties, ReactElement } from 'react'
import { useMemo } from 'react'
+import { Tooltip, Typography } from '@mui/material'
import { useAppSelector } from '@/store'
import { selectCurrency } from '@/store/settingsSlice'
-import { formatCurrency } from '@/utils/formatNumber'
+import { formatCurrency, formatCurrencyPrecise } from '@/utils/formatNumber'
const style = { whiteSpace: 'nowrap' } as CSSProperties
-const FiatValue = ({ value, maxLength }: { value: string | number; maxLength?: number }): ReactElement => {
+const FiatValue = ({
+ value,
+ maxLength,
+ precise,
+}: {
+ value: string | number
+ maxLength?: number
+ precise?: boolean
+}): ReactElement => {
const currency = useAppSelector(selectCurrency)
const fiat = useMemo(() => {
return formatCurrency(value, currency, maxLength)
}, [value, currency, maxLength])
+ const preciseFiat = useMemo(() => {
+ return formatCurrencyPrecise(value, currency)
+ }, [value, currency])
+
+ const [whole, decimals, endCurrency] = useMemo(() => {
+ const match = preciseFiat.match(/(.+)(\D\d+)(\D+)?$/)
+ return match ? match.slice(1) : ['', preciseFiat, '', '']
+ }, [preciseFiat])
+
return (
-
- {fiat}
-
+
+
+ {precise ? (
+ <>
+ {whole}
+ {decimals && (
+
+ {decimals}
+
+ )}
+ {endCurrency}
+ >
+ ) : (
+ fiat
+ )}
+
+
)
}
diff --git a/src/components/common/Footer/index.tsx b/src/components/common/Footer/index.tsx
index 2692e42e0..f37691d65 100644
--- a/src/components/common/Footer/index.tsx
+++ b/src/components/common/Footer/index.tsx
@@ -10,6 +10,8 @@ import packageJson from '../../../../package.json'
import ExternalLink from '../ExternalLink'
import MUILink from '@mui/material/Link'
import { HELP_CENTER_URL, IS_DEV, IS_OFFICIAL_HOST } from '@/config/constants'
+import darkPalette from '@/components/theme/darkPalette'
+import ProtofireLogo from '@/public/images/protofire-logo.svg'
const footerPages = [
AppRoutes.welcome.index,
@@ -75,9 +77,22 @@ const Footer = (): ReactElement | null => {
>
) : (
-
- Preferences
-
+ <>
+
+ Terms
+
+
+ Cookie policy
+
+
+ Preferences
+
+
+
+ Help
+
+
+ >
)}
@@ -85,9 +100,20 @@ const Footer = (): ReactElement | null => {
v{packageJson.version}
- {/*
-
- */}
+
+
+ Supported by{' '}
+
+
+ Protofire
+
+
+
)
diff --git a/src/components/common/GeoblockingProvider/index.tsx b/src/components/common/GeoblockingProvider/index.tsx
index b6451de00..47e660508 100644
--- a/src/components/common/GeoblockingProvider/index.tsx
+++ b/src/components/common/GeoblockingProvider/index.tsx
@@ -1,27 +1,19 @@
import { AppRoutes } from '@/config/routes'
-import { createContext, type ReactElement, type ReactNode, useEffect, useState } from 'react'
+import useAsync from '@/hooks/useAsync'
+import { createContext, type ReactElement, type ReactNode } from 'react'
export const GeoblockingContext = createContext(null)
+const checkBlocked = async () => {
+ const res = await fetch(AppRoutes.swap, { method: 'HEAD' })
+ return res.status === 403
+}
+
/**
* Endpoint returns a 403 if the requesting user is from one of the OFAC sanctioned countries
*/
const GeoblockingProvider = ({ children }: { children: ReactNode }): ReactElement => {
- const [isBlockedCountry, setIsBlockedCountry] = useState(null)
-
- useEffect(() => {
- const fetchSwaps = async () => {
- await fetch(AppRoutes.swap, { method: 'HEAD' }).then((res) => {
- if (res.status === 403) {
- setIsBlockedCountry(true)
- } else {
- setIsBlockedCountry(false)
- }
- })
- }
-
- fetchSwaps()
- }, [])
+ const [isBlockedCountry = null] = useAsync(checkBlocked, [])
return {children}
}
diff --git a/src/components/common/Header/index.tsx b/src/components/common/Header/index.tsx
index e08289543..cfa880fd6 100644
--- a/src/components/common/Header/index.tsx
+++ b/src/components/common/Header/index.tsx
@@ -8,11 +8,11 @@ import classnames from 'classnames'
import css from './styles.module.css'
import ConnectWallet from '@/components/common/ConnectWallet'
import NetworkSelector from '@/components/common/NetworkSelector'
-import SafeTokenWidget, { getSafeTokenAddress } from '@/components/common/SafeTokenWidget'
+import SafeTokenWidget from '@/components/common/SafeTokenWidget'
import NotificationCenter from '@/components/notification-center/NotificationCenter'
import { AppRoutes } from '@/config/routes'
-import useChainId from '@/hooks/useChainId'
import SafeLogo from '@/public/images/logo.svg'
+import SafeLogoMobile from '@/public/images/logo-no-text.svg'
import Link from 'next/link'
import useSafeAddress from '@/hooks/useSafeAddress'
import BatchIndicator from '@/components/batch/BatchIndicator'
@@ -21,6 +21,7 @@ import { FEATURES } from '@/utils/chains'
import { useHasFeature } from '@/hooks/useChains'
import Track from '@/components/common/Track'
import { OVERVIEW_EVENTS, OVERVIEW_LABELS } from '@/services/analytics'
+import { useSafeTokenEnabled } from '@/hooks/useSafeTokenEnabled'
type HeaderProps = {
onMenuToggle?: Dispatch>
@@ -36,9 +37,8 @@ function getLogoLink(router: ReturnType): Url {
}
const Header = ({ onMenuToggle, onBatchToggle }: HeaderProps): ReactElement => {
- const chainId = useChainId()
const safeAddress = useSafeAddress()
- const showSafeToken = safeAddress && !!getSafeTokenAddress(chainId)
+ const showSafeToken = useSafeTokenEnabled()
const router = useRouter()
const enableWc = useHasFeature(FEATURES.NATIVE_WALLETCONNECT)
@@ -61,10 +61,18 @@ const Header = ({ onMenuToggle, onBatchToggle }: HeaderProps): ReactElement => {
return (
-
-
-
-
+
+ {onMenuToggle && (
+
+
+
+ )}
+
+
+
+
+
+
diff --git a/src/components/common/Header/styles.module.css b/src/components/common/Header/styles.module.css
index 3051c3ba1..e2a572a9f 100644
--- a/src/components/common/Header/styles.module.css
+++ b/src/components/common/Header/styles.module.css
@@ -36,7 +36,12 @@
align-items: flex-start;
}
-.logo svg {
+.logoMobile {
+ display: none;
+}
+
+.logo svg,
+.logoMobile svg {
width: auto;
display: block;
color: var(--color-logo-main);
@@ -64,8 +69,17 @@
display: none;
}
+ .logoMobile {
+ display: flex;
+ flex: 1;
+ border: none;
+ align-items: flex-start;
+ margin-left: var(--space-2);
+ }
+
.menuButton {
display: flex;
+ flex: 0;
}
}
@@ -73,8 +87,4 @@
.hideMobile {
display: none;
}
-
- .hideSidebarMobile {
- visibility: hidden;
- }
}
diff --git a/src/components/common/Identicon/index.tsx b/src/components/common/Identicon/index.tsx
index 6b3d4d762..fb78a42e7 100644
--- a/src/components/common/Identicon/index.tsx
+++ b/src/components/common/Identicon/index.tsx
@@ -4,6 +4,7 @@ import { blo } from 'blo'
import Skeleton from '@mui/material/Skeleton'
import css from './styles.module.css'
+import { isAddress } from 'ethers'
export interface IdenticonProps {
address: string
@@ -13,6 +14,9 @@ export interface IdenticonProps {
const Identicon = ({ address, size = 40 }: IdenticonProps): ReactElement => {
const style = useMemo
(() => {
try {
+ if (!isAddress(address)) {
+ return null
+ }
const blockie = blo(address as `0x${string}`)
return {
backgroundImage: `url(${blockie})`,
diff --git a/src/components/common/ModalDialog/styles.module.css b/src/components/common/ModalDialog/styles.module.css
index 526648940..dc1b74134 100644
--- a/src/components/common/ModalDialog/styles.module.css
+++ b/src/components/common/ModalDialog/styles.module.css
@@ -1,5 +1,5 @@
.dialog :global .MuiDialogActions-root {
- border-top: 2px solid var(--color-border-light);
+ border-top: 1px solid var(--color-border-light);
padding: var(--space-3);
}
@@ -14,7 +14,7 @@
}
.dialog :global .MuiDialogTitle-root {
- border-bottom: 2px solid var(--color-border-light);
+ border-bottom: 1px solid var(--color-border-light);
}
@media (min-width: 600px) {
diff --git a/src/components/common/NameInput/index.tsx b/src/components/common/NameInput/index.tsx
index 09289fc8a..ac3a1ed27 100644
--- a/src/components/common/NameInput/index.tsx
+++ b/src/components/common/NameInput/index.tsx
@@ -1,17 +1,15 @@
import type { TextFieldProps } from '@mui/material'
import { TextField } from '@mui/material'
import get from 'lodash/get'
-import { type FieldError, type Validate, useFormContext } from 'react-hook-form'
+import { type FieldError, useFormContext } from 'react-hook-form'
import inputCss from '@/styles/inputs.module.css'
const NameInput = ({
name,
- validate,
required = false,
...props
}: Omit & {
name: string
- validate?: Validate
required?: boolean
}) => {
const { register, formState } = useFormContext() || {}
diff --git a/src/components/common/NamedAddressInfo/index.tsx b/src/components/common/NamedAddressInfo/index.tsx
index 7ebd1852e..575f8fa78 100644
--- a/src/components/common/NamedAddressInfo/index.tsx
+++ b/src/components/common/NamedAddressInfo/index.tsx
@@ -11,14 +11,10 @@ const NamedAddressInfo = ({ address, name, customAvatar, ...props }: EthHashInfo
[address, chainId, name, customAvatar],
)
- return (
-
- )
+ const finalName = name || contract?.displayName || contract?.name
+ const finalAvatar = customAvatar || contract?.logoUri
+
+ return
}
export default NamedAddressInfo
diff --git a/src/components/common/NetworkSelector/index.tsx b/src/components/common/NetworkSelector/index.tsx
index 296dabd33..22ea17309 100644
--- a/src/components/common/NetworkSelector/index.tsx
+++ b/src/components/common/NetworkSelector/index.tsx
@@ -1,7 +1,6 @@
import ChainIndicator from '@/components/common/ChainIndicator'
import { useDarkMode } from '@/hooks/useDarkMode'
import { useTheme } from '@mui/material/styles'
-import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import Link from 'next/link'
import type { SelectChangeEvent } from '@mui/material'
import { ListSubheader, MenuItem, Select, Skeleton } from '@mui/material'
@@ -16,6 +15,8 @@ import { useCallback } from 'react'
import { AppRoutes } from '@/config/routes'
import { trackEvent, OVERVIEW_EVENTS } from '@/services/analytics'
import useWallet from '@/hooks/wallets/useWallet'
+import { useAppSelector } from '@/store'
+import { selectChains } from '@/store/chainsSlice'
const NetworkSelector = (props: { onChainSelect?: () => void }): ReactElement => {
const isDarkMode = useDarkMode()
@@ -24,8 +25,8 @@ const NetworkSelector = (props: { onChainSelect?: () => void }): ReactElement =>
const chainId = useChainId()
const router = useRouter()
const isWalletConnected = !!useWallet()
-
const [testNets, prodNets] = useMemo(() => partition(configs, (config) => config.isTestnet), [configs])
+ const chains = useAppSelector(selectChains)
const getNetworkLink = useCallback(
(shortName: string) => {
@@ -67,16 +68,18 @@ const NetworkSelector = (props: { onChainSelect?: () => void }): ReactElement =>
}
const renderMenuItem = useCallback(
- (value: string, chain: ChainInfo) => {
+ (chainId: string, isSelected: boolean) => {
+ const chain = chains.data.find((chain) => chain.chainId === chainId)
+ if (!chain) return null
return (
-
+
-
+
)
},
- [getNetworkLink, props.onChainSelect],
+ [chains.data, getNetworkLink, props.onChainSelect],
)
return configs.length ? (
@@ -87,6 +90,7 @@ const NetworkSelector = (props: { onChainSelect?: () => void }): ReactElement =>
className={css.select}
variant="standard"
IconComponent={ExpandMoreIcon}
+ renderValue={(value) => renderMenuItem(value, true)}
MenuProps={{
transitionDuration: 0,
sx: {
@@ -108,11 +112,11 @@ const NetworkSelector = (props: { onChainSelect?: () => void }): ReactElement =>
},
}}
>
- {prodNets.map((chain) => renderMenuItem(chain.chainId, chain))}
+ {prodNets.map((chain) => renderMenuItem(chain.chainId, false))}
Testnets
- {testNets.map((chain) => renderMenuItem(chain.chainId, chain))}
+ {testNets.map((chain) => renderMenuItem(chain.chainId, false))}
) : (
diff --git a/src/components/common/NetworkSelector/styles.module.css b/src/components/common/NetworkSelector/styles.module.css
index a2ab95a39..1703446ac 100644
--- a/src/components/common/NetworkSelector/styles.module.css
+++ b/src/components/common/NetworkSelector/styles.module.css
@@ -28,9 +28,8 @@
pointer-events: none;
}
-.menuItem {
- padding-top: var(--space-1);
- padding-bottom: var(--space-1);
+.select :global .MuiMenuItem-root {
+ padding: 0;
}
.listSubHeader {
diff --git a/src/components/common/OnboardingTooltip/index.tsx b/src/components/common/OnboardingTooltip/index.tsx
index 7a08e26f7..c5868791d 100644
--- a/src/components/common/OnboardingTooltip/index.tsx
+++ b/src/components/common/OnboardingTooltip/index.tsx
@@ -33,6 +33,7 @@ export const OnboardingTooltip = ({
- {text}
+ {text}
jest.fn(() => '1'))
+jest.mock('@/hooks/useChainId')
jest.mock('@/hooks/useSafeTokenAllocation')
@@ -20,10 +22,17 @@ describe('SafeTokenWidget', () => {
get: () => fakeSafeAddress,
} as any),
)
+
+ jest.spyOn(safePass, 'useGetOwnGlobalCampaignRankQuery').mockReturnValue({
+ data: undefined,
+ isLoading: false,
+ refetch: jest.fn(),
+ })
+ ;(useChainId as jest.Mock).mockImplementation(jest.fn(() => '1'))
})
it('Should render nothing for unsupported chains', () => {
- ;(useChainId as jest.Mock).mockImplementationOnce(jest.fn(() => '100'))
+ ;(useChainId as jest.Mock).mockImplementation(jest.fn(() => '100'))
;(useSafeTokenAllocation as jest.Mock).mockImplementation(() => [[], , false])
;(useSafeVotingPower as jest.Mock).mockImplementation(() => [BigInt(0), , false])
@@ -43,16 +52,10 @@ describe('SafeTokenWidget', () => {
;(useSafeTokenAllocation as jest.Mock).mockImplementation(() => [[], , false])
;(useSafeVotingPower as jest.Mock).mockImplementation(() => [BigInt('472238796133701648384'), , false])
- // to avoid failing tests in some environments
- const NumberFormat = Intl.NumberFormat
- const englishTestLocale = 'en'
-
- jest.spyOn(Intl, 'NumberFormat').mockImplementation((_, ...rest) => new NumberFormat([englishTestLocale], ...rest))
-
const result = render( )
await waitFor(() => {
- expect(result.baseElement).toHaveTextContent('472.24')
- expect(result.baseElement).not.toHaveTextContent('472.2388')
+ expect(result.baseElement).toHaveTextContent('472')
+ expect(result.baseElement).not.toHaveTextContent('472.2')
})
})
@@ -70,13 +73,25 @@ describe('SafeTokenWidget', () => {
})
})
- it('Should render a claim button for SEP5 qualification', async () => {
- ;(useSafeTokenAllocation as jest.Mock).mockImplementation(() => [[{ tag: 'user_v2' }], , false])
- ;(useSafeVotingPower as jest.Mock).mockImplementation(() => [BigInt(420000), , false])
-
+ it('Should render the Safe{Pass} points', async () => {
+ ;(useSafeTokenAllocation as jest.Mock).mockImplementation(() => [[], , false])
+ ;(useSafeVotingPower as jest.Mock).mockImplementation(() => [BigInt(420 * 10 ** 18), , false])
+ const mockCampaignRank: CampaignLeaderboardEntry = {
+ boost: '2.0',
+ holder: fakeSafeAddress,
+ position: 421,
+ totalBoostedPoints: 138,
+ totalPoints: 69,
+ }
+ jest.spyOn(safePass, 'useGetOwnGlobalCampaignRankQuery').mockReturnValue({
+ data: mockCampaignRank,
+ isLoading: false,
+ refetch: jest.fn(),
+ })
const result = render( )
await waitFor(() => {
- expect(result.baseElement).toContainHTML('New allocation')
+ expect(result.queryByText('420')).toBeInTheDocument() // Safe Voting power
+ expect(result.queryByText('138')).toBeInTheDocument() // Safe Pass points
})
})
})
diff --git a/src/components/common/SafeTokenWidget/index.tsx b/src/components/common/SafeTokenWidget/index.tsx
index 79c4aa10f..ee8a9869d 100644
--- a/src/components/common/SafeTokenWidget/index.tsx
+++ b/src/components/common/SafeTokenWidget/index.tsx
@@ -1,17 +1,21 @@
import { IS_PRODUCTION, SAFE_TOKEN_ADDRESSES, SAFE_LOCKING_ADDRESS } from '@/config/constants'
import { AppRoutes } from '@/config/routes'
import useChainId from '@/hooks/useChainId'
-import useSafeTokenAllocation, { useSafeVotingPower, type Vesting } from '@/hooks/useSafeTokenAllocation'
+import useSafeTokenAllocation, { useSafeVotingPower } from '@/hooks/useSafeTokenAllocation'
import { OVERVIEW_EVENTS } from '@/services/analytics'
import { formatVisualAmount } from '@/utils/formatters'
-import { Box, Button, ButtonBase, Skeleton, Tooltip, Typography } from '@mui/material'
+import { Box, ButtonBase, Divider, Skeleton, SvgIcon, Tooltip, Typography } from '@mui/material'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
import Track from '../Track'
import SafeTokenIcon from '@/public/images/common/safe-token.svg'
+import SafePassStar from '@/public/images/common/safe-pass-star.svg'
import css from './styles.module.css'
-import UnreadBadge from '../UnreadBadge'
-import classnames from 'classnames'
+import useSafeAddress from '@/hooks/useSafeAddress'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+import { useDarkMode } from '@/hooks/useDarkMode'
+import { useGetOwnGlobalCampaignRankQuery } from '@/store/safePass'
+import { formatAmount } from '@/utils/formatNumber'
const TOKEN_DECIMALS = 18
@@ -28,25 +32,22 @@ export const getSafeLockingAddress = (chainId: string): string | undefined => {
return SAFE_LOCKING_ADDRESS[chainId]
}
-const canRedeemSep5Airdrop = (allocation?: Vesting[]): boolean => {
- const sep5Allocation = allocation?.find(({ tag }) => tag === 'user_v2')
-
- if (!sep5Allocation) {
- return false
- }
-
- return !sep5Allocation.isRedeemed && !sep5Allocation.isExpired
-}
-
const GOVERNANCE_APP_URL = IS_PRODUCTION ? 'https://community.safe.global' : 'https://safe-dao-governance.dev.5afe.dev'
const SafeTokenWidget = () => {
const chainId = useChainId()
+ const safeAddress = useSafeAddress()
const query = useSearchParams()
+ const darkMode = useDarkMode()
const [allocationData, , allocationDataLoading] = useSafeTokenAllocation()
const [allocation, , allocationLoading] = useSafeVotingPower(allocationData)
+ const { data: ownGlobalRank, isLoading: ownGlobalRankLoading } = useGetOwnGlobalCampaignRankQuery(
+ chainId !== '1' && chainId !== '11155111' ? skipToken : { chainId, safeAddress },
+ { refetchOnFocus: false },
+ )
+
const tokenAddress = getSafeTokenAddress(chainId)
if (!tokenAddress) {
return null
@@ -57,8 +58,7 @@ const SafeTokenWidget = () => {
query: { safe: query?.get('safe'), appUrl: GOVERNANCE_APP_URL },
}
- const canRedeemSep5 = canRedeemSep5Airdrop(allocationData)
- const flooredSafeBalance = formatVisualAmount(allocation || BigInt(0), TOKEN_DECIMALS, 2)
+ const flooredSafeBalance = formatVisualAmount(allocation || BigInt(0), TOKEN_DECIMALS, 0)
return (
@@ -66,40 +66,43 @@ const SafeTokenWidget = () => {
-
+
+ {allocationDataLoading || allocationLoading ? (
+
+ ) : (
+ flooredSafeBalance
+ )}
+
+
+
+
+
-
- {allocationDataLoading || allocationLoading ? (
-
- ) : (
- flooredSafeBalance
- )}
-
+ {ownGlobalRankLoading ? (
+
+ ) : (
+ formatAmount(Math.floor(ownGlobalRank?.totalBoostedPoints ?? 0), 0)
+ )}
- {canRedeemSep5 && (
-
-
- New allocation
-
-
- )}
diff --git a/src/components/common/Table/DataRow.tsx b/src/components/common/Table/DataRow.tsx
index be49473c6..67a8886d4 100644
--- a/src/components/common/Table/DataRow.tsx
+++ b/src/components/common/Table/DataRow.tsx
@@ -1,6 +1,5 @@
import type { ReactElement, ReactNode } from 'react'
-import { Typography } from '@mui/material'
-import css from './styles.module.css'
+import FieldsGrid from '@/components/tx/FieldsGrid'
type DataRowProps = {
datatestid?: String
@@ -10,19 +9,10 @@ type DataRowProps = {
export const DataRow = ({ datatestid, title, children }: DataRowProps): ReactElement | null => {
if (children == undefined) return null
- return (
-
-
- {title}
-
- {typeof children === 'string' ? (
-
- {children}
-
- ) : (
- children
- )}
-
+ return (
+
+ {children}
+
)
}
diff --git a/src/components/common/Table/styles.module.css b/src/components/common/Table/styles.module.css
index ccc68717d..33e2c2fd9 100644
--- a/src/components/common/Table/styles.module.css
+++ b/src/components/common/Table/styles.module.css
@@ -4,6 +4,12 @@
gap: var(--space-1);
justify-content: flex-start;
max-width: 900px;
+ overflow-x: auto;
+ margin-top: 4px;
+}
+
+.gridRow:first-of-type {
+ margin-bottom: 0;
}
.gridEmptyRow {
@@ -16,12 +22,17 @@
margin-bottom: var(--space-1);
border-top: 1px solid var(--color-border-light);
}
+
.title {
color: var(--color-primary-light);
font-weight: 400;
word-break: break-all;
}
+.title span:nth-child(2) {
+ word-break: normal;
+}
+
.gridRow > * {
flex-shrink: 0;
}
diff --git a/src/components/common/TokenAmount/index.tsx b/src/components/common/TokenAmount/index.tsx
index 0e8f87440..ed818ec7b 100644
--- a/src/components/common/TokenAmount/index.tsx
+++ b/src/components/common/TokenAmount/index.tsx
@@ -1,4 +1,5 @@
import { type ReactElement } from 'react'
+import { Tooltip } from '@mui/material'
import { TransferDirection } from '@safe-global/safe-gateway-typescript-sdk'
import css from './styles.module.css'
import { formatVisualAmount } from '@/utils/formatters'
@@ -27,15 +28,19 @@ const TokenAmount = ({
const sign = direction === TransferDirection.OUTGOING ? '-' : ''
const amount =
decimals !== undefined ? formatVisualAmount(value, decimals, preciseAmount ? PRECISION : undefined) : value
+ const fullAmount =
+ decimals !== undefined ? sign + formatVisualAmount(value, decimals, PRECISION) + ' ' + tokenSymbol : value
return (
-
- {logoUri && }
-
- {sign}
- {amount} {tokenSymbol}
-
-
+
+
+ {logoUri && }
+
+ {sign}
+ {amount} {tokenSymbol}
+
+
+
)
}
diff --git a/src/components/common/TxModalDialog/index.tsx b/src/components/common/TxModalDialog/index.tsx
index 904c15175..62f597867 100644
--- a/src/components/common/TxModalDialog/index.tsx
+++ b/src/components/common/TxModalDialog/index.tsx
@@ -1,23 +1,16 @@
-import { type ReactElement } from 'react'
-import { IconButton, Dialog, DialogTitle, type DialogProps } from '@mui/material'
+import { Dialog, DialogContent, DialogContentText, DialogTitle, IconButton, type DialogProps } from '@mui/material'
import classnames from 'classnames'
+import type { ReactElement } from 'react'
import CloseIcon from '@mui/icons-material/Close'
import css from './styles.module.css'
-interface ModalDialogProps extends DialogProps {
- dialogTitle?: React.ReactNode
- hideChainIndicator?: boolean
-}
-
const TxModalDialog = ({
- dialogTitle,
- hideChainIndicator,
children,
onClose,
fullScreen = false,
fullWidth = false,
...restProps
-}: ModalDialogProps): ReactElement => {
+}: DialogProps): ReactElement => {
return (
-
- {children}
+
+
+ {children}
+
+
)
}
diff --git a/src/components/common/WalletOverview/styles.module.css b/src/components/common/WalletOverview/styles.module.css
index 14dd5f668..46f6e86b0 100644
--- a/src/components/common/WalletOverview/styles.module.css
+++ b/src/components/common/WalletOverview/styles.module.css
@@ -37,12 +37,14 @@
font-size: 12px;
}
- .walletDetails {
- display: none;
- }
-
.imageContainer img {
width: 22px;
height: auto;
}
}
+
+@media (max-width: 899.95px) {
+ .walletDetails {
+ display: none;
+ }
+}
diff --git a/src/components/common/WalletProvider/index.tsx b/src/components/common/WalletProvider/index.tsx
index f1c360c68..6e14266d1 100644
--- a/src/components/common/WalletProvider/index.tsx
+++ b/src/components/common/WalletProvider/index.tsx
@@ -13,6 +13,7 @@ const WalletProvider = ({ children }: { children: ReactNode }): ReactElement =>
const walletSubscription = onboard.state.select('wallets').subscribe((wallets) => {
const newWallet = getConnectedWallet(wallets)
+
setWallet(newWallet)
})
diff --git a/src/features/swap/components/LegalDisclaimer/index.tsx b/src/components/common/WidgetDisclaimer/index.tsx
similarity index 77%
rename from src/features/swap/components/LegalDisclaimer/index.tsx
rename to src/components/common/WidgetDisclaimer/index.tsx
index a324af688..3c5969d78 100644
--- a/src/features/swap/components/LegalDisclaimer/index.tsx
+++ b/src/components/common/WidgetDisclaimer/index.tsx
@@ -4,7 +4,11 @@ import { Typography } from '@mui/material'
import css from './styles.module.css'
-const LegalDisclaimerContent = () => (
+const linkSx = {
+ textDecoration: 'none',
+}
+
+const WidgetDisclaimer = ({ widgetName }: { widgetName: string }) => (
@@ -12,21 +16,21 @@ const LegalDisclaimerContent = () => (
- Please note that we do not own, control, maintain or audit the CoW Swap Widget. Use of the widget is subject to
+ Please note that we do not own, control, maintain or audit the {widgetName}. Use of the widget is subject to
third party terms & conditions. We are not liable for any loss you may suffer in connection with interacting
with the widget, which is at your own risk.
Our{' '}
-
+
terms
{' '}
contain more detailed provisions binding on you relating to such third party content.
By clicking "continue" you re-confirm to have read and understood our{' '}
-
+
terms
{' '}
and this message, and agree to them.
@@ -35,4 +39,4 @@ const LegalDisclaimerContent = () => (
)
-export default LegalDisclaimerContent
+export default WidgetDisclaimer
diff --git a/src/features/swap/components/LegalDisclaimer/styles.module.css b/src/components/common/WidgetDisclaimer/styles.module.css
similarity index 100%
rename from src/features/swap/components/LegalDisclaimer/styles.module.css
rename to src/components/common/WidgetDisclaimer/styles.module.css
diff --git a/src/components/dashboard/ActivityRewardsSection/index.tsx b/src/components/dashboard/ActivityRewardsSection/index.tsx
index 6d4d31ca5..0dd0035ed 100644
--- a/src/components/dashboard/ActivityRewardsSection/index.tsx
+++ b/src/components/dashboard/ActivityRewardsSection/index.tsx
@@ -14,6 +14,8 @@ import NextLink from 'next/link'
import { OVERVIEW_EVENTS, trackEvent } from '@/services/analytics'
import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
+import useLocalStorage from '@/services/local-storage/useLocalStorage'
+import ExternalLink from '@/components/common/ExternalLink'
const Step = ({ active, title }: { active: boolean; title: ReactNode }) => {
return (
@@ -34,15 +36,19 @@ const Step = ({ active, title }: { active: boolean; title: ReactNode }) => {
)
}
+const LOCAL_STORAGE_KEY_HIDE_WIDGET = 'hideActivityRewardsBanner'
+
const ActivityRewardsSection = () => {
const [matchingApps] = useRemoteSafeApps(SafeAppsTag.SAFE_GOVERNANCE_APP)
const isDarkMode = useDarkMode()
const router = useRouter()
+ const [widgetHidden = false, setWidgetHidden] = useLocalStorage
(LOCAL_STORAGE_KEY_HIDE_WIDGET)
+
const isSAPBannerEnabled = useHasFeature(FEATURES.SAP_BANNER)
const governanceApp = matchingApps?.[0]
- if (!governanceApp || !governanceApp?.url || !isSAPBannerEnabled) return null
+ if (!governanceApp || !governanceApp?.url || !isSAPBannerEnabled || widgetHidden) return null
const appUrl = getSafeAppUrl(router, governanceApp?.url)
@@ -50,6 +56,15 @@ const ActivityRewardsSection = () => {
trackEvent(OVERVIEW_EVENTS.OPEN_ACTIVITY_APP)
}
+ const onHide = () => {
+ setWidgetHidden(true)
+ trackEvent(OVERVIEW_EVENTS.HIDE_ACTIVITY_APP_WIDGET)
+ }
+
+ const onLearnMore = () => {
+ trackEvent(OVERVIEW_EVENTS.OPEN_LEARN_MORE_ACTIVITY_APP)
+ }
+
return (
<>
@@ -82,14 +97,6 @@ const ActivityRewardsSection = () => {
>
Interact with Safe and get rewards
-
-
- {'Open Safe{Pass}'}
-
-
- Learn more
-
-
@@ -100,6 +107,22 @@ const ActivityRewardsSection = () => {
+
+ Learn more
+
+
+
+
+
+
+ {'Open Safe{Pass}'}
+
+
+
+
+ Don't show again
+
+
diff --git a/src/components/dashboard/ActivityRewardsSection/styles.module.css b/src/components/dashboard/ActivityRewardsSection/styles.module.css
index 92c6ac163..eda8e4a38 100644
--- a/src/components/dashboard/ActivityRewardsSection/styles.module.css
+++ b/src/components/dashboard/ActivityRewardsSection/styles.module.css
@@ -67,16 +67,24 @@
.links {
display: flex;
- flex-wrap: nowrap;
+ flex-wrap: wrap;
align-items: center;
margin-top: var(--space-3);
text-wrap: nowrap;
+ width: 100%;
}
@media (max-width: 899.99px) {
.header {
padding: 0;
}
+ .links {
+ flex-direction: column;
+ }
+
+ .links a {
+ width: 100%;
+ }
.widgetWrapper {
padding: 32px;
diff --git a/src/components/dashboard/Assets/index.tsx b/src/components/dashboard/Assets/index.tsx
index 8df89c164..12aca6161 100644
--- a/src/components/dashboard/Assets/index.tsx
+++ b/src/components/dashboard/Assets/index.tsx
@@ -43,7 +43,7 @@ const NoAssets = () => (
)
-const AssetRow = ({ item, showSwap }: { item: SafeBalanceResponse['items'][number]; showSwap: boolean }) => (
+const AssetRow = ({ item, showSwap }: { item: SafeBalanceResponse['items'][number]; showSwap?: boolean }) => (
(
-
-
-
-
-
-
-
-
- {description}
-
-
-
- Use {name}
-
-
-
-
-)
-
-const onWcWidgetClick = (e: SyntheticEvent) => {
- e.preventDefault()
- openWalletConnect()
-}
-
-export const FeaturedApps = ({ stackedLayout }: { stackedLayout: boolean }): ReactElement | null => {
- const txBuilder = useTxBuilderApp()
- const isWcEnabled = useHasFeature(FEATURES.NATIVE_WALLETCONNECT)
-
- return (
-
-
-
- Connect & transact
-
-
-
- {txBuilder?.app && (
-
-
-
-
-
- )}
- {isWcEnabled && (
-
-
-
-
-
- )}
-
-
-
-
- )
-}
diff --git a/src/components/dashboard/FirstSteps/index.tsx b/src/components/dashboard/FirstSteps/index.tsx
index 82eb34a5e..cf4dde16b 100644
--- a/src/components/dashboard/FirstSteps/index.tsx
+++ b/src/components/dashboard/FirstSteps/index.tsx
@@ -24,6 +24,7 @@ import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded'
import CheckCircleOutlineRoundedIcon from '@mui/icons-material/CheckCircleOutlineRounded'
import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined'
import css from './styles.module.css'
+import ActivateAccountButton from '@/features/counterfactual/ActivateAccountButton'
const calculateProgress = (items: boolean[]) => {
const totalNumberOfItems = items.length
@@ -133,22 +134,17 @@ const AddFundsWidget = ({ completed }: { completed: boolean }) => {
{!completed && (
<>
-
- {(isOk) => (
-
-
- Add funds
-
-
- )}
-
+
+
+ Add funds
+
+
{
)
}
+const ActivateSafeWidget = () => {
+ const [open, setOpen] = useState(false)
+
+ const title = 'Activate your Safe account.'
+
+ return (
+ <>
+
+ Activate your Safe
+
+ }
+ title={title}
+ completed={false}
+ content=""
+ >
+
+
+ setOpen(false)} />
+ >
+ )
+}
+
const AccountReadyWidget = () => {
return (
@@ -283,6 +303,8 @@ const FirstSteps = () => {
const chain = useCurrentChain()
const undeployedSafe = useAppSelector((state) => selectUndeployedSafe(state, safe.chainId, safeAddress))
+ const isMultiSig = safe.threshold > 1
+
const hasNonZeroBalance = balances && (balances.items.length > 1 || BigInt(balances.items[0]?.balance || 0) > 0)
const hasOutgoingTransactions = !!outgoingTransactions && outgoingTransactions.length > 0
const completedItems = [hasNonZeroBalance, hasOutgoingTransactions]
@@ -359,7 +381,13 @@ const FirstSteps = () => {
- {isActivating ? : }
+ {isActivating ? (
+
+ ) : isMultiSig ? (
+
+ ) : (
+
+ )}
diff --git a/src/components/dashboard/Overview/Overview.tsx b/src/components/dashboard/Overview/Overview.tsx
index f0b069ea4..79f956849 100644
--- a/src/components/dashboard/Overview/Overview.tsx
+++ b/src/components/dashboard/Overview/Overview.tsx
@@ -72,7 +72,7 @@ const Overview = (): ReactElement => {
{safe.deployed ? (
-
+
) : (
{
const router = useRouter()
const { id } = transaction
- const { safe } = useSafeInfo()
const url = useMemo(
() => ({
@@ -36,17 +34,21 @@ const PendingTx = ({ transaction }: PendingTxType): ReactElement => {
return (
- {isMultisigExecutionInfo(transaction.executionInfo) && transaction.executionInfo.nonce}
+
+
+ {isMultisigExecutionInfo(transaction.executionInfo) && transaction.executionInfo.nonce}
+
-
-
-
-
-
+
+
+
-
+
+
+
+
-
+
{isMultisigExecutionInfo(transaction.executionInfo) && (
({
...jest.requireActual('@safe-global/safe-gateway-typescript-sdk'),
- getSafeApps: (chainId: string): Promise =>
+ getSafeApps: (): Promise =>
Promise.resolve([
{
id: 13,
diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx
index fa480c931..300998424 100644
--- a/src/components/dashboard/index.tsx
+++ b/src/components/dashboard/index.tsx
@@ -1,31 +1,31 @@
import FirstSteps from '@/components/dashboard/FirstSteps'
import useSafeInfo from '@/hooks/useSafeInfo'
-import type { ReactElement } from 'react'
+import { type ReactElement } from 'react'
import dynamic from 'next/dynamic'
import { Grid } from '@mui/material'
import PendingTxsList from '@/components/dashboard/PendingTxs/PendingTxsList'
import AssetsWidget from '@/components/dashboard/Assets'
import Overview from '@/components/dashboard/Overview/Overview'
-import { FeaturedApps } from '@/components/dashboard/FeaturedApps/FeaturedApps'
import SafeAppsDashboardSection from '@/components/dashboard/SafeAppsDashboardSection/SafeAppsDashboardSection'
import GovernanceSection from '@/components/dashboard/GovernanceSection/GovernanceSection'
-import useRecovery from '@/features/recovery/hooks/useRecovery'
import { useIsRecoverySupported } from '@/features/recovery/hooks/useIsRecoverySupported'
import ActivityRewardsSection from '@/components/dashboard/ActivityRewardsSection'
import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
import css from './styles.module.css'
import SwapWidget from '@/features/swap/components/SwapWidget'
+import useIsSwapFeatureEnabled from '@/features/swap/hooks/useIsSwapFeatureEnabled'
+import { useSafeTokenEnabled } from '@/hooks/useSafeTokenEnabled'
const RecoveryHeader = dynamic(() => import('@/features/recovery/components/RecoveryHeader'))
const Dashboard = (): ReactElement => {
const { safe } = useSafeInfo()
const showSafeApps = useHasFeature(FEATURES.SAFE_APPS)
- const isSAPBannerEnabled = useHasFeature(FEATURES.SAP_BANNER)
+ const isSafeTokenEnabled = useSafeTokenEnabled()
+ const isSwapFeatureEnabled = useIsSwapFeatureEnabled()
+ const isSAPBannerEnabled = useHasFeature(FEATURES.SAP_BANNER) && isSafeTokenEnabled
const supportsRecovery = useIsRecoverySupported()
- const [recovery] = useRecovery()
- const showRecoveryWidget = supportsRecovery && !recovery
return (
<>
@@ -42,13 +42,17 @@ const Dashboard = (): ReactElement => {
{safe.deployed && (
<>
-
-
-
+ {isSwapFeatureEnabled && (
+
+
+
+ )}
-
-
-
+ {isSAPBannerEnabled && (
+
+
+
+ )}
@@ -60,12 +64,6 @@ const Dashboard = (): ReactElement => {
- {showSafeApps && (
-
-
-
- )}
-
{showSafeApps && (
diff --git a/src/components/new-safe/create/AdvancedCreateSafe.tsx b/src/components/new-safe/create/AdvancedCreateSafe.tsx
new file mode 100644
index 000000000..3c434e3bf
--- /dev/null
+++ b/src/components/new-safe/create/AdvancedCreateSafe.tsx
@@ -0,0 +1,127 @@
+import { Container, Typography, Grid } from '@mui/material'
+import { useRouter } from 'next/router'
+
+import useWallet from '@/hooks/wallets/useWallet'
+import OverviewWidget from '@/components/new-safe/create/OverviewWidget'
+import type { TxStepperProps } from '@/components/new-safe/CardStepper/useCardStepper'
+import SetNameStep from '@/components/new-safe/create/steps/SetNameStep'
+import OwnerPolicyStep from '@/components/new-safe/create/steps/OwnerPolicyStep'
+import ReviewStep from '@/components/new-safe/create/steps/ReviewStep'
+import { CreateSafeStatus } from '@/components/new-safe/create/steps/StatusStep'
+import { CardStepper } from '@/components/new-safe/CardStepper'
+import { AppRoutes } from '@/config/routes'
+import { CREATE_SAFE_CATEGORY } from '@/services/analytics'
+import type { CreateSafeInfoItem } from '@/components/new-safe/create/CreateSafeInfos'
+import CreateSafeInfos from '@/components/new-safe/create/CreateSafeInfos'
+import { useState } from 'react'
+import { type NewSafeFormData } from '.'
+import AdvancedOptionsStep from './steps/AdvancedOptionsStep'
+import { getLatestSafeVersion } from '@/utils/chains'
+import { useCurrentChain } from '@/hooks/useChains'
+
+const AdvancedCreateSafe = () => {
+ const router = useRouter()
+ const wallet = useWallet()
+ const chain = useCurrentChain()
+
+ const [safeName, setSafeName] = useState('')
+ const [dynamicHint, setDynamicHint] = useState()
+ const [activeStep, setActiveStep] = useState(0)
+
+ const CreateSafeSteps: TxStepperProps['steps'] = [
+ {
+ title: 'Select network and name of your Safe Account',
+ subtitle: 'Select the network on which to create your Safe Account',
+ render: (data, onSubmit, onBack, setStep) => (
+
+ ),
+ },
+ {
+ title: 'Signers and confirmations',
+ subtitle:
+ 'Set the signer wallets of your Safe Account and how many need to confirm to execute a valid transaction.',
+ render: (data, onSubmit, onBack, setStep) => (
+
+ ),
+ },
+ {
+ title: 'Advanced settings',
+ subtitle: 'Choose the Safe version and optionally a specific salt nonce',
+ render: (data, onSubmit, onBack, setStep) => (
+
+ ),
+ },
+ {
+ title: 'Review',
+ subtitle:
+ "You're about to create a new Safe Account and will have to confirm the transaction with your connected wallet.",
+ render: (data, onSubmit, onBack, setStep) => (
+
+ ),
+ },
+ {
+ title: '',
+ subtitle: '',
+ render: (data, onSubmit, onBack, setStep, setProgressColor, setStepData) => (
+
+ ),
+ },
+ ]
+
+ const initialStep = 0
+ const initialData: NewSafeFormData = {
+ name: '',
+ owners: [],
+ threshold: 1,
+ saltNonce: 0,
+ safeVersion: getLatestSafeVersion(chain),
+ }
+
+ const onClose = () => {
+ router.push(AppRoutes.welcome.index)
+ }
+
+ return (
+
+
+
+
+ Create new Safe Account
+
+
+
+
+
+
+
+
+ {activeStep < 2 && }
+ {wallet?.address && }
+
+
+
+
+ )
+}
+
+export default AdvancedCreateSafe
diff --git a/src/components/new-safe/create/NetworkWarning/index.tsx b/src/components/new-safe/create/NetworkWarning/index.tsx
index 1a7aa0156..d0a87b5c7 100644
--- a/src/components/new-safe/create/NetworkWarning/index.tsx
+++ b/src/components/new-safe/create/NetworkWarning/index.tsx
@@ -1,17 +1,19 @@
import { Alert, AlertTitle, Box } from '@mui/material'
import { useCurrentChain } from '@/hooks/useChains'
import ChainSwitcher from '@/components/common/ChainSwitcher'
+import useIsWrongChain from '@/hooks/useIsWrongChain'
-const NetworkWarning = () => {
+const NetworkWarning = ({ action }: { action?: string }) => {
const chain = useCurrentChain()
+ const isWrongChain = useIsWrongChain()
- if (!chain) return null
+ if (!chain || !isWrongChain) return null
return (
-
+
Change your wallet network
- You are trying to create a Safe Account on {chain.chainName}. Make sure that your wallet is set to the same
- network.
+ You are trying to {action || 'sign or execute a transaction'} on {chain.chainName}. Make sure that your wallet is
+ set to the same network.
diff --git a/src/components/new-safe/create/NoWalletConnectedWarning/index.tsx b/src/components/new-safe/create/NoWalletConnectedWarning/index.tsx
new file mode 100644
index 000000000..e81293b48
--- /dev/null
+++ b/src/components/new-safe/create/NoWalletConnectedWarning/index.tsx
@@ -0,0 +1,23 @@
+import { Alert, AlertTitle, Box } from '@mui/material'
+import useWallet from '@/hooks/wallets/useWallet'
+import ConnectWalletButton from '@/components/common/ConnectWallet/ConnectWalletButton'
+
+const NoWalletConnectedWarning = () => {
+ const wallet = useWallet()
+
+ if (wallet) {
+ return null
+ }
+
+ return (
+
+ No wallet connected
+ You need to connect a wallet to create a Safe account.
+
+
+
+
+ )
+}
+
+export default NoWalletConnectedWarning
diff --git a/src/components/new-safe/create/OverviewWidget/index.tsx b/src/components/new-safe/create/OverviewWidget/index.tsx
index 2d0a5f858..a9eb6f8ac 100644
--- a/src/components/new-safe/create/OverviewWidget/index.tsx
+++ b/src/components/new-safe/create/OverviewWidget/index.tsx
@@ -2,11 +2,12 @@ import ChainIndicator from '@/components/common/ChainIndicator'
import WalletOverview from 'src/components/common/WalletOverview'
import { useCurrentChain } from '@/hooks/useChains'
import useWallet from '@/hooks/wallets/useWallet'
-import { Card, Grid, Typography } from '@mui/material'
+import { Box, Card, Grid, Typography } from '@mui/material'
import type { ReactElement } from 'react'
import SafeLogo from '@/public/images/logo-no-text.svg'
import css from '@/components/new-safe/create/OverviewWidget/styles.module.css'
+import ConnectWalletButton from '@/components/common/ConnectWallet/ConnectWalletButton'
const LOGO_DIMENSIONS = '22px'
@@ -34,11 +35,12 @@ const OverviewWidget = ({ safeName }: { safeName: string }): ReactElement | null
))
) : (
-
-
+
+
Connect your wallet to continue
-
+
+
)}
diff --git a/src/components/new-safe/create/__tests__/useEstimateSafeCreationGas.test.ts b/src/components/new-safe/create/__tests__/useEstimateSafeCreationGas.test.ts
index 6acc65087..172c33ebb 100644
--- a/src/components/new-safe/create/__tests__/useEstimateSafeCreationGas.test.ts
+++ b/src/components/new-safe/create/__tests__/useEstimateSafeCreationGas.test.ts
@@ -7,9 +7,9 @@ import * as web3 from '@/hooks/wallets/web3'
import * as safeContracts from '@/services/contracts/safeContracts'
import * as store from '@/store'
import { renderHook } from '@/tests/test-utils'
+import type { SafeProxyFactoryContractImplementationType } from '@safe-global/protocol-kit/dist/src/types/contracts'
import { JsonRpcProvider } from 'ethers'
import { EMPTY_DATA, ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants'
-import type { SafeProxyFactoryEthersContract } from '@safe-global/protocol-kit'
import { waitFor } from '@testing-library/react'
import { type EIP1193Provider } from '@web3-onboard/core'
@@ -27,7 +27,7 @@ describe('useEstimateSafeCreationGas', () => {
jest.spyOn(chainIdModule, 'useChainId').mockReturnValue('4')
jest
.spyOn(safeContracts, 'getReadOnlyProxyFactoryContract')
- .mockResolvedValue({ getAddress: () => ZERO_ADDRESS } as unknown as SafeProxyFactoryEthersContract)
+ .mockResolvedValue({ getAddress: () => ZERO_ADDRESS } as unknown as SafeProxyFactoryContractImplementationType)
jest.spyOn(sender, 'encodeSafeCreationTx').mockReturnValue(Promise.resolve(EMPTY_DATA))
jest.spyOn(wallet, 'default').mockReturnValue({} as ConnectedWallet)
})
diff --git a/src/components/new-safe/create/__tests__/useSyncSafeCreationStep.test.ts b/src/components/new-safe/create/__tests__/useSyncSafeCreationStep.test.ts
index 1c0d1cb8c..ff9ff360d 100644
--- a/src/components/new-safe/create/__tests__/useSyncSafeCreationStep.test.ts
+++ b/src/components/new-safe/create/__tests__/useSyncSafeCreationStep.test.ts
@@ -1,15 +1,11 @@
-import { PayMethod } from '@/features/counterfactual/PayNowPayLater'
-import { PendingSafeStatus } from '@/features/counterfactual/store/undeployedSafesSlice'
import { renderHook } from '@/tests/test-utils'
import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep'
import * as wallet from '@/hooks/wallets/useWallet'
import * as localStorage from '@/services/local-storage/useLocalStorage'
import type { ConnectedWallet } from '@/hooks/wallets/useOnboard'
-import * as useChainId from '@/hooks/useChainId'
import * as useIsWrongChain from '@/hooks/useIsWrongChain'
import * as useRouter from 'next/router'
import { type NextRouter } from 'next/router'
-import { AppRoutes } from '@/config/routes'
describe('useSyncSafeCreationStep', () => {
beforeEach(() => {
@@ -26,37 +22,10 @@ describe('useSyncSafeCreationStep', () => {
renderHook(() => useSyncSafeCreationStep(mockSetStep))
- expect(mockSetStep).not.toHaveBeenCalled()
- expect(mockPushRoute).toHaveBeenCalledWith({ pathname: AppRoutes.welcome.index, query: undefined })
- })
-
- it('should go to the fourth step if there is a pending safe', async () => {
- const mockPushRoute = jest.fn()
- jest.spyOn(localStorage, 'default').mockReturnValue([{}, jest.fn()])
- jest.spyOn(wallet, 'default').mockReturnValue({ address: '0x1' } as ConnectedWallet)
- jest.spyOn(useChainId, 'default').mockReturnValue('11155111')
- jest.spyOn(useRouter, 'useRouter').mockReturnValue({
- push: mockPushRoute,
- } as unknown as NextRouter)
-
- const mockSetStep = jest.fn()
-
- renderHook(() => useSyncSafeCreationStep(mockSetStep), {
- initialReduxState: {
- undeployedSafes: {
- '11155111': {
- '0x123': { status: { status: PendingSafeStatus.PROCESSING, type: PayMethod.PayNow }, props: {} as any },
- },
- },
- },
- })
-
- expect(mockSetStep).toHaveBeenCalledWith(3)
-
- expect(mockPushRoute).not.toHaveBeenCalled()
+ expect(mockSetStep).toHaveBeenCalledWith(0)
})
- it('should go to the second step if the wrong chain is connected', async () => {
+ it('should go to the first step if the wrong chain is connected', async () => {
jest.spyOn(localStorage, 'default').mockReturnValue([{}, jest.fn()])
jest.spyOn(wallet, 'default').mockReturnValue({ address: '0x1' } as ConnectedWallet)
jest.spyOn(useIsWrongChain, 'default').mockReturnValue(true)
diff --git a/src/components/new-safe/create/index.tsx b/src/components/new-safe/create/index.tsx
index 8f738c082..7683691eb 100644
--- a/src/components/new-safe/create/index.tsx
+++ b/src/components/new-safe/create/index.tsx
@@ -18,12 +18,16 @@ import CreateSafeInfos from '@/components/new-safe/create/CreateSafeInfos'
import { type ReactElement, useMemo, useState } from 'react'
import ExternalLink from '@/components/common/ExternalLink'
import { HelpCenterArticle } from '@/config/constants'
+import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
+import { getLatestSafeVersion } from '@/utils/chains'
+import { useCurrentChain } from '@/hooks/useChains'
export type NewSafeFormData = {
name: string
threshold: number
owners: NamedAddress[]
saltNonce: number
+ safeVersion: SafeVersion
safeAddress?: string
willRelay?: boolean
}
@@ -97,6 +101,7 @@ const staticHints: Record<
const CreateSafe = () => {
const router = useRouter()
const wallet = useWallet()
+ const chain = useCurrentChain()
const [safeName, setSafeName] = useState('')
const [dynamicHint, setDynamicHint] = useState()
@@ -156,6 +161,7 @@ const CreateSafe = () => {
owners: [],
threshold: 1,
saltNonce: Date.now(),
+ safeVersion: getLatestSafeVersion(chain) as SafeVersion,
}
const onClose = () => {
diff --git a/src/components/new-safe/create/logic/index.test.ts b/src/components/new-safe/create/logic/index.test.ts
index 6457eca09..8d93077d3 100644
--- a/src/components/new-safe/create/logic/index.test.ts
+++ b/src/components/new-safe/create/logic/index.test.ts
@@ -1,8 +1,12 @@
import { JsonRpcProvider } from 'ethers'
+import * as contracts from '@/services/contracts/safeContracts'
+import type { SafeProvider } from '@safe-global/protocol-kit'
+import type { CompatibilityFallbackHandlerContractImplementationType } from '@safe-global/protocol-kit/dist/src/types'
import { EMPTY_DATA, ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants'
import * as web3 from '@/hooks/wallets/web3'
-import { relaySafeCreation } from '@/components/new-safe/create/logic/index'
-import { relayTransaction, type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import * as sdkHelpers from '@/services/tx/tx-sender/sdk'
+import { getRedirect, relaySafeCreation } from '@/components/new-safe/create/logic/index'
+import { relayTransaction } from '@safe-global/safe-gateway-typescript-sdk'
import { toBeHex } from 'ethers'
import {
Gnosis_safe__factory,
@@ -13,50 +17,30 @@ import {
getReadOnlyGnosisSafeContract,
getReadOnlyProxyFactoryContract,
} from '@/services/contracts/safeContracts'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import * as gateway from '@safe-global/safe-gateway-typescript-sdk'
+import { FEATURES, getLatestSafeVersion } from '@/utils/chains'
+import { type FEATURES as GatewayFeatures } from '@safe-global/safe-gateway-typescript-sdk'
+import { chainBuilder } from '@/tests/builders/chains'
-const provider = new JsonRpcProvider(undefined, { name: 'rinkeby', chainId: 4 })
-
-const mockTransaction = {
- data: EMPTY_DATA,
- nonce: 1,
- from: '0x10',
- to: '0x11',
- value: BigInt(0),
-}
-
-const mockPendingTx = {
- data: EMPTY_DATA,
- from: ZERO_ADDRESS,
- to: ZERO_ADDRESS,
- nonce: 0,
- startBlock: 0,
- value: BigInt(0),
-}
-
-jest.mock('@safe-global/protocol-kit', () => {
- const originalModule = jest.requireActual('@safe-global/protocol-kit')
-
- // Mock class
- class MockEthersAdapter extends originalModule.EthersAdapter {
- getChainId = jest.fn().mockImplementation(() => Promise.resolve(BigInt(4)))
- }
-
- return {
- ...originalModule,
- EthersAdapter: MockEthersAdapter,
- }
-})
+const provider = new JsonRpcProvider(undefined, { name: 'ethereum', chainId: 1 })
+
+const latestSafeVersion = getLatestSafeVersion(
+ chainBuilder()
+ .with({ chainId: '1', features: [FEATURES.SAFE_141 as unknown as GatewayFeatures] })
+ .build(),
+)
describe('createNewSafeViaRelayer', () => {
const owner1 = toBeHex('0x1', 20)
const owner2 = toBeHex('0x2', 20)
- const mockChainInfo = {
- chainId: '5',
- l2: false,
- } as ChainInfo
+ const mockChainInfo = chainBuilder()
+ .with({
+ chainId: '1',
+ l2: false,
+ features: [FEATURES.SAFE_141 as unknown as GatewayFeatures],
+ })
+ .build()
beforeAll(() => {
jest.resetAllMocks()
@@ -64,13 +48,26 @@ describe('createNewSafeViaRelayer', () => {
})
it('returns taskId if create Safe successfully relayed', async () => {
+ const mockSafeProvider = {
+ getExternalProvider: jest.fn(),
+ getExternalSigner: jest.fn(),
+ getChainId: jest.fn().mockReturnValue(BigInt(1)),
+ } as unknown as SafeProvider
+
jest.spyOn(gateway, 'relayTransaction').mockResolvedValue({ taskId: '0x123' })
+ jest.spyOn(sdkHelpers, 'getSafeProvider').mockImplementation(() => mockSafeProvider)
+
+ jest.spyOn(contracts, 'getReadOnlyFallbackHandlerContract').mockResolvedValue({
+ getAddress: () => '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4',
+ } as unknown as CompatibilityFallbackHandlerContractImplementationType)
const expectedSaltNonce = 69
const expectedThreshold = 1
- const proxyFactoryAddress = await (await getReadOnlyProxyFactoryContract('5', LATEST_SAFE_VERSION)).getAddress()
- const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract('5', LATEST_SAFE_VERSION)
- const safeContractAddress = await (await getReadOnlyGnosisSafeContract(mockChainInfo)).getAddress()
+ const proxyFactoryAddress = await (await getReadOnlyProxyFactoryContract(latestSafeVersion)).getAddress()
+ const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract(latestSafeVersion)
+ const safeContractAddress = await (
+ await getReadOnlyGnosisSafeContract(mockChainInfo, latestSafeVersion)
+ ).getAddress()
const expectedInitializer = Gnosis_safe__factory.createInterface().encodeFunctionData('setup', [
[owner1, owner2],
@@ -93,10 +90,10 @@ describe('createNewSafeViaRelayer', () => {
expect(taskId).toEqual('0x123')
expect(relayTransaction).toHaveBeenCalledTimes(1)
- expect(relayTransaction).toHaveBeenCalledWith('5', {
+ expect(relayTransaction).toHaveBeenCalledWith('1', {
to: proxyFactoryAddress,
data: expectedCallData,
- version: LATEST_SAFE_VERSION,
+ version: latestSafeVersion,
})
})
@@ -106,4 +103,27 @@ describe('createNewSafeViaRelayer', () => {
expect(relaySafeCreation(mockChainInfo, [owner1, owner2], 1, 69)).rejects.toEqual(relayFailedError)
})
+
+ describe('getRedirect', () => {
+ it("should redirect to home for any redirect that doesn't start with /apps", () => {
+ const expected = {
+ pathname: '/home',
+ query: {
+ safe: 'sep:0x1234',
+ },
+ }
+ expect(getRedirect('sep', '0x1234', 'https://google.com')).toEqual(expected)
+ expect(getRedirect('sep', '0x1234', '/queue')).toEqual(expected)
+ })
+
+ it('should redirect to an app if an app URL is passed', () => {
+ expect(getRedirect('sep', '0x1234', '/apps?appUrl=https://safe-eth.everstake.one/?chain=eth')).toEqual(
+ '/apps?appUrl=https://safe-eth.everstake.one/?chain=eth&safe=sep:0x1234',
+ )
+
+ expect(getRedirect('sep', '0x1234', '/apps?appUrl=https://safe-eth.everstake.one')).toEqual(
+ '/apps?appUrl=https://safe-eth.everstake.one&safe=sep:0x1234',
+ )
+ })
+ })
})
diff --git a/src/components/new-safe/create/logic/index.ts b/src/components/new-safe/create/logic/index.ts
index 9c15781a5..e5713f5b5 100644
--- a/src/components/new-safe/create/logic/index.ts
+++ b/src/components/new-safe/create/logic/index.ts
@@ -1,5 +1,5 @@
import type { SafeVersion } from '@safe-global/safe-core-sdk-types'
-import { type BrowserProvider, type Provider } from 'ethers'
+import { type Eip1193Provider, type Provider } from 'ethers'
import { getSafeInfo, type SafeInfo, type ChainInfo, relayTransaction } from '@safe-global/safe-gateway-typescript-sdk'
import {
@@ -10,16 +10,17 @@ import {
import type { UrlObject } from 'url'
import { AppRoutes } from '@/config/routes'
import { SAFE_APPS_EVENTS, trackEvent } from '@/services/analytics'
-import { predictSafeAddress, SafeFactory } from '@safe-global/protocol-kit'
+import { predictSafeAddress, SafeFactory, SafeProvider } from '@safe-global/protocol-kit'
import type Safe from '@safe-global/protocol-kit'
import type { DeploySafeProps } from '@safe-global/protocol-kit'
-import { createEthersAdapter, isValidSafeVersion } from '@/hooks/coreSDK/safeCoreSDK'
+import { isValidSafeVersion } from '@/hooks/coreSDK/safeCoreSDK'
import { backOff } from 'exponential-backoff'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import { EMPTY_DATA, ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants'
// import { sponsoredCall } from '@/services/tx/relaying'
import { checksumAddress } from '@/utils/addresses'
+import { getLatestSafeVersion } from '@/utils/chains'
+import { ECOSYSTEM_ID_ADDRESS } from '@/config/constants'
export type SafeCreationProps = {
owners: string[]
@@ -27,27 +28,22 @@ export type SafeCreationProps = {
saltNonce: number
}
-const getSafeFactory = async (
- ethersProvider: BrowserProvider,
- safeVersion = LATEST_SAFE_VERSION,
-): Promise => {
+const getSafeFactory = async (provider: Eip1193Provider, safeVersion: SafeVersion): Promise => {
if (!isValidSafeVersion(safeVersion)) {
throw new Error('Invalid Safe version')
}
- const ethAdapter = await createEthersAdapter(ethersProvider)
- const safeFactory = await SafeFactory.create({ ethAdapter, safeVersion })
- return safeFactory
+ return SafeFactory.init({ provider, safeVersion })
}
/**
* Create a Safe creation transaction via Core SDK and submits it to the wallet
*/
export const createNewSafe = async (
- ethersProvider: BrowserProvider,
+ provider: Eip1193Provider,
props: DeploySafeProps,
- safeVersion?: SafeVersion,
+ safeVersion: SafeVersion,
): Promise => {
- const safeFactory = await getSafeFactory(ethersProvider, safeVersion)
+ const safeFactory = await getSafeFactory(provider, safeVersion)
return safeFactory.deploySafe(props)
}
@@ -55,19 +51,20 @@ export const createNewSafe = async (
* Compute the new counterfactual Safe address before it is actually created
*/
export const computeNewSafeAddress = async (
- ethersProvider: BrowserProvider,
+ provider: Eip1193Provider,
props: DeploySafeProps,
- chainId: string,
+ chain: ChainInfo,
+ safeVersion?: SafeVersion,
): Promise => {
- const ethAdapter = await createEthersAdapter(ethersProvider)
+ const safeProvider = new SafeProvider({ provider })
return predictSafeAddress({
- ethAdapter,
- chainId: BigInt(chainId),
+ safeProvider,
+ chainId: BigInt(chain.chainId),
safeAccountConfig: props.safeAccountConfig,
safeDeploymentConfig: {
saltNonce: props.saltNonce,
- safeVersion: LATEST_SAFE_VERSION as SafeVersion,
+ safeVersion: safeVersion ?? getLatestSafeVersion(chain),
},
})
}
@@ -81,26 +78,40 @@ export const encodeSafeCreationTx = async ({
threshold,
saltNonce,
chain,
-}: SafeCreationProps & { chain: ChainInfo }) => {
- const readOnlySafeContract = await getReadOnlyGnosisSafeContract(chain, LATEST_SAFE_VERSION)
- const readOnlyProxyContract = await getReadOnlyProxyFactoryContract(chain.chainId, LATEST_SAFE_VERSION)
- const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract(chain.chainId, LATEST_SAFE_VERSION)
+ safeVersion,
+}: SafeCreationProps & { chain: ChainInfo; safeVersion?: SafeVersion }) => {
+ const usedSafeVersion = safeVersion ?? getLatestSafeVersion(chain)
+ const readOnlySafeContract = await getReadOnlyGnosisSafeContract(chain, usedSafeVersion)
+ const readOnlyProxyContract = await getReadOnlyProxyFactoryContract(usedSafeVersion)
+ const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract(usedSafeVersion)
- const setupData = readOnlySafeContract.encode('setup', [
+ const callData = {
owners,
threshold,
- ZERO_ADDRESS,
- EMPTY_DATA,
- await readOnlyFallbackHandlerContract.getAddress(),
- ZERO_ADDRESS,
- '0',
- ZERO_ADDRESS,
+ to: ZERO_ADDRESS,
+ data: EMPTY_DATA,
+ fallbackHandler: await readOnlyFallbackHandlerContract.getAddress(),
+ paymentToken: ZERO_ADDRESS,
+ payment: 0,
+ paymentReceiver: ECOSYSTEM_ID_ADDRESS,
+ }
+
+ // @ts-ignore union type is too complex
+ const setupData = readOnlySafeContract.encode('setup', [
+ callData.owners,
+ callData.threshold,
+ callData.to,
+ callData.data,
+ callData.fallbackHandler,
+ callData.paymentToken,
+ callData.payment,
+ callData.paymentReceiver,
])
return readOnlyProxyContract.encode('createProxyWithNonce', [
await readOnlySafeContract.getAddress(),
setupData,
- saltNonce,
+ BigInt(saltNonce),
])
}
@@ -109,8 +120,9 @@ export const estimateSafeCreationGas = async (
provider: Provider,
from: string,
safeParams: SafeCreationProps,
+ safeVersion?: SafeVersion,
): Promise => {
- const readOnlyProxyFactoryContract = await getReadOnlyProxyFactoryContract(chain.chainId, LATEST_SAFE_VERSION)
+ const readOnlyProxyFactoryContract = await getReadOnlyProxyFactoryContract(safeVersion ?? getLatestSafeVersion(chain))
const encodedSafeCreationTx = await encodeSafeCreationTx({ ...safeParams, chain })
const gas = await provider.estimateGas({
@@ -147,17 +159,14 @@ export const getRedirect = (
if (!chainPrefix) return AppRoutes.index
// Go to the dashboard if no specific redirect is provided
- if (!redirectUrl) {
+ if (!redirectUrl || !redirectUrl.startsWith(AppRoutes.apps.index)) {
return { pathname: AppRoutes.home, query: { safe: address } }
}
// Otherwise, redirect to the provided URL (e.g. from a Safe App)
// Track the redirect to Safe App
- // TODO: Narrow this down to /apps only
- if (redirectUrl.includes('apps')) {
- trackEvent(SAFE_APPS_EVENTS.SHARED_APP_OPEN_AFTER_SAFE_CREATION)
- }
+ trackEvent(SAFE_APPS_EVENTS.SHARED_APP_OPEN_AFTER_SAFE_CREATION)
// We're prepending the safe address directly here because the `router.push` doesn't parse
// The URL for already existing query params
@@ -174,13 +183,15 @@ export const relaySafeCreation = async (
saltNonce: number,
version?: SafeVersion,
) => {
- const safeVersion = version ?? LATEST_SAFE_VERSION
+ const latestSafeVersion = getLatestSafeVersion(chain)
+
+ const safeVersion = version ?? latestSafeVersion
- const readOnlyProxyFactoryContract = await getReadOnlyProxyFactoryContract(chain.chainId, safeVersion)
+ const readOnlyProxyFactoryContract = await getReadOnlyProxyFactoryContract(safeVersion)
const proxyFactoryAddress = await readOnlyProxyFactoryContract.getAddress()
- const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract(chain.chainId, safeVersion)
+ const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract(safeVersion)
const fallbackHandlerAddress = await readOnlyFallbackHandlerContract.getAddress()
- const readOnlySafeContract = await getReadOnlyGnosisSafeContract(chain)
+ const readOnlySafeContract = await getReadOnlyGnosisSafeContract(chain, safeVersion)
const safeContractAddress = await readOnlySafeContract.getAddress()
const callData = {
@@ -191,9 +202,10 @@ export const relaySafeCreation = async (
fallbackHandler: fallbackHandlerAddress,
paymentToken: ZERO_ADDRESS,
payment: 0,
- paymentReceiver: ZERO_ADDRESS,
+ paymentReceiver: ECOSYSTEM_ID_ADDRESS,
}
+ // @ts-ignore
const initializer = readOnlySafeContract.encode('setup', [
callData.owners,
callData.threshold,
@@ -208,7 +220,7 @@ export const relaySafeCreation = async (
const createProxyWithNonceCallData = readOnlyProxyFactoryContract.encode('createProxyWithNonce', [
safeContractAddress,
initializer,
- saltNonce,
+ BigInt(saltNonce),
])
const relayResponse = await relayTransaction(chain.chainId, {
diff --git a/src/components/new-safe/create/logic/utils.test.ts b/src/components/new-safe/create/logic/utils.test.ts
index 91b5c4c54..0a9f54378 100644
--- a/src/components/new-safe/create/logic/utils.test.ts
+++ b/src/components/new-safe/create/logic/utils.test.ts
@@ -1,19 +1,17 @@
import * as creationUtils from '@/components/new-safe/create/logic/index'
import { getAvailableSaltNonce } from '@/components/new-safe/create/logic/utils'
-import * as web3Utils from '@/hooks/wallets/web3'
+import * as walletUtils from '@/utils/wallets'
import { faker } from '@faker-js/faker'
import type { DeploySafeProps } from '@safe-global/protocol-kit'
-import { BrowserProvider } from 'ethers'
import { MockEip1193Provider } from '@/tests/mocks/providers'
+import { chainBuilder } from '@/tests/builders/chains'
describe('getAvailableSaltNonce', () => {
jest.spyOn(creationUtils, 'computeNewSafeAddress').mockReturnValue(Promise.resolve(faker.finance.ethereumAddress()))
- let mockProvider: BrowserProvider
let mockDeployProps: DeploySafeProps
beforeAll(() => {
- mockProvider = new BrowserProvider(MockEip1193Provider)
mockDeployProps = {
safeAccountConfig: {
threshold: 1,
@@ -28,31 +26,31 @@ describe('getAvailableSaltNonce', () => {
})
it('should return initial nonce if no contract is deployed to the computed address', async () => {
- jest.spyOn(web3Utils, 'isSmartContract').mockReturnValue(Promise.resolve(false))
+ jest.spyOn(walletUtils, 'isSmartContract').mockReturnValue(Promise.resolve(false))
const initialNonce = faker.string.numeric()
- const mockChainId = faker.string.numeric()
+ const mockChain = chainBuilder().build()
const result = await getAvailableSaltNonce(
- mockProvider,
+ MockEip1193Provider,
{ ...mockDeployProps, saltNonce: initialNonce },
- mockChainId,
+ mockChain,
)
expect(result).toEqual(initialNonce)
})
it('should return an increased nonce if a contract is deployed to the computed address', async () => {
- jest.spyOn(web3Utils, 'isSmartContract').mockReturnValueOnce(Promise.resolve(true))
+ jest.spyOn(walletUtils, 'isSmartContract').mockReturnValueOnce(Promise.resolve(true))
const initialNonce = faker.string.numeric()
- const mockChainId = faker.string.numeric()
+ const mockChain = chainBuilder().build()
const result = await getAvailableSaltNonce(
- mockProvider,
+ MockEip1193Provider,
{ ...mockDeployProps, saltNonce: initialNonce },
- mockChainId,
+ mockChain,
)
- jest.spyOn(web3Utils, 'isSmartContract').mockReturnValueOnce(Promise.resolve(false))
+ jest.spyOn(walletUtils, 'isSmartContract').mockReturnValueOnce(Promise.resolve(false))
const increasedNonce = (Number(initialNonce) + 1).toString()
diff --git a/src/components/new-safe/create/logic/utils.ts b/src/components/new-safe/create/logic/utils.ts
index 5d616c2d1..ff6305875 100644
--- a/src/components/new-safe/create/logic/utils.ts
+++ b/src/components/new-safe/create/logic/utils.ts
@@ -1,19 +1,27 @@
import { computeNewSafeAddress } from '@/components/new-safe/create/logic/index'
-import { isSmartContract } from '@/hooks/wallets/web3'
+import { isSmartContract } from '@/utils/wallets'
import type { DeploySafeProps } from '@safe-global/protocol-kit'
-import type { BrowserProvider } from 'ethers'
+import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
+import type { Eip1193Provider } from 'ethers'
export const getAvailableSaltNonce = async (
- provider: BrowserProvider,
+ provider: Eip1193Provider,
props: DeploySafeProps,
- chainId: string,
+ chain: ChainInfo,
+ safeVersion?: SafeVersion,
): Promise => {
- const safeAddress = await computeNewSafeAddress(provider, props, chainId)
- const isContractDeployed = await isSmartContract(provider, safeAddress)
+ const safeAddress = await computeNewSafeAddress(provider, props, chain, safeVersion)
+ const isContractDeployed = await isSmartContract(safeAddress)
// Safe is already deployed so we try the next saltNonce
if (isContractDeployed) {
- return getAvailableSaltNonce(provider, { ...props, saltNonce: (Number(props.saltNonce) + 1).toString() }, chainId)
+ return getAvailableSaltNonce(
+ provider,
+ { ...props, saltNonce: (Number(props.saltNonce) + 1).toString() },
+ chain,
+ safeVersion,
+ )
}
// We know that there will be a saltNonce but the type has it as optional
diff --git a/src/components/new-safe/create/steps/AdvancedOptionsStep/index.tsx b/src/components/new-safe/create/steps/AdvancedOptionsStep/index.tsx
new file mode 100644
index 000000000..f7627ef66
--- /dev/null
+++ b/src/components/new-safe/create/steps/AdvancedOptionsStep/index.tsx
@@ -0,0 +1,201 @@
+import { Button, MenuItem, Divider, Box, TextField, Stack, Skeleton, SvgIcon, Tooltip, Typography } from '@mui/material'
+import { Controller, FormProvider, useForm } from 'react-hook-form'
+import type { ReactElement } from 'react'
+
+import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper'
+import type { NewSafeFormData } from '@/components/new-safe/create'
+import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep'
+import ArrowBackIcon from '@mui/icons-material/ArrowBack'
+import layoutCss from '@/components/new-safe/create/styles.module.css'
+import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
+import NumberField from '@/components/common/NumberField'
+import { useCurrentChain } from '@/hooks/useChains'
+import useAsync from '@/hooks/useAsync'
+import { computeNewSafeAddress } from '../../logic'
+import { getReadOnlyFallbackHandlerContract } from '@/services/contracts/safeContracts'
+import EthHashInfo from '@/components/common/EthHashInfo'
+import InfoIcon from '@/public/images/notifications/info.svg'
+import useWallet from '@/hooks/wallets/useWallet'
+import { isSmartContract } from '@/utils/wallets'
+
+enum AdvancedOptionsFields {
+ safeVersion = 'safeVersion',
+ saltNonce = 'saltNonce',
+}
+
+export type AdvancedOptionsStepForm = {
+ [AdvancedOptionsFields.safeVersion]: SafeVersion
+ [AdvancedOptionsFields.saltNonce]: number
+}
+
+const ADVANCED_OPTIONS_STEP_FORM_ID = 'create-safe-advanced-options-step-form'
+
+const AdvancedOptionsStep = ({ onSubmit, onBack, data, setStep }: StepRenderProps): ReactElement => {
+ const wallet = useWallet()
+ useSyncSafeCreationStep(setStep)
+ const chain = useCurrentChain()
+
+ const formMethods = useForm({
+ mode: 'onChange',
+ defaultValues: data,
+ })
+
+ const { handleSubmit, control, watch, formState, getValues, register } = formMethods
+
+ const selectedSafeVersion = watch(AdvancedOptionsFields.safeVersion)
+ const selectedSaltNonce = watch(AdvancedOptionsFields.saltNonce)
+
+ const [readOnlyFallbackHandlerContract] = useAsync(
+ () => (chain ? getReadOnlyFallbackHandlerContract(selectedSafeVersion) : undefined),
+ [chain, selectedSafeVersion],
+ )
+
+ const [predictedSafeAddress] = useAsync(async () => {
+ if (!chain || !readOnlyFallbackHandlerContract || !wallet) {
+ return undefined
+ }
+ return computeNewSafeAddress(
+ wallet.provider,
+ {
+ safeAccountConfig: {
+ owners: data.owners.map((owner) => owner.address),
+ threshold: data.threshold,
+ fallbackHandler: await readOnlyFallbackHandlerContract.getAddress(),
+ },
+ saltNonce: selectedSaltNonce.toString(),
+ },
+ chain,
+ selectedSafeVersion,
+ )
+ }, [
+ chain,
+ data.owners,
+ data.threshold,
+ wallet,
+ readOnlyFallbackHandlerContract,
+ selectedSafeVersion,
+ selectedSaltNonce,
+ ])
+
+ const [isDeployed] = useAsync(
+ async () => (predictedSafeAddress ? await isSmartContract(predictedSafeAddress) : false),
+ [predictedSafeAddress],
+ )
+
+ const isDisabled = !formState.isValid || Boolean(isDeployed)
+
+ const handleBack = () => {
+ const formData = getValues()
+ onBack(formData)
+ }
+
+ const onFormSubmit = handleSubmit((data) => {
+ onSubmit(data)
+
+ // TODO: Tracking of advanced setup
+ })
+
+ return (
+
+
+ Next
+
+
+
+
+
+
+ )
+}
+
+export default AdvancedOptionsStep
diff --git a/src/components/new-safe/create/steps/ReviewStep/index.test.tsx b/src/components/new-safe/create/steps/ReviewStep/index.test.tsx
index 9ab659c24..23a7a6703 100644
--- a/src/components/new-safe/create/steps/ReviewStep/index.test.tsx
+++ b/src/components/new-safe/create/steps/ReviewStep/index.test.tsx
@@ -8,6 +8,8 @@ import ReviewStep, { NetworkFee } from '@/components/new-safe/create/steps/Revie
import * as useWallet from '@/hooks/wallets/useWallet'
import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
import { act, fireEvent, screen } from '@testing-library/react'
+import { LATEST_SAFE_VERSION } from '@/config/constants'
+import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
const mockChainInfo = {
chainId: '100',
@@ -22,7 +24,7 @@ describe('NetworkFee', () => {
it('should display the total fee', () => {
jest.spyOn(useWallet, 'default').mockReturnValue({ label: 'MetaMask' } as unknown as ConnectedWallet)
const mockTotalFee = '0.0123'
- const result = render( )
+ const result = render( )
expect(result.getByText(`โ ${mockTotalFee} ${mockChainInfo.nativeCurrency.symbol}`)).toBeInTheDocument()
})
@@ -39,6 +41,7 @@ describe('ReviewStep', () => {
threshold: 1,
owners: [{ name: '', address: '0x1' }],
saltNonce: 0,
+ safeVersion: LATEST_SAFE_VERSION as SafeVersion,
}
jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
@@ -55,6 +58,7 @@ describe('ReviewStep', () => {
threshold: 1,
owners: [{ name: '', address: '0x1' }],
saltNonce: 0,
+ safeVersion: LATEST_SAFE_VERSION as SafeVersion,
}
jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
@@ -70,6 +74,7 @@ describe('ReviewStep', () => {
threshold: 1,
owners: [{ name: '', address: '0x1' }],
saltNonce: 0,
+ safeVersion: LATEST_SAFE_VERSION as SafeVersion,
}
jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
@@ -86,6 +91,7 @@ describe('ReviewStep', () => {
threshold: 1,
owners: [{ name: '', address: '0x1' }],
saltNonce: 0,
+ safeVersion: LATEST_SAFE_VERSION as SafeVersion,
}
jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
@@ -102,6 +108,7 @@ describe('ReviewStep', () => {
threshold: 1,
owners: [{ name: '', address: '0x1' }],
saltNonce: 0,
+ safeVersion: LATEST_SAFE_VERSION as SafeVersion,
}
jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
@@ -124,6 +131,7 @@ describe('ReviewStep', () => {
threshold: 1,
owners: [{ name: '', address: '0x1' }],
saltNonce: 0,
+ safeVersion: LATEST_SAFE_VERSION as SafeVersion,
}
jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
jest.spyOn(relay, 'hasRemainingRelays').mockReturnValue(true)
diff --git a/src/components/new-safe/create/steps/ReviewStep/index.tsx b/src/components/new-safe/create/steps/ReviewStep/index.tsx
index e7bfa4a4f..2eea468a2 100644
--- a/src/components/new-safe/create/steps/ReviewStep/index.tsx
+++ b/src/components/new-safe/create/steps/ReviewStep/index.tsx
@@ -2,7 +2,6 @@ import ChainIndicator from '@/components/common/ChainIndicator'
import type { NamedAddress } from '@/components/new-safe/create/types'
import EthHashInfo from '@/components/common/EthHashInfo'
import { safeCreationDispatch, SafeCreationEvent } from '@/features/counterfactual/services/safeCreationEvents'
-import { addUndeployedSafe } from '@/features/counterfactual/store/undeployedSafesSlice'
import { getTotalFeeFormatted } from '@/hooks/useGasPrice'
import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper'
import type { NewSafeFormData } from '@/components/new-safe/create'
@@ -16,7 +15,6 @@ import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCre
import ReviewRow from '@/components/new-safe/ReviewRow'
import ErrorMessage from '@/components/tx/ErrorMessage'
import { ExecutionMethod, ExecutionMethodSelector } from '@/components/tx/ExecutionMethodSelector'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import PayNowPayLater, { PayMethod } from '@/features/counterfactual/PayNowPayLater'
import { CF_TX_GROUP_KEY, createCounterfactualSafe } from '@/features/counterfactual/utils'
import { useCurrentChain, useHasFeature } from '@/hooks/useChains'
@@ -25,7 +23,6 @@ import useIsWrongChain from '@/hooks/useIsWrongChain'
import { useLeastRemainingRelays } from '@/hooks/useRemainingRelays'
import useWalletCanPay from '@/hooks/useWalletCanPay'
import useWallet from '@/hooks/wallets/useWallet'
-import { useWeb3 } from '@/hooks/wallets/web3'
import { CREATE_SAFE_CATEGORY, CREATE_SAFE_EVENTS, OVERVIEW_EVENTS, trackEvent } from '@/services/analytics'
import { gtmSetSafeAddress } from '@/services/analytics/gtm'
import { getReadOnlyFallbackHandlerContract } from '@/services/contracts/safeContracts'
@@ -37,29 +34,27 @@ import { isWalletRejection } from '@/utils/wallets'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import { Box, Button, CircularProgress, Divider, Grid, Typography } from '@mui/material'
import { type DeploySafeProps } from '@safe-global/protocol-kit'
-import type { SafeVersion } from '@safe-global/safe-core-sdk-types'
import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import classnames from 'classnames'
import { useRouter } from 'next/router'
import { useMemo, useState } from 'react'
import { checksumAddress } from '@/utils/addresses'
+import { ECOSYSTEM_ID_ADDRESS } from '@/config/constants'
export const NetworkFee = ({
totalFee,
chain,
- willRelay,
+ isWaived,
inline = false,
}: {
totalFee: string
chain: ChainInfo | undefined
- willRelay: boolean
+ isWaived: boolean
inline?: boolean
}) => {
- const wallet = useWallet()
-
return (
-
+
≈ {totalFee} {chain?.nativeCurrency.symbol}
@@ -119,7 +114,6 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps {
- if (!wallet || !provider || !chain) return
+ if (!wallet || !chain) return
setIsCreating(true)
try {
- const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract(
- chain.chainId,
- LATEST_SAFE_VERSION,
- )
+ const readOnlyFallbackHandlerContract = await getReadOnlyFallbackHandlerContract(data.safeVersion)
const props: DeploySafeProps = {
safeAccountConfig: {
threshold: data.threshold,
owners: data.owners.map((owner) => checksumAddress(owner.address)),
fallbackHandler: await readOnlyFallbackHandlerContract.getAddress(),
+ paymentReceiver: ECOSYSTEM_ID_ADDRESS,
},
}
- const saltNonce = await getAvailableSaltNonce(provider, { ...props, saltNonce: '0' }, chain.chainId)
- const safeAddress = await computeNewSafeAddress(provider, { ...props, saltNonce }, chain.chainId)
+ const saltNonce = await getAvailableSaltNonce(
+ wallet.provider,
+ { ...props, saltNonce: '0' },
+ chain,
+ data.safeVersion,
+ )
+ const safeAddress = await computeNewSafeAddress(wallet.provider, { ...props, saltNonce }, chain, data.safeVersion)
if (isCounterfactual && payMethod === PayMethod.PayLater) {
gtmSetSafeAddress(safeAddress)
trackEvent({ ...OVERVIEW_EVENTS.PROCEED_WITH_TX, label: 'counterfactual', category: CREATE_SAFE_CATEGORY })
- await createCounterfactualSafe(chain, safeAddress, saltNonce, data, dispatch, props, router)
+ createCounterfactualSafe(chain, safeAddress, saltNonce, data, dispatch, props, PayMethod.PayLater, router)
trackEvent({ ...CREATE_SAFE_EVENTS.CREATED_SAFE, label: 'counterfactual' })
return
}
@@ -200,21 +197,9 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps {
- dispatch(addUndeployedSafe(undeployedSafe))
+ // Create a counterfactual Safe
+ createCounterfactualSafe(chain, safeAddress, saltNonce, data, dispatch, props, PayMethod.PayNow)
if (taskId) {
safeCreationDispatch(SafeCreationEvent.RELAYING, { groupKey: CF_TX_GROUP_KEY, taskId, safeAddress })
@@ -240,17 +225,22 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps {
- onSubmitCallback(undefined, txHash)
+ await createNewSafe(
+ wallet.provider,
+ {
+ safeAccountConfig: props.safeAccountConfig,
+ saltNonce,
+ options,
+ callback: (txHash) => {
+ onSubmitCallback(undefined, txHash)
+ },
},
- })
+ data.safeVersion,
+ )
}
} catch (_err) {
const error = asError(_err)
@@ -299,7 +289,7 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps
You will have to confirm a transaction and pay an estimated fee of{' '}
- with your connected
+ with your connected
wallet
@@ -332,7 +322,7 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps
-
+
{!willRelay && (
@@ -344,7 +334,7 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps
- {isWrongChain && }
+
{!walletCanPay && !willRelay && (
diff --git a/src/components/new-safe/create/steps/ReviewStep/styles.module.css b/src/components/new-safe/create/steps/ReviewStep/styles.module.css
index 47bb71ae8..f495e3d66 100644
--- a/src/components/new-safe/create/steps/ReviewStep/styles.module.css
+++ b/src/components/new-safe/create/steps/ReviewStep/styles.module.css
@@ -5,7 +5,7 @@
font-size: 14px;
}
-.sponsoredFee {
+.strikethrough {
text-decoration: line-through;
color: var(--color-text-secondary);
}
diff --git a/src/components/new-safe/create/steps/SetNameStep/index.tsx b/src/components/new-safe/create/steps/SetNameStep/index.tsx
index 703cafc30..62c9b1b01 100644
--- a/src/components/new-safe/create/steps/SetNameStep/index.tsx
+++ b/src/components/new-safe/create/steps/SetNameStep/index.tsx
@@ -5,7 +5,6 @@ import InfoIcon from '@/public/images/notifications/info.svg'
import NetworkSelector from '@/components/common/NetworkSelector'
import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper'
import type { NewSafeFormData } from '@/components/new-safe/create'
-import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep'
import css from '@/components/new-safe/create/steps/SetNameStep/styles.module.css'
import layoutCss from '@/components/new-safe/create/styles.module.css'
@@ -17,13 +16,20 @@ import { AppRoutes } from '@/config/routes'
import MUILink from '@mui/material/Link'
import Link from 'next/link'
import { useRouter } from 'next/router'
+import NoWalletConnectedWarning from '../../NoWalletConnectedWarning'
+import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
+import { useCurrentChain } from '@/hooks/useChains'
+import { useEffect } from 'react'
+import { getLatestSafeVersion } from '@/utils/chains'
type SetNameStepForm = {
name: string
+ safeVersion: SafeVersion
}
enum SetNameStepFields {
name = 'name',
+ safeVersion = 'safeVersion',
}
const SET_NAME_STEP_FORM_ID = 'create-safe-set-name-step-form'
@@ -31,23 +37,22 @@ const SET_NAME_STEP_FORM_ID = 'create-safe-set-name-step-form'
function SetNameStep({
data,
onSubmit,
- setStep,
setSafeName,
}: StepRenderProps & { setSafeName: (name: string) => void }) {
const router = useRouter()
const fallbackName = useMnemonicSafeName()
const isWrongChain = useIsWrongChain()
- useSyncSafeCreationStep(setStep)
+
+ const chain = useCurrentChain()
const formMethods = useForm({
mode: 'all',
- defaultValues: {
- [SetNameStepFields.name]: data.name,
- },
+ defaultValues: data,
})
const {
handleSubmit,
+ setValue,
formState: { errors, isValid },
} = formMethods
@@ -66,6 +71,11 @@ function SetNameStep({
router.push(AppRoutes.welcome.index)
}
+ // whenever the chain switches we need to update the latest Safe version
+ useEffect(() => {
+ setValue(SetNameStepFields.safeVersion, getLatestSafeVersion(chain))
+ }, [chain, setValue])
+
const isDisabled = isWrongChain || !isValid
return (
@@ -112,7 +122,11 @@ function SetNameStep({
.
- {isWrongChain && }
+
+
+
+
+
diff --git a/src/components/new-safe/create/steps/StatusStep/StatusMessage.tsx b/src/components/new-safe/create/steps/StatusStep/StatusMessage.tsx
index 3776d953f..fc0b04589 100644
--- a/src/components/new-safe/create/steps/StatusStep/StatusMessage.tsx
+++ b/src/components/new-safe/create/steps/StatusStep/StatusMessage.tsx
@@ -72,7 +72,9 @@ const StatusMessage = ({
{stepInfo.instruction}
)}
- {!isError && explorerLink && Check Status }
+ {!isError && explorerLink && (
+ Check status on block explorer
+ )}
>
)
diff --git a/src/components/new-safe/create/steps/StatusStep/index.tsx b/src/components/new-safe/create/steps/StatusStep/index.tsx
index 2a6b01ac0..cd896b796 100644
--- a/src/components/new-safe/create/steps/StatusStep/index.tsx
+++ b/src/components/new-safe/create/steps/StatusStep/index.tsx
@@ -17,7 +17,7 @@ import { Alert, AlertTitle, Box, Button, Paper, Stack, SvgIcon, Typography } fro
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
-import useSyncSafeCreationStep from '../../useSyncSafeCreationStep'
+import { getLatestSafeVersion } from '@/utils/chains'
const SPEED_UP_THRESHOLD_IN_SECONDS = 15
@@ -37,8 +37,6 @@ export const CreateSafeStatus = ({
const isError = status === SafeCreationEvent.FAILED || status === SafeCreationEvent.REVERTED
- useSyncSafeCreationStep(setStep)
-
useEffect(() => {
const unsubFns = Object.entries(safeCreationPendingStatuses).map(([event]) =>
safeCreationSubscribe(event as SafeCreationEvent, async () => {
@@ -56,7 +54,10 @@ export const CreateSafeStatus = ({
if (status === SafeCreationEvent.SUCCESS) {
dispatch(updateAddressBook(chain.chainId, safeAddress, data.name, data.owners, data.threshold))
- router.push(getRedirect(chain.shortName, safeAddress, router.query?.safeViewRedirectURL))
+ const redirect = getRedirect(chain.shortName, safeAddress, router.query?.safeViewRedirectURL)
+ if (typeof redirect !== 'string' || redirect.startsWith('/')) {
+ router.push(redirect)
+ }
}
}, [dispatch, chain, data.name, data.owners, data.threshold, router, safeAddress, status])
@@ -86,6 +87,7 @@ export const CreateSafeStatus = ({
threshold: pendingSafe.props.safeAccountConfig.threshold,
saltNonce: Number(pendingSafe.props.safeDeploymentConfig?.saltNonce),
safeAddress,
+ safeVersion: pendingSafe.props.safeDeploymentConfig?.safeVersion ?? getLatestSafeVersion(chain),
})
}
diff --git a/src/components/new-safe/create/useEstimateSafeCreationGas.ts b/src/components/new-safe/create/useEstimateSafeCreationGas.ts
index b285f007c..4cbe4c04a 100644
--- a/src/components/new-safe/create/useEstimateSafeCreationGas.ts
+++ b/src/components/new-safe/create/useEstimateSafeCreationGas.ts
@@ -3,9 +3,11 @@ import useWallet from '@/hooks/wallets/useWallet'
import useAsync from '@/hooks/useAsync'
import { useCurrentChain } from '@/hooks/useChains'
import { estimateSafeCreationGas, type SafeCreationProps } from '@/components/new-safe/create/logic'
+import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
export const useEstimateSafeCreationGas = (
safeParams: SafeCreationProps,
+ safeVersion?: SafeVersion,
): {
gasLimit?: bigint
gasLimitError?: Error
@@ -18,8 +20,8 @@ export const useEstimateSafeCreationGas = (
const [gasLimit, gasLimitError, gasLimitLoading] = useAsync(() => {
if (!wallet?.address || !chain || !web3ReadOnly) return
- return estimateSafeCreationGas(chain, web3ReadOnly, wallet.address, safeParams)
- }, [wallet, chain, web3ReadOnly, safeParams])
+ return estimateSafeCreationGas(chain, web3ReadOnly, wallet.address, safeParams, safeVersion)
+ }, [wallet, chain, web3ReadOnly, safeParams, safeVersion])
return { gasLimit, gasLimitError, gasLimitLoading }
}
diff --git a/src/components/new-safe/create/useSyncSafeCreationStep.ts b/src/components/new-safe/create/useSyncSafeCreationStep.ts
index 1e645d6c9..5ba811a03 100644
--- a/src/components/new-safe/create/useSyncSafeCreationStep.ts
+++ b/src/components/new-safe/create/useSyncSafeCreationStep.ts
@@ -1,37 +1,20 @@
-import useUndeployedSafe from '@/components/new-safe/create/steps/StatusStep/useUndeployedSafe'
import { useEffect } from 'react'
import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper'
import type { NewSafeFormData } from '@/components/new-safe/create/index'
import useWallet from '@/hooks/wallets/useWallet'
import useIsWrongChain from '@/hooks/useIsWrongChain'
-import { useRouter } from 'next/router'
-import { AppRoutes } from '@/config/routes'
const useSyncSafeCreationStep = (setStep: StepRenderProps['setStep']) => {
- const [safeAddress, pendingSafe] = useUndeployedSafe()
-
const wallet = useWallet()
const isWrongChain = useIsWrongChain()
- const router = useRouter()
useEffect(() => {
- // Jump to the status screen if there is already a tx submitted
- if (pendingSafe && pendingSafe.status.status !== 'AWAITING_EXECUTION') {
- setStep(3)
- return
- }
-
- // Jump to the welcome page if there is no wallet
- if (!wallet) {
- router.push({ pathname: AppRoutes.welcome.index, query: router.query })
- }
-
// Jump to choose name and network step if the wallet is connected to the wrong chain and there is no pending Safe
- if (isWrongChain) {
+ if (!wallet || isWrongChain) {
setStep(0)
return
}
- }, [wallet, setStep, pendingSafe, isWrongChain, router])
+ }, [wallet, setStep, isWrongChain])
}
export default useSyncSafeCreationStep
diff --git a/src/components/notification-center/NotificationCenter/index.tsx b/src/components/notification-center/NotificationCenter/index.tsx
index ebce3aee2..bd7c47371 100644
--- a/src/components/notification-center/NotificationCenter/index.tsx
+++ b/src/components/notification-center/NotificationCenter/index.tsx
@@ -110,6 +110,10 @@ const NotificationCenter = (): ReactElement => {
({ defaultValues: { riskAcknowledgement: false }, mode: 'onChange' })
- const onSubmit: SubmitHandler = (_, __) => {
+ const onSubmit: SubmitHandler = () => {
if (safeApp) {
onSave(safeApp)
trackSafeAppEvent(SAFE_APPS_EVENTS.ADD_CUSTOM_APP, safeApp.url)
diff --git a/src/components/safe-apps/AppFrame/index.tsx b/src/components/safe-apps/AppFrame/index.tsx
index b2e1155ea..de48f57e0 100644
--- a/src/components/safe-apps/AppFrame/index.tsx
+++ b/src/components/safe-apps/AppFrame/index.tsx
@@ -29,15 +29,14 @@ import css from './styles.module.css'
import SafeAppIframe from './SafeAppIframe'
import { useCustomAppCommunicator } from '@/hooks/safe-apps/useCustomAppCommunicator'
-const UNKNOWN_APP_NAME = 'Unknown Safe App'
-
type AppFrameProps = {
appUrl: string
allowedFeaturesList: string
safeAppFromManifest: SafeAppDataWithPermissions
+ isNativeEmbed?: boolean
}
-const AppFrame = ({ appUrl, allowedFeaturesList, safeAppFromManifest }: AppFrameProps): ReactElement => {
+const AppFrame = ({ appUrl, allowedFeaturesList, safeAppFromManifest, isNativeEmbed }: AppFrameProps): ReactElement => {
const { safe, safeLoaded } = useSafeInfo()
const addressBook = useAddressBook()
const chainId = useChainId()
@@ -98,11 +97,14 @@ const AppFrame = ({ appUrl, allowedFeaturesList, safeAppFromManifest }: AppFrame
}
setAppIsLoading(false)
- gtmTrackPageview(`${router.pathname}?appUrl=${router.query.appUrl}`, router.asPath)
- }, [appUrl, iframeRef, setAppIsLoading, router])
+
+ if (!isNativeEmbed) {
+ gtmTrackPageview(`${router.pathname}?appUrl=${router.query.appUrl}`, router.asPath)
+ }
+ }, [appUrl, iframeRef, setAppIsLoading, router, isNativeEmbed])
useEffect(() => {
- if (!appIsLoading && !isBackendAppsLoading) {
+ if (!isNativeEmbed && !appIsLoading && !isBackendAppsLoading) {
trackSafeAppEvent(
{
...SAFE_APPS_EVENTS.OPEN_APP,
@@ -110,7 +112,7 @@ const AppFrame = ({ appUrl, allowedFeaturesList, safeAppFromManifest }: AppFrame
appName,
)
}
- }, [appIsLoading, isBackendAppsLoading, appName])
+ }, [appIsLoading, isBackendAppsLoading, appName, isNativeEmbed])
if (!safeLoaded) {
return
@@ -118,9 +120,11 @@ const AppFrame = ({ appUrl, allowedFeaturesList, safeAppFromManifest }: AppFrame
return (
<>
-
- {`Safe Apps - Viewer - ${remoteApp ? remoteApp.name : UNKNOWN_APP_NAME}`}
-
+ {!isNativeEmbed && (
+
+ {`Safe{Wallet} - Safe Apps${remoteApp ? ' - ' + remoteApp.name : ''}`}
+
+ )}
{thirdPartyCookiesDisabled &&
setThirdPartyCookiesDisabled(false)} />}
@@ -160,7 +164,7 @@ const AppFrame = ({ appUrl, allowedFeaturesList, safeAppFromManifest }: AppFrame
transactions={transactions}
/>
- {permissionsRequest && (
+ {!isNativeEmbed && permissionsRequest && (
{
- it('should detect unsafe src', () => {
- expect(_isSafeSrc('https://google.com/test.jpg')).toBe(false)
- expect(_isSafeSrc('data:image/png;base64,')).toBe(false)
- })
-
- it('should detect safe src', () => {
- expect(_isSafeSrc('https://safe-transaction-assets.safe.global/contracts/logos/0x34CfAC646f3.png')).toBe(true)
- expect(_isSafeSrc('https://safe-transaction-assets.staging.5afe.dev/contracts/logos/0x34CfAC.png')).toBe(true)
- expect(_isSafeSrc('/images/transactions/incoming.svg')).toBe(true)
- })
-})
diff --git a/src/components/safe-apps/SafeAppIconCard/index.test.tsx b/src/components/safe-apps/SafeAppIconCard/index.test.tsx
new file mode 100644
index 000000000..5997c6f67
--- /dev/null
+++ b/src/components/safe-apps/SafeAppIconCard/index.test.tsx
@@ -0,0 +1,18 @@
+import { render } from '@/tests/test-utils'
+import SafeAppIconCard from '.'
+
+describe('SafeAppIconCard', () => {
+ it('should render an icon', () => {
+ const src = 'https://safe-transaction-assets.safe.global/safe_apps/160/icon.png'
+ const { queryByAltText } = render(
+ ,
+ )
+
+ const img = queryByAltText('test')
+ expect(img).toBeInTheDocument()
+ expect(img).toHaveAttribute('src', src)
+ expect(img).toHaveAttribute('height', '100')
+ expect(img).toHaveAttribute('width', '100')
+ expect(img).not.toHaveAttribute('crossorigin')
+ })
+})
diff --git a/src/components/safe-apps/SafeAppIconCard/index.tsx b/src/components/safe-apps/SafeAppIconCard/index.tsx
index b0333aa34..32b398fff 100644
--- a/src/components/safe-apps/SafeAppIconCard/index.tsx
+++ b/src/components/safe-apps/SafeAppIconCard/index.tsx
@@ -1,38 +1,7 @@
import ImageFallback from '@/components/common/ImageFallback'
-import { type ReactElement, memo } from 'react'
const APP_LOGO_FALLBACK_IMAGE = `/images/apps/app-placeholder.svg`
-const getIframeContent = (url: string, width: number, height: number, fallback: string): string => {
- return `
-
-
-
-
- `
-}
-
-export const _isSafeSrc = (src: string) => {
- const allowedHosts = ['.safe.global', '.5afe.dev']
- const isRelative = src.startsWith('/')
-
- let hostname = ''
- if (!isRelative) {
- try {
- hostname = new URL(src).hostname
- } catch (e) {
- return false
- }
- }
-
- return isRelative || allowedHosts.some((host) => hostname.endsWith(host))
-}
-
const SafeAppIconCard = ({
src,
alt,
@@ -45,24 +14,6 @@ const SafeAppIconCard = ({
width?: number
height?: number
fallback?: string
-}): ReactElement => {
- if (_isSafeSrc(src)) {
- return
- }
-
- return (
-
- )
-}
+}) =>
-export default memo(SafeAppIconCard)
+export default SafeAppIconCard
diff --git a/src/components/safe-apps/SafeAppsZeroResultsPlaceholder/index.tsx b/src/components/safe-apps/SafeAppsZeroResultsPlaceholder/index.tsx
index e495f3519..0d40eb3b1 100644
--- a/src/components/safe-apps/SafeAppsZeroResultsPlaceholder/index.tsx
+++ b/src/components/safe-apps/SafeAppsZeroResultsPlaceholder/index.tsx
@@ -1,48 +1,18 @@
-import React, { useMemo } from 'react'
-import { useRouter } from 'next/router'
-import Link from 'next/link'
-import Button from '@mui/material/Button'
import Typography from '@mui/material/Typography'
-import type { LinkProps } from 'next/link'
-
import PagePlaceholder from '@/components/common/PagePlaceholder'
import AddCustomAppIcon from '@/public/images/apps/add-custom-app.svg'
-import { AppRoutes } from '@/config/routes'
-import { SafeAppsTag } from '@/config/constants'
-import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps'
-
-const useWCAppLink = (): LinkProps['href'] => {
- const router = useRouter()
- const [matchingApps] = useRemoteSafeApps(SafeAppsTag.WALLET_CONNECT)
- const app = matchingApps?.[0]
-
- return useMemo(
- () => ({
- pathname: AppRoutes.apps.open,
- query: { safe: router.query.safe, appUrl: app?.url },
- }),
- [app?.url, router.query.safe],
- )
-}
const SafeAppsZeroResultsPlaceholder = ({ searchQuery }: { searchQuery: string }) => {
- const wcLink = useWCAppLink()
return (
}
text={
No Safe Apps found matching {searchQuery} . Connect to dApps that haven't yet been
- integrated with the {'Rootstock Safe'} using the WalletConnect Safe App.
+ integrated with the {'Rootstock Safe'} using WalletConnect.
}
- >
-
-
- Use WalletConnect
-
-
-
+ />
)
}
diff --git a/src/components/safe-messages/SingleMsg/index.tsx b/src/components/safe-messages/SingleMsg/index.tsx
index dc5c18165..90fc41d06 100644
--- a/src/components/safe-messages/SingleMsg/index.tsx
+++ b/src/components/safe-messages/SingleMsg/index.tsx
@@ -9,7 +9,7 @@ const SingleMsg = () => {
const router = useRouter()
const { messageHash } = router.query
const safeMessageHash = Array.isArray(messageHash) ? messageHash[0] : messageHash
- const [safeMessage, _, messageError] = useSafeMessage(safeMessageHash)
+ const [safeMessage, , messageError] = useSafeMessage(safeMessageHash)
if (safeMessage) {
return (
diff --git a/src/components/settings/ContractVersion/index.tsx b/src/components/settings/ContractVersion/index.tsx
index 334ca921d..151548f3d 100644
--- a/src/components/settings/ContractVersion/index.tsx
+++ b/src/components/settings/ContractVersion/index.tsx
@@ -1,7 +1,6 @@
import { useContext, useMemo } from 'react'
import { SvgIcon, Typography, Alert, AlertTitle, Skeleton, Button } from '@mui/material'
import { ImplementationVersionState } from '@safe-global/safe-gateway-typescript-sdk'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import { sameAddress } from '@/utils/addresses'
import type { MasterCopy } from '@/hooks/useMasterCopies'
import { MasterCopyDeployer, useMasterCopies } from '@/hooks/useMasterCopies'
@@ -12,11 +11,14 @@ import { TxModalContext } from '@/components/tx-flow'
import { UpdateSafeFlow } from '@/components/tx-flow/flows'
import ExternalLink from '@/components/common/ExternalLink'
import CheckWallet from '@/components/common/CheckWallet'
+import { getLatestSafeVersion } from '@/utils/chains'
+import { useCurrentChain } from '@/hooks/useChains'
export const ContractVersion = () => {
const { setTxFlow } = useContext(TxModalContext)
const [masterCopies] = useMasterCopies()
const { safe, safeLoaded } = useSafeInfo()
+ const currentChain = useCurrentChain()
const masterCopyAddress = safe.implementation.value
const safeMasterCopy: MasterCopy | undefined = useMemo(() => {
@@ -27,6 +29,8 @@ export const ContractVersion = () => {
const showUpdateDialog = safeMasterCopy?.deployer === MasterCopyDeployer.GNOSIS && needsUpdate
const isLatestVersion = safe.version && !showUpdateDialog
+ const latestSafeVersion = getLatestSafeVersion(currentChain)
+
return (
<>
@@ -53,7 +57,7 @@ export const ContractVersion = () => {
sx={{ mt: 2, borderRadius: '2px', borderColor: '#B0FFC9' }}
icon={ }
>
- New version is available: {LATEST_SAFE_VERSION}
+ New version is available: {latestSafeVersion}
Update now to take advantage of new features and the highest security standards available. You will need to
diff --git a/src/components/settings/DataManagement/FileListCard.tsx b/src/components/settings/DataManagement/FileListCard.tsx
index 984d373ba..36384b32a 100644
--- a/src/components/settings/DataManagement/FileListCard.tsx
+++ b/src/components/settings/DataManagement/FileListCard.tsx
@@ -2,14 +2,15 @@ import { Box, Card, CardContent, CardHeader, List, ListItem, ListItemIcon, ListI
import type { ListItemTextProps } from '@mui/material'
import type { CardHeaderProps } from '@mui/material'
import type { ReactElement } from 'react'
-
import FileIcon from '@/public/images/settings/data/file.svg'
+
import useChains from '@/hooks/useChains'
import { ImportErrors } from '@/components/settings/DataManagement/useGlobalImportFileParser'
import type { AddedSafesState } from '@/store/addedSafesSlice'
import type { AddressBookState } from '@/store/addressBookSlice'
import type { SafeAppsState } from '@/store/safeAppsSlice'
import type { SettingsState } from '@/store/settingsSlice'
+import type { UndeployedSafesState } from '@/features/counterfactual/store/undeployedSafesSlice'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import css from './styles.module.css'
@@ -51,6 +52,7 @@ type Data = {
addressBook?: AddressBookState
settings?: SettingsState
safeApps?: SafeAppsState
+ undeployedSafes?: UndeployedSafesState
error?: string
}
@@ -65,6 +67,7 @@ const getItems = ({
addressBook,
settings,
safeApps,
+ undeployedSafes,
error,
chains,
showPreview = false,
@@ -75,6 +78,7 @@ const getItems = ({
const addedSafeChainAmount = Object.keys(addedSafes || {}).length
const addressBookChainAmount = Object.keys(addressBook || {}).length
+ const undeployedSafesCount = Object.values(undeployedSafes || {}).flatMap((items) => Object.keys(items)).length
const items: Array = []
@@ -129,6 +133,18 @@ const getItems = ({
items.push(safeAppsPreview)
}
+ if (undeployedSafes) {
+ const undeployedSafesPreview: ListItemTextProps = {
+ primary: (
+ <>
+ Not activated Safe Accounts {undeployedSafesCount}
+ >
+ ),
+ }
+
+ items.push(undeployedSafesPreview)
+ }
+
if (items.length === 0) {
return [{ primary: <>{ImportErrors.NO_IMPORT_DATA_FOUND}> }]
}
@@ -143,12 +159,22 @@ export const FileListCard = ({
addressBook,
settings,
safeApps,
+ undeployedSafes,
error,
showPreview = false,
...cardHeaderProps
}: Props): ReactElement => {
const chains = useChains()
- const items = getItems({ addedSafes, addressBook, settings, safeApps, error, chains: chains.configs, showPreview })
+ const items = getItems({
+ addedSafes,
+ addressBook,
+ settings,
+ safeApps,
+ undeployedSafes,
+ error,
+ chains: chains.configs,
+ showPreview,
+ })
return (
diff --git a/src/components/settings/DataManagement/ImportDialog.tsx b/src/components/settings/DataManagement/ImportDialog.tsx
index 0a5fe18d4..b6668e5c7 100644
--- a/src/components/settings/DataManagement/ImportDialog.tsx
+++ b/src/components/settings/DataManagement/ImportDialog.tsx
@@ -1,3 +1,4 @@
+import { undeployedSafesSlice } from '@/features/counterfactual/store/undeployedSafesSlice'
import { DialogContent, Alert, AlertTitle, DialogActions, Button, Box, SvgIcon } from '@mui/material'
import type { ReactElement, Dispatch, SetStateAction } from 'react'
@@ -30,7 +31,7 @@ export const ImportDialog = ({
setJsonData: Dispatch>
}): ReactElement => {
const dispatch = useAppDispatch()
- const { addedSafes, addedSafesCount, addressBook, addressBookEntriesCount, settings, safeApps, error } =
+ const { addedSafes, addressBook, addressBookEntriesCount, settings, safeApps, undeployedSafes, error } =
useGlobalImportJsonParser(jsonData)
const isDisabled = (!addedSafes && !addressBook && !settings && !safeApps) || !!error
@@ -67,6 +68,11 @@ export const ImportDialog = ({
trackEvent(SETTINGS_EVENTS.DATA.IMPORT_SAFE_APPS)
}
+ if (undeployedSafes) {
+ dispatch(undeployedSafesSlice.actions.addUndeployedSafes(undeployedSafes))
+ trackEvent(SETTINGS_EVENTS.DATA.IMPORT_UNDEPLOYED_SAFES)
+ }
+
dispatch(
showNotification({
variant: 'success',
@@ -104,6 +110,7 @@ export const ImportDialog = ({
addressBook={addressBook}
settings={settings}
safeApps={safeApps}
+ undeployedSafes={undeployedSafes}
error={error}
showPreview
/>
diff --git a/src/components/settings/DataManagement/index.tsx b/src/components/settings/DataManagement/index.tsx
index a91fac1ae..f753c119b 100644
--- a/src/components/settings/DataManagement/index.tsx
+++ b/src/components/settings/DataManagement/index.tsx
@@ -8,6 +8,7 @@ import { addressBookSlice, selectAllAddressBooks } from '@/store/addressBookSlic
import { addedSafesSlice, selectAllAddedSafes } from '@/store/addedSafesSlice'
import { safeAppsSlice, selectSafeApps } from '@/store/safeAppsSlice'
import { selectSettings, settingsSlice } from '@/store/settingsSlice'
+import { selectUndeployedSafes, undeployedSafesSlice } from '@/features/counterfactual/store/undeployedSafesSlice'
import { ImportFileUpload } from '@/components/settings/DataManagement/ImportFileUpload'
import { ImportDialog } from '@/components/settings/DataManagement/ImportDialog'
import { SAFE_EXPORT_VERSION } from '@/components/settings/DataManagement/useGlobalImportFileParser'
@@ -29,6 +30,7 @@ export const exportAppData = () => {
[addedSafesSlice.name]: addedSafes,
[settingsSlice.name]: setting,
[safeAppsSlice.name]: safeApps,
+ [undeployedSafesSlice.name]: undeployedSafes,
} = getPersistedState()
// Ensure they are under the same name as the slice
@@ -37,6 +39,7 @@ export const exportAppData = () => {
[addedSafesSlice.name]: addedSafes,
[settingsSlice.name]: setting,
[safeAppsSlice.name]: safeApps,
+ [undeployedSafesSlice.name]: undeployedSafes,
}
const data = JSON.stringify({ version: SAFE_EXPORT_VERSION.V2, data: exportData })
@@ -59,6 +62,7 @@ const DataManagement = () => {
const addressBook = useAppSelector(selectAllAddressBooks)
const settings = useAppSelector(selectSettings)
const safeApps = useAppSelector(selectSafeApps)
+ const undeployedSafes = useAppSelector(selectUndeployedSafes)
useEffect(() => {
// Prevent hydration errors
@@ -96,6 +100,7 @@ const DataManagement = () => {
addressBook={addressBook}
settings={settings}
safeApps={safeApps}
+ undeployedSafes={undeployedSafes}
/>
diff --git a/src/components/settings/DataManagement/useGlobalImportFileParser.ts b/src/components/settings/DataManagement/useGlobalImportFileParser.ts
index ff5c081e9..d45450b7e 100644
--- a/src/components/settings/DataManagement/useGlobalImportFileParser.ts
+++ b/src/components/settings/DataManagement/useGlobalImportFileParser.ts
@@ -7,6 +7,7 @@ import type { AddressBook, AddressBookState } from '@/store/addressBookSlice'
import type { AddedSafesState } from '@/store/addedSafesSlice'
import type { SafeAppsState } from '@/store/safeAppsSlice'
import type { SettingsState } from '@/store/settingsSlice'
+import type { UndeployedSafesState } from '@/features/counterfactual/store/undeployedSafesSlice'
import { useMemo } from 'react'
@@ -67,6 +68,7 @@ type Data = {
addressBook?: AddressBookState
settings?: SettingsState
safeApps?: SafeAppsState
+ undeployedSafes?: UndeployedSafesState
error?: ImportErrors
addressBookEntriesCount: number
addedSafesCount: number
@@ -81,6 +83,7 @@ export const useGlobalImportJsonParser = (jsonData: string | undefined): Data =>
addedSafes: undefined,
settings: undefined,
safeApps: undefined,
+ undeployedSafes: undefined,
}
if (!jsonData) {
@@ -116,6 +119,7 @@ export const useGlobalImportJsonParser = (jsonData: string | undefined): Data =>
data.addedSafes = parsedFile.data.addedSafes
data.settings = parsedFile.data.settings
data.safeApps = parsedFile.data.safeApps
+ data.undeployedSafes = parsedFile.data.undeployedSafes
break
}
diff --git a/src/components/settings/DelegatesList/index.tsx b/src/components/settings/DelegatesList/index.tsx
index 1610e2db8..9aa65bb02 100644
--- a/src/components/settings/DelegatesList/index.tsx
+++ b/src/components/settings/DelegatesList/index.tsx
@@ -1,28 +1,14 @@
-import { getDelegates } from '@safe-global/safe-gateway-typescript-sdk'
-import useAsync from '@/hooks/useAsync'
-import useSafeInfo from '@/hooks/useSafeInfo'
+import useDelegates from '@/hooks/useDelegates'
import { Box, Grid, Paper, SvgIcon, Tooltip, Typography } from '@mui/material'
import EthHashInfo from '@/components/common/EthHashInfo'
import InfoIcon from '@/public/images/notifications/info.svg'
import ExternalLink from '@/components/common/ExternalLink'
import { HelpCenterArticle } from '@/config/constants'
-const useDelegates = () => {
- const {
- safe: { chainId },
- safeAddress,
- } = useSafeInfo()
- const [delegates] = useAsync(() => {
- if (!chainId || !safeAddress) return
- return getDelegates(chainId, { safe: safeAddress })
- }, [chainId, safeAddress])
- return delegates
-}
-
const DelegatesList = () => {
const delegates = useDelegates()
- if (!delegates?.results.length) return null
+ if (!delegates.data?.results) return null
return (
@@ -55,7 +41,7 @@ const DelegatesList = () => {
- {delegates.results.map((item) => (
+ {delegates.data.results.map((item) => (
{
beforeEach(() => {
@@ -18,7 +18,7 @@ describe('FallbackHandler', () => {
link: { href: 'https://mock.link/tx-builder' },
}))
- jest.spyOn(useChainId, 'default').mockImplementation(() => mockChain.chainId)
+ jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(mockChain)
})
it('should render the Fallback Handler when one is set', async () => {
@@ -27,7 +27,7 @@ describe('FallbackHandler', () => {
({
safe: {
version: '1.3.0',
- chainId: '5',
+ chainId: '1',
fallbackHandler: {
value: GOERLI_FALLBACK_HANDLER,
name: 'FallbackHandlerName',
diff --git a/src/components/settings/FallbackHandler/index.tsx b/src/components/settings/FallbackHandler/index.tsx
index 81540190a..3c6e3117d 100644
--- a/src/components/settings/FallbackHandler/index.tsx
+++ b/src/components/settings/FallbackHandler/index.tsx
@@ -11,18 +11,23 @@ import { getFallbackHandlerContractDeployment } from '@/services/contracts/deplo
import { HelpCenterArticle } from '@/config/constants'
import ExternalLink from '@/components/common/ExternalLink'
import { useTxBuilderApp } from '@/hooks/safe-apps/useTxBuilderApp'
+import { useCurrentChain } from '@/hooks/useChains'
const FALLBACK_HANDLER_VERSION = '>=1.1.1'
export const FallbackHandler = (): ReactElement | null => {
const { safe } = useSafeInfo()
const txBuilder = useTxBuilderApp()
+ const chain = useCurrentChain()
const supportsFallbackHandler = !!safe.version && semverSatisfies(safe.version, FALLBACK_HANDLER_VERSION)
const fallbackHandlerDeployment = useMemo(() => {
- return getFallbackHandlerContractDeployment(safe.chainId, safe.version)
- }, [safe.version, safe.chainId])
+ if (!chain) {
+ return undefined
+ }
+ return getFallbackHandlerContractDeployment(chain, safe.version)
+ }, [safe.version, chain])
if (!supportsFallbackHandler) {
return null
diff --git a/src/components/settings/PushNotifications/__tests__/logic.test.ts b/src/components/settings/PushNotifications/__tests__/logic.test.ts
index cb5993237..7a103f5b1 100644
--- a/src/components/settings/PushNotifications/__tests__/logic.test.ts
+++ b/src/components/settings/PushNotifications/__tests__/logic.test.ts
@@ -123,7 +123,7 @@ describe('Notifications', () => {
const mockProvider = new BrowserProvider(MockEip1193Provider)
- jest.spyOn(mockProvider, 'getSigner').mockImplementation((address?: string | number | undefined) =>
+ jest.spyOn(mockProvider, 'getSigner').mockImplementation(() =>
Promise.resolve({
signMessage: jest.fn().mockResolvedValueOnce(MM_SIGNATURE),
} as unknown as JsonRpcSigner),
diff --git a/src/components/settings/PushNotifications/hooks/useNotificationRegistrations.ts b/src/components/settings/PushNotifications/hooks/useNotificationRegistrations.ts
index 7bde23e3d..bb59d75e3 100644
--- a/src/components/settings/PushNotifications/hooks/useNotificationRegistrations.ts
+++ b/src/components/settings/PushNotifications/hooks/useNotificationRegistrations.ts
@@ -1,4 +1,5 @@
import { registerDevice, unregisterDevice, unregisterSafe } from '@safe-global/safe-gateway-typescript-sdk'
+import isEmpty from 'lodash/isEmpty'
import { useAppDispatch } from '@/store'
import { showNotification } from '@/store/notificationsSlice'
@@ -19,7 +20,7 @@ const registrationFlow = async (registrationFn: Promise, callback: () =
// Gateway will return 200 with an empty payload if the device was (un-)registered successfully
// @see https://github.com/safe-global/safe-client-gateway-nest/blob/27b6b3846b4ecbf938cdf5d0595ca464c10e556b/src/routes/notifications/notifications.service.ts#L29
- success = response == null
+ success = isEmpty(response)
} catch (e) {
logError(ErrorCodes._633, e)
}
diff --git a/src/components/settings/PushNotifications/index.tsx b/src/components/settings/PushNotifications/index.tsx
index eeb1a1172..a03d43453 100644
--- a/src/components/settings/PushNotifications/index.tsx
+++ b/src/components/settings/PushNotifications/index.tsx
@@ -9,6 +9,8 @@ import {
Switch,
Divider,
Link as MuiLink,
+ useMediaQuery,
+ useTheme,
} from '@mui/material'
import Link from 'next/link'
import { useState } from 'react'
@@ -27,11 +29,10 @@ import { PUSH_NOTIFICATION_EVENTS } from '@/services/analytics/events/push-notif
import { AppRoutes } from '@/config/routes'
import CheckWallet from '@/components/common/CheckWallet'
import { useIsMac } from '@/hooks/useIsMac'
-import useOnboard from '@/hooks/wallets/useOnboard'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import ExternalLink from '@/components/common/ExternalLink'
import css from './styles.module.css'
+import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
export const PushNotifications = (): ReactElement => {
const { safe, safeLoaded } = useSafeInfo()
@@ -39,7 +40,8 @@ export const PushNotifications = (): ReactElement => {
const isMac = useIsMac()
const [isRegistering, setIsRegistering] = useState(false)
const [isUpdatingIndexedDb, setIsUpdatingIndexedDb] = useState(false)
- const onboard = useOnboard()
+ const theme = useTheme()
+ const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'))
const { updatePreferences, getPreferences, getAllPreferences } = useNotificationPreferences()
const { unregisterSafeNotifications, unregisterDeviceNotifications, registerNotifications } =
@@ -58,18 +60,8 @@ export const PushNotifications = (): ReactElement => {
const shouldShowMacHelper = isMac || IS_DEV
const handleOnChange = async () => {
- if (!onboard) {
- return
- }
-
setIsRegistering(true)
- try {
- await assertWalletChain(onboard, safe.chainId)
- } catch {
- return
- }
-
if (!preferences) {
await registerNotifications({ [safe.chainId]: [safe.address.value] })
trackEvent(PUSH_NOTIFICATION_EVENTS.ENABLE_SAFE)
@@ -126,16 +118,17 @@ export const PushNotifications = (): ReactElement => {
{safeLoaded ? (
<>
+
-
+
{(isOk) => (
{(isOk) => (
-
+
)}
diff --git a/src/components/settings/TransactionGuards/index.tsx b/src/components/settings/TransactionGuards/index.tsx
index e4a4bbb07..470c7f4fc 100644
--- a/src/components/settings/TransactionGuards/index.tsx
+++ b/src/components/settings/TransactionGuards/index.tsx
@@ -26,7 +26,7 @@ const GuardDisplay = ({ guardAddress, chainId }: { guardAddress: string; chainId
return (
-
+
{(isOk) => (
void }): ReactElement => {
/>
-
+ 0}
+ hasExplorer
+ showCopyButton
+ />
diff --git a/src/components/sidebar/Sidebar/index.tsx b/src/components/sidebar/Sidebar/index.tsx
index ac405379c..51655d35a 100644
--- a/src/components/sidebar/Sidebar/index.tsx
+++ b/src/components/sidebar/Sidebar/index.tsx
@@ -5,7 +5,7 @@ import ChevronRight from '@mui/icons-material/ChevronRight'
import ChainIndicator from '@/components/common/ChainIndicator'
import SidebarHeader from '@/components/sidebar/SidebarHeader'
import SidebarNavigation from '@/components/sidebar/SidebarNavigation'
-//import SidebarFooter from '@/components/sidebar/SidebarFooter'
+import SidebarFooter from '@/components/sidebar/SidebarFooter'
import css from './styles.module.css'
import { trackEvent, OVERVIEW_EVENTS } from '@/services/analytics'
@@ -47,7 +47,7 @@ const Sidebar = (): ReactElement => {
{/* What's new + Need help? */}
- {/* */}
+
diff --git a/src/components/sidebar/SidebarFooter/index.tsx b/src/components/sidebar/SidebarFooter/index.tsx
index 31b0486e4..d1f23139a 100644
--- a/src/components/sidebar/SidebarFooter/index.tsx
+++ b/src/components/sidebar/SidebarFooter/index.tsx
@@ -1,5 +1,5 @@
import type { ReactElement } from 'react'
-import { useCallback, useEffect } from 'react'
+import { useEffect } from 'react'
import {
SidebarList,
@@ -9,33 +9,34 @@ import {
} from '@/components/sidebar/SidebarList'
import { BEAMER_SELECTOR, loadBeamer } from '@/services/beamer'
import { useAppDispatch, useAppSelector } from '@/store'
-import { selectCookies, CookieAndTermType } from '@/store/cookiesAndTermsSlice'
+import { CookieAndTermType, hasConsentFor } from '@/store/cookiesAndTermsSlice'
import { openCookieBanner } from '@/store/popupSlice'
import BeamerIcon from '@/public/images/sidebar/whats-new.svg'
-import HelpCenterIcon from '@/public/images/sidebar/help-center.svg'
-import { ListItem } from '@mui/material'
+import { Link, ListItem, SvgIcon, Typography } from '@mui/material'
import DebugToggle from '../DebugToggle'
-import { HELP_CENTER_URL, IS_PRODUCTION } from '@/config/constants'
-import Track from '@/components/common/Track'
-import { OVERVIEW_EVENTS } from '@/services/analytics/events/overview'
+import { HELP_CENTER_URL, IS_PRODUCTION, NEW_SUGGESTION_FORM } from '@/config/constants'
import { useCurrentChain } from '@/hooks/useChains'
+import Track from '@/components/common/Track'
+import { OVERVIEW_EVENTS } from '@/services/analytics'
+import HelpCenterIcon from '@/public/images/sidebar/help-center.svg'
+import darkPalette from '@/components/theme/darkPalette'
+import SuggestionIcon from '@/public/images/sidebar/lightbulb_icon.svg'
+import ProtofireLogo from '@/public/images/protofire-logo.svg'
const SidebarFooter = (): ReactElement => {
const dispatch = useAppDispatch()
- const cookies = useAppSelector(selectCookies)
const chain = useCurrentChain()
-
- const hasBeamerConsent = useCallback(() => cookies[CookieAndTermType.UPDATES], [cookies])
+ const hasBeamerConsent = useAppSelector((state) => hasConsentFor(state, CookieAndTermType.UPDATES))
useEffect(() => {
// Initialise Beamer when consent was previously given
- if (hasBeamerConsent() && chain?.shortName) {
+ if (hasBeamerConsent && chain?.shortName) {
loadBeamer(chain.shortName)
}
}, [hasBeamerConsent, chain?.shortName])
const handleBeamer = () => {
- if (!hasBeamerConsent()) {
+ if (!hasBeamerConsent) {
dispatch(openCookieBanner({ warningKey: CookieAndTermType.UPDATES }))
}
}
@@ -47,20 +48,6 @@ const SidebarFooter = (): ReactElement => {
)}
-
-
-
-
-
-
-
-
- What's new
-
-
-
-
-
@@ -74,7 +61,39 @@ const SidebarFooter = (): ReactElement => {
+ {' '}
+
+
+
+
+
+
+
+ New Features Suggestion?
+
+
+
+
+
+
+ Supported by{' '}
+
+
+ Protofire
+
+
+
+
)
}
diff --git a/src/components/sidebar/SidebarHeader/index.tsx b/src/components/sidebar/SidebarHeader/index.tsx
index 234fc0602..f5684cd49 100644
--- a/src/components/sidebar/SidebarHeader/index.tsx
+++ b/src/components/sidebar/SidebarHeader/index.tsx
@@ -41,7 +41,9 @@ const SafeHeader = (): ReactElement => {
const settings = useAppSelector(selectSettings)
const { ens } = useAddressResolver(safeAddress)
- const addressCopyText = settings.shortName.copy && chain ? `${chain.shortName}:${safeAddress}` : safeAddress
+ const addressCopyText = (
+ settings.shortName.copy && chain ? `${chain.shortName}:${safeAddress}` : safeAddress
+ ).toLowerCase()
const blockExplorerLink = chain ? getBlockExplorerLink(chain, safeAddress) : undefined
@@ -97,7 +99,7 @@ const SafeHeader = (): ReactElement => {
-
+
diff --git a/src/components/sidebar/SidebarList/index.tsx b/src/components/sidebar/SidebarList/index.tsx
index 409275b24..ae5e59742 100644
--- a/src/components/sidebar/SidebarList/index.tsx
+++ b/src/components/sidebar/SidebarList/index.tsx
@@ -18,10 +18,11 @@ export const SidebarList = ({ children, ...rest }: Omit)
export const SidebarListItemButton = ({
href,
children,
+ disabled,
...rest
}: Omit & { href?: LinkProps['href'] }): ReactElement => {
const button = (
-
+
{children}
)
diff --git a/src/components/sidebar/SidebarNavigation/config.tsx b/src/components/sidebar/SidebarNavigation/config.tsx
index efa82d35f..688c77aea 100644
--- a/src/components/sidebar/SidebarNavigation/config.tsx
+++ b/src/components/sidebar/SidebarNavigation/config.tsx
@@ -8,6 +8,7 @@ import ABIcon from '@/public/images/sidebar/address-book.svg'
import AppsIcon from '@/public/images/apps/apps-icon.svg'
import SettingsIcon from '@/public/images/sidebar/settings.svg'
import SwapIcon from '@/public/images/common/swap.svg'
+import StakeIcon from '@/public/images/common/stake.svg'
import { SvgIcon } from '@mui/material'
import { Chip } from '@/components/common/Chip'
@@ -16,6 +17,7 @@ export type NavItem = {
icon?: ReactElement
href: string
tag?: ReactElement
+ disabled?: boolean
}
export const navItems: NavItem[] = [
@@ -33,7 +35,12 @@ export const navItems: NavItem[] = [
label: 'Swap',
icon: ,
href: AppRoutes.swap,
- tag: ,
+ },
+ {
+ label: 'Stake',
+ icon: ,
+ href: AppRoutes.stake,
+ tag: ,
},
{
label: 'Transactions',
diff --git a/src/components/sidebar/SidebarNavigation/index.tsx b/src/components/sidebar/SidebarNavigation/index.tsx
index 1a5632c80..587e47a37 100644
--- a/src/components/sidebar/SidebarNavigation/index.tsx
+++ b/src/components/sidebar/SidebarNavigation/index.tsx
@@ -19,11 +19,19 @@ import { isRouteEnabled } from '@/utils/chains'
import { trackEvent } from '@/services/analytics'
import { SWAP_EVENTS, SWAP_LABELS } from '@/services/analytics/events/swaps'
import { GeoblockingContext } from '@/components/common/GeoblockingProvider'
+import { STAKE_EVENTS, STAKE_LABELS } from '@/services/analytics/events/stake'
const getSubdirectory = (pathname: string): string => {
return pathname.split('/')[1]
}
+const geoBlockedRoutes = [AppRoutes.swap, AppRoutes.stake]
+
+const customSidebarEvents: { [key: string]: { event: any; label: string } } = {
+ [AppRoutes.swap]: { event: SWAP_EVENTS.OPEN_SWAPS, label: SWAP_LABELS.sidebar },
+ [AppRoutes.stake]: { event: STAKE_EVENTS.OPEN_STAKE, label: STAKE_LABELS.sidebar },
+}
+
const Navigation = (): ReactElement => {
const chain = useCurrentChain()
const router = useRouter()
@@ -31,14 +39,14 @@ const Navigation = (): ReactElement => {
const currentSubdirectory = getSubdirectory(router.pathname)
const queueSize = useQueuedTxsLength()
const isBlockedCountry = useContext(GeoblockingContext)
+
const enabledNavItems = useMemo(() => {
return navItems.filter((item) => {
- const enabled = isRouteEnabled(item.href, chain)
-
- if (item.href === AppRoutes.swap && isBlockedCountry) {
+ if (isBlockedCountry && geoBlockedRoutes.includes(item.href)) {
return false
}
- return enabled
+
+ return isRouteEnabled(item.href, chain)
})
}, [chain, isBlockedCountry])
@@ -58,8 +66,9 @@ const Navigation = (): ReactElement => {
}
const handleNavigationClick = (href: string) => {
- if (href === AppRoutes.swap) {
- trackEvent({ ...SWAP_EVENTS.OPEN_SWAPS, label: SWAP_LABELS.sidebar })
+ const eventInfo = customSidebarEvents[href]
+ if (eventInfo) {
+ trackEvent({ ...eventInfo.event, label: eventInfo.label })
}
}
@@ -76,14 +85,15 @@ const Navigation = (): ReactElement => {
return (
handleNavigationClick(item.href)}
+ key={item.href}
>
{item.icon && {item.icon} }
diff --git a/src/components/transactions/BatchExecuteButton/index.tsx b/src/components/transactions/BatchExecuteButton/index.tsx
index 5d795a743..c53db8032 100644
--- a/src/components/transactions/BatchExecuteButton/index.tsx
+++ b/src/components/transactions/BatchExecuteButton/index.tsx
@@ -48,7 +48,7 @@ const BatchExecuteButton = () => {
title={
isDisabled
? 'Batch execution is only available for transactions that have been fully signed and are strictly sequential in Safe Account nonce.'
- : 'All transactions highlighted in light green will be included in the batch execution.'
+ : 'All highlighted transactions will be included in the batch execution.'
}
>
diff --git a/src/components/transactions/HexEncodedData/HexEncodedData.test.tsx b/src/components/transactions/HexEncodedData/HexEncodedData.test.tsx
new file mode 100644
index 000000000..6aa788bcd
--- /dev/null
+++ b/src/components/transactions/HexEncodedData/HexEncodedData.test.tsx
@@ -0,0 +1,39 @@
+import { render } from '@/tests/test-utils'
+import { HexEncodedData } from '.'
+
+const hexData = '0xed2ad31ed00088fc64d00c49774b2fe3fb7fd7db1c2a714700892607b9f77dc1'
+
+describe('HexEncodedData', () => {
+ it('should render the default component markup', () => {
+ const result = render( )
+ const showMoreButton = result.getByTestId('show-more')
+ const tooltipComponent = result.getByLabelText(
+ 'The first 4 bytes determine the contract method that is being called',
+ )
+ const copyButton = result.getByTestId('copy-btn-icon')
+
+ expect(showMoreButton).toBeInTheDocument()
+ expect(showMoreButton).toHaveTextContent('Show more')
+ expect(tooltipComponent).toBeInTheDocument()
+ expect(copyButton).toBeInTheDocument()
+
+ expect(result.container).toMatchSnapshot()
+ })
+
+ it('should not highlight the data if highlight option is false', () => {
+ const result = render(
+ ,
+ )
+
+ expect(result.container.querySelector('b')).not.toBeInTheDocument()
+ expect(result.container).toMatchSnapshot()
+ })
+
+ it('should not cut the text in case the limit option is higher than the provided hexData', () => {
+ const result = render( )
+
+ expect(result.container.querySelector("button[data-testid='show-more']")).not.toBeInTheDocument()
+
+ expect(result.container).toMatchSnapshot()
+ })
+})
diff --git a/src/components/transactions/HexEncodedData/__snapshots__/HexEncodedData.test.tsx.snap b/src/components/transactions/HexEncodedData/__snapshots__/HexEncodedData.test.tsx.snap
new file mode 100644
index 000000000..807c28588
--- /dev/null
+++ b/src/components/transactions/HexEncodedData/__snapshots__/HexEncodedData.test.tsx.snap
@@ -0,0 +1,196 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`HexEncodedData should not cut the text in case the limit option is higher than the provided hexData 1`] = `
+
+
+
+
+ Data (hex-encoded)
+
+
+
+
+
+
+
+
+
+
+
+ 0xed2ad31e
+
+ d00088fc64d00c49774b2fe3fb7fd7db1c2a714700892607b9f77dc1
+
+
+
+
+
+`;
+
+exports[`HexEncodedData should not highlight the data if highlight option is false 1`] = `
+
+
+
+
+ Some arbitrary data
+
+
+
+
+
+
+
+
+
+
+ 0x10238476...
+
+
+ Show
+ more
+
+
+
+
+
+`;
+
+exports[`HexEncodedData should render the default component markup 1`] = `
+
+
+
+
+ Data (hex-encoded)
+
+
+
+
+
+
+
+
+
+
+
+ 0xed2ad31e
+
+ d00088fc64...
+
+
+ Show
+ more
+
+
+
+
+
+`;
diff --git a/src/components/transactions/HexEncodedData/index.tsx b/src/components/transactions/HexEncodedData/index.tsx
index a72ccab00..fe635c5dc 100644
--- a/src/components/transactions/HexEncodedData/index.tsx
+++ b/src/components/transactions/HexEncodedData/index.tsx
@@ -1,16 +1,21 @@
import { shortenText } from '@/utils/formatters'
-import { Box, Link } from '@mui/material'
+import { Box, Link, Tooltip } from '@mui/material'
import type { ReactElement } from 'react'
import { useState } from 'react'
import css from './styles.module.css'
+import CopyButton from '@/components/common/CopyButton'
+import FieldsGrid from '@/components/tx/FieldsGrid'
interface Props {
hexData: string
+ highlightFirstBytes?: boolean
title?: string
limit?: number
}
-export const HexEncodedData = ({ hexData, title, limit = 20 }: Props): ReactElement => {
+const FIRST_BYTES = 10
+
+export const HexEncodedData = ({ hexData, title, highlightFirstBytes = true, limit = 20 }: Props): ReactElement => {
const [showTxData, setShowTxData] = useState(false)
const showExpandBtn = hexData.length > limit
@@ -18,23 +23,34 @@ export const HexEncodedData = ({ hexData, title, limit = 20 }: Props): ReactElem
setShowTxData((val) => !val)
}
- return (
+ const firstBytes = highlightFirstBytes ? (
+
+ {hexData.slice(0, FIRST_BYTES)}
+
+ ) : null
+ const restBytes = highlightFirstBytes ? hexData.slice(FIRST_BYTES) : hexData
+
+ const content = (
- {title && (
-
- {title}:
-
- )}
- {showExpandBtn ? (
- <>
- {showTxData ? hexData : shortenText(hexData, 25)}{' '}
-
+
+
+ <>
+ {firstBytes}
+ {showTxData || !showExpandBtn ? restBytes : shortenText(restBytes, limit - FIRST_BYTES)}{' '}
+ {showExpandBtn && (
+
Show {showTxData ? 'less' : 'more'}
- >
- ) : (
- {hexData}
- )}
+ )}
+ >
)
+
+ return title ? {content} : content
}
diff --git a/src/components/transactions/MaliciousTxWarning/index.tsx b/src/components/transactions/MaliciousTxWarning/index.tsx
index adbb973fb..a77b14d72 100644
--- a/src/components/transactions/MaliciousTxWarning/index.tsx
+++ b/src/components/transactions/MaliciousTxWarning/index.tsx
@@ -3,7 +3,7 @@ import WarningIcon from '@/public/images/notifications/warning.svg'
const MaliciousTxWarning = ({ withTooltip = true }: { withTooltip?: boolean }) => {
return withTooltip ? (
-
+
diff --git a/src/components/transactions/SafeCreationTx/styles.module.css b/src/components/transactions/SafeCreationTx/styles.module.css
index 88c13c650..b34c47f87 100644
--- a/src/components/transactions/SafeCreationTx/styles.module.css
+++ b/src/components/transactions/SafeCreationTx/styles.module.css
@@ -4,5 +4,8 @@
display: flex;
flex-direction: column;
padding-right: 60px; /* to not overlap with the share link */
- border-top: 2px solid var(--color-border-light);
+}
+
+.txSummary {
+ border-top: 1px solid var(--color-border-light);
}
diff --git a/src/components/transactions/SingleTx/index.tsx b/src/components/transactions/SingleTx/index.tsx
index 07f9d33da..c2bd9e52a 100644
--- a/src/components/transactions/SingleTx/index.tsx
+++ b/src/components/transactions/SingleTx/index.tsx
@@ -1,12 +1,11 @@
import ErrorMessage from '@/components/tx/ErrorMessage'
import { useRouter } from 'next/router'
import useSafeInfo from '@/hooks/useSafeInfo'
-import useAsync from '@/hooks/useAsync'
import type { Label, Transaction, TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { LabelValue } from '@safe-global/safe-gateway-typescript-sdk'
-import { getTransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { sameAddress } from '@/utils/addresses'
import type { ReactElement } from 'react'
+import { useEffect } from 'react'
import { makeTxFromDetails } from '@/utils/transactions'
import { TxListGrid } from '@/components/transactions/TxList'
import ExpandableTransactionItem, {
@@ -14,6 +13,9 @@ import ExpandableTransactionItem, {
} from '@/components/transactions/TxListItem/ExpandableTransactionItem'
import GroupLabel from '../GroupLabel'
import { isMultisigDetailedExecutionInfo } from '@/utils/transaction-guards'
+import { useGetTransactionDetailsQuery } from '@/store/gateway'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+import { asError } from '@/services/exceptions/utils'
const SingleTxGrid = ({ txDetails }: { txDetails: TransactionDetails }): ReactElement => {
const tx: Transaction = makeTxFromDetails(txDetails)
@@ -40,25 +42,30 @@ const SingleTx = () => {
const transactionId = Array.isArray(id) ? id[0] : id
const { safe, safeAddress } = useSafeInfo()
- const [txDetails, txDetailsError] = useAsync(
- () => {
- if (!transactionId || !safeAddress) return
-
- return getTransactionDetails(safe.chainId, transactionId).then((details) => {
- // If the transaction is not related to the current safe, throw an error
- if (!sameAddress(details.safeAddress, safeAddress)) {
- return Promise.reject(new Error('Transaction with this id was not found in this Safe Account'))
+ let {
+ data: txDetails,
+ error: txDetailsError,
+ refetch,
+ isUninitialized,
+ } = useGetTransactionDetailsQuery(
+ transactionId && safe.chainId
+ ? {
+ chainId: safe.chainId,
+ txId: transactionId,
}
- return details
- })
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [transactionId, safe.chainId, safe.txQueuedTag, safe.txHistoryTag, safeAddress],
- false,
+ : skipToken,
)
+ useEffect(() => {
+ !isUninitialized && refetch()
+ }, [safe.txHistoryTag, safe.txQueuedTag, safeAddress, refetch, isUninitialized])
+
+ if (txDetails && !sameAddress(txDetails.safeAddress, safeAddress)) {
+ txDetailsError = new Error('Transaction with this id was not found in this Safe Account')
+ }
+
if (txDetailsError) {
- return Failed to load transaction
+ return Failed to load transaction
}
if (txDetails) {
diff --git a/src/components/transactions/TrustedToggle/TrustedToggleButton.tsx b/src/components/transactions/TrustedToggle/TrustedToggleButton.tsx
index c5bc393a8..918d64d91 100644
--- a/src/components/transactions/TrustedToggle/TrustedToggleButton.tsx
+++ b/src/components/transactions/TrustedToggle/TrustedToggleButton.tsx
@@ -10,7 +10,7 @@ const _TrustedToggleButton = ({
}: {
onlyTrusted: boolean
setOnlyTrusted: (on: boolean) => void
- hasDefaultTokenlist: boolean
+ hasDefaultTokenlist?: boolean
}): ReactElement | null => {
const onClick = () => {
setOnlyTrusted(!onlyTrusted)
diff --git a/src/components/transactions/TrustedToggle/index.tsx b/src/components/transactions/TrustedToggle/index.tsx
index 19dc0f980..dfb513fb7 100644
--- a/src/components/transactions/TrustedToggle/index.tsx
+++ b/src/components/transactions/TrustedToggle/index.tsx
@@ -1,13 +1,13 @@
import { useHasFeature } from '@/hooks/useChains'
import { useAppDispatch, useAppSelector } from '@/store'
-import { selectSettings, setshowOnlyTrustedTransactions } from '@/store/settingsSlice'
+import { selectSettings, hideSuspiciousTransactions } from '@/store/settingsSlice'
import { FEATURES } from '@/utils/chains'
import madProps from '@/utils/mad-props'
import _TrustedToggleButton from './TrustedToggleButton'
const useOnlyTrusted = () => {
const userSettings = useAppSelector(selectSettings)
- return userSettings.showOnlyTrustedTransactions || false
+ return userSettings.hideSuspiciousTransactions || false
}
const useHasDefaultTokenList = () => {
@@ -17,7 +17,7 @@ const useHasDefaultTokenList = () => {
const useSetOnlyTrusted = () => {
const dispatch = useAppDispatch()
return (isOn: boolean) => {
- dispatch(setshowOnlyTrustedTransactions(isOn))
+ dispatch(hideSuspiciousTransactions(isOn))
}
}
diff --git a/src/components/transactions/TxDetails/Summary/TxDataRow/index.tsx b/src/components/transactions/TxDetails/Summary/TxDataRow/index.tsx
index 1700365c3..f0b68a02b 100644
--- a/src/components/transactions/TxDetails/Summary/TxDataRow/index.tsx
+++ b/src/components/transactions/TxDetails/Summary/TxDataRow/index.tsx
@@ -2,9 +2,8 @@ import type { ReactElement } from 'react'
import type { AddressEx } from '@safe-global/safe-gateway-typescript-sdk'
import CopyButton from '@/components/common/CopyButton'
import { HexEncodedData } from '@/components/transactions/HexEncodedData'
-import { Typography } from '@mui/material'
+import { Typography, Box } from '@mui/material'
import { dataLength } from 'ethers'
-import css from './styles.module.css'
import EthHashInfo from '@/components/common/EthHashInfo'
import { DataRow } from '@/components/common/Table/DataRow'
@@ -35,13 +34,13 @@ export const generateDataRowValue = (
)
case 'rawData':
return (
-
+
{value ? dataLength(value) : 0} bytes
-
+
)
case 'bytes':
- return
+ return
default:
return {value}
}
diff --git a/src/components/transactions/TxDetails/Summary/TxDataRow/styles.module.css b/src/components/transactions/TxDetails/Summary/TxDataRow/styles.module.css
deleted file mode 100644
index 4c1f50dc3..000000000
--- a/src/components/transactions/TxDetails/Summary/TxDataRow/styles.module.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.rawData {
- display: flex;
- align-items: center;
-}
diff --git a/src/components/transactions/TxDetails/Summary/index.tsx b/src/components/transactions/TxDetails/Summary/index.tsx
index 8ec312f29..090a57e4e 100644
--- a/src/components/transactions/TxDetails/Summary/index.tsx
+++ b/src/components/transactions/TxDetails/Summary/index.tsx
@@ -1,21 +1,23 @@
import type { ReactElement } from 'react'
import React, { useState } from 'react'
-import { Link } from '@mui/material'
+import { Link, Box } from '@mui/material'
import { generateDataRowValue, TxDataRow } from '@/components/transactions/TxDetails/Summary/TxDataRow'
-import { isMultisigDetailedExecutionInfo } from '@/utils/transaction-guards'
+import { isCustomTxInfo, isMultisigDetailedExecutionInfo } from '@/utils/transaction-guards'
import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { Operation } from '@safe-global/safe-gateway-typescript-sdk'
import { dateString } from '@/utils/formatters'
import css from './styles.module.css'
import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
import SafeTxGasForm from '../SafeTxGasForm'
+import DecodedData from '../TxData/DecodedData'
interface Props {
txDetails: TransactionDetails
defaultExpanded?: boolean
+ hideDecodedData?: boolean
}
-const Summary = ({ txDetails, defaultExpanded = false }: Props): ReactElement => {
+const Summary = ({ txDetails, defaultExpanded = false, hideDecodedData = false }: Props): ReactElement => {
const [expanded, setExpanded] = useState(defaultExpanded)
const toggleExpanded = () => {
@@ -30,17 +32,22 @@ const Summary = ({ txDetails, defaultExpanded = false }: Props): ReactElement =>
refundReceiver = detailedExecutionInfo.refundReceiver?.value
}
+ const isCustom = isCustomTxInfo(txDetails.txInfo)
+
return (
<>
-
- {generateDataRowValue(txHash, 'hash', true)}{' '}
-
+ {txHash && (
+
+ {generateDataRowValue(txHash, 'hash', true)}{' '}
+
+ )}
{generateDataRowValue(safeTxHash, 'hash')}
{submittedAt ? dateString(submittedAt) : null}
+
{executedAt && (
{dateString(executedAt)}
@@ -63,7 +70,13 @@ const Summary = ({ txDetails, defaultExpanded = false }: Props): ReactElement =>
)}
{expanded && (
-
+
+ {!isCustom && !hideDecodedData && (
+
+
+
+ )}
+
{`${txData.operation} (${Operation[txData.operation].toLowerCase()})`}
@@ -90,7 +103,7 @@ const Summary = ({ txDetails, defaultExpanded = false }: Props): ReactElement =>
{generateDataRowValue(txData.hexData, 'rawData')}
-
+
)}
>
)}
diff --git a/src/components/transactions/TxDetails/TxData/DecodedData/MethodCall.tsx b/src/components/transactions/TxDetails/TxData/DecodedData/MethodCall.tsx
new file mode 100644
index 000000000..7d78a51b9
--- /dev/null
+++ b/src/components/transactions/TxDetails/TxData/DecodedData/MethodCall.tsx
@@ -0,0 +1,59 @@
+import NamedAddressInfo from '@/components/common/NamedAddressInfo'
+import { Divider } from '@/components/tx/DecodedTx'
+import { Typography } from '@mui/material'
+
+const MethodCall = ({
+ method,
+ contractAddress,
+ contractName,
+ contractLogo,
+}: {
+ method: string
+ contractAddress: string
+ contractName?: string
+ contractLogo?: string
+}) => {
+ return (
+ <>
+
+ Call
+
+ {method}
+ {' '}
+ on
+
+
+
+
+ >
+ )
+}
+
+export default MethodCall
diff --git a/src/components/transactions/TxDetails/TxData/DecodedData/MethodDetails/index.tsx b/src/components/transactions/TxDetails/TxData/DecodedData/MethodDetails/index.tsx
index a4b39a91b..1818c49a5 100644
--- a/src/components/transactions/TxDetails/TxData/DecodedData/MethodDetails/index.tsx
+++ b/src/components/transactions/TxDetails/TxData/DecodedData/MethodDetails/index.tsx
@@ -4,7 +4,6 @@ import { isAddress, isArrayParameter, isByte } from '@/utils/transaction-guards'
import type { AddressEx, DataDecoded } from '@safe-global/safe-gateway-typescript-sdk'
import { Box, Typography } from '@mui/material'
import { Value } from '@/components/transactions/TxDetails/TxData/DecodedData/ValueArray'
-import { camelCaseToSpaces } from '@/utils/formatters'
type MethodDetailsProps = {
data: DataDecoded
@@ -14,10 +13,14 @@ type MethodDetailsProps = {
}
export const MethodDetails = ({ data, addressInfoIndex }: MethodDetailsProps): ReactElement => {
+ if (!data.parameters?.length) {
+ return No parameters
+ }
+
return (
-
- {camelCaseToSpaces(data.method)}
+
+ Parameters
{data.parameters?.map((param, index) => {
@@ -25,8 +28,17 @@ export const MethodDetails = ({ data, addressInfoIndex }: MethodDetailsProps): R
const inlineType = isAddress(param.type) ? 'address' : isByte(param.type) ? 'bytes' : undefined
const addressEx = typeof param.value === 'string' ? addressInfoIndex?.[param.value] : undefined
+ const title = (
+ <>
+ {param.name} {' '}
+
+ {param.type}
+
+ >
+ )
+
return (
-
+
{isArrayValueParam ? (
) : (
diff --git a/src/components/transactions/TxDetails/TxData/DecodedData/Multisend/index.tsx b/src/components/transactions/TxDetails/TxData/DecodedData/Multisend/index.tsx
index 092f12193..0672be131 100644
--- a/src/components/transactions/TxDetails/TxData/DecodedData/Multisend/index.tsx
+++ b/src/components/transactions/TxDetails/TxData/DecodedData/Multisend/index.tsx
@@ -11,7 +11,6 @@ import classnames from 'classnames'
type MultisendProps = {
txData?: TransactionData
- showDelegateCallWarning?: boolean
compact?: boolean
}
@@ -45,11 +44,7 @@ export const MultisendActionsHeader = ({
)
}
-export const Multisend = ({
- txData,
- showDelegateCallWarning = true,
- compact = false,
-}: MultisendProps): ReactElement | null => {
+export const Multisend = ({ txData, compact = false }: MultisendProps): ReactElement | null => {
const [openMap, setOpenMap] = useState>()
const isOpenMapUndefined = openMap == null
@@ -59,13 +54,9 @@ export const Multisend = ({
useEffect(() => {
// Initialise whether each transaction should be expanded or not
if (isOpenMapUndefined && multiSendTransactions) {
- setOpenMap(
- multiSendTransactions.map(({ operation }) => {
- return showDelegateCallWarning ? operation === Operation.DELEGATE : false
- }),
- )
+ setOpenMap(multiSendTransactions.map(({ operation }) => operation === Operation.DELEGATE))
}
- }, [multiSendTransactions, isOpenMapUndefined, showDelegateCallWarning])
+ }, [multiSendTransactions, isOpenMapUndefined])
if (!txData) return null
@@ -105,7 +96,6 @@ export const Multisend = ({
operation,
}}
txData={txData}
- showDelegateCallWarning={showDelegateCallWarning}
actionTitle={`${index + 1}`}
variant={compact ? 'outlined' : 'elevation'}
expanded={openMap?.[index] ?? false}
diff --git a/src/components/transactions/TxDetails/TxData/DecodedData/SingleTxDecoded/index.test.tsx b/src/components/transactions/TxDetails/TxData/DecodedData/SingleTxDecoded/index.test.tsx
index 357ca4fc1..7a6f20a6f 100644
--- a/src/components/transactions/TxDetails/TxData/DecodedData/SingleTxDecoded/index.test.tsx
+++ b/src/components/transactions/TxDetails/TxData/DecodedData/SingleTxDecoded/index.test.tsx
@@ -11,7 +11,6 @@ describe('SingleTxDecoded', () => {
const result = render(
{
const result = render(
{
const result = render(
{
- const chain = useCurrentChain()
+export const SingleTxDecoded = ({ tx, txData, actionTitle, variant, expanded, onChange }: SingleTxDecodedProps) => {
const isNativeTransfer = tx.value !== '0' && (!tx.data || isEmptyHexData(tx.data))
const method = tx.dataDecoded?.method || (isNativeTransfer ? 'native transfer' : 'contract interaction')
- const { decimals } = chain?.nativeCurrency || {}
- const amount = tx.value ? safeFormatUnits(tx.value, decimals) : 0
-
- let details
- if (tx.dataDecoded) {
- details =
- } else if (tx.data) {
- // If data is not decoded in the backend response
- details =
- }
const addressInfo = txData.addressInfoIndex?.[tx.to]
const name = addressInfo?.name
- const isDelegateCall = tx.operation === Operation.DELEGATE && showDelegateCallWarning
- const isSpendingLimitMethod = isSetAllowance(tx.dataDecoded?.method) || isDeleteAllowance(tx.dataDecoded?.method)
+
+ const singleTxData = {
+ to: { value: tx.to },
+ value: tx.value,
+ operation: tx.operation,
+ dataDecoded: tx.dataDecoded,
+ hexData: tx.data ?? undefined,
+ addressInfoIndex: txData.addressInfoIndex,
+ trustedDelegateCallTarget: false, // Nested delegate calls are always untrusted
+ }
return (
@@ -73,26 +48,9 @@ export const SingleTxDecoded = ({
- {/* We always warn of nested delegate calls */}
- {isDelegateCall && }
- {!isSpendingLimitMethod && (
-
- {amount !== '0' && (
-
- )}
-
-
- )}
- {details}
+
+
+
)
diff --git a/src/components/transactions/TxDetails/TxData/DecodedData/ValueArray/index.tsx b/src/components/transactions/TxDetails/TxData/DecodedData/ValueArray/index.tsx
index 408aa7b40..8bc19b7cf 100644
--- a/src/components/transactions/TxDetails/TxData/DecodedData/ValueArray/index.tsx
+++ b/src/components/transactions/TxDetails/TxData/DecodedData/ValueArray/index.tsx
@@ -62,7 +62,7 @@ export const Value = ({ type, value, ...props }: ValueArrayProps): ReactElement
}
const getTextValue = (value: string, key?: string) => {
- return
+ return
}
const getArrayValue = (parentId: string, value: string[], separator?: boolean) => (
diff --git a/src/components/transactions/TxDetails/TxData/DecodedData/index.tsx b/src/components/transactions/TxDetails/TxData/DecodedData/index.tsx
index 156d8b194..37f8735a4 100644
--- a/src/components/transactions/TxDetails/TxData/DecodedData/index.tsx
+++ b/src/components/transactions/TxDetails/TxData/DecodedData/index.tsx
@@ -1,43 +1,77 @@
import type { ReactElement } from 'react'
-import { TokenType, type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
+import { Stack } from '@mui/material'
+import { type AddressEx, TokenType, type TransactionDetails, Operation } from '@safe-global/safe-gateway-typescript-sdk'
import { HexEncodedData } from '@/components/transactions/HexEncodedData'
import { MethodDetails } from '@/components/transactions/TxDetails/TxData/DecodedData/MethodDetails'
import { useCurrentChain } from '@/hooks/useChains'
-import { formatVisualAmount } from '@/utils/formatters'
import SendAmountBlock from '@/components/tx-flow/flows/TokenTransfer/SendAmountBlock'
import { ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants'
import SendToBlock from '@/components/tx/SendToBlock'
-import { Stack } from '@mui/material'
-import { isCustomTxInfo } from '@/utils/transaction-guards'
+import MethodCall from './MethodCall'
+import useSafeAddress from '@/hooks/useSafeAddress'
+import { sameAddress } from '@/utils/addresses'
+import { DelegateCallWarning } from '@/components/transactions/Warning'
interface Props {
txData: TransactionDetails['txData']
- txInfo: TransactionDetails['txInfo']
+ toInfo?: AddressEx
}
-export const DecodedData = ({ txData, txInfo }: Props): ReactElement | null => {
+export const DecodedData = ({ txData, toInfo }: Props): ReactElement | null => {
+ const safeAddress = useSafeAddress()
const chainInfo = useCurrentChain()
+
// nothing to render
- if (!txData) {
- return null
+ if (!txData && !toInfo) return null
+
+ if (!txData && toInfo) {
+ return (
+
+ )
}
+ if (!txData) return null
+
+ const amountInWei = txData.value ?? '0'
+ const isDelegateCall = txData.operation === Operation.DELEGATE
+ const toAddress = toInfo?.value || txData.to.value
+ const method = txData.dataDecoded?.method || ''
+ const addressInfo = txData.addressInfoIndex?.[toAddress]
+ const name = sameAddress(toAddress, safeAddress)
+ ? 'this Safe Account'
+ : addressInfo?.name || toInfo?.name || txData.to.name
+ const avatar = addressInfo?.logoUri || toInfo?.logoUri || txData.to.logoUri
+ const isFallback = !method && !txData?.dataDecoded?.parameters
+
let decodedData = <>>
- if (txData.dataDecoded) {
+ if (txData.dataDecoded && !isFallback) {
decodedData =
} else if (txData.hexData) {
// When no decoded data, display raw hex data
- decodedData =
+ decodedData =
}
- const amount = txData.value ? formatVisualAmount(txData.value, chainInfo?.nativeCurrency.decimals) : '0'
- // we render the decoded data
return (
-
- {amount !== '0' && (
+
+ {isDelegateCall && }
+
+ {method ? (
+
+ ) : (
+
+ )}
+
+ {amountInWei !== '0' && (
{
}}
/>
)}
-
{decodedData}
diff --git a/src/components/transactions/TxDetails/TxData/MultiSendTxInfo/index.tsx b/src/components/transactions/TxDetails/TxData/MultiSendTxInfo/index.tsx
deleted file mode 100644
index 99cd427bc..000000000
--- a/src/components/transactions/TxDetails/TxData/MultiSendTxInfo/index.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { InfoDetails } from '@/components/transactions/InfoDetails'
-import EthHashInfo from '@/components/common/EthHashInfo'
-import { TxDataRow } from '@/components/transactions/TxDetails/Summary/TxDataRow'
-import type { ReactElement } from 'react'
-import React from 'react'
-import type { MultiSend } from '@safe-global/safe-gateway-typescript-sdk'
-
-export const MultiSendTxInfo = ({ txInfo }: { txInfo: MultiSend }): ReactElement => {
- return (
-
-
-
-
- {txInfo?.value}
-
- )
-}
diff --git a/src/components/transactions/TxDetails/TxData/Transfer/index.test.tsx b/src/components/transactions/TxDetails/TxData/Transfer/index.test.tsx
index d90437295..630e6f8f9 100644
--- a/src/components/transactions/TxDetails/TxData/Transfer/index.test.tsx
+++ b/src/components/transactions/TxDetails/TxData/Transfer/index.test.tsx
@@ -56,7 +56,7 @@ describe('TransferTxInfo', () => {
expect(result.getByText('1 TST')).toBeInTheDocument()
expect(result.getByText(recipient)).toBeInTheDocument()
expect(result.queryByText('malicious', { exact: false })).toBeNull()
- expect(result.queryByLabelText('This token is unfamiliar', { exact: false })).toBeNull()
+ expect(result.queryByLabelText('This token isnโt verified on major token lists', { exact: false })).toBeNull()
})
it('incoming tx', () => {
@@ -91,7 +91,7 @@ describe('TransferTxInfo', () => {
expect(result.getByText('12.34 TST')).toBeInTheDocument()
expect(result.getByText(sender)).toBeInTheDocument()
expect(result.queryByText('malicious', { exact: false })).toBeNull()
- expect(result.queryByLabelText('This token is unfamiliar', { exact: false })).toBeNull()
+ expect(result.queryByLabelText('This token isnโt verified on major token lists', { exact: false })).toBeNull()
})
})
@@ -128,7 +128,9 @@ describe('TransferTxInfo', () => {
expect(result.getByText('1 TST')).toBeInTheDocument()
expect(result.getByText(recipient)).toBeInTheDocument()
expect(result.queryByText('malicious', { exact: false })).toBeNull()
- expect(result.getByLabelText('This token is unfamiliar', { exact: false })).toBeInTheDocument()
+ expect(
+ result.getByLabelText('This token isnโt verified on major token lists', { exact: false }),
+ ).toBeInTheDocument()
})
it('incoming tx', () => {
@@ -163,7 +165,9 @@ describe('TransferTxInfo', () => {
expect(result.getByText('12.34 TST')).toBeInTheDocument()
expect(result.getByText(sender)).toBeInTheDocument()
expect(result.queryByText('malicious', { exact: false })).toBeNull()
- expect(result.queryByLabelText('This token is unfamiliar', { exact: false })).toBeInTheDocument()
+ expect(
+ result.queryByLabelText('This token isnโt verified on major token lists', { exact: false }),
+ ).toBeInTheDocument()
})
})
@@ -200,7 +204,7 @@ describe('TransferTxInfo', () => {
expect(result.getByText('1 TST')).toBeInTheDocument()
expect(result.getByText(recipient)).toBeInTheDocument()
expect(result.getByText('malicious', { exact: false })).toBeInTheDocument()
- expect(result.queryByLabelText('This token is unfamiliar', { exact: false })).toBeNull()
+ expect(result.queryByLabelText('This token isnโt verified on major token lists', { exact: false })).toBeNull()
})
it('incoming tx', () => {
@@ -235,7 +239,7 @@ describe('TransferTxInfo', () => {
expect(result.getByText('12.34 TST')).toBeInTheDocument()
expect(result.getByText(sender)).toBeInTheDocument()
expect(result.getByText('malicious', { exact: false })).toBeInTheDocument()
- expect(result.queryByLabelText('This token is unfamiliar', { exact: false })).toBeNull()
+ expect(result.queryByLabelText('This token isnโt verified on major token lists', { exact: false })).toBeNull()
})
it('untrusted and imitation tx', () => {
@@ -270,7 +274,7 @@ describe('TransferTxInfo', () => {
expect(result.getByText('12.34 TST')).toBeInTheDocument()
expect(result.getByText(sender)).toBeInTheDocument()
expect(result.getByText('malicious', { exact: false })).toBeInTheDocument()
- expect(result.queryByLabelText('This token is unfamiliar', { exact: false })).toBeNull()
+ expect(result.queryByLabelText('This token isnโt verified on major token lists', { exact: false })).toBeNull()
})
})
})
diff --git a/src/components/transactions/TxDetails/TxData/index.tsx b/src/components/transactions/TxDetails/TxData/index.tsx
index 060e7656a..000f56357 100644
--- a/src/components/transactions/TxDetails/TxData/index.tsx
+++ b/src/components/transactions/TxDetails/TxData/index.tsx
@@ -1,14 +1,16 @@
import SettingsChangeTxInfo from '@/components/transactions/TxDetails/TxData/SettingsChange'
import type { SpendingLimitMethods } from '@/utils/transaction-guards'
+import { isStakingTxWithdrawInfo } from '@/utils/transaction-guards'
+import { isStakingTxExitInfo } from '@/utils/transaction-guards'
import {
isCancellationTxInfo,
isCustomTxInfo,
- isMultiSendTxInfo,
isMultisigDetailedExecutionInfo,
+ isOrderTxInfo,
isSettingsChangeTxInfo,
isSpendingLimitMethod,
+ isStakingTxDepositInfo,
isSupportedSpendingLimitAddress,
- isSwapOrderTxInfo,
isTransferTxInfo,
} from '@/utils/transaction-guards'
import { SpendingLimits } from '@/components/transactions/TxDetails/TxData/SpendingLimits'
@@ -18,8 +20,10 @@ import RejectionTxInfo from '@/components/transactions/TxDetails/TxData/Rejectio
import DecodedData from '@/components/transactions/TxDetails/TxData/DecodedData'
import TransferTxInfo from '@/components/transactions/TxDetails/TxData/Transfer'
import useChainId from '@/hooks/useChainId'
-import { MultiSendTxInfo } from '@/components/transactions/TxDetails/TxData/MultiSendTxInfo'
-import InteractWith from '@/features/swap/components/SwapTxInfo/interactWith'
+import SwapOrder from '@/features/swap/components/SwapOrder'
+import StakingTxDepositDetails from '@/features/stake/components/StakingTxDepositDetails'
+import StakingTxExitDetails from '@/features/stake/components/StakingTxExitDetails'
+import StakingTxWithdrawDetails from '@/features/stake/components/StakingTxWithdrawDetails'
const TxData = ({
txDetails,
@@ -32,6 +36,23 @@ const TxData = ({
}): ReactElement => {
const chainId = useChainId()
const txInfo = txDetails.txInfo
+ const toInfo = isCustomTxInfo(txDetails.txInfo) ? txDetails.txInfo.to : undefined
+
+ if (isOrderTxInfo(txDetails.txInfo)) {
+ return
+ }
+
+ if (isStakingTxDepositInfo(txDetails.txInfo)) {
+ return
+ }
+
+ if (isStakingTxExitInfo(txDetails.txInfo)) {
+ return
+ }
+
+ if (isStakingTxWithdrawInfo(txDetails.txInfo)) {
+ return
+ }
if (isTransferTxInfo(txInfo)) {
return
@@ -45,20 +66,12 @@ const TxData = ({
return
}
- if (isMultiSendTxInfo(txInfo)) {
- return
- }
-
const method = txDetails.txData?.dataDecoded?.method as SpendingLimitMethods
if (isCustomTxInfo(txInfo) && isSupportedSpendingLimitAddress(txInfo, chainId) && isSpendingLimitMethod(method)) {
return
}
- if (isSwapOrderTxInfo(txInfo)) {
- return
- }
-
- return
+ return
}
export default TxData
diff --git a/src/components/transactions/TxDetails/index.tsx b/src/components/transactions/TxDetails/index.tsx
index c84423d4d..1387362d0 100644
--- a/src/components/transactions/TxDetails/index.tsx
+++ b/src/components/transactions/TxDetails/index.tsx
@@ -1,16 +1,12 @@
-import { POLLING_INTERVAL } from '@/config/constants'
import useIsExpiredSwap from '@/features/swap/hooks/useIsExpiredSwap'
-import useIntervalCounter from '@/hooks/useIntervalCounter'
-import React, { type ReactElement } from 'react'
+import React, { type ReactElement, useEffect } from 'react'
import type { TransactionDetails, TransactionSummary } from '@safe-global/safe-gateway-typescript-sdk'
-import { getTransactionDetails, Operation } from '@safe-global/safe-gateway-typescript-sdk'
import { Box, CircularProgress, Typography } from '@mui/material'
import TxSigners from '@/components/transactions/TxSigners'
import Summary from '@/components/transactions/TxDetails/Summary'
import TxData from '@/components/transactions/TxDetails/TxData'
import useChainId from '@/hooks/useChainId'
-import useAsync from '@/hooks/useAsync'
import {
isAwaitingExecution,
isOrderTxInfo,
@@ -20,10 +16,9 @@ import {
isMultisigExecutionInfo,
isOpenSwapOrder,
isTxQueued,
- isSwapTransferOrderTxInfo,
} from '@/utils/transaction-guards'
import { InfoDetails } from '@/components/transactions/InfoDetails'
-import EthHashInfo from '@/components/common/EthHashInfo'
+import NamedAddressInfo from '@/components/common/NamedAddressInfo'
import css from './styles.module.css'
import ErrorMessage from '@/components/tx/ErrorMessage'
import TxShareLink from '../TxShareLink'
@@ -31,14 +26,16 @@ import { ErrorBoundary } from '@sentry/react'
import ExecuteTxButton from '@/components/transactions/ExecuteTxButton'
import SignTxButton from '@/components/transactions/SignTxButton'
import RejectTxButton from '@/components/transactions/RejectTxButton'
-import { DelegateCallWarning, UnsignedWarning } from '@/components/transactions/Warning'
+import { UnsignedWarning } from '@/components/transactions/Warning'
import Multisend from '@/components/transactions/TxDetails/TxData/DecodedData/Multisend'
import useSafeInfo from '@/hooks/useSafeInfo'
import useIsPending from '@/hooks/useIsPending'
import { isImitation, isTrustedTx } from '@/utils/transactions'
import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
-import { SwapOrder } from '@/features/swap/components/SwapOrder'
+import { useGetTransactionDetailsQuery } from '@/store/gateway'
+import { asError } from '@/services/exceptions/utils'
+import { POLLING_INTERVAL } from '@/config/constants'
export const NOT_AVAILABLE = 'n/a'
@@ -71,18 +68,14 @@ const TxDetailsBlock = ({ txSummary, txDetails }: TxDetailsProps): ReactElement
const expiredSwap = useIsExpiredSwap(txSummary.txInfo)
+ // Module address, name and logoUri
+ const moduleAddress = isModuleExecutionInfo(txSummary.executionInfo) ? txSummary.executionInfo.address : undefined
+ const moduleAddressInfo = moduleAddress ? txDetails.txData?.addressInfoIndex?.[moduleAddress.value] : undefined
+
return (
<>
{/* /Details */}
- {isOrderTxInfo(txDetails.txInfo) && (
-
- Error parsing data
}>
-
-
-
- )}
-
@@ -90,22 +83,17 @@ const TxDetailsBlock = ({ txSummary, txDetails }: TxDetailsProps): ReactElement
Error parsing data
}>
- {isSwapTransferOrderTxInfo(txDetails.txInfo) && (
-
- Error parsing data
}>
-
-
-
- )}
{/* Module information*/}
- {isModuleExecutionInfo(txSummary.executionInfo) && (
+ {moduleAddress && (
-
-
+
{isUntrusted && !isPending && }
-
- {txDetails.txData?.operation === Operation.DELEGATE && (
-
-
-
- )}
@@ -167,21 +149,23 @@ const TxDetails = ({
const chainId = useChainId()
const { safe } = useSafeInfo()
- const [pollCount] = useIntervalCounter(POLLING_INTERVAL)
- const swapPollCount = isOpenSwapOrder(txSummary.txInfo) ? pollCount : 0
-
- const [txDetailsData, error, loading] = useAsync(
- async () => {
- if (txDetails && swapPollCount <= 0) {
- return txDetails
- }
- return getTransactionDetails(chainId, txSummary.id)
+ const {
+ data: txDetailsData,
+ error,
+ isLoading: loading,
+ refetch,
+ isUninitialized,
+ } = useGetTransactionDetailsQuery(
+ { chainId, txId: txSummary.id },
+ {
+ pollingInterval: isOpenSwapOrder(txSummary.txInfo) ? POLLING_INTERVAL : undefined,
},
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [txDetails, chainId, txSummary.id, safe.txQueuedTag, swapPollCount],
- false,
)
+ useEffect(() => {
+ !isUninitialized && refetch()
+ }, [safe.txQueuedTag, refetch, txDetails, isUninitialized])
+
return (
{txDetailsData ? (
@@ -193,7 +177,7 @@ const TxDetails = ({
) : (
error && (
- Couldn't load the transaction details
+ Couldn't load the transaction details
)
)}
diff --git a/src/components/transactions/TxDetails/styles.module.css b/src/components/transactions/TxDetails/styles.module.css
index 21d7b6ae2..5c3bc427d 100644
--- a/src/components/transactions/TxDetails/styles.module.css
+++ b/src/components/transactions/TxDetails/styles.module.css
@@ -22,21 +22,11 @@
.txData,
.txSummary,
.advancedDetails,
-.txModule,
-.swapOrder {
+.txModule {
padding: var(--space-2);
}
-.swapOrderTransfer {
- border-top: 1px solid var(--color-border-light);
- margin-top: var(--space-2);
- margin-left: calc(var(--space-2) * -1);
- margin-right: calc(var(--space-2) * -1);
- padding: var(--space-2);
- padding-top: var(--space-3)
-}
-.txData,
-.swapOrder {
+.txData {
border-bottom: 1px solid var(--color-border-light);
}
@@ -58,9 +48,8 @@
padding: 0 var(--space-1);
}
-.multiSend,
-.swapOrder {
- border-top: 1px solid var(--color-border-light);
+.multiSend {
+ border-bottom: 1px solid var(--color-border-light);
}
.buttons {
diff --git a/src/components/transactions/TxInfo/index.tsx b/src/components/transactions/TxInfo/index.tsx
index 255636526..7d4675e82 100644
--- a/src/components/transactions/TxInfo/index.tsx
+++ b/src/components/transactions/TxInfo/index.tsx
@@ -19,10 +19,18 @@ import {
isNativeTokenTransfer,
isSettingsChangeTxInfo,
isTransferTxInfo,
+ isStakingTxDepositInfo,
+ isStakingTxExitInfo,
+ isStakingTxWithdrawInfo,
} from '@/utils/transaction-guards'
import { ellipsis, shortenAddress } from '@/utils/formatters'
import { useCurrentChain } from '@/hooks/useChains'
import { SwapTx } from '@/features/swap/components/SwapTxInfo/SwapTx'
+import StakingTxExitInfo from '@/features/stake/components/StakingTxExitInfo'
+import StakingTxWithdrawInfo from '@/features/stake/components/StakingTxWithdrawInfo'
+import { Box } from '@mui/material'
+import css from './styles.module.css'
+import StakingTxDepositInfo from '@/features/stake/components/StakingTxDepositInfo'
export const TransferTx = ({
info,
@@ -73,6 +81,7 @@ export const TransferTx = ({
withLogo ? 16 : 100,
)}
value="1"
+ decimals={0}
direction={undefined}
logoUri={withLogo ? transfer?.logoUri : undefined}
fallbackSrc="/images/common/nft-placeholder.png"
@@ -84,18 +93,18 @@ export const TransferTx = ({
}
const CustomTx = ({ info }: { info: Custom }): ReactElement => {
- return <>{info.methodName}>
+ return
{info.methodName}
}
const CreationTx = ({ info }: { info: Creation }): ReactElement => {
- return <>Safe Account created by {shortenAddress(info.creator.value)}>
+ return
Created by {shortenAddress(info.creator.value)}
}
const MultiSendTx = ({ info }: { info: MultiSend }): ReactElement => {
return (
- <>
+
{info.actionCount} {`action${info.actionCount > 1 ? 's' : ''}`}
- >
+
)
}
@@ -104,9 +113,8 @@ const SettingsChangeTx = ({ info }: { info: SettingsChange }): ReactElement => {
info.settingsInfo?.type === SettingsInfoType.ENABLE_MODULE ||
info.settingsInfo?.type === SettingsInfoType.DISABLE_MODULE
) {
- return <>{info.settingsInfo.module.name}>
+ return
{info.settingsInfo.module.name}
}
-
return <>>
}
@@ -123,10 +131,6 @@ const TxInfo = ({ info, ...rest }: { info: TransactionInfo; omitSign?: boolean;
return
}
- if (isCustomTxInfo(info)) {
- return
- }
-
if (isCreationTxInfo(info)) {
return
}
@@ -135,6 +139,22 @@ const TxInfo = ({ info, ...rest }: { info: TransactionInfo; omitSign?: boolean;
return
}
+ if (isStakingTxDepositInfo(info)) {
+ return
+ }
+
+ if (isStakingTxExitInfo(info)) {
+ return
+ }
+
+ if (isStakingTxWithdrawInfo(info)) {
+ return
+ }
+
+ if (isCustomTxInfo(info)) {
+ return
+ }
+
return <>>
}
diff --git a/src/components/transactions/TxInfo/styles.module.css b/src/components/transactions/TxInfo/styles.module.css
new file mode 100644
index 000000000..5e6635d9b
--- /dev/null
+++ b/src/components/transactions/TxInfo/styles.module.css
@@ -0,0 +1,5 @@
+.txInfo {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
diff --git a/src/components/transactions/TxStatusChip/index.tsx b/src/components/transactions/TxStatusChip/index.tsx
index c9bb49c87..339236fb7 100644
--- a/src/components/transactions/TxStatusChip/index.tsx
+++ b/src/components/transactions/TxStatusChip/index.tsx
@@ -1,19 +1,28 @@
import type { ReactElement, ReactNode } from 'react'
import { Typography, Chip } from '@mui/material'
-const TxStatusChip = ({
- children,
- color,
-}: {
+export type TxStatusChipProps = {
children: ReactNode
color?: 'primary' | 'secondary' | 'info' | 'warning' | 'success' | 'error'
-}): ReactElement => {
+}
+
+const TxStatusChip = ({ children, color }: TxStatusChipProps): ReactElement => {
return (
+
{children}
}
diff --git a/src/components/transactions/TxType/index.tsx b/src/components/transactions/TxType/index.tsx
index e246bc189..2c954f0b0 100644
--- a/src/components/transactions/TxType/index.tsx
+++ b/src/components/transactions/TxType/index.tsx
@@ -3,6 +3,7 @@ import type { TransactionSummary } from '@safe-global/safe-gateway-typescript-sd
import { Box } from '@mui/material'
import css from './styles.module.css'
import SafeAppIconCard from '@/components/safe-apps/SafeAppIconCard'
+import { isValidElement } from 'react'
type TxTypeProps = {
tx: TransactionSummary
@@ -13,13 +14,18 @@ const TxType = ({ tx }: TxTypeProps) => {
return (
-
+ {isValidElement(type.icon) ? (
+ type.icon
+ ) : typeof type.icon == 'string' ? (
+
+ ) : null}
+
{type.text}
)
diff --git a/src/components/transactions/Warning/index.tsx b/src/components/transactions/Warning/index.tsx
index d5123dac4..049ab9842 100644
--- a/src/components/transactions/Warning/index.tsx
+++ b/src/components/transactions/Warning/index.tsx
@@ -22,7 +22,7 @@ const Warning = ({
`3px solid ${palette[severity].main} !important` }}
+ sx={{ borderLeft: ({ palette }) => `3px solid ${palette[severity].main} !important`, alignItems: 'center' }}
severity={severity}
icon={ }
>
diff --git a/src/components/tx-flow/common/TxLayout/index.tsx b/src/components/tx-flow/common/TxLayout/index.tsx
index de243b6d8..02d77dcee 100644
--- a/src/components/tx-flow/common/TxLayout/index.tsx
+++ b/src/components/tx-flow/common/TxLayout/index.tsx
@@ -62,7 +62,6 @@ type TxLayoutProps = {
isBatch?: boolean
isReplacement?: boolean
isMessage?: boolean
- isRecovery?: boolean
}
const TxLayout = ({
diff --git a/src/components/tx-flow/common/TxLayout/styles.module.css b/src/components/tx-flow/common/TxLayout/styles.module.css
index f8dfb36df..5344a505c 100644
--- a/src/components/tx-flow/common/TxLayout/styles.module.css
+++ b/src/components/tx-flow/common/TxLayout/styles.module.css
@@ -108,7 +108,7 @@
/* Height of transaction type title */
margin-top: 46px;
}
-@media (max-width: 1199.95px) {
+@media (max-width: 1199px) {
.backButton {
left: 50%;
transform: translateX(-50%);
diff --git a/src/components/tx-flow/common/TxNonce/index.tsx b/src/components/tx-flow/common/TxNonce/index.tsx
index 9fb124ca5..5d3f243e3 100644
--- a/src/components/tx-flow/common/TxNonce/index.tsx
+++ b/src/components/tx-flow/common/TxNonce/index.tsx
@@ -33,11 +33,10 @@ import classNames from 'classnames'
const CustomPopper = function ({
// Don't set width of Popper to that of the field
- style: _,
className,
...props
}: PopperProps) {
- return
+ return
}
const NonceFormHeader = memo(function NonceFormSubheader({ children, ...props }: ListSubheaderProps) {
diff --git a/src/components/tx-flow/flows/ConfirmTx/ConfirmProposedTx.tsx b/src/components/tx-flow/flows/ConfirmTx/ConfirmProposedTx.tsx
index 29f73d5ab..c9e4587cb 100644
--- a/src/components/tx-flow/flows/ConfirmTx/ConfirmProposedTx.tsx
+++ b/src/components/tx-flow/flows/ConfirmTx/ConfirmProposedTx.tsx
@@ -1,11 +1,11 @@
import { type ReactElement, useContext, useEffect } from 'react'
+import { Typography } from '@mui/material'
import type { TransactionSummary } from '@safe-global/safe-gateway-typescript-sdk'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useChainId } from '@/hooks/useChainId'
import useWallet from '@/hooks/wallets/useWallet'
import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm'
import { isExecutable, isMultisigExecutionInfo, isSignableBy } from '@/utils/transaction-guards'
-import { Typography } from '@mui/material'
import { createExistingTx } from '@/services/tx/tx-sender'
import { SafeTxContext } from '../../SafeTxProvider'
@@ -39,8 +39,8 @@ const ConfirmProposedTx = ({ txSummary }: ConfirmProposedTxProps): ReactElement
const text = canSign ? (canExecute ? SIGN_EXECUTE_TEXT : SIGN_TEXT) : EXECUTE_TEXT
return (
-
- {text}
+
+ {text}
)
}
diff --git a/src/components/tx-flow/flows/ExecuteBatch/DecodedTxs.tsx b/src/components/tx-flow/flows/ExecuteBatch/DecodedTxs.tsx
index 1c28414bc..5b2b83f2b 100644
--- a/src/components/tx-flow/flows/ExecuteBatch/DecodedTxs.tsx
+++ b/src/components/tx-flow/flows/ExecuteBatch/DecodedTxs.tsx
@@ -59,7 +59,6 @@ const DecodedTxs = ({ txs }: { txs: TransactionDetails[] | undefined }) => {
}}
txData={transaction.txData}
actionTitle={`${idx + 1}`}
- showDelegateCallWarning={false}
expanded={openMap?.[idx] ?? false}
onChange={onChange}
/>
diff --git a/src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx b/src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx
index 5c3d9bb5f..e5c951588 100644
--- a/src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx
+++ b/src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx
@@ -1,27 +1,23 @@
import useWallet from '@/hooks/wallets/useWallet'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import { CircularProgress, Typography, Button, CardActions, Divider, Alert } from '@mui/material'
import useAsync from '@/hooks/useAsync'
import { FEATURES } from '@/utils/chains'
-import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { getReadOnlyMultiSendCallOnlyContract } from '@/services/contracts/safeContracts'
import { useCurrentChain } from '@/hooks/useChains'
import useSafeInfo from '@/hooks/useSafeInfo'
import { encodeMultiSendData } from '@safe-global/protocol-kit/dist/src/utils/transactions/utils'
import { useState, useMemo, useContext } from 'react'
import type { SyntheticEvent } from 'react'
-import { generateDataRowValue } from '@/components/transactions/TxDetails/Summary/TxDataRow'
import ErrorMessage from '@/components/tx/ErrorMessage'
import { ExecutionMethod, ExecutionMethodSelector } from '@/components/tx/ExecutionMethodSelector'
import DecodedTxs from '@/components/tx-flow/flows/ExecuteBatch/DecodedTxs'
import { TxSimulation } from '@/components/tx/security/tenderly'
-import { WrongChainWarning } from '@/components/tx/WrongChainWarning'
import { useRelaysBySafe } from '@/hooks/useRemainingRelays'
import useOnboard from '@/hooks/wallets/useOnboard'
import { logError, Errors } from '@/services/exceptions'
import { dispatchBatchExecution, dispatchBatchExecutionRelay } from '@/services/tx/tx-sender'
import { hasRemainingRelays } from '@/utils/relaying'
-import { getTxsWithDetails, getMultiSendTxs } from '@/utils/transactions'
+import { getMultiSendTxs } from '@/utils/transactions'
import TxCard from '../../common/TxCard'
import CheckWallet from '@/components/common/CheckWallet'
import type { ExecuteBatchFlowProps } from '.'
@@ -37,8 +33,12 @@ import { trackEvent } from '@/services/analytics'
import { TX_EVENTS, TX_TYPES } from '@/services/analytics/events/transactions'
import { isWalletRejection } from '@/utils/wallets'
import WalletRejectionError from '@/components/tx/SignOrExecuteForm/WalletRejectionError'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import useUserNonce from '@/components/tx/AdvancedParams/useUserNonce'
+import { getLatestSafeVersion } from '@/utils/chains'
+import { HexEncodedData } from '@/components/transactions/HexEncodedData'
+import { useGetMultipleTransactionDetailsQuery } from '@/store/gateway'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
const [isSubmittable, setIsSubmittable] = useState(true)
@@ -53,6 +53,8 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
const userNonce = useUserNonce()
+ const latestSafeVersion = getLatestSafeVersion(chain)
+
const maxFeePerGas = gasPrice?.maxFeePerGas
const maxPriorityFeePerGas = gasPrice?.maxPriorityFeePerGas
@@ -64,15 +66,23 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
const onboard = useOnboard()
const wallet = useWallet()
- const [txsWithDetails, error, loading] = useAsync(() => {
- if (!chain?.chainId) return
- return getTxsWithDetails(params.txs, chain.chainId)
- }, [params.txs, chain?.chainId])
+ const {
+ data: txsWithDetails,
+ error,
+ isLoading: loading,
+ } = useGetMultipleTransactionDetailsQuery(
+ chain?.chainId && params.txs.length
+ ? {
+ chainId: chain.chainId,
+ txIds: params.txs.map((tx) => tx.transaction.id),
+ }
+ : skipToken,
+ )
const [multiSendContract] = useAsync(async () => {
- if (!chain?.chainId || !safe.version) return
- return await getReadOnlyMultiSendCallOnlyContract(chain.chainId, safe.version)
- }, [chain?.chainId, safe.version])
+ if (!safe.version) return
+ return await getReadOnlyMultiSendCallOnlyContract(safe.version)
+ }, [safe.version])
const [multisendContractAddress = ''] = useAsync(async () => {
if (!multiSendContract) return ''
@@ -99,8 +109,6 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
overrides.nonce = userNonce
- await assertWalletChain(onboard, safe.chainId)
-
await dispatchBatchExecution(
txsWithDetails,
multiSendContract,
@@ -109,6 +117,7 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
wallet.address,
safe.address.value,
overrides as Overrides & { nonce: number },
+ safe.nonce,
)
}
@@ -121,7 +130,7 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
multiSendTxData,
safe.chainId,
safe.address.value,
- safe.version ?? LATEST_SAFE_VERSION,
+ safe.version ?? latestSafeVersion,
)
}
@@ -158,20 +167,13 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
This transaction batches a total of {params.txs.length} transactions from your queue into a single Ethereum
transaction. Please check every included transaction carefully, especially if you have rejection transactions,
- and make sure you want to execute all of them. Included transactions are highlighted in green when you hover
- over the execute button.
+ and make sure you want to execute all of them. Included transactions are highlighted when you hover over the
+ execute button.
{multiSendContract && }
- {multiSendTxData && (
-
-
- Data (hex encoded)
-
- {generateDataRowValue(multiSendTxData, 'rawData')}
-
- )}
+ {multiSendTxData && }
@@ -189,7 +191,7 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
-
+
{canRelay ? (
<>
@@ -208,7 +210,7 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
{error && (
-
+
This transaction will most likely fail. To save gas costs, avoid creating the transaction.
)}
@@ -223,7 +225,7 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => {
-
+
{(isOk) => (
item.tokenInfo.address === params.tokenAddress)
const { decimals } = token?.tokenInfo || {}
+ const amountInWei = useMemo(
+ () => safeParseUnits(params.amount, token?.tokenInfo.decimals)?.toString() || '0',
+ [params.amount, token?.tokenInfo.decimals],
+ )
+
const existingSpendingLimit = useMemo(() => {
return spendingLimits.find(
(spendingLimit) =>
@@ -76,7 +81,7 @@ export const ReviewSpendingLimit = ({ params }: { params: NewSpendingLimitFlowPr
return (
{token && (
-
+
{existingAmount && existingAmount !== params.amount && (
<>
-
+
-
+
-
-
-
+
<>
@@ -164,7 +154,7 @@ export function RecoverAccountFlowReview({ params }: { params: RecoverAccountFlo
Error submitting the transaction. Please try again.
)}
-
+
{recovery?.delay !== undefined && (
@@ -179,7 +169,7 @@ export function RecoverAccountFlowReview({ params }: { params: RecoverAccountFlo
-
+
{(isOk) => (
{
+ const { asyncCallback, isLoading, error } = useAsyncCallback(dispatchRecoveryExecution)
+ const wallet = useWallet()
+ const { safe } = useSafeInfo()
+ const { setTxFlow } = useContext(TxModalContext)
+ const { setNonceNeeded } = useContext(SafeTxContext)
+
+ const onFormSubmit = useCallback(
+ async (e: SyntheticEvent) => {
+ e.preventDefault()
+
+ if (!wallet) return
+
+ try {
+ await asyncCallback({
+ provider: wallet.provider,
+ chainId: safe.chainId,
+ args: item.args,
+ delayModifierAddress: item.address,
+ signerAddress: wallet.address,
+ })
+ setTxFlow(undefined)
+ } catch (err) {
+ trackError(Errors._812, err)
+ }
+ },
+ [asyncCallback, setTxFlow, wallet, safe, item.address, item.args],
+ )
+
+ useEffect(() => {
+ setNonceNeeded(false)
+ }, [setNonceNeeded])
+
+ return (
+
+
+
+ )
+}
+
+export default RecoveryAttemptReview
diff --git a/src/components/tx-flow/flows/RecoveryAttempt/index.tsx b/src/components/tx-flow/flows/RecoveryAttempt/index.tsx
new file mode 100644
index 000000000..eda57215d
--- /dev/null
+++ b/src/components/tx-flow/flows/RecoveryAttempt/index.tsx
@@ -0,0 +1,14 @@
+import TxLayout from '@/components/tx-flow/common/TxLayout'
+import SaveAddressIcon from '@/public/images/common/save-address.svg'
+import RecoveryAttemptReview from './RecoveryAttemptReview'
+import type { RecoveryQueueItem } from '@/features/recovery/services/recovery-state'
+
+const RecoveryAttemptFlow = ({ item }: { item: RecoveryQueueItem }) => {
+ return (
+
+
+
+ )
+}
+
+export default RecoveryAttemptFlow
diff --git a/src/components/tx-flow/flows/RejectTx/RejectTx.tsx b/src/components/tx-flow/flows/RejectTx/RejectTx.tsx
index 654e46156..5e47cc5b5 100644
--- a/src/components/tx-flow/flows/RejectTx/RejectTx.tsx
+++ b/src/components/tx-flow/flows/RejectTx/RejectTx.tsx
@@ -19,7 +19,7 @@ const RejectTx = ({ txNonce }: RejectTxProps): ReactElement => {
}, [txNonce, setNonce, setSafeTx, setSafeTxError])
return (
-
+
To reject the transaction, a separate rejection transaction will be created to replace the original one.
diff --git a/src/components/tx-flow/flows/RemoveSpendingLimit/RemoveSpendingLimit.tsx b/src/components/tx-flow/flows/RemoveSpendingLimit/RemoveSpendingLimit.tsx
index c8655047b..4889c4c47 100644
--- a/src/components/tx-flow/flows/RemoveSpendingLimit/RemoveSpendingLimit.tsx
+++ b/src/components/tx-flow/flows/RemoveSpendingLimit/RemoveSpendingLimit.tsx
@@ -10,7 +10,6 @@ import { relativeTime } from '@/utils/date'
import { trackEvent, SETTINGS_EVENTS } from '@/services/analytics'
import useBalances from '@/hooks/useBalances'
import SendAmountBlock from '@/components/tx-flow/flows/TokenTransfer/SendAmountBlock'
-import { safeFormatUnits } from '@/utils/formatters'
import SpendingLimitLabel from '@/components/common/SpendingLimitLabel'
import { createTx } from '@/services/tx/tx-sender'
@@ -24,6 +23,8 @@ export const RemoveSpendingLimit = ({ params }: { params: SpendingLimitState })
const { balances } = useBalances()
const token = balances.items.find((item) => item.tokenInfo.address === params.token.address)
+ const amountInWei = params.amount
+
useEffect(() => {
const spendingLimitAddress = getSpendingLimitModuleAddress(chainId)
@@ -48,13 +49,7 @@ export const RemoveSpendingLimit = ({ params }: { params: SpendingLimitState })
return (
- {token && (
-
- )}
+ {token && }
diff --git a/src/components/tx-flow/flows/SafeAppsTx/ReviewSafeAppsTx.tsx b/src/components/tx-flow/flows/SafeAppsTx/ReviewSafeAppsTx.tsx
index 815195e7b..9b28b681f 100644
--- a/src/components/tx-flow/flows/SafeAppsTx/ReviewSafeAppsTx.tsx
+++ b/src/components/tx-flow/flows/SafeAppsTx/ReviewSafeAppsTx.tsx
@@ -1,5 +1,4 @@
import useWallet from '@/hooks/wallets/useWallet'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import { useContext, useEffect, useMemo } from 'react'
import type { ReactElement } from 'react'
import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
@@ -9,13 +8,11 @@ import { trackSafeAppTxCount } from '@/services/safe-apps/track-app-usage-count'
import { getTxOrigin } from '@/utils/transactions'
import { createMultiSendCallOnlyTx, createTx, dispatchSafeAppsTx } from '@/services/tx/tx-sender'
import useOnboard from '@/hooks/wallets/useOnboard'
-import useSafeInfo from '@/hooks/useSafeInfo'
import useHighlightHiddenTab from '@/hooks/useHighlightHiddenTab'
import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
import { isTxValid } from '@/components/safe-apps/utils'
import ErrorMessage from '@/components/tx/ErrorMessage'
import { asError } from '@/services/exceptions/utils'
-import { SWAP_TITLE } from '@/features/swap/constants'
type ReviewSafeAppsTxProps = {
safeAppsTx: SafeAppsTxParams
@@ -26,7 +23,6 @@ const ReviewSafeAppsTx = ({
safeAppsTx: { txs, requestId, params, appId, app },
onSubmit,
}: ReviewSafeAppsTxProps): ReactElement => {
- const { safe } = useSafeInfo()
const onboard = useOnboard()
const wallet = useWallet()
const { safeTx, setSafeTx, safeTxError, setSafeTxError } = useContext(SafeTxContext)
@@ -56,7 +52,6 @@ const ReviewSafeAppsTx = ({
let safeTxHash = ''
try {
- await assertWalletChain(onboard, safe.chainId)
safeTxHash = await dispatchSafeAppsTx(safeTx, requestId, wallet.provider, txId)
} catch (error) {
setSafeTxError(asError(error))
@@ -69,7 +64,7 @@ const ReviewSafeAppsTx = ({
const error = !isTxValid(txs)
return (
-
+
{error ? (
This Safe App initiated a transaction which cannot be processed. Please get in touch with the developer of
diff --git a/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx b/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx
index c43fd4929..0699137ea 100644
--- a/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx
+++ b/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx
@@ -1,3 +1,4 @@
+import { act } from 'react'
import { extendedSafeInfoBuilder } from '@/tests/builders/safe'
import { hexlify, zeroPadValue, toUtf8Bytes } from 'ethers'
import type { SafeInfo, SafeMessage } from '@safe-global/safe-gateway-typescript-sdk'
@@ -11,9 +12,8 @@ import * as useSafeInfoHook from '@/hooks/useSafeInfo'
import * as useChainsHook from '@/hooks/useChains'
import * as sender from '@/services/safe-messages/safeMsgSender'
import * as onboard from '@/hooks/wallets/useOnboard'
-import * as sdk from '@/services/tx/tx-sender/sdk'
import * as useSafeMessage from '@/hooks/messages/useSafeMessage'
-import { render, act, fireEvent, waitFor } from '@/tests/test-utils'
+import { render, fireEvent, waitFor } from '@/tests/test-utils'
import type { ConnectedWallet } from '@/hooks/wallets/useOnboard'
import type { EIP1193Provider, WalletState, AppState, OnboardAPI } from '@web3-onboard/core'
import { generateSafeMessageHash } from '@/utils/safe-messages'
@@ -102,8 +102,6 @@ describe('SignMessage', () => {
}))
jest.spyOn(useIsWrongChainHook, 'default').mockImplementation(() => false)
-
- jest.spyOn(sdk, 'assertWalletChain').mockImplementation(jest.fn())
})
describe('EIP-191 messages', () => {
@@ -243,7 +241,7 @@ describe('SignMessage', () => {
const button = getByText('Sign')
- await act(() => {
+ act(() => {
fireEvent.click(button)
})
@@ -257,9 +255,11 @@ describe('SignMessage', () => {
)
// Immediately refetches message and displays confirmation
- expect(baseElement).toHaveTextContent('0x0000...0002')
- expect(baseElement).toHaveTextContent('1 of 2')
- expect(baseElement).toHaveTextContent('Confirmation #2')
+ await waitFor(() => {
+ expect(baseElement).toHaveTextContent('0x0000...0002')
+ expect(baseElement).toHaveTextContent('1 of 2')
+ expect(baseElement).toHaveTextContent('Confirmation #2')
+ })
})
it('confirms the message if already proposed', async () => {
@@ -326,7 +326,7 @@ describe('SignMessage', () => {
;(getSafeMessage as jest.Mock).mockResolvedValue(newMsg)
- await act(() => {
+ act(() => {
fireEvent.click(button)
})
@@ -367,14 +367,14 @@ describe('SignMessage', () => {
expect(getByText('Sign')).toBeDisabled()
})
- it('displays an error if connected to the wrong chain', () => {
+ it('displays a network switch warning if connected to the wrong chain', () => {
jest.spyOn(onboard, 'default').mockReturnValue(mockOnboard)
jest.spyOn(useIsSafeOwnerHook, 'default').mockImplementation(() => true)
jest.spyOn(useIsWrongChainHook, 'default').mockImplementation(() => true)
jest.spyOn(useChainsHook, 'useCurrentChain').mockReturnValue(chainBuilder().build())
jest.spyOn(useSafeMessage, 'default').mockImplementation(() => [undefined, jest.fn(), undefined])
- const { getByText } = render(
+ const { getByText, queryByText } = render(
{
/>,
)
- expect(getByText('Wallet network switch')).toBeInTheDocument()
-
- expect(getByText('Sign')).not.toBeDisabled()
+ expect(getByText('Change your wallet network')).toBeInTheDocument()
+ expect(queryByText('Sign')).toBeDisabled()
})
it('displays an error if not an owner', () => {
@@ -495,7 +494,7 @@ describe('SignMessage', () => {
const button = getByText('Sign')
expect(button).not.toBeDisabled()
- await act(() => {
+ act(() => {
fireEvent.click(button)
})
@@ -559,7 +558,7 @@ describe('SignMessage', () => {
expect(button).toBeEnabled()
- await act(() => {
+ act(() => {
fireEvent.click(button)
})
diff --git a/src/components/tx-flow/flows/SignMessage/SignMessage.tsx b/src/components/tx-flow/flows/SignMessage/SignMessage.tsx
index f67a8b9ed..a976e23a7 100644
--- a/src/components/tx-flow/flows/SignMessage/SignMessage.tsx
+++ b/src/components/tx-flow/flows/SignMessage/SignMessage.tsx
@@ -29,7 +29,6 @@ import useSafeMessage from '@/hooks/messages/useSafeMessage'
import useOnboard, { switchWallet } from '@/hooks/wallets/useOnboard'
import { TxModalContext } from '@/components/tx-flow'
import CopyButton from '@/components/common/CopyButton'
-import { WrongChainWarning } from '@/components/tx/WrongChainWarning'
import MsgSigners from '@/components/safe-messages/MsgSigners'
import useDecodedSafeMessage from '@/hooks/messages/useDecodedSafeMessage'
import useSyncSafeMessageSigner from '@/hooks/messages/useSyncSafeMessageSigner'
@@ -43,7 +42,6 @@ import { trackEvent } from '@/services/analytics'
import { TX_EVENTS, TX_TYPES } from '@/services/analytics/events/transactions'
import { SafeTxContext } from '../../SafeTxProvider'
import RiskConfirmationError from '@/components/tx/SignOrExecuteForm/RiskConfirmationError'
-import { Redefine } from '@/components/tx/security/redefine'
import { TxSecurityContext } from '@/components/tx/security/shared/TxSecurityContext'
import { isBlindSigningPayload, isEIP712TypedData } from '@/utils/safe-messages'
import ApprovalEditor from '@/components/tx/ApprovalEditor'
@@ -56,6 +54,9 @@ import { AppRoutes } from '@/config/routes'
import { useRouter } from 'next/router'
import MsgShareLink from '@/components/safe-messages/MsgShareLink'
import LinkIcon from '@/public/images/messages/link.svg'
+import { Blockaid } from '@/components/tx/security/blockaid'
+import CheckWallet from '@/components/common/CheckWallet'
+import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
const createSkeletonMessage = (confirmationsRequired: number): SafeMessage => {
return {
@@ -305,7 +306,7 @@ const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmPr
-
+
}>
SafeMessage details
@@ -315,7 +316,9 @@ const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmPr
-
+
+
+
@@ -358,7 +361,7 @@ const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmPr
/>
)}
-
+
@@ -368,9 +371,13 @@ const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmPr
-
- Sign
-
+
+ {(isOk) => (
+
+ Sign
+
+ )}
+
>
diff --git a/src/components/tx-flow/flows/SignMessage/index.tsx b/src/components/tx-flow/flows/SignMessage/index.tsx
index ac9503ef5..6c608ba33 100644
--- a/src/components/tx-flow/flows/SignMessage/index.tsx
+++ b/src/components/tx-flow/flows/SignMessage/index.tsx
@@ -8,6 +8,8 @@ import SafeAppIconCard from '@/components/safe-apps/SafeAppIconCard'
import { ErrorBoundary } from '@sentry/react'
import { type BaseTransaction } from '@safe-global/safe-apps-sdk'
import { SWAP_TITLE } from '@/features/swap/constants'
+import { STAKE_TITLE } from '@/features/stake/constants'
+import { getStakeTitle } from '@/features/stake/helpers/utils'
const APP_LOGO_FALLBACK_IMAGE = '/images/apps/apps-icon.svg'
const APP_NAME_FALLBACK = 'Sign message'
@@ -26,7 +28,14 @@ export const AppTitle = ({
const appName = name || APP_NAME_FALLBACK
const appLogo = logoUri || APP_LOGO_FALLBACK_IMAGE
- const title = name === SWAP_TITLE ? getSwapTitle(swapParams.tradeType, txs) : appName
+ let title = appName
+ if (name === SWAP_TITLE) {
+ title = getSwapTitle(swapParams.tradeType, txs) || title
+ }
+
+ if (name === STAKE_TITLE) {
+ title = getStakeTitle(txs) || title
+ }
return (
diff --git a/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.test.tsx b/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.test.tsx
index 15ea1e874..4acae50f1 100644
--- a/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.test.tsx
+++ b/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.test.tsx
@@ -2,24 +2,13 @@ import { Methods } from '@safe-global/safe-apps-sdk'
import * as web3 from '@/hooks/wallets/web3'
import * as useSafeInfo from '@/hooks/useSafeInfo'
import { render, screen } from '@/tests/test-utils'
+import * as execThroughRoleHooks from '@/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/hooks'
import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk'
import ReviewSignMessageOnChain from '@/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain'
import { JsonRpcProvider, zeroPadValue } from 'ethers'
import { act } from '@testing-library/react'
-jest.mock('@safe-global/protocol-kit', () => {
- const originalModule = jest.requireActual('@safe-global/protocol-kit')
-
- // Mock class
- class MockEthersAdapter extends originalModule.EthersAdapter {
- getChainId = jest.fn().mockImplementation(() => Promise.resolve(BigInt(1)))
- }
-
- return {
- ...originalModule,
- EthersAdapter: MockEthersAdapter,
- }
-})
+jest.spyOn(execThroughRoleHooks, 'useRoles').mockReturnValue([])
describe('ReviewSignMessageOnChain', () => {
test('can handle messages with EIP712Domain type in the JSON-RPC payload', async () => {
diff --git a/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.tsx b/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.tsx
index 74c54a9fd..1cc055aa1 100644
--- a/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.tsx
+++ b/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.tsx
@@ -1,5 +1,4 @@
import useWallet from '@/hooks/wallets/useWallet'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import type { ReactElement } from 'react'
import { useContext, useEffect, useState } from 'react'
import { useMemo } from 'react'
@@ -14,8 +13,6 @@ import SendFromBlock from '@/components/tx/SendFromBlock'
import { InfoDetails } from '@/components/transactions/InfoDetails'
import EthHashInfo from '@/components/common/EthHashInfo'
import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm'
-import { generateDataRowValue } from '@/components/transactions/TxDetails/Summary/TxDataRow'
-import useChainId from '@/hooks/useChainId'
import { getReadOnlySignMessageLibContract } from '@/services/contracts/safeContracts'
import { DecodedMsg } from '@/components/safe-messages/DecodedMsg'
import CopyButton from '@/components/common/CopyButton'
@@ -31,6 +28,7 @@ import { isEIP712TypedData } from '@/utils/safe-messages'
import ApprovalEditor from '@/components/tx/ApprovalEditor'
import { ErrorBoundary } from '@sentry/react'
import useAsync from '@/hooks/useAsync'
+import { HexEncodedData } from '@/components/transactions/HexEncodedData'
export type SignMessageOnChainProps = {
app?: SafeAppData
@@ -40,7 +38,6 @@ export type SignMessageOnChainProps = {
}
const ReviewSignMessageOnChain = ({ message, method, requestId }: SignMessageOnChainProps): ReactElement => {
- const chainId = useChainId()
const { safe } = useSafeInfo()
const onboard = useOnboard()
const wallet = useWallet()
@@ -51,8 +48,8 @@ const ReviewSignMessageOnChain = ({ message, method, requestId }: SignMessageOnC
const isTypedMessage = method === Methods.signTypedMessage && isEIP712TypedData(message)
const [readOnlySignMessageLibContract] = useAsync(
- async () => getReadOnlySignMessageLibContract(chainId, safe.version),
- [chainId, safe.version],
+ async () => getReadOnlySignMessageLibContract(safe.version),
+ [safe.version],
)
const [signMessageAddress, setSignMessageAddress] = useState('')
@@ -114,7 +111,6 @@ const ReviewSignMessageOnChain = ({ message, method, requestId }: SignMessageOnC
if (!safeTx || !onboard || !wallet) return
try {
- await assertWalletChain(onboard, safe.chainId)
await dispatchSafeAppsTx(safeTx, requestId, wallet.provider)
} catch (error) {
setSafeTxError(asError(error))
@@ -137,10 +133,7 @@ const ReviewSignMessageOnChain = ({ message, method, requestId }: SignMessageOnC
{safeTx && (
-
- Data (hex encoded)
-
- {generateDataRowValue(safeTx.data.data, 'rawData')}
+
)}
diff --git a/src/components/tx-flow/flows/TokenTransfer/ReviewSpendingLimitTx.tsx b/src/components/tx-flow/flows/TokenTransfer/ReviewSpendingLimitTx.tsx
index 2f15e2a6f..d7d830e95 100644
--- a/src/components/tx-flow/flows/TokenTransfer/ReviewSpendingLimitTx.tsx
+++ b/src/components/tx-flow/flows/TokenTransfer/ReviewSpendingLimitTx.tsx
@@ -1,5 +1,4 @@
import useWallet from '@/hooks/wallets/useWallet'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import type { ReactElement, SyntheticEvent } from 'react'
import { useContext, useMemo, useState } from 'react'
import { type BigNumberish, type BytesLike, parseUnits } from 'ethers'
@@ -21,7 +20,6 @@ import { dispatchSpendingLimitTxExecution } from '@/services/tx/tx-sender'
import { getTxOptions } from '@/utils/transactions'
import { MODALS_EVENTS, trackEvent } from '@/services/analytics'
import useOnboard from '@/hooks/wallets/useOnboard'
-import { WrongChainWarning } from '@/components/tx/WrongChainWarning'
import { asError } from '@/services/exceptions/utils'
import TxCard from '@/components/tx-flow/common/TxCard'
import { TxModalContext } from '@/components/tx-flow'
@@ -29,6 +27,9 @@ import { type SubmitCallback } from '@/components/tx/SignOrExecuteForm'
import { TX_EVENTS, TX_TYPES } from '@/services/analytics/events/transactions'
import { isWalletRejection } from '@/utils/wallets'
import { checksumAddress } from '@/utils/addresses'
+import { safeParseUnits } from '@/utils/formatters'
+import CheckWallet from '@/components/common/CheckWallet'
+import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
export type SpendingLimitTxParams = {
safeAddress: string
@@ -60,6 +61,11 @@ const ReviewSpendingLimitTx = ({
const token = balances.items.find((item) => item.tokenInfo.address === params.tokenAddress)
const spendingLimit = useSpendingLimit(token?.tokenInfo)
+ const amountInWei = useMemo(
+ () => safeParseUnits(params.amount, token?.tokenInfo.decimals)?.toString() || '0',
+ [params.amount, token?.tokenInfo.decimals],
+ )
+
const txParams: SpendingLimitTxParams = useMemo(
() => ({
safeAddress,
@@ -98,7 +104,6 @@ const ReviewSpendingLimitTx = ({
const txOptions = getTxOptions(advancedParams, currentChain)
try {
- await assertWalletChain(onboard, safe.chainId)
await dispatchSpendingLimitTxExecution(txParams, txOptions, wallet.provider, safe.chainId, safeAddress)
onSubmit('', true)
setTxFlow(undefined)
@@ -111,10 +116,11 @@ const ReviewSpendingLimitTx = ({
setSubmitError(err)
}
setIsSubmittable(true)
+ return
}
- trackEvent({ ...TX_EVENTS.CREATE, label: TX_TYPES.transfer_token })
- trackEvent({ ...TX_EVENTS.EXECUTE, label: TX_TYPES.transfer_token })
+ trackEvent({ ...TX_EVENTS.CREATE_VIA_SPENDING_LIMTI, label: TX_TYPES.transfer_token })
+ trackEvent({ ...TX_EVENTS.EXECUTE_VIA_SPENDING_LIMIT, label: TX_TYPES.transfer_token })
}
const submitDisabled = !isSubmittable || gasLimitLoading
@@ -128,13 +134,13 @@ const ReviewSpendingLimitTx = ({
Blockchain Explorer.
- {token && }
+ {token && }
-
+
{submitError && (
Error submitting the transaction. Please try again.
@@ -147,9 +153,13 @@ const ReviewSpendingLimitTx = ({
-
- Submit
-
+
+ {(isOk) => (
+
+ Submit
+
+ )}
+
diff --git a/src/components/tx-flow/flows/TokenTransfer/ReviewTokenTransfer.tsx b/src/components/tx-flow/flows/TokenTransfer/ReviewTokenTransfer.tsx
index 5adda6f94..d7ec1e3ea 100644
--- a/src/components/tx-flow/flows/TokenTransfer/ReviewTokenTransfer.tsx
+++ b/src/components/tx-flow/flows/TokenTransfer/ReviewTokenTransfer.tsx
@@ -1,4 +1,4 @@
-import { useContext, useEffect } from 'react'
+import { useContext, useEffect, useMemo } from 'react'
import useBalances from '@/hooks/useBalances'
import SignOrExecuteForm, { type SubmitCallback } from '@/components/tx/SignOrExecuteForm'
import SendAmountBlock from '@/components/tx-flow/flows/TokenTransfer/SendAmountBlock'
@@ -7,6 +7,7 @@ import { createTokenTransferParams } from '@/services/tx/tokenTransferParams'
import { createTx } from '@/services/tx/tx-sender'
import type { TokenTransferParams } from '.'
import { SafeTxContext } from '../../SafeTxProvider'
+import { safeParseUnits } from '@/utils/formatters'
const ReviewTokenTransfer = ({
params,
@@ -21,6 +22,11 @@ const ReviewTokenTransfer = ({
const { balances } = useBalances()
const token = balances.items.find((item) => item.tokenInfo.address === params.tokenAddress)
+ const amountInWei = useMemo(
+ () => safeParseUnits(params.amount, token?.tokenInfo.decimals)?.toString() || '0',
+ [params.amount, token?.tokenInfo.decimals],
+ )
+
useEffect(() => {
if (txNonce !== undefined) {
setNonce(txNonce)
@@ -40,7 +46,7 @@ const ReviewTokenTransfer = ({
return (
- {token && }
+ {token && }
diff --git a/src/components/tx-flow/flows/TokenTransfer/SendAmountBlock.tsx b/src/components/tx-flow/flows/TokenTransfer/SendAmountBlock.tsx
index 9f9a49fa1..4e1e0171b 100644
--- a/src/components/tx-flow/flows/TokenTransfer/SendAmountBlock.tsx
+++ b/src/components/tx-flow/flows/TokenTransfer/SendAmountBlock.tsx
@@ -2,17 +2,17 @@ import { type ReactNode } from 'react'
import { type TokenInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { Box, Typography } from '@mui/material'
import TokenIcon from '@/components/common/TokenIcon'
-import { formatAmountPrecise } from '@/utils/formatNumber'
-import { PSEUDO_APPROVAL_VALUES } from '@/components/tx/ApprovalEditor/utils/approvals'
import FieldsGrid from '@/components/tx/FieldsGrid'
+import { formatVisualAmount } from '@/utils/formatters'
const SendAmountBlock = ({
- amount,
+ amountInWei,
tokenInfo,
children,
- title = 'Send',
+ title = 'Send:',
}: {
- amount: number | string
+ /** Amount in WEI */
+ amountInWei: number | string
tokenInfo: Omit & { logoUri?: string }
children?: ReactNode
title?: string
@@ -26,11 +26,9 @@ const SendAmountBlock = ({
{children}
- {amount === PSEUDO_APPROVAL_VALUES.UNLIMITED ? (
- {PSEUDO_APPROVAL_VALUES.UNLIMITED}
- ) : (
- {formatAmountPrecise(amount, tokenInfo.decimals)}
- )}
+
+ {formatVisualAmount(amountInWei, tokenInfo.decimals, tokenInfo.decimals)}
+
)
diff --git a/src/components/tx-flow/flows/UpdateSafe/UpdateSafeReview.tsx b/src/components/tx-flow/flows/UpdateSafe/UpdateSafeReview.tsx
index 834c26d97..4fe31a6fd 100644
--- a/src/components/tx-flow/flows/UpdateSafe/UpdateSafeReview.tsx
+++ b/src/components/tx-flow/flows/UpdateSafe/UpdateSafeReview.tsx
@@ -2,7 +2,6 @@ import { useContext } from 'react'
import { Typography } from '@mui/material'
import ExternalLink from '@/components/common/ExternalLink'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import { useCurrentChain } from '@/hooks/useChains'
import useSafeInfo from '@/hooks/useSafeInfo'
import { createUpdateSafeTxs } from '@/services/tx/safeUpdateParams'
@@ -10,12 +9,15 @@ import { createMultiSendCallOnlyTx } from '@/services/tx/tx-sender'
import { SafeTxContext } from '../../SafeTxProvider'
import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm'
import useAsync from '@/hooks/useAsync'
+import { getLatestSafeVersion } from '@/utils/chains'
export const UpdateSafeReview = () => {
const { safe, safeLoaded } = useSafeInfo()
const chain = useCurrentChain()
const { setSafeTx, setSafeTxError } = useContext(SafeTxContext)
+ const latestSafeVersion = getLatestSafeVersion(chain)
+
useAsync(async () => {
if (!chain || !safeLoaded) {
return
@@ -33,7 +35,7 @@ export const UpdateSafeReview = () => {
To check details about updates added by this smart contract version please visit{' '}
-
+
latest Safe Account contracts changelog
diff --git a/src/components/tx-flow/flows/UpsertRecovery/UpsertRecoveryFlowSettings.tsx b/src/components/tx-flow/flows/UpsertRecovery/UpsertRecoveryFlowSettings.tsx
index 85b4f01bc..cde340132 100644
--- a/src/components/tx-flow/flows/UpsertRecovery/UpsertRecoveryFlowSettings.tsx
+++ b/src/components/tx-flow/flows/UpsertRecovery/UpsertRecoveryFlowSettings.tsx
@@ -198,7 +198,13 @@ export function UpsertRecoveryFlowSettings({
)}
-
+
Advanced {showAdvanced ? : }
@@ -229,7 +235,14 @@ export function UpsertRecoveryFlowSettings({
// Don't reset value if advanced section is collapsed
shouldUnregister={false}
render={({ field: { ref, ...field } }) => (
-
+
{periods.expiration.map(({ label, value }, index) => (
{label}
diff --git a/src/components/tx-flow/flows/UpsertRecovery/index.tsx b/src/components/tx-flow/flows/UpsertRecovery/index.tsx
index 48339019a..f00004e8d 100644
--- a/src/components/tx-flow/flows/UpsertRecovery/index.tsx
+++ b/src/components/tx-flow/flows/UpsertRecovery/index.tsx
@@ -64,7 +64,6 @@ function UpsertRecoveryFlow({ delayModifier }: { delayModifier?: RecoveryState[n
onBack={prevStep}
hideNonce={isIntro}
hideProgress={isIntro}
- isRecovery={!isIntro}
>
{steps}
diff --git a/src/components/tx-flow/flows/index.ts b/src/components/tx-flow/flows/index.ts
index fd28c11e8..48335b9bb 100644
--- a/src/components/tx-flow/flows/index.ts
+++ b/src/components/tx-flow/flows/index.ts
@@ -25,3 +25,4 @@ export const SuccessScreenFlow = dynamic(() => import('./SuccessScreen'))
export const TokenTransferFlow = dynamic(() => import('./TokenTransfer'))
export const UpdateSafeFlow = dynamic(() => import('./UpdateSafe'))
export const UpsertRecoveryFlow = dynamic(() => import('./UpsertRecovery'))
+export const RecoveryAttemptFlow = dynamic(() => import('./RecoveryAttempt'))
diff --git a/src/components/tx/AdvancedParams/AdvancedParamsForm.tsx b/src/components/tx/AdvancedParams/AdvancedParamsForm.tsx
index 9e1ae1c83..a01ea2c64 100644
--- a/src/components/tx/AdvancedParams/AdvancedParamsForm.tsx
+++ b/src/components/tx/AdvancedParams/AdvancedParamsForm.tsx
@@ -15,7 +15,7 @@ type AdvancedParamsFormProps = {
onSubmit: (params: AdvancedParameters) => void
recommendedGasLimit?: AdvancedParameters['gasLimit']
isExecution: boolean
- isEIP1559: boolean
+ isEIP1559?: boolean
willRelay?: boolean
}
diff --git a/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx b/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx
index a2e473df3..b2e6fd829 100644
--- a/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx
+++ b/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx
@@ -61,7 +61,7 @@ describe('ApprovalEditor', () => {
const result = render( )
- expect(await result.queryByText('Error while decoding approval transactions.')).toBeInTheDocument()
+ expect(result.getByText('Error while decoding approval transactions.')).toBeInTheDocument()
})
it('renders a loading skeleton', async () => {
@@ -70,7 +70,7 @@ describe('ApprovalEditor', () => {
const result = render( )
- expect(await result.queryByTestId('approval-editor-loading')).toBeInTheDocument()
+ expect(result.getByTestId('approval-editor-loading')).toBeInTheDocument()
})
it('renders a read-only view if the transaction contains signatures', async () => {
diff --git a/src/components/tx/ApprovalEditor/ApprovalItem.tsx b/src/components/tx/ApprovalEditor/ApprovalItem.tsx
index 27c077af7..146466849 100644
--- a/src/components/tx/ApprovalEditor/ApprovalItem.tsx
+++ b/src/components/tx/ApprovalEditor/ApprovalItem.tsx
@@ -1,13 +1,15 @@
-import { Box, Stack, Typography } from '@mui/material'
+import TokenIcon from '@/components/common/TokenIcon'
import css from '@/components/tx/ApprovalEditor/styles.module.css'
import type { Approval } from '@/services/security/modules/ApprovalModule'
+import { Box, Stack, Typography } from '@mui/material'
+import { TokenType } from '@safe-global/safe-gateway-typescript-sdk/dist/types/common'
import type { ApprovalInfo } from './hooks/useApprovalInfos'
-import TokenIcon from '@/components/common/TokenIcon'
import { PSEUDO_APPROVAL_VALUES } from './utils/approvals'
import { formatAmountPrecise } from '@/utils/formatNumber'
-export const approvalMethodDescription: Record string> = {
- approve: (symbol: string) => `Set ${symbol} allowance to`,
+export const approvalMethodDescription: Record string> = {
+ approve: (symbol: string, type?: TokenType) =>
+ type === TokenType.ERC721 ? `Allow to transfer ${symbol}` : `Set ${symbol} allowance to`,
increaseAllowance: (symbol: string) => `Increase ${symbol} allowance by`,
Permit2: (symbol: string) => `Give permission to spend ${symbol}`,
}
@@ -15,24 +17,30 @@ export const approvalMethodDescription: Record
method: Approval['method']
}) => {
return (
-
+
- {approvalMethodDescription[method](tokenInfo.symbol ?? '')}
+ {approvalMethodDescription[method](tokenInfo.symbol ?? '', tokenInfo.type)}
{amount === PSEUDO_APPROVAL_VALUES.UNLIMITED ? (
{PSEUDO_APPROVAL_VALUES.UNLIMITED}
) : (
- {formatAmountPrecise(amount, tokenInfo.decimals)}
+
+ {tokenInfo.type === TokenType.ERC20
+ ? formatAmountPrecise(amount, tokenInfo.decimals)
+ : `#${rawAmount.toString()}`}
+
)}
diff --git a/src/components/tx/ApprovalEditor/Approvals.tsx b/src/components/tx/ApprovalEditor/Approvals.tsx
index 0d978291d..d10cd49c6 100644
--- a/src/components/tx/ApprovalEditor/Approvals.tsx
+++ b/src/components/tx/ApprovalEditor/Approvals.tsx
@@ -29,6 +29,7 @@ const Approvals = ({ approvalInfos }: { approvalInfos: ApprovalInfo[] }) => {
spender={tx.spender}
method={tx.method}
amount={tx.amountFormatted}
+ rawAmount={tx.amount}
tokenInfo={tx.tokenInfo}
/>
diff --git a/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts b/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts
index 2e7eff887..4a010e8c8 100644
--- a/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts
+++ b/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts
@@ -1,13 +1,19 @@
import useAsync from '@/hooks/useAsync'
import useBalances from '@/hooks/useBalances'
import { type Approval, ApprovalModule } from '@/services/security/modules/ApprovalModule'
-import { getERC20TokenInfoOnChain, UNLIMITED_APPROVAL_AMOUNT, UNLIMITED_PERMIT2_AMOUNT } from '@/utils/tokens'
+import { sameAddress } from '@/utils/addresses'
+import {
+ getERC20TokenInfoOnChain,
+ getErc721Symbol,
+ isErc721Token,
+ UNLIMITED_APPROVAL_AMOUNT,
+ UNLIMITED_PERMIT2_AMOUNT,
+} from '@/utils/tokens'
import { type SafeTransaction } from '@safe-global/safe-core-sdk-types'
+import { type EIP712TypedData, type TokenInfo, TokenType } from '@safe-global/safe-gateway-typescript-sdk'
import { formatUnits } from 'ethers'
-import { PSEUDO_APPROVAL_VALUES } from '../utils/approvals'
import { useMemo } from 'react'
-import { type EIP712TypedData, type TokenInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import { sameAddress } from '@/utils/addresses'
+import { PSEUDO_APPROVAL_VALUES } from '../utils/approvals'
export type ApprovalInfo = {
tokenInfo: (Omit & { logoUri?: string }) | undefined
@@ -50,7 +56,19 @@ export const useApprovalInfos = (payload: {
)?.tokenInfo
if (!tokenInfo) {
- tokenInfo = await getERC20TokenInfoOnChain(approval.tokenAddress)
+ try {
+ tokenInfo = await getERC20TokenInfoOnChain(approval.tokenAddress)
+ } catch (e) {
+ const isErc721 = await isErc721Token(approval.tokenAddress)
+ const symbol = await getErc721Symbol(approval.tokenAddress)
+
+ tokenInfo = {
+ address: approval.tokenAddress,
+ symbol,
+ decimals: 1, // Doesn't exist for ERC-721 tokens
+ type: isErc721 ? TokenType.ERC721 : TokenType.ERC20,
+ }
+ }
}
const amountFormatted =
diff --git a/src/components/tx/ApprovalEditor/index.tsx b/src/components/tx/ApprovalEditor/index.tsx
index 3f1a3bcf0..41bc1ae2d 100644
--- a/src/components/tx/ApprovalEditor/index.tsx
+++ b/src/components/tx/ApprovalEditor/index.tsx
@@ -1,21 +1,26 @@
import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
+import Approvals from '@/components/tx/ApprovalEditor/Approvals'
import { createMultiSendCallOnlyTx, createTx } from '@/services/tx/tx-sender'
+import { decodeSafeTxToBaseTransactions } from '@/utils/transactions'
import { Alert, Box, Skeleton, Typography } from '@mui/material'
import { type SafeTransaction } from '@safe-global/safe-core-sdk-types'
+import { type EIP712TypedData, TokenType } from '@safe-global/safe-gateway-typescript-sdk'
import { useContext } from 'react'
-import css from './styles.module.css'
import { ApprovalEditorForm } from './ApprovalEditorForm'
-import { updateApprovalTxs } from './utils/approvals'
import { useApprovalInfos } from './hooks/useApprovalInfos'
-import { decodeSafeTxToBaseTransactions } from '@/utils/transactions'
-import Approvals from '@/components/tx/ApprovalEditor/Approvals'
-import { type EIP712TypedData } from '@safe-global/safe-gateway-typescript-sdk'
+import css from './styles.module.css'
+import { updateApprovalTxs } from './utils/approvals'
+
+const Title = ({ isErc721 }: { isErc721: boolean }) => {
+ const title = 'Allow access to tokens?'
+ const subtitle = isErc721
+ ? 'This allows the spender to transfer the specified token.'
+ : 'This allows the spender to spend the specified amount of your tokens.'
-const Title = () => {
return (
- Allow access to tokens?
- This allows the spender to spend the specified amount of your tokens.
+ {title}
+ {subtitle}
)
}
@@ -51,11 +56,15 @@ export const ApprovalEditor = ({
createSafeTx().then(setSafeTx).catch(setSafeTxError)
}
- const isReadOnly = (safeTransaction && safeTransaction.signatures.size > 0) || safeMessage !== undefined
+ const isErc721Approval = !!readableApprovals?.some((approval) => approval.tokenInfo?.type === TokenType.ERC721)
+
+ const isReadOnly =
+ (safeTransaction && safeTransaction.signatures.size > 0) || safeMessage !== undefined || isErc721Approval
return (
-
-
+
+
+
{error ? (
Error while decoding approval transactions.
) : loading || !readableApprovals ? (
diff --git a/src/components/tx/ConfirmationOrder/ConfirmationOrderHeader.tsx b/src/components/tx/ConfirmationOrder/ConfirmationOrderHeader.tsx
new file mode 100644
index 000000000..89b09f0fd
--- /dev/null
+++ b/src/components/tx/ConfirmationOrder/ConfirmationOrderHeader.tsx
@@ -0,0 +1,77 @@
+import { Stack, Box, Typography, SvgIcon } from '@mui/material'
+import EastRoundedIcon from '@mui/icons-material/EastRounded'
+import TokenIcon from '@/components/common/TokenIcon'
+import TokenAmount from '@/components/common/TokenAmount'
+
+export type InfoBlock = {
+ value: string
+ label: string
+ tokenInfo?: {
+ decimals: number
+ symbol: string
+ logoUri?: string | null
+ }
+}
+
+const ConfirmationOrderHeader = ({ blocks, showArrow }: { blocks: [InfoBlock, InfoBlock]; showArrow?: boolean }) => {
+ return (
+
+ {blocks.map((block, index) => (
+
+ {block.tokenInfo && (
+
+
+
+ )}
+
+
+
+ {block.label}
+
+
+
+ {block.tokenInfo ? (
+
+ ) : (
+ block.value
+ )}
+
+
+
+ {showArrow && index === 0 && (
+
+
+
+ )}
+
+ ))}
+
+ )
+}
+
+export default ConfirmationOrderHeader
diff --git a/src/components/tx/ConfirmationOrder/index.tsx b/src/components/tx/ConfirmationOrder/index.tsx
new file mode 100644
index 000000000..33142de06
--- /dev/null
+++ b/src/components/tx/ConfirmationOrder/index.tsx
@@ -0,0 +1,23 @@
+import StrakingConfirmationTx from '@/features/stake/components/StakingConfirmationTx'
+import SwapOrderConfirmationView from '@/features/swap/components/SwapOrderConfirmationView'
+import type useDecodeTx from '@/hooks/useDecodeTx'
+import { isAnyStakingConfirmationView, isAnySwapConfirmationViewOrder } from '@/utils/transaction-guards'
+
+type OrderConfirmationViewProps = {
+ decodedData: ReturnType[0]
+ toAddress: string
+}
+
+const ConfirmationOrder = ({ decodedData, toAddress }: OrderConfirmationViewProps) => {
+ if (isAnySwapConfirmationViewOrder(decodedData)) {
+ return
+ }
+
+ if (isAnyStakingConfirmationView(decodedData)) {
+ return
+ }
+
+ return null
+}
+
+export default ConfirmationOrder
diff --git a/src/components/tx/DecodedTx/HelpTooltip.tsx b/src/components/tx/DecodedTx/HelpTooltip.tsx
new file mode 100644
index 000000000..a979bc26a
--- /dev/null
+++ b/src/components/tx/DecodedTx/HelpTooltip.tsx
@@ -0,0 +1,36 @@
+import { Tooltip, SvgIcon } from '@mui/material'
+import InfoIcon from '@/public/images/notifications/info.svg'
+import ExternalLink from '@/components/common/ExternalLink'
+import { HelpCenterArticle } from '@/config/constants'
+
+const HelpToolTip = () => (
+
+ Learn more about{' '}
+
+ advanced details
+
+ .
+ >
+ }
+ arrow
+ placement="top"
+ >
+
+
+
+
+)
+
+export default HelpToolTip
diff --git a/src/components/tx/DecodedTx/index.test.tsx b/src/components/tx/DecodedTx/index.test.tsx
index b591b4746..078c5c889 100644
--- a/src/components/tx/DecodedTx/index.test.tsx
+++ b/src/components/tx/DecodedTx/index.test.tsx
@@ -2,6 +2,7 @@ import { fireEvent, render } from '@/tests/test-utils'
import { type SafeTransaction } from '@safe-global/safe-core-sdk-types'
import DecodedTx from '.'
import { waitFor } from '@testing-library/react'
+import type { DecodedDataResponse } from '@safe-global/safe-gateway-typescript-sdk'
describe('DecodedTx', () => {
it('should render a native transfer', async () => {
@@ -24,7 +25,7 @@ describe('DecodedTx', () => {
} as SafeTransaction
}
decodedData={{
- method: 'Native token transfer',
+ method: '',
parameters: [
{
name: 'to',
@@ -38,16 +39,57 @@ describe('DecodedTx', () => {
},
],
}}
+ showMethodCall
/>,
)
- fireEvent.click(result.getByText('Transaction details'))
+ expect(result.queryByText('Value:')).toBeInTheDocument()
- expect(result.queryAllByText('Native token transfer').length).toBe(2)
- expect(result.queryByText('to(address):')).toBeInTheDocument()
- expect(result.queryByText('0x3430...7600')).toBeInTheDocument()
- expect(result.queryByText('value(uint256):')).toBeInTheDocument()
- expect(result.queryByText('1000000')).toBeInTheDocument()
+ fireEvent.click(result.getByText('Advanced details'))
+
+ await waitFor(() => {
+ expect(result.queryByText('safeTxGas:')).toBeInTheDocument()
+ expect(result.queryByText('Raw data:')).toBeInTheDocument()
+ })
+ })
+
+ it('should render a transfer with custom data details', async () => {
+ const result = render(
+ ,
+ )
+
+ expect(result.queryByText('Value:')).toBeInTheDocument()
+ expect(result.queryByText('Data (hex-encoded)')).toBeInTheDocument()
+
+ fireEvent.click(result.getByText('Advanced details'))
+
+ await waitFor(() => {
+ expect(result.queryByText('safeTxGas:')).toBeInTheDocument()
+ expect(result.queryByText('Raw data:')).toBeInTheDocument()
+ })
})
it('should render an ERC20 transfer', async () => {
@@ -84,16 +126,20 @@ describe('DecodedTx', () => {
},
],
}}
+ showMethodCall
/>,
)
- fireEvent.click(result.getByText('Transaction details'))
+ fireEvent.click(result.getByText('Advanced details'))
await waitFor(() => {
- expect(result.queryAllByText('transfer').length).toBe(2)
- expect(result.queryByText('to(address):')).toBeInTheDocument()
+ expect(result.queryByText('transfer')).toBeInTheDocument()
+ expect(result.queryByText('Parameters')).toBeInTheDocument()
+ expect(result.queryByText('to')).toBeInTheDocument()
+ expect(result.queryByText('address')).toBeInTheDocument()
expect(result.queryByText('0x474e...78C8')).toBeInTheDocument()
- expect(result.queryByText('value(uint256):')).toBeInTheDocument()
+ expect(result.queryByText('value')).toBeInTheDocument()
+ expect(result.queryByText('uint256')).toBeInTheDocument()
expect(result.queryByText('16745726664999765048')).toBeInTheDocument()
})
})
@@ -181,15 +227,11 @@ describe('DecodedTx', () => {
},
],
}}
+ showMethodCall
/>,
)
- await waitFor(() => {
- expect(result.queryByText('multi Send')).toBeInTheDocument()
- expect(result.queryByText('transactions(bytes):')).toBeInTheDocument()
- expect(result.queryByText('1')).toBeInTheDocument()
- expect(result.queryByText('2')).toBeInTheDocument()
- })
+ expect(result.queryAllByText('safeTransferFrom').length).toBeGreaterThan(1)
})
it('should render a function call without parameters', async () => {
@@ -215,11 +257,12 @@ describe('DecodedTx', () => {
method: 'deposit',
parameters: [],
}}
+ showMethodCall
/>,
)
- fireEvent.click(result.getByText('Transaction details'))
+ fireEvent.click(result.getByText('Advanced details'))
- expect((await result.findAllByText('deposit')).length).toBe(2)
+ expect(result.queryByText('deposit')).toBeInTheDocument()
})
})
diff --git a/src/components/tx/DecodedTx/index.tsx b/src/components/tx/DecodedTx/index.tsx
index e31efca39..6ea7df0e3 100644
--- a/src/components/tx/DecodedTx/index.tsx
+++ b/src/components/tx/DecodedTx/index.tsx
@@ -1,118 +1,97 @@
-import SendToBlock from '@/components/tx/SendToBlock'
-import { useCurrentChain } from '@/hooks/useChains'
-import { isConfirmationViewOrder } from '@/utils/transaction-guards'
import { type SyntheticEvent, type ReactElement, memo } from 'react'
-import {
- Accordion,
- AccordionDetails,
- AccordionSummary,
- Box,
- Skeleton,
- Stack,
- SvgIcon,
- Tooltip,
- Typography,
-} from '@mui/material'
+import { isCustomTxInfo } from '@/utils/transaction-guards'
+import { Accordion, AccordionDetails, AccordionSummary, Box, Skeleton, Stack } from '@mui/material'
import { OperationType, type SafeTransaction } from '@safe-global/safe-core-sdk-types'
import type { DecodedDataResponse } from '@safe-global/safe-gateway-typescript-sdk'
-import {
- getTransactionDetails,
- type TransactionDetails,
- Operation,
- TokenType,
-} from '@safe-global/safe-gateway-typescript-sdk'
+import { Operation } from '@safe-global/safe-gateway-typescript-sdk'
import useChainId from '@/hooks/useChainId'
-import useAsync from '@/hooks/useAsync'
-import { MethodDetails } from '@/components/transactions/TxDetails/TxData/DecodedData/MethodDetails'
import ErrorMessage from '../ErrorMessage'
import Summary, { PartialSummary } from '@/components/transactions/TxDetails/Summary'
import { trackEvent, MODALS_EVENTS } from '@/services/analytics'
import Multisend from '@/components/transactions/TxDetails/TxData/DecodedData/Multisend'
-import InfoIcon from '@/public/images/notifications/info.svg'
-import ExternalLink from '@/components/common/ExternalLink'
-import { HelpCenterArticle } from '@/config/constants'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
+import DecodedData from '@/components/transactions/TxDetails/TxData/DecodedData'
import accordionCss from '@/styles/accordion.module.css'
-import { formatVisualAmount } from '@/utils/formatters'
-import SendAmountBlock from '@/components/tx-flow/flows/TokenTransfer/SendAmountBlock'
-import { ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants'
+import HelpToolTip from './HelpTooltip'
+import { useGetTransactionDetailsQuery } from '@/store/gateway'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+import { asError } from '@/services/exceptions/utils'
type DecodedTxProps = {
tx?: SafeTransaction
txId?: string
showMultisend?: boolean
decodedData?: DecodedDataResponse
- decodedDataError?: Error
- decodedDataLoading?: boolean
- showToBlock?: boolean
+ showMethodCall?: boolean
}
+export const Divider = () => (
+
+)
+
const DecodedTx = ({
tx,
txId,
- showMultisend = true,
decodedData,
- decodedDataError,
- decodedDataLoading = false,
- showToBlock = false,
-}: DecodedTxProps): ReactElement | null => {
+ showMultisend = true,
+ showMethodCall = false,
+}: DecodedTxProps): ReactElement => {
const chainId = useChainId()
- const chain = useCurrentChain()
- const isSwapOrder = isConfirmationViewOrder(decodedData)
-
const isMultisend = !!decodedData?.parameters?.[0]?.valueDecoded
-
- const [txDetails, txDetailsError, txDetailsLoading] = useAsync(() => {
- if (!txId) return
- return getTransactionDetails(chainId, txId)
- }, [chainId, txId])
-
- const addressInfoIndex = txDetails?.txData?.addressInfoIndex
+ const isMethodCallInAdvanced = !showMethodCall || (isMultisend && showMultisend)
+
+ const {
+ data: txDetails,
+ error: txDetailsError,
+ isLoading: txDetailsLoading,
+ } = useGetTransactionDetailsQuery(
+ chainId && txId
+ ? {
+ chainId,
+ txId,
+ }
+ : skipToken,
+ )
const onChangeExpand = (_: SyntheticEvent, expanded: boolean) => {
trackEvent({ ...MODALS_EVENTS.TX_DETAILS, label: expanded ? 'Open' : 'Close' })
}
+ const addressInfoIndex = txDetails?.txData?.addressInfoIndex
- if (!decodedData) return null
+ const txData = {
+ dataDecoded: decodedData,
+ to: { value: tx?.data.to || '' },
+ value: tx?.data.value,
+ hexData: tx?.data.data,
+ operation: tx?.data.operation === OperationType.DelegateCall ? Operation.DELEGATE : Operation.CALL,
+ trustedDelegateCallTarget: txDetails?.txData?.trustedDelegateCallTarget ?? true,
+ addressInfoIndex,
+ }
- const amount = tx?.data.value ? formatVisualAmount(tx.data.value, chain?.nativeCurrency.decimals) : '0'
+ let toInfo = tx && {
+ value: tx.data.to,
+ }
+ if (txDetails && isCustomTxInfo(txDetails.txInfo)) {
+ toInfo = txDetails.txInfo.to
+ }
+
+ const decodedDataBlock =
return (
- {!isSwapOrder && tx && showToBlock && (
- <>
- {amount !== '0' && (
-
- )}
-
- >
- )}
-
- {isMultisend && showMultisend && (
-
-
+ {!isMethodCallInAdvanced && (
+
+ {decodedDataBlock}
)}
+ {isMultisend && showMultisend && }
+
}
className={accordionCss.accordion}
>
- Transaction details
-
- {decodedData
- ? decodedData.method
- : tx?.data.operation === OperationType.DelegateCall
- ? 'Delegate call'
- : ''}
+ Advanced details
+
+
+ {isMethodCallInAdvanced && decodedData?.method}
+ {!showMethodCall && !decodedData?.method && Number(tx?.data.value) > 0 && 'native transfer'}
- {decodedData ? (
-
- ) : decodedDataError ? (
- Failed decoding transaction data
+ {isMethodCallInAdvanced && decodedData?.method && (
+ <>
+ {decodedDataBlock}
+
+ >
+ )}
+
+ {txDetails ? (
+
) : (
- decodedDataLoading &&
+ tx &&
)}
-
-
- Advanced details
-
- We recommend not changing the default values unless necessary.{' '}
-
- Learn more about advanced details
-
- .
- >
- }
- arrow
- placement="top"
- >
-
-
-
-
-
-
- {txDetails ? : tx && }
-
- {txDetailsLoading && }
-
- {txDetailsError && (
- Failed loading all transaction details
- )}
-
+ {txDetailsLoading && }
+
+ {txDetailsError && (
+ Failed loading all transaction details
+ )}
diff --git a/src/components/tx/ErrorMessage/index.tsx b/src/components/tx/ErrorMessage/index.tsx
index 2f16b760c..924e94473 100644
--- a/src/components/tx/ErrorMessage/index.tsx
+++ b/src/components/tx/ErrorMessage/index.tsx
@@ -5,6 +5,8 @@ import WarningIcon from '@/public/images/notifications/warning.svg'
import InfoIcon from '@/public/images/notifications/info.svg'
import css from './styles.module.css'
+const ETHERS_PREFIX = 'could not coalesce error'
+
const ErrorMessage = ({
children,
error,
@@ -46,7 +48,7 @@ const ErrorMessage = ({
{error && showDetails && (
- {error.reason || error.message.slice(0, 300)}
+ {(error.reason || error.message).replace(ETHERS_PREFIX, '').trim().slice(0, 500)}
)}
diff --git a/src/components/tx/ExecutionMethodSelector/index.tsx b/src/components/tx/ExecutionMethodSelector/index.tsx
index da3c52f0e..645829ed1 100644
--- a/src/components/tx/ExecutionMethodSelector/index.tsx
+++ b/src/components/tx/ExecutionMethodSelector/index.tsx
@@ -56,7 +56,7 @@ const _ExecutionMethodSelector = ({
sx={{ flex: 1 }}
value={ExecutionMethod.RELAY}
label={
-
+
Sponsored by
diff --git a/src/components/tx/FieldsGrid/index.tsx b/src/components/tx/FieldsGrid/index.tsx
index 375ed8532..1c58c8325 100644
--- a/src/components/tx/FieldsGrid/index.tsx
+++ b/src/components/tx/FieldsGrid/index.tsx
@@ -1,16 +1,18 @@
import { type ReactNode } from 'react'
import { Grid, Typography } from '@mui/material'
-const FieldsGrid = ({ title, children }: { title: string; children: ReactNode }) => {
+const width = { xl: '25%', lg: '200px', xs: 'auto' }
+const minWidth = { xl: '25%', lg: '200px' }
+const wrap = { flexWrap: { xl: 'nowrap' } }
+
+const FieldsGrid = ({ title, children }: { title: string | ReactNode; children: ReactNode }) => {
return (
-
-
-
- {title}
-
+
+
+ {title}
-
+
{children}
diff --git a/src/components/tx/GasParams/index.tsx b/src/components/tx/GasParams/index.tsx
index 9aaabf6a9..3510cb1d8 100644
--- a/src/components/tx/GasParams/index.tsx
+++ b/src/components/tx/GasParams/index.tsx
@@ -28,7 +28,7 @@ const GasDetail = ({ name, value, isLoading }: { name: string; value: string; is
type GasParamsProps = {
params: AdvancedParameters
isExecution: boolean
- isEIP1559: boolean
+ isEIP1559?: boolean
onEdit?: () => void
gasLimitError?: Error
willRelay?: boolean
diff --git a/src/components/tx/SendToBlock/index.tsx b/src/components/tx/SendToBlock/index.tsx
index af5423f65..51b128a89 100644
--- a/src/components/tx/SendToBlock/index.tsx
+++ b/src/components/tx/SendToBlock/index.tsx
@@ -1,28 +1,31 @@
import { Typography } from '@mui/material'
-import EthHashInfo from '@/components/common/EthHashInfo'
+import NamedAddressInfo from '@/components/common/NamedAddressInfo'
import FieldsGrid from '../FieldsGrid'
const SendToBlock = ({
address,
- title = 'To',
+ title = 'To:',
+ customAvatar,
avatarSize,
name,
}: {
address: string
name?: string
title?: string
+ customAvatar?: string
avatarSize?: number
}) => {
return (
-
diff --git a/src/components/tx/SignOrExecuteForm/DelegateForm.tsx b/src/components/tx/SignOrExecuteForm/DelegateForm.tsx
new file mode 100644
index 000000000..37b222ef9
--- /dev/null
+++ b/src/components/tx/SignOrExecuteForm/DelegateForm.tsx
@@ -0,0 +1,121 @@
+import WalletRejectionError from '@/components/tx/SignOrExecuteForm/WalletRejectionError'
+import { isWalletRejection } from '@/utils/wallets'
+import { type ReactElement, type SyntheticEvent, useContext, useState } from 'react'
+import { Box, Button, CardActions, CircularProgress, Divider } from '@mui/material'
+import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
+import CheckWallet from '@/components/common/CheckWallet'
+import { TxModalContext } from '@/components/tx-flow'
+import commonCss from '@/components/tx-flow/common/styles.module.css'
+import ErrorMessage from '@/components/tx/ErrorMessage'
+import { TxSecurityContext } from '@/components/tx/security/shared/TxSecurityContext'
+import { useTxActions } from '@/components/tx/SignOrExecuteForm/hooks'
+import type { SignOrExecuteProps } from '@/components/tx/SignOrExecuteForm/index'
+import useWallet from '@/hooks/wallets/useWallet'
+import { Errors, trackError } from '@/services/exceptions'
+import { asError } from '@/services/exceptions/utils'
+import madProps from '@/utils/mad-props'
+import Stack from '@mui/system/Stack'
+
+export const DelegateForm = ({
+ safeTx,
+ disableSubmit = false,
+ txActions,
+ txSecurity,
+ onSubmit,
+}: SignOrExecuteProps & {
+ txActions: ReturnType
+ txSecurity: ReturnType
+ safeTx?: SafeTransaction
+}): ReactElement => {
+ // Form state
+ const [isSubmittable, setIsSubmittable] = useState(true)
+ const [isRejectedByUser, setIsRejectedByUser] = useState(false)
+
+ // Hooks
+ const wallet = useWallet()
+ const { signDelegateTx } = txActions
+ const { setTxFlow } = useContext(TxModalContext)
+ const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = txSecurity
+
+ // On modal submit
+ const handleSubmit = async (e: SyntheticEvent) => {
+ e.preventDefault()
+
+ if (needsRiskConfirmation && !isRiskConfirmed) {
+ setIsRiskIgnored(true)
+ return
+ }
+
+ if (!safeTx || !wallet) return
+
+ setIsSubmittable(false)
+ setIsRejectedByUser(false)
+
+ try {
+ const txId = await signDelegateTx(safeTx)
+ onSubmit?.(txId)
+ } catch (_err) {
+ const err = asError(_err)
+ if (isWalletRejection(err)) {
+ setIsRejectedByUser(true)
+ } else {
+ trackError(Errors._805, err)
+ }
+ setIsSubmittable(true)
+ return
+ }
+
+ setTxFlow(undefined)
+ }
+
+ const submitDisabled = !safeTx || !isSubmittable || disableSubmit || (needsRiskConfirmation && !isRiskConfirmed)
+
+ return (
+
+ )
+}
+
+const useTxSecurityContext = () => useContext(TxSecurityContext)
+
+export default madProps(DelegateForm, {
+ txActions: useTxActions,
+ txSecurity: useTxSecurityContext,
+})
diff --git a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx
index 9d4d4f862..61d3acbd3 100644
--- a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx
+++ b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx
@@ -116,7 +116,6 @@ export const ExecuteForm = ({
const walletCanPay = useWalletCanPay({
gasLimit,
maxFeePerGas: advancedParams.maxFeePerGas,
- maxPriorityFeePerGas: advancedParams.maxPriorityFeePerGas,
})
const cannotPropose = !isOwner && !onlyExecute
@@ -188,14 +187,14 @@ export const ExecuteForm = ({
{/* Submit button */}
-
+
{(isOk) => (
{!isSubmittable ? : 'Execute'}
diff --git a/src/components/tx/SignOrExecuteForm/PermissionsCheck/__test__/PermissionsCheck.test.tsx b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/__test__/ExecuteThroughRoleForm.test.tsx
similarity index 63%
rename from src/components/tx/SignOrExecuteForm/PermissionsCheck/__test__/PermissionsCheck.test.tsx
rename to src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/__test__/ExecuteThroughRoleForm.test.tsx
index 814c6faca..ee0382dcd 100644
--- a/src/components/tx/SignOrExecuteForm/PermissionsCheck/__test__/PermissionsCheck.test.tsx
+++ b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/__test__/ExecuteThroughRoleForm.test.tsx
@@ -12,12 +12,23 @@ import * as txSender from '@/services/tx/tx-sender/dispatch'
import { extendedSafeInfoBuilder } from '@/tests/builders/safe'
import { type OnboardAPI } from '@web3-onboard/core'
import { AbiCoder, ZeroAddress, encodeBytes32String } from 'ethers'
-import PermissionsCheck from '..'
+import ExecuteThroughRoleForm from '..'
import * as hooksModule from '../hooks'
import { FEATURES } from '@/utils/chains'
import { chainBuilder } from '@/tests/builders/chains'
import { useHasFeature } from '@/hooks/useChains'
+// Mock fetch
+Object.defineProperty(window, 'fetch', {
+ writable: true,
+ value: jest.fn(() =>
+ Promise.resolve({
+ ok: false,
+ json: () => Promise.resolve({}),
+ }),
+ ),
+})
+
// We assume that CheckWallet always returns true
jest.mock('@/components/common/CheckWallet', () => ({
__esModule: true,
@@ -52,9 +63,23 @@ jest.mock('@/services/transactions', () => ({
getModuleTransactionId: jest.fn(() => 'i1234567890'),
}))
-describe('PermissionsCheck', () => {
+// Mock useGasPrice
+jest.mock('@/hooks/useGasPrice', () => ({
+ __esModule: true,
+ default() {
+ return [
+ {
+ maxFeePerGas: undefined,
+ maxPriorityFeePerGas: undefined,
+ },
+ undefined,
+ false,
+ ]
+ },
+}))
+
+describe('ExecuteThroughRoleForm', () => {
let executeSpy: jest.SpyInstance
- let fetchRolesModMock: jest.SpyInstance
const mockConnectedWalletAddress = (address: string) => {
// Onboard
@@ -84,21 +109,15 @@ describe('PermissionsCheck', () => {
beforeEach(() => {
jest.clearAllMocks()
- ;(useHasFeature as jest.Mock).mockImplementation((feature) => mockChain.features.includes(feature)),
- // Safe info
- jest.spyOn(useSafeInfoHook, 'default').mockImplementation(() => ({
- safe: SAFE_INFO,
- safeAddress: SAFE_INFO.address.value,
- safeError: undefined,
- safeLoading: false,
- safeLoaded: true,
- }))
-
- // Roles mod fetching
-
- // Mock the Roles mod fetching function to return the test roles mod
-
- fetchRolesModMock = jest.spyOn(zodiacRoles, 'fetchRolesMod').mockReturnValue(Promise.resolve(TEST_ROLES_MOD as any))
+ ;(useHasFeature as jest.Mock).mockImplementation((feature) => mockChain.features.includes(feature))
+ // Safe info
+ jest.spyOn(useSafeInfoHook, 'default').mockImplementation(() => ({
+ safe: SAFE_INFO,
+ safeAddress: SAFE_INFO.address.value,
+ safeError: undefined,
+ safeLoading: false,
+ safeLoaded: true,
+ }))
// Mock signing and dispatching the module transaction
executeSpy = jest
@@ -112,46 +131,6 @@ describe('PermissionsCheck', () => {
jest.spyOn(hooksModule, 'pollModuleTransactionId').mockReturnValue(Promise.resolve('i1234567890'))
})
- it('only fetch roles and show the card if the feature is enabled', async () => {
- ;(useHasFeature as jest.Mock).mockImplementation((feature) => feature !== FEATURES.ZODIAC_ROLES)
- mockConnectedWalletAddress(SAFE_INFO.owners[0].value) // connect as safe owner (not a role member)
-
- const safeTx = createMockSafeTransaction({
- to: ZeroAddress,
- data: '0xd0e30db0', // deposit()
- value: AbiCoder.defaultAbiCoder().encode(['uint256'], [123]),
- operation: OperationType.Call,
- })
-
- const { queryByText } = render( )
-
- // the card is not shown
- expect(queryByText('Execute without confirmations')).not.toBeInTheDocument()
-
- expect(fetchRolesModMock).not.toHaveBeenCalled()
- })
-
- it('only shows the card when the user is a member of any role', async () => {
- mockConnectedWalletAddress(SAFE_INFO.owners[0].value) // connect as safe owner (not a role member)
-
- const safeTx = createMockSafeTransaction({
- to: ZeroAddress,
- data: '0xd0e30db0', // deposit()
- value: AbiCoder.defaultAbiCoder().encode(['uint256'], [123]),
- operation: OperationType.Call,
- })
-
- const { queryByText } = render( )
-
- // wait for the Roles mod to be fetched
- await waitFor(() => {
- expect(fetchRolesModMock).toBeCalled()
- })
-
- // the card is not shown
- expect(queryByText('Execute without confirmations')).not.toBeInTheDocument()
- })
-
it('disables the submit button when the call is not allowed and shows the permission check status', async () => {
mockConnectedWalletAddress(MEMBER_ADDRESS)
@@ -162,7 +141,12 @@ describe('PermissionsCheck', () => {
operation: OperationType.Call,
})
- const { findByText, getByText } = render( )
+ const { findByText, getByText } = render(
+ ,
+ )
expect(await findByText('Execute')).toBeDisabled()
expect(
@@ -171,10 +155,10 @@ describe('PermissionsCheck', () => {
),
).toBeInTheDocument()
- expect(getByText('TargetAddressNotAllowed')).toBeInTheDocument()
+ expect(getByText('Role is not allowed to call target address')).toBeInTheDocument()
})
- it('execute the tx when the submit button is clicked', async () => {
+ it('executes the tx when the submit button is clicked', async () => {
mockConnectedWalletAddress(MEMBER_ADDRESS)
const safeTx = createMockSafeTransaction({
@@ -186,7 +170,7 @@ describe('PermissionsCheck', () => {
const onSubmit = jest.fn()
- const { findByText } = render( )
+ const { findByText } = render( )
fireEvent.click(await findByText('Execute'))
@@ -194,7 +178,7 @@ describe('PermissionsCheck', () => {
expect(executeSpy).toHaveBeenCalledWith(
// call to the Roles mod's execTransactionWithRole function
expect.objectContaining({
- to: TEST_ROLES_MOD.address,
+ to: ROLES_MOD_ADDRESS,
data: '0xc6fe8747000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000006574685f7772617070696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004d0e30db000000000000000000000000000000000000000000000000000000000',
value: '0',
}),
@@ -218,54 +202,13 @@ const SAFE_INFO = extendedSafeInfoBuilder().build()
SAFE_INFO.modules = [{ value: ROLES_MOD_ADDRESS }]
SAFE_INFO.chainId = '1'
-const lowercaseSafeAddress = SAFE_INFO.address.value.toLowerCase()
-
const WETH_ADDRESS = '0xfff9976782d46cc05630d1f6ebab18b2324d6b14'
-const { Clearance, ExecutionOptions } = zodiacRoles
-
-const TEST_ROLES_MOD = {
- address: ROLES_MOD_ADDRESS,
- owner: lowercaseSafeAddress,
- avatar: lowercaseSafeAddress,
- target: lowercaseSafeAddress,
- roles: [
- {
- key: ROLE_KEY,
- members: [MEMBER_ADDRESS],
- targets: [
- {
- address: '0xc36442b4a4522e871399cd717abdd847ab11fe88',
- clearance: Clearance.Function,
- executionOptions: ExecutionOptions.None,
- functions: [
- {
- selector: '0x49404b7c',
- wildcarded: false,
- executionOptions: ExecutionOptions.None,
- },
- ],
- },
- {
- address: WETH_ADDRESS, // WETH
- clearance: Clearance.Function,
- executionOptions: ExecutionOptions.None,
- functions: [
- {
- selector: '0x2e1a7d4d', // withdraw(uint256)
- wildcarded: true,
- executionOptions: ExecutionOptions.None,
- },
- {
- selector: '0xd0e30db0', // deposit()
- wildcarded: true,
- executionOptions: ExecutionOptions.Send,
- },
- ],
- },
- ],
- },
- ],
+const TEST_ROLE_OK: hooksModule.Role = {
+ modAddress: ROLES_MOD_ADDRESS,
+ roleKey: ROLE_KEY as `0x${string}`,
+ multiSend: '0x9641d764fc13c8b624c04430c7356c1c7c8102e2',
+ status: zodiacRoles.Status.Ok,
}
/**
diff --git a/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/__test__/hooks.test.ts b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/__test__/hooks.test.ts
new file mode 100644
index 000000000..3e01cdb74
--- /dev/null
+++ b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/__test__/hooks.test.ts
@@ -0,0 +1,228 @@
+import { createMockSafeTransaction } from '@/tests/transactions'
+import { OperationType } from '@safe-global/safe-core-sdk-types'
+import * as zodiacRoles from 'zodiac-roles-deployments'
+import { waitFor, renderHook, mockWeb3Provider } from '@/tests/test-utils'
+
+import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
+import * as useSafeInfoHook from '@/hooks/useSafeInfo'
+import * as wallet from '@/hooks/wallets/useWallet'
+import * as onboardHooks from '@/hooks/wallets/useOnboard'
+import { extendedSafeInfoBuilder } from '@/tests/builders/safe'
+import { type OnboardAPI } from '@web3-onboard/core'
+import { AbiCoder, ZeroAddress, encodeBytes32String } from 'ethers'
+import { FEATURES } from '@/utils/chains'
+import { chainBuilder } from '@/tests/builders/chains'
+import { useHasFeature } from '@/hooks/useChains'
+import { useRoles } from '../hooks'
+
+const mockChain = chainBuilder()
+ // @ts-expect-error - we are using a local FEATURES enum
+ .with({ features: [FEATURES.ZODIAC_ROLES, FEATURES.EIP1559] })
+ .with({ chainId: '1' })
+ .with({ shortName: 'eth' })
+ .with({ chainName: 'Ethereum' })
+ .with({ transactionService: 'https://tx.service.mock' })
+ .build()
+
+// mock useCurrentChain
+jest.mock('@/hooks/useChains', () => ({
+ __esModule: true,
+ ...jest.requireActual('@/hooks/useChains'),
+ useCurrentChain: jest.fn(() => mockChain),
+ useHasFeature: jest.fn(),
+}))
+
+jest.mock('@/hooks/useChainId', () => ({
+ useChainId: jest.fn().mockReturnValue(() => '1'),
+}))
+
+describe('useRoles', () => {
+ let fetchRolesModMock: jest.SpyInstance
+
+ const mockConnectedWalletAddress = (address: string) => {
+ // Onboard
+ jest.spyOn(onboardHooks, 'default').mockReturnValue({
+ setChain: jest.fn(),
+ state: {
+ get: () => ({
+ wallets: [
+ {
+ label: 'MetaMask',
+ accounts: [{ address }],
+ connected: true,
+ chains: [{ id: '1' }],
+ },
+ ],
+ }),
+ },
+ } as unknown as OnboardAPI)
+
+ // Wallet
+ jest.spyOn(wallet, 'default').mockReturnValue({
+ chainId: '1',
+ label: 'MetaMask',
+ address,
+ } as unknown as ConnectedWallet)
+ }
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ ;(useHasFeature as jest.Mock).mockImplementation((feature) => mockChain.features.includes(feature))
+
+ // Safe info
+ jest.spyOn(useSafeInfoHook, 'default').mockImplementation(() => ({
+ safe: SAFE_INFO,
+ safeAddress: SAFE_INFO.address.value,
+ safeError: undefined,
+ safeLoading: false,
+ safeLoaded: true,
+ }))
+
+ mockWeb3Provider([])
+
+ // Mock the Roles mod fetching function to return the test roles mod
+ fetchRolesModMock = jest.spyOn(zodiacRoles, 'fetchRolesMod').mockReturnValue(Promise.resolve(TEST_ROLES_MOD as any))
+ })
+
+ it('only fetches and offers roles if the feature is enabled', async () => {
+ ;(useHasFeature as jest.Mock).mockImplementation((feature) => feature !== FEATURES.ZODIAC_ROLES)
+ mockConnectedWalletAddress(SAFE_INFO.owners[0].value) // connect as safe owner (not a role member)
+
+ const safeTx = createMockSafeTransaction({
+ to: ZeroAddress,
+ data: '0xd0e30db0', // deposit()
+ value: AbiCoder.defaultAbiCoder().encode(['uint256'], [123]),
+ operation: OperationType.Call,
+ })
+
+ const { result } = renderHook(() => useRoles(safeTx))
+
+ // no roles will be offered
+ expect(result.current).toEqual([])
+ // no fetch has been triggered
+ expect(fetchRolesModMock).not.toHaveBeenCalled()
+ })
+
+ it('only offers roles if the user is a member of any role', async () => {
+ mockConnectedWalletAddress(SAFE_INFO.owners[0].value) // connect as safe owner (not a role member)
+
+ const safeTx = createMockSafeTransaction({
+ to: ZeroAddress,
+ data: '0xd0e30db0', // deposit()
+ value: AbiCoder.defaultAbiCoder().encode(['uint256'], [123]),
+ operation: OperationType.Call,
+ })
+
+ const { result } = renderHook(() => useRoles(safeTx))
+
+ // wait for the Roles mod to be fetched & and the cache state update to be propagated
+ await waitFor(() => {
+ expect(fetchRolesModMock).toBeCalled()
+ })
+ await new Promise((resolve) => setTimeout(resolve, 25))
+
+ // no role will be offered
+ expect(result.current).toEqual([])
+ })
+
+ it('reports the role status correctly for allowed calls', async () => {
+ mockConnectedWalletAddress(MEMBER_ADDRESS) // connect as a role member
+
+ const safeTxOk = createMockSafeTransaction({
+ to: WETH_ADDRESS,
+ data: '0xd0e30db0', // deposit()
+ value: '0',
+ operation: OperationType.Call,
+ })
+
+ const { result } = renderHook(() => useRoles(safeTxOk))
+
+ // wait for the Roles mod to be fetched & and the cache state update to be propagated
+ await waitFor(() => {
+ expect(fetchRolesModMock).toBeCalled()
+ })
+ await waitFor(() => expect(result.current).toHaveLength(1))
+
+ expect(result.current[0].status).toBe(zodiacRoles.Status.Ok)
+ })
+
+ it('reports the role status correctly for calls that are not allowed', async () => {
+ mockConnectedWalletAddress(MEMBER_ADDRESS) // connect as a role member
+
+ const safeTxWrongTarget = createMockSafeTransaction({
+ to: ZeroAddress,
+ data: '0xd0e30db0', // deposit()
+ value: '0',
+ operation: OperationType.Call,
+ })
+
+ const { result } = renderHook(() => useRoles(safeTxWrongTarget))
+
+ // wait for the Roles mod to be fetched & and the cache state update to be propagated
+ await waitFor(() => {
+ expect(fetchRolesModMock).toBeCalled()
+ })
+ await waitFor(() => expect(result.current).toHaveLength(1))
+
+ expect(result.current[0].status).toBe(zodiacRoles.Status.TargetAddressNotAllowed)
+ })
+})
+
+const ROLES_MOD_ADDRESS = '0x1234567890000000000000000000000000000000'
+const MEMBER_ADDRESS = '0x1111111110000000000000000000000000000000'
+const ROLE_KEY = encodeBytes32String('eth_wrapping')
+
+const SAFE_INFO = extendedSafeInfoBuilder().build()
+SAFE_INFO.modules = [{ value: ROLES_MOD_ADDRESS }]
+SAFE_INFO.chainId = '1'
+
+const lowercaseSafeAddress = SAFE_INFO.address.value.toLowerCase()
+
+const WETH_ADDRESS = '0xfff9976782d46cc05630d1f6ebab18b2324d6b14'
+
+const { Clearance, ExecutionOptions } = zodiacRoles
+
+const TEST_ROLES_MOD = {
+ address: ROLES_MOD_ADDRESS,
+ owner: lowercaseSafeAddress,
+ avatar: lowercaseSafeAddress,
+ target: lowercaseSafeAddress,
+ multiSendAddresses: ['0x9641d764fc13c8b624c04430c7356c1c7c8102e2'],
+ roles: [
+ {
+ key: ROLE_KEY,
+ members: [MEMBER_ADDRESS],
+ targets: [
+ {
+ address: '0xc36442b4a4522e871399cd717abdd847ab11fe88',
+ clearance: Clearance.Function,
+ executionOptions: ExecutionOptions.None,
+ functions: [
+ {
+ selector: '0x49404b7c',
+ wildcarded: false,
+ executionOptions: ExecutionOptions.None,
+ },
+ ],
+ },
+ {
+ address: WETH_ADDRESS, // WETH
+ clearance: Clearance.Function,
+ executionOptions: ExecutionOptions.None,
+ functions: [
+ {
+ selector: '0x2e1a7d4d', // withdraw(uint256)
+ wildcarded: true,
+ executionOptions: ExecutionOptions.None,
+ },
+ {
+ selector: '0xd0e30db0', // deposit()
+ wildcarded: true,
+ executionOptions: ExecutionOptions.Send,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+}
diff --git a/src/components/tx/SignOrExecuteForm/PermissionsCheck/hooks.ts b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/hooks.ts
similarity index 55%
rename from src/components/tx/SignOrExecuteForm/PermissionsCheck/hooks.ts
rename to src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/hooks.ts
index 6922a9678..c99b10daa 100644
--- a/src/components/tx/SignOrExecuteForm/PermissionsCheck/hooks.ts
+++ b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/hooks.ts
@@ -14,14 +14,52 @@ import {
ExecutionOptions,
Status,
} from 'zodiac-roles-deployments'
-import { OperationType, type Transaction, type MetaTransactionData } from '@safe-global/safe-core-sdk-types'
+import {
+ OperationType,
+ type Transaction,
+ type MetaTransactionData,
+ type SafeTransaction,
+} from '@safe-global/safe-core-sdk-types'
import { type JsonRpcProvider } from 'ethers'
import { KnownContracts, getModuleInstance } from '@gnosis.pm/zodiac'
import useWallet from '@/hooks/wallets/useWallet'
import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
+import { decodeMultiSendTxs } from '@/utils/transactions'
+import { encodeMultiSendData } from '@safe-global/protocol-kit'
+import { Multi_send__factory } from '@/types/contracts'
const ROLES_V2_SUPPORTED_CHAINS = Object.keys(chains)
+const multiSendInterface = Multi_send__factory.createInterface()
+
+/**
+ * Turns a Safe Transaction into a set of meta transactions, unbundling multisend calls
+ */
+export const useMetaTransactions = (safeTx?: SafeTransaction): MetaTransactionData[] => {
+ const safeTxData = safeTx?.data
+ return useMemo(() => {
+ if (!safeTxData) return []
+
+ const metaTx: MetaTransactionData = {
+ to: safeTxData.to,
+ value: safeTxData.value,
+ data: safeTxData.data,
+ operation: safeTxData.operation,
+ }
+
+ if (metaTx.operation === OperationType.DelegateCall) {
+ // try decoding as multisend
+ try {
+ const baseTransactions = decodeMultiSendTxs(metaTx.data)
+ if (baseTransactions.length > 0) {
+ return baseTransactions.map((tx) => ({ ...tx, operation: OperationType.Call }))
+ }
+ } catch (e) {}
+ }
+
+ return [metaTx]
+ }, [safeTxData])
+}
/**
* Returns all Zodiac Roles Modifiers v2 instances that are enabled and correctly configured on this Safe
@@ -52,31 +90,60 @@ export const useRolesMods = () => {
return data
}
+const KNOWN_MULTISEND_ADDRESSES = [
+ '0x38869bf66a61cf6bdb996a6ae40d5853fd43b526', // MultiSend 1.4.1
+ '0xa238cbeb142c10ef7ad8442c6d1f9e89e07e7761', // MultiSend 1.3.0
+ '0x998739bfdaadde7c933b942a68053933098f9eda', // MultiSend 1.3.0 alternative
+ '0x8d29be29923b68abfdd21e541b9374737b49cdad', // MultiSend 1.1.1
+]
+const KNOWN_MULTISEND_CALL_ONLY_ADDRESSES = [
+ '0x9641d764fc13c8b624c04430c7356c1c7c8102e2', // MultiSendCallOnly 1.4.1
+ '0x40a2accbd92bca938b02010e17a5b8929b49130d', // MultiSendCallOnly 1.3.0
+ '0xa1dabef33b3b82c7814b6d82a79e50f4ac44102b', // MultiSendCallOnly 1.3.0 alternative
+]
+
+export interface Role {
+ modAddress: `0x${string}`
+ roleKey: `0x${string}`
+ multiSend?: `0x${string}`
+ status: Status | null
+}
+
/**
* Returns a list of roles mod address + role key assigned to the connected wallet.
* For each role, checks if the role allows the given meta transaction and returns the status.
*/
-export const useRoles = (metaTx?: MetaTransactionData) => {
+export const useRoles = (safeTx?: SafeTransaction) => {
+ const metaTransactions = useMetaTransactions(safeTx)
const rolesMods = useRolesMods()
const wallet = useWallet()
const walletAddress = wallet?.address.toLowerCase() as undefined | `0x${string}`
// find all roles assigned to the connected wallet, statically check if they allow the given meta transaction
const potentialRoles = useMemo(() => {
- const result: {
- modAddress: `0x${string}`
- roleKey: `0x${string}`
- status: Status | null
- }[] = []
+ const result: Role[] = []
+ if (metaTransactions.length === 0) return result
if (walletAddress && rolesMods) {
for (const rolesMod of rolesMods) {
+ const multiSend = rolesMod.multiSendAddresses.find((addr) => KNOWN_MULTISEND_ADDRESSES.includes(addr))
+ const multiSendCallOnly = rolesMod.multiSendAddresses.find((addr) =>
+ KNOWN_MULTISEND_CALL_ONLY_ADDRESSES.includes(addr),
+ )
+
for (const role of rolesMod.roles) {
if (role.members.includes(walletAddress)) {
+ const statuses = metaTransactions.map((metaTx) => checkPermissions(role, metaTx))
result.push({
modAddress: rolesMod.address,
roleKey: role.key,
- status: metaTx ? checkTransaction(role, metaTx) : null,
+ multiSend: metaTransactions.some((metaTx) => metaTx.operation === OperationType.DelegateCall)
+ ? multiSend
+ : multiSendCallOnly,
+ status:
+ statuses.find((status) => status !== Status.Ok && status !== null) ||
+ statuses.find((status) => status !== Status.Ok) ||
+ Status.Ok,
})
}
}
@@ -84,31 +151,39 @@ export const useRoles = (metaTx?: MetaTransactionData) => {
}
return result
- }, [rolesMods, walletAddress, metaTx])
+ }, [rolesMods, walletAddress, metaTransactions])
const web3ReadOnly = useWeb3ReadOnly()
// if the static check is inconclusive (status: null), evaluate the condition through a test call
const [dynamicallyCheckedPotentialRoles] = useAsync(
() =>
Promise.all(
- potentialRoles.map(async (entry) => {
- if (entry.status === null && metaTx && walletAddress && web3ReadOnly) {
- entry.status = await checkCondition(entry.modAddress, entry.roleKey, metaTx, walletAddress, web3ReadOnly)
+ potentialRoles.map(async (role: Role) => {
+ if (role.status === null && walletAddress && web3ReadOnly) {
+ role.status = await checkCondition(role, metaTransactions, walletAddress, web3ReadOnly)
}
- return entry
+ return role
}),
),
- [potentialRoles, metaTx, walletAddress, web3ReadOnly],
+ [potentialRoles, metaTransactions, walletAddress, web3ReadOnly],
)
// Return the statically checked roles while the dynamic checks are still pending
return dynamicallyCheckedPotentialRoles || potentialRoles
}
+export const findAllowingRole = (roles: Role[]): Role | undefined => roles.find((role) => role.status === Status.Ok)
+
+export const findMostLikelyRole = (roles: Role[]): Role | undefined =>
+ findAllowingRole(roles) ||
+ roles.find((role) => role.status !== Status.TargetAddressNotAllowed && role.status !== Status.FunctionNotAllowed) ||
+ roles.find((role) => role.status !== Status.TargetAddressNotAllowed) ||
+ roles[0]
+
/**
* Returns the status of the permission check, `null` if it depends on the condition evaluation.
*/
-const checkTransaction = (role: RoleSummary, metaTx: MetaTransactionData): Status | null => {
+const checkPermissions = (role: RoleSummary, metaTx: MetaTransactionData): Status | null => {
const target = role.targets.find((t) => t.address === metaTx.to.toLowerCase())
if (!target) return Status.TargetAddressNotAllowed
@@ -121,6 +196,7 @@ const checkTransaction = (role: RoleSummary, metaTx: MetaTransactionData): Statu
// check if the function is allowed
const selector = metaTx.data.slice(0, 10) as `0x${string}`
const func = target.functions.find((f) => f.selector === selector)
+
if (func) {
const execOptionsStatus = checkExecutionOptions(func.executionOptions, metaTx)
if (execOptionsStatus !== Status.Ok) return execOptionsStatus
@@ -146,13 +222,11 @@ const checkExecutionOptions = (execOptions: ExecutionOptions, metaTx: MetaTransa
}
export const useExecuteThroughRole = ({
- modAddress,
- roleKey,
- metaTx,
+ role,
+ metaTransactions,
}: {
- modAddress?: `0x${string}`
- roleKey?: `0x${string}`
- metaTx?: MetaTransactionData
+ role?: Role
+ metaTransactions: MetaTransactionData[]
}) => {
const web3ReadOnly = useWeb3ReadOnly()
const wallet = useWallet()
@@ -160,32 +234,51 @@ export const useExecuteThroughRole = ({
return useMemo(
() =>
- modAddress && roleKey && metaTx && walletAddress && web3ReadOnly
- ? encodeExecuteThroughRole(modAddress, roleKey, metaTx, walletAddress, web3ReadOnly)
+ role && walletAddress && web3ReadOnly
+ ? encodeExecuteThroughRole(role, metaTransactions, walletAddress, web3ReadOnly)
: undefined,
- [modAddress, roleKey, metaTx, walletAddress, web3ReadOnly],
+ [role, metaTransactions, walletAddress, web3ReadOnly],
)
}
+const encodeMetaTransactions = (role: Role, metaTransactions: MetaTransactionData[]): MetaTransactionData => {
+ if (metaTransactions.length === 0) {
+ throw new Error('No meta transactions to encode')
+ }
+ if (metaTransactions.length === 1) {
+ return metaTransactions[0]
+ } else {
+ const to = role.multiSend || KNOWN_MULTISEND_ADDRESSES[0]
+
+ return {
+ to,
+ value: '0',
+ data: multiSendInterface.encodeFunctionData('multiSend', [encodeMultiSendData(metaTransactions)]),
+ operation: OperationType.DelegateCall,
+ }
+ }
+}
+
const encodeExecuteThroughRole = (
- modAddress: `0x${string}`,
- roleKey: `0x${string}`,
- metaTx: MetaTransactionData,
+ role: Role,
+ metaTransactions: MetaTransactionData[],
from: `0x${string}`,
provider: JsonRpcProvider,
): Transaction => {
- const rolesModifier = getModuleInstance(KnownContracts.ROLES_V2, modAddress, provider)
+ const combinedMetaTx = encodeMetaTransactions(role, metaTransactions)
+
+ const rolesModifier = getModuleInstance(KnownContracts.ROLES_V2, role.modAddress, provider)
const data = rolesModifier.interface.encodeFunctionData('execTransactionWithRole', [
- metaTx.to,
- BigInt(metaTx.value),
- metaTx.data,
- metaTx.operation || 0,
- roleKey,
+ combinedMetaTx.to,
+ BigInt(combinedMetaTx.value),
+ combinedMetaTx.data,
+ combinedMetaTx.operation || 0,
+ role.roleKey,
true,
])
return {
- to: modAddress,
+ to: role.modAddress,
data,
value: '0',
from,
@@ -193,20 +286,21 @@ const encodeExecuteThroughRole = (
}
const checkCondition = async (
- modAddress: `0x${string}`,
- roleKey: `0x${string}`,
- metaTx: MetaTransactionData,
+ role: Role,
+ metaTransactions: MetaTransactionData[],
from: `0x${string}`,
provider: JsonRpcProvider,
) => {
- const rolesModifier = getModuleInstance(KnownContracts.ROLES_V2, modAddress, provider)
+ const combinedMetaTx = encodeMetaTransactions(role, metaTransactions)
+
+ const rolesModifier = getModuleInstance(KnownContracts.ROLES_V2, role.modAddress, provider)
try {
await rolesModifier.execTransactionWithRole.estimateGas(
- metaTx.to,
- BigInt(metaTx.value),
- metaTx.data,
- metaTx.operation || 0,
- roleKey,
+ combinedMetaTx.to,
+ BigInt(combinedMetaTx.value),
+ combinedMetaTx.data,
+ combinedMetaTx.operation || 0,
+ role.roleKey,
false,
{ from },
)
@@ -249,13 +343,13 @@ export const useGasLimit = (
return { gasLimit, gasLimitError, gasLimitLoading }
}
-export const pollModuleTransactionId = async (props: {
- transactionService: string
- safeAddress: string
- txHash: string
-}): Promise => {
+export const pollModuleTransactionId = async (
+ chainId: string,
+ safeAddress: string,
+ txHash: string,
+): Promise => {
// exponential delay between attempts for around 4 min
- return backOff(() => getModuleTransactionId(props), {
+ return backOff(() => getModuleTransactionId(chainId, safeAddress, txHash), {
startingDelay: 750,
maxDelay: 20000,
numOfAttempts: 19,
diff --git a/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/index.tsx b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/index.tsx
new file mode 100644
index 000000000..39acb5a1d
--- /dev/null
+++ b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/index.tsx
@@ -0,0 +1,273 @@
+import useWalletCanPay from '@/hooks/useWalletCanPay'
+import madProps from '@/utils/mad-props'
+import { type ReactElement, type SyntheticEvent, useContext, useState } from 'react'
+import { CircularProgress, Box, Button, CardActions, Divider, Typography } from '@mui/material'
+
+import ErrorMessage from '@/components/tx/ErrorMessage'
+import { trackError, Errors } from '@/services/exceptions'
+import { useCurrentChain } from '@/hooks/useChains'
+import { getTxOptions } from '@/utils/transactions'
+import CheckWallet from '@/components/common/CheckWallet'
+
+import type { SignOrExecuteProps } from '..'
+import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
+import { TxModalContext } from '@/components/tx-flow'
+import { SuccessScreenFlow } from '@/components/tx-flow/flows'
+import AdvancedParams, { useAdvancedParams } from '../../AdvancedParams'
+import { asError } from '@/services/exceptions/utils'
+import { isWalletRejection } from '@/utils/wallets'
+
+import css from '../styles.module.css'
+import commonCss from '@/components/tx-flow/common/styles.module.css'
+import { TxSecurityContext } from '../../security/shared/TxSecurityContext'
+
+import WalletRejectionError from '@/components/tx/SignOrExecuteForm/WalletRejectionError'
+import { pollModuleTransactionId, useExecuteThroughRole, useGasLimit, useMetaTransactions, type Role } from './hooks'
+import { decodeBytes32String } from 'ethers'
+import useOnboard from '@/hooks/wallets/useOnboard'
+import useWallet from '@/hooks/wallets/useWallet'
+import useSafeInfo from '@/hooks/useSafeInfo'
+import { assertOnboard, assertWallet } from '@/utils/helpers'
+import { dispatchModuleTxExecution } from '@/services/tx/tx-sender'
+import { Status } from 'zodiac-roles-deployments'
+
+const Role = ({ children }: { children: string }) => {
+ let humanReadableRoleKey = children
+ try {
+ humanReadableRoleKey = decodeBytes32String(children)
+ } catch (e) {}
+
+ return {humanReadableRoleKey}
+}
+
+export const ExecuteThroughRoleForm = ({
+ safeTx,
+ role,
+ onSubmit,
+ disableSubmit = false,
+ txSecurity,
+}: SignOrExecuteProps & {
+ safeTx?: SafeTransaction
+ safeTxError?: Error
+ role: Role
+ txSecurity: ReturnType
+}): ReactElement => {
+ const currentChain = useCurrentChain()
+ const onboard = useOnboard()
+ const wallet = useWallet()
+ const { safe } = useSafeInfo()
+
+ const chainId = currentChain?.chainId || '1'
+
+ const [isPending, setIsPending] = useState(false)
+ const [isRejectedByUser, setIsRejectedByUser] = useState(false)
+ const [submitError, setSubmitError] = useState()
+
+ const { setTxFlow } = useContext(TxModalContext)
+ const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = txSecurity
+
+ const permissionsError = role.status !== null ? PermissionsErrorMessage[role.status] : null
+ const metaTransactions = useMetaTransactions(safeTx)
+ const multiSendImpossible = metaTransactions.length > 1 && !role.multiSend
+
+ // Wrap call, routing it through the Roles mod with the allowing role
+ const txThroughRole = useExecuteThroughRole({
+ role: role.status === Status.Ok && !multiSendImpossible ? role : undefined,
+ metaTransactions,
+ })
+
+ // Estimate gas limit
+ const { gasLimit, gasLimitError } = useGasLimit(txThroughRole)
+ const [advancedParams, setAdvancedParams] = useAdvancedParams(gasLimit)
+
+ // On form submit
+ const handleSubmit = async (e: SyntheticEvent) => {
+ e.preventDefault()
+
+ if (needsRiskConfirmation && !isRiskConfirmed) {
+ setIsRiskIgnored(true)
+ return
+ }
+
+ assertWallet(wallet)
+ assertOnboard(onboard)
+
+ setIsRejectedByUser(false)
+ setIsPending(true)
+ setSubmitError(undefined)
+ setIsRejectedByUser(false)
+
+ if (!txThroughRole) {
+ throw new Error('Execution through role is not possible')
+ }
+
+ const txOptions = getTxOptions(advancedParams, currentChain)
+
+ let txHash: string
+ try {
+ txHash = await dispatchModuleTxExecution({ ...txThroughRole, ...txOptions }, wallet.provider, safe.address.value)
+ } catch (_err) {
+ const err = asError(_err)
+ if (isWalletRejection(err)) {
+ setIsRejectedByUser(true)
+ } else {
+ trackError(Errors._815, err)
+ setSubmitError(err)
+ }
+ setIsPending(false)
+ return
+ }
+
+ // On success, forward to the success screen, initially without a txId
+ setTxFlow( , undefined, false)
+
+ // Wait for module tx to be indexed
+ const transactionService = currentChain?.transactionService
+ if (!transactionService) {
+ throw new Error('Transaction service not found')
+ }
+ const txId = await pollModuleTransactionId(chainId, safe.address.value, txHash)
+ onSubmit?.(txId, true)
+
+ // Update the success screen so it shows a link to the transaction
+ setTxFlow( , undefined, false)
+ }
+
+ const walletCanPay = useWalletCanPay({
+ gasLimit,
+ maxFeePerGas: advancedParams.maxFeePerGas,
+ })
+
+ const submitDisabled = !txThroughRole || isPending || disableSubmit || (needsRiskConfirmation && !isRiskConfirmed)
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+const useTxSecurityContext = () => useContext(TxSecurityContext)
+
+export default madProps(ExecuteThroughRoleForm, {
+ txSecurity: useTxSecurityContext,
+})
+
+const PermissionsErrorMessage: Record = {
+ [Status.Ok]: null,
+
+ [Status.DelegateCallNotAllowed]: 'Role is not allowed to delegate call to target address',
+ [Status.TargetAddressNotAllowed]: 'Role is not allowed to call target address',
+ [Status.FunctionNotAllowed]: 'Role is not allowed to call this function on the target address',
+ [Status.SendNotAllowed]: 'Role is not allowed to send to target address',
+ [Status.OrViolation]: 'Condition violation: None of the Or branch conditions are met',
+ [Status.NorViolation]: 'Condition violation: At least one Nor branch condition is met',
+ [Status.ParameterNotAllowed]: 'Condition violation: Parameter value is not allowed',
+ [Status.ParameterLessThanAllowed]: 'Condition violation: Parameter value is less than allowed',
+ [Status.ParameterGreaterThanAllowed]: 'Condition violation: Parameter value is greater than allowed',
+ [Status.ParameterNotAMatch]: 'Condition violation: Parameter value does not match',
+ [Status.NotEveryArrayElementPasses]: 'Condition violation: Not every array element meets the criteria',
+ [Status.NoArrayElementPasses]: 'Condition violation: None of the array elements meet the criteria',
+ [Status.ParameterNotSubsetOfAllowed]: 'Condition violation: Parameter value is not a subset of allowed values',
+ [Status.BitmaskOverflow]: 'Condition violation: Bitmask exceeded value length',
+ [Status.BitmaskNotAllowed]: 'Condition violation: Bitmask does not allow the value',
+ [Status.CustomConditionViolation]: 'Condition violation: Custom condition is not met',
+ [Status.AllowanceExceeded]: 'Condition violation: Allowance is exceeded',
+ [Status.CallAllowanceExceeded]: 'Condition violation: Call allowance is exceeded',
+ [Status.EtherAllowanceExceeded]: 'Condition violation: Ether allowance is exceeded',
+}
diff --git a/src/components/tx/SignOrExecuteForm/PermissionsCheck/index.tsx b/src/components/tx/SignOrExecuteForm/PermissionsCheck/index.tsx
deleted file mode 100644
index edbac6d42..000000000
--- a/src/components/tx/SignOrExecuteForm/PermissionsCheck/index.tsx
+++ /dev/null
@@ -1,220 +0,0 @@
-import { useContext, useState } from 'react'
-import { Status } from 'zodiac-roles-deployments'
-import { type SafeTransaction } from '@safe-global/safe-core-sdk-types'
-import { decodeBytes32String } from 'ethers'
-
-import { Box, Button, CardActions, Chip, CircularProgress, Divider, Typography } from '@mui/material'
-
-import commonCss from '@/components/tx-flow/common/styles.module.css'
-import CheckWallet from '@/components/common/CheckWallet'
-import TxCard from '@/components/tx-flow/common/TxCard'
-import { getTransactionTrackingType } from '@/services/analytics/tx-tracking'
-import { TX_EVENTS } from '@/services/analytics/events/transactions'
-import { trackEvent } from '@/services/analytics'
-import useSafeInfo from '@/hooks/useSafeInfo'
-import WalletRejectionError from '../WalletRejectionError'
-import ErrorMessage from '../../ErrorMessage'
-import useWallet from '@/hooks/wallets/useWallet'
-import { type SubmitCallback } from '..'
-import { getTxOptions } from '@/utils/transactions'
-import { isWalletRejection } from '@/utils/wallets'
-import { Errors, trackError } from '@/services/exceptions'
-import { asError } from '@/services/exceptions/utils'
-import { SuccessScreenFlow } from '@/components/tx-flow/flows'
-import AdvancedParams, { useAdvancedParams } from '../../AdvancedParams'
-import { useCurrentChain } from '@/hooks/useChains'
-import { dispatchModuleTxExecution } from '@/services/tx/tx-sender'
-import useOnboard from '@/hooks/wallets/useOnboard'
-import { assertOnboard, assertWallet } from '@/utils/helpers'
-import { TxModalContext } from '@/components/tx-flow'
-import { pollModuleTransactionId, useExecuteThroughRole, useRoles, useGasLimit } from './hooks'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
-
-const Role = ({ children }: { children: string }) => {
- let humanReadableRoleKey = children
- try {
- humanReadableRoleKey = decodeBytes32String(children)
- } catch (e) {}
-
- return
-}
-
-const PermissionsCheck: React.FC<{ onSubmit?: SubmitCallback; safeTx: SafeTransaction; safeTxError?: Error }> = ({
- onSubmit,
- safeTx,
- safeTxError,
-}) => {
- const currentChain = useCurrentChain()
- const onboard = useOnboard()
- const wallet = useWallet()
- const { safe } = useSafeInfo()
-
- const chainId = currentChain?.chainId || '1'
-
- const [isPending, setIsPending] = useState(false)
- const [isRejectedByUser, setIsRejectedByUser] = useState(false)
- const [submitError, setSubmitError] = useState()
-
- const { setTxFlow } = useContext(TxModalContext)
-
- const roles = useRoles(safeTx?.data)
- const allowingRole = roles.find((role) => role.status === Status.Ok)
-
- // If a user has multiple roles, we should prioritize the one that allows the transaction's to address (and function selector)
- const mostLikelyRole =
- allowingRole ||
- roles.find((role) => role.status !== Status.TargetAddressNotAllowed && role.status !== Status.FunctionNotAllowed) ||
- roles.find((role) => role.status !== Status.TargetAddressNotAllowed) ||
- roles[0]
-
- // Wrap call routing it through the Roles mod with the allowing role
- const txThroughRole = useExecuteThroughRole({
- modAddress: allowingRole?.modAddress,
- roleKey: allowingRole?.roleKey,
- metaTx: safeTx?.data,
- })
- // Estimate gas limit
- const { gasLimit, gasLimitError } = useGasLimit(txThroughRole)
- const [advancedParams, setAdvancedParams] = useAdvancedParams(gasLimit)
-
- const handleExecute = async () => {
- assertWallet(wallet)
- assertOnboard(onboard)
-
- await assertWalletChain(onboard, chainId)
-
- setIsRejectedByUser(false)
- setIsPending(true)
- setSubmitError(undefined)
- setIsRejectedByUser(false)
-
- if (!txThroughRole) {
- throw new Error('Execution through role is not possible')
- }
-
- const txOptions = getTxOptions(advancedParams, currentChain)
-
- let txHash: string
- try {
- txHash = await dispatchModuleTxExecution({ ...txThroughRole, ...txOptions }, wallet.provider, safe.address.value)
- } catch (_err) {
- const err = asError(_err)
- if (isWalletRejection(err)) {
- setIsRejectedByUser(true)
- } else {
- trackError(Errors._815, err)
- setSubmitError(err)
- }
- setIsPending(false)
- return
- }
-
- // On success, forward to the success screen, initially without a txId
- setTxFlow( , undefined, false)
-
- // Wait for module tx to be indexed
- const transactionService = currentChain?.transactionService
- if (!transactionService) {
- throw new Error('Transaction service not found')
- }
- const moduleTxId = await pollModuleTransactionId({
- transactionService,
- safeAddress: safe.address.value,
- txHash,
- })
-
- const txId = `module_${safe.address.value}_${moduleTxId}`
-
- onSubmit?.(txId, true)
-
- // Track tx event
- const txType = await getTransactionTrackingType(chainId, txId)
- trackEvent({ ...TX_EVENTS.EXECUTE_THROUGH_ROLE, label: txType })
-
- // Update the success screen so it shows a link to the transaction
- setTxFlow( , undefined, false)
- }
-
- // Only render the card if the connected wallet is a member of any role
- if (roles.length === 0) {
- return null
- }
-
- return (
-
- Execute without confirmations
-
- {allowingRole && (
- <>
-
- As a member of the {allowingRole.roleKey} you can execute this transaction immediately without
- confirmations from other owners.
-
-
- >
- )}
-
- {!allowingRole && (
- <>
-
- You are a member of the {mostLikelyRole.roleKey} role but it does not allow this transaction.
-
-
- {mostLikelyRole.status && (
-
- The permission check fails with the following status:
-
- {Status[mostLikelyRole.status]}
-
- )}
- >
- )}
-
- {safeTxError && (
-
- This transaction will most likely fail. To save gas costs, avoid confirming the transaction.
-
- )}
-
- {submitError && (
-
- Error submitting the transaction. Please try again.
-
- )}
-
- {isRejectedByUser && (
-
-
-
- )}
-
-
-
-
-
-
- {(isOk) => (
-
- {isPending ? : 'Execute'}
-
- )}
-
-
-
-
- )
-}
-
-export default PermissionsCheck
diff --git a/src/components/tx/SignOrExecuteForm/SignForm.tsx b/src/components/tx/SignOrExecuteForm/SignForm.tsx
index da8faf2ae..850432f10 100644
--- a/src/components/tx/SignOrExecuteForm/SignForm.tsx
+++ b/src/components/tx/SignOrExecuteForm/SignForm.tsx
@@ -131,7 +131,7 @@ export const SignForm = ({
)}
{/* Submit button */}
-
+
{(isOk) => (
{
+const TxChecks = ({ executionOwner }: { executionOwner?: string }): ReactElement | null => {
const { safeTx } = useContext(SafeTxContext)
+ const chain = useCurrentChain()
+ const isRiskMitigationFeatureEnabled = useHasFeature(FEATURES.RISK_MITIGATION)
+ const isTxSimulationFeatureEnabled = isTxSimulationEnabled(chain)
+
+ if (!isTxSimulationFeatureEnabled && !isRiskMitigationFeatureEnabled) {
+ return null
+ }
return (
- <>
+
Transaction checks
@@ -18,13 +28,7 @@ const TxChecks = ({ executionOwner }: { executionOwner?: string }): ReactElement
-
-
-
-
-
-
- >
+
)
}
diff --git a/src/components/tx/SignOrExecuteForm/__tests__/ExecuteForm.test.tsx b/src/components/tx/SignOrExecuteForm/__tests__/ExecuteForm.test.tsx
index 662a1a950..60141794f 100644
--- a/src/components/tx/SignOrExecuteForm/__tests__/ExecuteForm.test.tsx
+++ b/src/components/tx/SignOrExecuteForm/__tests__/ExecuteForm.test.tsx
@@ -33,7 +33,7 @@ describe('ExecuteForm', () => {
isOwner: true,
isExecutionLoop: false,
relays: [undefined, undefined, false] as AsyncResult,
- txActions: { signTx: jest.fn(), addToBatch: jest.fn(), executeTx: jest.fn() },
+ txActions: { signTx: jest.fn(), addToBatch: jest.fn(), executeTx: jest.fn(), signDelegateTx: jest.fn() },
txSecurity: defaultSecurityContextValues,
}
@@ -105,7 +105,10 @@ describe('ExecuteForm', () => {
.mockReturnValue({ executionValidationError: new Error('Some error'), isValidExecutionLoading: false })
const { getByText } = render(
- ,
+ ,
)
expect(
@@ -135,7 +138,7 @@ describe('ExecuteForm', () => {
{...defaultProps}
safeTx={safeTransaction}
onSubmit={jest.fn()}
- txActions={{ signTx: jest.fn(), addToBatch: jest.fn(), executeTx: mockExecuteTx }}
+ txActions={{ signTx: jest.fn(), addToBatch: jest.fn(), executeTx: mockExecuteTx, signDelegateTx: jest.fn() }}
/>,
)
@@ -155,7 +158,7 @@ describe('ExecuteForm', () => {
,
)
diff --git a/src/components/tx/SignOrExecuteForm/__tests__/SignForm.test.tsx b/src/components/tx/SignOrExecuteForm/__tests__/SignForm.test.tsx
index 25dd58fb2..ea18ea7c9 100644
--- a/src/components/tx/SignOrExecuteForm/__tests__/SignForm.test.tsx
+++ b/src/components/tx/SignOrExecuteForm/__tests__/SignForm.test.tsx
@@ -25,7 +25,7 @@ describe('SignForm', () => {
const defaultProps = {
onSubmit: jest.fn(),
isOwner: true,
- txActions: { signTx: jest.fn(), addToBatch: jest.fn(), executeTx: jest.fn() },
+ txActions: { signTx: jest.fn(), addToBatch: jest.fn(), executeTx: jest.fn(), signDelegateTx: jest.fn() },
txSecurity: defaultSecurityContextValues,
}
@@ -70,7 +70,7 @@ describe('SignForm', () => {
,
)
@@ -90,7 +90,7 @@ describe('SignForm', () => {
,
)
@@ -133,7 +133,7 @@ describe('SignForm', () => {
safeTx={safeTransaction}
isBatchable
isCreation
- txActions={{ signTx: jest.fn(), addToBatch: mockAddToBatch, executeTx: jest.fn() }}
+ txActions={{ signTx: jest.fn(), addToBatch: mockAddToBatch, executeTx: jest.fn(), signDelegateTx: jest.fn() }}
/>,
)
diff --git a/src/components/tx/SignOrExecuteForm/__tests__/SignOrExecute.test.tsx b/src/components/tx/SignOrExecuteForm/__tests__/SignOrExecute.test.tsx
index b38d972c6..7008fa0c1 100644
--- a/src/components/tx/SignOrExecuteForm/__tests__/SignOrExecute.test.tsx
+++ b/src/components/tx/SignOrExecuteForm/__tests__/SignOrExecute.test.tsx
@@ -1,10 +1,24 @@
import { SignOrExecuteForm } from '@/components/tx/SignOrExecuteForm'
import * as hooks from '@/components/tx/SignOrExecuteForm/hooks'
+import * as execThroughRoleHooks from '@/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/hooks'
import { safeTxBuilder } from '@/tests/builders/safeTx'
import { render } from '@/tests/test-utils'
import { fireEvent } from '@testing-library/react'
+import { encodeBytes32String } from 'ethers'
+import { Status } from 'zodiac-roles-deployments'
+
+let isSafeOwner = true
+// mock useIsSafeOwner
+jest.mock('@/hooks/useIsSafeOwner', () => ({
+ __esModule: true,
+ default: jest.fn(() => isSafeOwner),
+}))
describe('SignOrExecute', () => {
+ beforeEach(() => {
+ isSafeOwner = true
+ })
+
it('should display a safeTxError', () => {
const { getByText } = render(
{
expect(getByText('Would you like to execute the transaction immediately?')).toBeInTheDocument()
})
+
+ it('should offer to execute through a role if the user is a role member and the transaction is executable through the role', () => {
+ jest.spyOn(execThroughRoleHooks, 'useRoles').mockReturnValue([TEST_ROLE_OK])
+ jest.spyOn(hooks, 'useValidateNonce').mockReturnValue(true)
+ jest.spyOn(hooks, 'useImmediatelyExecutable').mockReturnValue(false)
+
+ const { queryByTestId } = render(
+ ,
+ )
+
+ expect(queryByTestId('execute-through-role-form-btn')).toBeInTheDocument()
+ })
+
+ it('should not offer to execute through a role if the user is a safe owner and role member but the role lacks permissions', () => {
+ jest.spyOn(execThroughRoleHooks, 'useRoles').mockReturnValue([TEST_ROLE_TARGET_NOT_ALLOWED])
+ jest.spyOn(hooks, 'useValidateNonce').mockReturnValue(true)
+ jest.spyOn(hooks, 'useImmediatelyExecutable').mockReturnValue(false)
+ isSafeOwner = true
+
+ const { queryByTestId } = render(
+ ,
+ )
+
+ expect(queryByTestId('execute-through-role-form-btn')).not.toBeInTheDocument()
+ })
+
+ it('should offer to execute through a role if the user is a role member but not a safe owner, even if the role lacks permissions', () => {
+ jest.spyOn(execThroughRoleHooks, 'useRoles').mockReturnValue([TEST_ROLE_TARGET_NOT_ALLOWED])
+ jest.spyOn(hooks, 'useValidateNonce').mockReturnValue(true)
+ jest.spyOn(hooks, 'useImmediatelyExecutable').mockReturnValue(false)
+ isSafeOwner = false
+
+ const { queryByTestId } = render(
+ ,
+ )
+
+ expect(queryByTestId('execute-through-role-form-btn')).toBeInTheDocument()
+ })
+
+ it('should not offer to execute through a role if the transaction can also be directly executed without going through the role', () => {
+ jest.spyOn(execThroughRoleHooks, 'useRoles').mockReturnValue([TEST_ROLE_OK])
+ jest.spyOn(hooks, 'useValidateNonce').mockReturnValue(true)
+
+ const { queryByTestId } = render(
+ ,
+ )
+
+ expect(queryByTestId('execute-through-role-form-btn')).not.toBeInTheDocument()
+ })
})
it('should not display radio options if execution is the only option', () => {
+ jest.spyOn(execThroughRoleHooks, 'useRoles').mockReturnValue([])
+
const { queryByText } = render(
{
chainId="1"
/>,
)
-
expect(queryByText('Would you like to execute the transaction immediately?')).not.toBeInTheDocument()
})
@@ -113,3 +209,20 @@ describe('SignOrExecute', () => {
).not.toBeInTheDocument()
})
})
+
+const ROLES_MOD_ADDRESS = '0x1234567890000000000000000000000000000000'
+const ROLE_KEY = encodeBytes32String('eth_wrapping')
+
+const TEST_ROLE_OK: execThroughRoleHooks.Role = {
+ modAddress: ROLES_MOD_ADDRESS,
+ roleKey: ROLE_KEY as `0x${string}`,
+ multiSend: '0x9641d764fc13c8b624c04430c7356c1c7c8102e2',
+ status: Status.Ok,
+}
+
+const TEST_ROLE_TARGET_NOT_ALLOWED: execThroughRoleHooks.Role = {
+ modAddress: ROLES_MOD_ADDRESS,
+ roleKey: ROLE_KEY as `0x${string}`,
+ multiSend: '0x9641d764fc13c8b624c04430c7356c1c7c8102e2',
+ status: Status.TargetAddressNotAllowed,
+}
diff --git a/src/components/tx/SignOrExecuteForm/hooks.test.ts b/src/components/tx/SignOrExecuteForm/hooks.test.ts
index 98def6566..b6099d987 100644
--- a/src/components/tx/SignOrExecuteForm/hooks.test.ts
+++ b/src/components/tx/SignOrExecuteForm/hooks.test.ts
@@ -20,6 +20,10 @@ import {
} from './hooks'
import * as recommendedNonce from '@/services/tx/tx-sender/recommendedNonce'
import { defaultSafeInfo } from '@/store/safeInfoSlice'
+import { chainBuilder } from '@/tests/builders/chains'
+import * as useChains from '@/hooks/useChains'
+
+const chainInfo = chainBuilder().with({ chainId: '1' }).build()
describe('SignOrExecute hooks', () => {
const extendedSafeInfo = extendedSafeInfoBuilder().build()
@@ -50,6 +54,8 @@ describe('SignOrExecute hooks', () => {
label: 'MetaMask',
address: '0x1234567890000000000000000000000000000000',
} as unknown as ConnectedWallet)
+
+ jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(chainInfo)
})
describe('useValidateNonce', () => {
diff --git a/src/components/tx/SignOrExecuteForm/hooks.ts b/src/components/tx/SignOrExecuteForm/hooks.ts
index e7ba87529..4d5bf2e47 100644
--- a/src/components/tx/SignOrExecuteForm/hooks.ts
+++ b/src/components/tx/SignOrExecuteForm/hooks.ts
@@ -1,5 +1,4 @@
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
-import { assertTx, assertWallet, assertOnboard } from '@/utils/helpers'
+import { assertTx, assertWallet, assertOnboard, assertChainInfo } from '@/utils/helpers'
import { useMemo } from 'react'
import { type TransactionOptions, type SafeTransaction } from '@safe-global/safe-core-sdk-types'
import { sameString } from '@safe-global/protocol-kit/dist/src/utils'
@@ -8,6 +7,7 @@ import useWallet from '@/hooks/wallets/useWallet'
import useOnboard from '@/hooks/wallets/useOnboard'
import { isSmartContractWallet } from '@/utils/wallets'
import {
+ dispatchDelegateTxSigning,
dispatchOnChainSigning,
dispatchTxExecution,
dispatchTxProposal,
@@ -20,6 +20,7 @@ import useAsync from '@/hooks/useAsync'
import { useUpdateBatch } from '@/hooks/useDraftBatch'
import { type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { checksumAddress } from '@/utils/addresses'
+import { useCurrentChain } from '@/hooks/useChains'
type TxActions = {
addToBatch: (safeTx?: SafeTransaction, origin?: string) => Promise
@@ -31,6 +32,7 @@ type TxActions = {
origin?: string,
isRelayed?: boolean,
) => Promise
+ signDelegateTx: (safeTx?: SafeTransaction) => Promise
}
export const useTxActions = (): TxActions => {
@@ -38,6 +40,7 @@ export const useTxActions = (): TxActions => {
const onboard = useOnboard()
const wallet = useWallet()
const [addTxToBatch] = useUpdateBatch()
+ const chain = useCurrentChain()
return useMemo(() => {
const safeAddress = safe.address.value
@@ -83,15 +86,13 @@ export const useTxActions = (): TxActions => {
assertWallet(wallet)
assertOnboard(onboard)
- await assertWalletChain(onboard, chainId)
-
// Smart contract wallets must sign via an on-chain tx
if (await isSmartContractWallet(wallet.chainId, wallet.address)) {
// If the first signature is a smart contract wallet, we have to propose w/o signatures
// Otherwise the backend won't pick up the tx
// The signature will be added once the on-chain signature is indexed
const id = txId || (await proposeTx(wallet.address, safeTx, txId, origin)).txId
- await dispatchOnChainSigning(safeTx, id, wallet.provider, chainId)
+ await dispatchOnChainSigning(safeTx, id, wallet.provider, chainId, wallet.address, safeAddress)
return id
}
@@ -101,14 +102,24 @@ export const useTxActions = (): TxActions => {
return tx.txId
}
+ const signDelegateTx: TxActions['signDelegateTx'] = async (safeTx) => {
+ assertTx(safeTx)
+ assertWallet(wallet)
+ assertOnboard(onboard)
+
+ const signedTx = await dispatchDelegateTxSigning(safeTx, wallet)
+
+ const tx = await proposeTx(wallet.address, signedTx)
+ return tx.txId
+ }
+
const executeTx: TxActions['executeTx'] = async (txOptions, _safeTx, txId, origin, isRelayed) => {
let safeTx = _safeTx ? { ..._safeTx, data: { ..._safeTx.data, to: checksumAddress(_safeTx.data.to) } } : undefined
assertTx(safeTx)
assertWallet(wallet)
assertOnboard(onboard)
-
- await assertWalletChain(onboard, chainId)
+ assertChainInfo(chain)
let tx: TransactionDetails | undefined
// Relayed transactions must be fully signed, so request a final signature if needed
@@ -131,16 +142,18 @@ export const useTxActions = (): TxActions => {
// Relay or execute the tx via connected wallet
if (isRelayed) {
- await dispatchTxRelay(safeTx, safe, txId, txOptions.gasLimit)
+ await dispatchTxRelay(safeTx, safe, txId, chain, txOptions.gasLimit)
} else {
- await dispatchTxExecution(safeTx, txOptions, txId, wallet.provider, wallet.address, safeAddress)
+ const isSmartAccount = await isSmartContractWallet(wallet.chainId, wallet.address)
+
+ await dispatchTxExecution(safeTx, txOptions, txId, wallet.provider, wallet.address, safeAddress, isSmartAccount)
}
return txId
}
- return { addToBatch, signTx, executeTx }
- }, [safe, wallet, addTxToBatch, onboard])
+ return { addToBatch, signTx, executeTx, signDelegateTx }
+ }, [safe, wallet, addTxToBatch, onboard, chain])
}
export const useValidateNonce = (safeTx: SafeTransaction | undefined): boolean => {
diff --git a/src/components/tx/SignOrExecuteForm/index.tsx b/src/components/tx/SignOrExecuteForm/index.tsx
index 84e2397e2..9aa8133da 100644
--- a/src/components/tx/SignOrExecuteForm/index.tsx
+++ b/src/components/tx/SignOrExecuteForm/index.tsx
@@ -1,10 +1,11 @@
+import DelegateForm from '@/components/tx/SignOrExecuteForm/DelegateForm'
import CounterfactualForm from '@/features/counterfactual/CounterfactualForm'
+import { useIsWalletDelegate } from '@/hooks/useDelegates'
import useSafeInfo from '@/hooks/useSafeInfo'
import { type ReactElement, type ReactNode, useState, useContext, useCallback } from 'react'
import madProps from '@/utils/mad-props'
import DecodedTx from '../DecodedTx'
import ExecuteCheckbox from '../ExecuteCheckbox'
-import { WrongChainWarning } from '../WrongChainWarning'
import { useImmediatelyExecutable, useValidateNonce } from './hooks'
import ExecuteForm from './ExecuteForm'
import SignForm from './SignForm'
@@ -15,9 +16,7 @@ import TxCard from '@/components/tx-flow/common/TxCard'
import ConfirmationTitle, { ConfirmationTitleTypes } from '@/components/tx/SignOrExecuteForm/ConfirmationTitle'
import { useAppSelector } from '@/store'
import { selectSettings } from '@/store/settingsSlice'
-import { RedefineBalanceChanges } from '../security/redefine/RedefineBalanceChange'
import UnknownContractError from './UnknownContractError'
-import RiskConfirmationError from './RiskConfirmationError'
import useDecodeTx from '@/hooks/useDecodeTx'
import { ErrorBoundary } from '@sentry/react'
import ApprovalEditor from '../ApprovalEditor'
@@ -26,11 +25,21 @@ import { getTransactionTrackingType } from '@/services/analytics/tx-tracking'
import { TX_EVENTS } from '@/services/analytics/events/transactions'
import { trackEvent } from '@/services/analytics'
import useChainId from '@/hooks/useChainId'
-import PermissionsCheck from './PermissionsCheck'
-import { isConfirmationViewOrder } from '@/utils/transaction-guards'
-import SwapOrderConfirmationView from '@/features/swap/components/SwapOrderConfirmationView'
-import { isSettingTwapFallbackHandler } from '@/features/swap/helpers/utils'
-import { TwapFallbackHandlerWarning } from '@/features/swap/components/TwapFallbackHandlerWarning'
+import ExecuteThroughRoleForm from './ExecuteThroughRoleForm'
+import { findAllowingRole, findMostLikelyRole, useRoles } from './ExecuteThroughRoleForm/hooks'
+import { isAnyStakingTxInfo, isCustomTxInfo, isGenericConfirmation, isOrderTxInfo } from '@/utils/transaction-guards'
+import useIsSafeOwner from '@/hooks/useIsSafeOwner'
+import { BlockaidBalanceChanges } from '../security/blockaid/BlockaidBalanceChange'
+import { Blockaid } from '../security/blockaid'
+
+import TxData from '@/components/transactions/TxDetails/TxData'
+import ConfirmationOrder from '@/components/tx/ConfirmationOrder'
+import { useApprovalInfos } from '../ApprovalEditor/hooks/useApprovalInfos'
+
+import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
+import { useGetTransactionDetailsQuery, useLazyGetTransactionDetailsQuery } from '@/store/gateway'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
export type SubmitCallback = (txId: string, isExecuted?: boolean) => void
@@ -46,17 +55,29 @@ export type SignOrExecuteProps = {
disableSubmit?: boolean
origin?: string
isCreation?: boolean
- showToBlock?: boolean
+ showMethodCall?: boolean
}
-const trackTxEvents = async (chainId: string, txId: string, isCreation: boolean, isExecuted: boolean) => {
- const event = isCreation ? TX_EVENTS.CREATE : isExecuted ? TX_EVENTS.EXECUTE : TX_EVENTS.CONFIRM
- const txType = await getTransactionTrackingType(chainId, txId)
+const trackTxEvents = (
+ details: TransactionDetails | undefined,
+ isCreation: boolean,
+ isExecuted: boolean,
+ isRoleExecution: boolean,
+ isDelegateCreation: boolean,
+) => {
+ const creationEvent = isRoleExecution
+ ? TX_EVENTS.CREATE_VIA_ROLE
+ : isDelegateCreation
+ ? TX_EVENTS.CREATE_VIA_DELEGATE
+ : TX_EVENTS.CREATE
+ const executionEvent = isRoleExecution ? TX_EVENTS.EXECUTE_VIA_ROLE : TX_EVENTS.EXECUTE
+ const event = isCreation ? creationEvent : isExecuted ? executionEvent : TX_EVENTS.CONFIRM
+ const txType = getTransactionTrackingType(details)
trackEvent({ ...event, label: txType })
// Immediate execution on creation
if (isCreation && isExecuted) {
- trackEvent({ ...TX_EVENTS.EXECUTE, label: txType })
+ trackEvent({ ...executionEvent, label: txType })
}
}
@@ -76,26 +97,67 @@ export const SignOrExecuteForm = ({
const isCreation = !props.txId
const isNewExecutableTx = useImmediatelyExecutable() && isCreation
const isCorrectNonce = useValidateNonce(safeTx)
- const [decodedData, decodedDataError, decodedDataLoading] = useDecodeTx(safeTx)
+ const [decodedData] = useDecodeTx(safeTx)
+
const isBatchable = props.isBatchable !== false && safeTx && !isDelegateCall(safeTx)
- const isSwapOrder = isConfirmationViewOrder(decodedData)
+
+ const { data: txDetails } = useGetTransactionDetailsQuery(
+ chainId && props.txId
+ ? {
+ chainId,
+ txId: props.txId,
+ }
+ : skipToken,
+ )
+ const showTxDetails =
+ props.txId &&
+ txDetails &&
+ !isCustomTxInfo(txDetails.txInfo) &&
+ !isAnyStakingTxInfo(txDetails.txInfo) &&
+ !isOrderTxInfo(txDetails.txInfo)
+ const isDelegate = useIsWalletDelegate()
+ const [trigger] = useLazyGetTransactionDetailsQuery()
+ const [readableApprovals] = useApprovalInfos({ safeTransaction: safeTx })
+ const isApproval = readableApprovals && readableApprovals.length > 0
const { safe } = useSafeInfo()
+ const isSafeOwner = useIsSafeOwner()
const isCounterfactualSafe = !safe.deployed
- const isChangingFallbackHandler = isSettingTwapFallbackHandler(decodedData)
+
+ // Check if a Zodiac Roles mod is enabled and if the user is a member of any role that allows the transaction
+ const roles = useRoles(
+ !isCounterfactualSafe && isCreation && !(isNewExecutableTx && isSafeOwner) ? safeTx : undefined,
+ )
+ const allowingRole = findAllowingRole(roles)
+ const mostLikelyRole = findMostLikelyRole(roles)
+ const canExecuteThroughRole = !!allowingRole || (!!mostLikelyRole && !isSafeOwner)
+ const preferThroughRole = canExecuteThroughRole && !isSafeOwner // execute through role if a non-owner role member wallet is connected
// If checkbox is checked and the transaction is executable, execute it, otherwise sign it
const canExecute = isCorrectNonce && (props.isExecutable || isNewExecutableTx)
- const willExecute = (props.onlyExecute || shouldExecute) && canExecute
+ const willExecute = (props.onlyExecute || shouldExecute) && canExecute && !preferThroughRole
+ const willExecuteThroughRole =
+ (props.onlyExecute || shouldExecute) && canExecuteThroughRole && (!canExecute || preferThroughRole)
- const onFormSubmit = useCallback(
- async (txId, isExecuted = false) => {
+ const onFormSubmit = useCallback(
+ async (txId: string, isExecuted = false, isRoleExecution = false, isDelegateCreation = false) => {
onSubmit?.(txId, isExecuted)
+ const { data: details } = await trigger({ chainId, txId })
// Track tx event
- trackTxEvents(chainId, txId, isCreation, isExecuted)
+ trackTxEvents(details, isCreation, isExecuted, isRoleExecution, isDelegateCreation)
},
- [chainId, isCreation, onSubmit],
+ [chainId, isCreation, onSubmit, trigger],
+ )
+
+ const onRoleExecutionSubmit = useCallback(
+ (txId, isExecuted) => onFormSubmit(txId, isExecuted, true),
+ [onFormSubmit],
+ )
+
+ const onDelegateFormSubmit = useCallback(
+ (txId, isExecuted) => onFormSubmit(txId, isExecuted, false, true),
+ [onFormSubmit],
)
return (
@@ -103,42 +165,33 @@ export const SignOrExecuteForm = ({
{props.children}
- {isChangingFallbackHandler && }
-
- {isSwapOrder && (
+ {decodedData && (
>}>
-
+
)}
- Error parsing data }>
-
-
-
-
+ {!props.isRejection && decodedData && (
+ Error parsing data}>
+ {isApproval && }
- {!isCounterfactualSafe && }
-
+ {showTxDetails && }
- {!isCounterfactualSafe && (
-
-
-
- )}
+
+
+ )}
+ {!isCounterfactualSafe && !props.isRejection && }
+
- {!isCounterfactualSafe && safeTx && isCreation && (
-
-
-
- )}
+ {!isCounterfactualSafe && !props.isRejection && }
)}
- {canExecute && !props.onlyExecute && !isCounterfactualSafe && }
+ {(canExecute || canExecuteThroughRole) && !props.onlyExecute && !isCounterfactualSafe && !isDelegate && (
+
+ )}
-
+
-
+
- {isCounterfactualSafe ? (
+ {isCounterfactualSafe && !isDelegate && (
- ) : willExecute ? (
+ )}
+ {!isCounterfactualSafe && willExecute && !isDelegate && (
- ) : (
+ )}
+ {!isCounterfactualSafe && willExecuteThroughRole && (
+
+ )}
+ {!isCounterfactualSafe && !willExecute && !willExecuteThroughRole && !isDelegate && (
)}
+
+ {isDelegate && }
>
)
diff --git a/src/components/tx/SignOrExecuteForm/styles.module.css b/src/components/tx/SignOrExecuteForm/styles.module.css
index b04c0a44e..96f1f2223 100644
--- a/src/components/tx/SignOrExecuteForm/styles.module.css
+++ b/src/components/tx/SignOrExecuteForm/styles.module.css
@@ -64,3 +64,14 @@
display: block;
}
}
+
+.zodiac {
+ color: var(--color-text-primary);
+}
+
+.roleChip {
+ background-color: var(--color-background-main);
+ font-size: 12px;
+ border-radius: 4px;
+ padding: 2px 8px;
+}
diff --git a/src/components/tx/WrongChainWarning/index.tsx b/src/components/tx/WrongChainWarning/index.tsx
deleted file mode 100644
index 35cc18f9a..000000000
--- a/src/components/tx/WrongChainWarning/index.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { ReactElement } from 'react'
-import { Typography } from '@mui/material'
-import { useCurrentChain } from '@/hooks/useChains'
-import useIsWrongChain from '@/hooks/useIsWrongChain'
-import ErrorMessage from '@/components/tx/ErrorMessage'
-
-export const WrongChainWarning = (): ReactElement | null => {
- const chain = useCurrentChain()
- const isWrongChain = useIsWrongChain()
-
- if (!isWrongChain || !chain) {
- return null
- }
-
- return (
-
- Wallet network switch
- When you submit the transaction, you will be asked to switch your wallet network to {chain.chainName}.
-
- )
-}
diff --git a/src/components/tx/security/SecurityWarnings.tsx b/src/components/tx/security/SecurityWarnings.tsx
index f78c9fde2..92a16d034 100644
--- a/src/components/tx/security/SecurityWarnings.tsx
+++ b/src/components/tx/security/SecurityWarnings.tsx
@@ -1,9 +1,7 @@
-import { RedefineMessage } from './redefine'
import { TxSimulationMessage } from './tenderly'
const SecurityWarnings = () => (
<>
-
>
)
diff --git a/src/components/tx/security/redefine/RedefineBalanceChange.tsx b/src/components/tx/security/blockaid/BlockaidBalanceChange.tsx
similarity index 62%
rename from src/components/tx/security/redefine/RedefineBalanceChange.tsx
rename to src/components/tx/security/blockaid/BlockaidBalanceChange.tsx
index 2d6a50c4b..71bce871c 100644
--- a/src/components/tx/security/redefine/RedefineBalanceChange.tsx
+++ b/src/components/tx/security/blockaid/BlockaidBalanceChange.tsx
@@ -3,10 +3,8 @@ import TokenIcon from '@/components/common/TokenIcon'
import useBalances from '@/hooks/useBalances'
import useChainId from '@/hooks/useChainId'
import { useHasFeature } from '@/hooks/useChains'
-import { type RedefineModuleResponse } from '@/services/security/modules/RedefineModule'
import { sameAddress } from '@/utils/addresses'
import { FEATURES } from '@/utils/chains'
-import { formatVisualAmount } from '@/utils/formatters'
import { Box, Chip, CircularProgress, Grid, SvgIcon, Tooltip, Typography } from '@mui/material'
import { TokenType } from '@safe-global/safe-gateway-typescript-sdk'
import { ErrorBoundary } from '@sentry/react'
@@ -19,63 +17,75 @@ import ExternalLink from '@/components/common/ExternalLink'
import { REDEFINE_ARTICLE } from '@/config/constants'
import css from './styles.module.css'
+import type {
+ AssetDiff,
+ Erc1155Diff,
+ Erc1155TokenDetails,
+ Erc20Diff,
+ Erc721Diff,
+ Erc721TokenDetails,
+ GeneralAssetDiff,
+ NativeDiff,
+} from '@/services/security/modules/BlockaidModule/types'
+import { formatAmount } from '@/utils/formatNumber'
-const FungibleBalanceChange = ({
- change,
-}: {
- change: NonNullable['in' | 'out'][number] & { type: 'ERC20' | 'NATIVE' }
-}) => {
+const FungibleBalanceChange = ({ change, asset }: { asset: AssetDiff['asset']; change: Erc20Diff | NativeDiff }) => {
const { balances } = useBalances()
-
- const logoUri = balances.items.find((item) => {
- return change.type === 'NATIVE'
- ? item.tokenInfo.type === TokenType.NATIVE_TOKEN
- : sameAddress(item.tokenInfo.address, change.address)
- })?.tokenInfo.logoUri
+ const logoUri =
+ asset.logo_url ??
+ balances.items.find((item) => {
+ return asset.type === 'NATIVE'
+ ? item.tokenInfo.type === TokenType.NATIVE_TOKEN
+ : sameAddress(item.tokenInfo.address, asset.address)
+ })?.tokenInfo.logoUri
return (
<>
- {formatVisualAmount(change.amount.value, change.decimals)}
+ {change.value ? formatAmount(change.value) : 'unknown'}
-
+
- {change.symbol}
+ {asset.symbol}
-
+
>
)
}
const NFTBalanceChange = ({
change,
+ asset,
}: {
- change: NonNullable['in' | 'out'][number] & { type: 'ERC721' }
+ asset: Erc721TokenDetails | Erc1155TokenDetails
+ change: Erc721Diff | Erc1155Diff
}) => {
const chainId = useChainId()
return (
<>
- {change.symbol ? (
+ {asset.symbol ? (
- {change.symbol}
+ {asset.symbol}
) : (
)}
- #{change.tokenId}
+ #{Number(change.token_id)}
@@ -84,27 +94,36 @@ const NFTBalanceChange = ({
}
const BalanceChange = ({
- change,
+ asset,
positive = false,
+ diff,
}: {
- change: NonNullable['in' | 'out'][number]
+ asset: NonNullable
positive?: boolean
+ diff: GeneralAssetDiff
}) => {
return (
{positive ? : }
- {change.type === 'ERC721' ? : }
+ {asset.type === 'ERC721' || asset.type === 'ERC1155' ? (
+
+ ) : (
+
+ )}
)
}
-
const BalanceChanges = () => {
- const { balanceChange, isLoading } = useContext(TxSecurityContext)
- const totalBalanceChanges = balanceChange ? balanceChange.in.length + balanceChange.out.length : 0
+ const { blockaidResponse } = useContext(TxSecurityContext)
+ const { isLoading, balanceChange, error } = blockaidResponse ?? {}
+
+ const totalBalanceChanges = balanceChange
+ ? balanceChange.reduce((prev, current) => prev + current.in.length + current.out.length, 0)
+ : 0
- if (isLoading && !balanceChange) {
+ if (isLoading) {
return (
{
)
}
-
+ if (error) {
+ return (
+
+ Could not calculate balance changes.
+
+ )
+ }
if (totalBalanceChanges === 0) {
return (
@@ -131,18 +156,22 @@ const BalanceChanges = () => {
return (
<>
- {balanceChange?.in.map((change, idx) => (
-
- ))}
- {balanceChange?.out.map((change, idx) => (
-
+ {balanceChange?.map((change, assetIdx) => (
+ <>
+ {change.in.map((diff, changeIdx) => (
+
+ ))}
+ {change.out.map((diff, changeIdx) => (
+
+ ))}
+ >
))}
>
)
}
-export const RedefineBalanceChanges = () => {
+export const BlockaidBalanceChanges = () => {
const isFeatureEnabled = useHasFeature(FEATURES.RISK_MITIGATION)
if (!isFeatureEnabled) {
diff --git a/src/components/tx/security/blockaid/BlockaidHint.tsx b/src/components/tx/security/blockaid/BlockaidHint.tsx
new file mode 100644
index 000000000..8c7e2192a
--- /dev/null
+++ b/src/components/tx/security/blockaid/BlockaidHint.tsx
@@ -0,0 +1,13 @@
+import { List, ListItem, Typography } from '@mui/material'
+
+export const BlockaidHint = ({ warnings }: { warnings: string[] }) => {
+ return (
+
+ {warnings.map((warning) => (
+
+ {warning}
+
+ ))}
+
+ )
+}
diff --git a/src/components/tx/security/blockaid/__tests__/useBlockaid.test.ts b/src/components/tx/security/blockaid/__tests__/useBlockaid.test.ts
new file mode 100644
index 000000000..0043375c3
--- /dev/null
+++ b/src/components/tx/security/blockaid/__tests__/useBlockaid.test.ts
@@ -0,0 +1,244 @@
+import * as useChains from '@/hooks/useChains'
+import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
+import * as useWallet from '@/hooks/wallets/useWallet'
+import { SecuritySeverity } from '@/services/security/modules/types'
+import { eip712TypedDataBuilder } from '@/tests/builders/messages'
+import { safeTxBuilder } from '@/tests/builders/safeTx'
+import { parseUnits, toBeHex } from 'ethers'
+import { useBlockaid } from '../useBlockaid'
+import { type AssetDiff, type TransactionScanResponse } from '@/services/security/modules/BlockaidModule/types'
+import { faker } from '@faker-js/faker/locale/af_ZA'
+import useSafeInfo from '@/hooks/useSafeInfo'
+import { safeInfoBuilder } from '@/tests/builders/safe'
+import { CLASSIFICATION_MAPPING, REASON_MAPPING } from '..'
+import { renderHook, waitFor } from '@/tests/test-utils'
+
+const setupFetchStub = (data: any) => () => {
+ return Promise.resolve({
+ json: () => Promise.resolve(data),
+ status: 200,
+ ok: true,
+ })
+}
+
+// Mock BLOCKAID_API
+jest.mock('@/config/constants', () => ({
+ ...jest.requireActual('@/config/constants'),
+ BLOCKAID_CLIENT_ID: 'some-client-id',
+}))
+
+enum TEST_CASES {
+ MESSAGE = 'EIP 712 Message',
+ TRANSACTION = 'SafeTransaction',
+}
+
+jest.mock('@/hooks/useSafeInfo')
+
+const mockUseSafeInfo = useSafeInfo as jest.MockedFunction
+
+describe.each([TEST_CASES.MESSAGE, TEST_CASES.TRANSACTION])('useBlockaid for %s', (testCase) => {
+ let mockUseWallet: jest.SpyInstance
+
+ const mockPayload = testCase === TEST_CASES.TRANSACTION ? safeTxBuilder().build() : eip712TypedDataBuilder().build()
+
+ const mockSafeInfo = safeInfoBuilder().with({ chainId: '1' }).build()
+
+ beforeEach(() => {
+ jest.resetAllMocks()
+ jest.useFakeTimers()
+ mockUseWallet = jest.spyOn(useWallet, 'default')
+ mockUseWallet.mockImplementation(() => null)
+ mockUseSafeInfo.mockReturnValue({
+ safe: { ...mockSafeInfo, deployed: true },
+ safeAddress: mockSafeInfo.address.value,
+ safeLoaded: true,
+ safeLoading: false,
+ })
+
+ global.fetch = jest.fn()
+ })
+
+ it('should return undefined without data', async () => {
+ const { result } = renderHook(() => useBlockaid(undefined))
+
+ await waitFor(() => {
+ expect(result.current[0]).toBeUndefined()
+ expect(result.current[1]).toBeUndefined()
+ expect(result.current[2]).toBeFalsy()
+ })
+ })
+
+ it('should return undefined without connected wallet', async () => {
+ const { result } = renderHook(() => useBlockaid(mockPayload))
+
+ await waitFor(() => {
+ expect(result.current[0]).toBeUndefined()
+ expect(result.current[1]).toBeUndefined()
+ expect(result.current[2]).toBeFalsy()
+ })
+ })
+
+ it('should return undefined without feature enabled', async () => {
+ const walletAddress = toBeHex('0x1', 20)
+
+ mockUseWallet.mockImplementation(() => ({
+ address: walletAddress,
+ chainId: '1',
+ label: 'Testwallet',
+ provider: {} as any,
+ }))
+
+ jest.spyOn(useChains, 'useHasFeature').mockReturnValue(false)
+
+ const { result } = renderHook(() => useBlockaid(mockPayload))
+
+ await waitFor(() => {
+ expect(result.current[0]).toBeUndefined()
+ expect(result.current[1]).toEqual(undefined)
+ expect(result.current[2]).toBeFalsy()
+ })
+ })
+
+ it('should handle request errors', async () => {
+ const walletAddress = toBeHex('0x1', 20)
+
+ mockUseWallet.mockImplementation(() => ({
+ address: walletAddress,
+ chainId: '1',
+ label: 'Testwallet',
+ provider: {} as any,
+ }))
+
+ jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
+
+ const mockFetch = jest.spyOn(global, 'fetch')
+ mockFetch.mockImplementation(() => Promise.reject({ message: '403 not authorized' }))
+
+ const { result } = renderHook(() => useBlockaid(mockPayload))
+
+ await waitFor(() => {
+ expect(result.current[0]).toBeUndefined()
+ expect(result.current[1]).toEqual(new Error('Unavailable'))
+ expect(result.current[2]).toBeFalsy()
+ })
+ })
+
+ it('should handle failed simulations', async () => {
+ const walletAddress = toBeHex('0x1', 20)
+
+ mockUseWallet.mockImplementation(() => ({
+ address: walletAddress,
+ chainId: '1',
+ label: 'Testwallet',
+ provider: {} as any,
+ }))
+
+ jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
+
+ const mockBlockaidResponse: TransactionScanResponse = {
+ chain: '1',
+ block: faker.number.int().toString(),
+ simulation: {
+ status: 'Error',
+ error: 'Simulation failed: GS13',
+ },
+ validation: {
+ status: 'Success',
+ features: [],
+ result_type: 'Benign',
+ classification: '',
+ description: '',
+ reason: '',
+ },
+ }
+
+ global.fetch = jest.fn().mockImplementation(setupFetchStub(mockBlockaidResponse))
+
+ const { result } = renderHook(() => useBlockaid(mockPayload))
+
+ await waitFor(() => {
+ expect(result.current[0]).toBeDefined()
+ expect(result.current[1]).toEqual(new Error('Simulation failed'))
+ expect(result.current[2]).toBeFalsy()
+ })
+ })
+
+ it('should return the redefine issues and balance change', async () => {
+ const walletAddress = toBeHex('0x1', 20)
+
+ const accountDiff = {
+ asset: {
+ address: faker.finance.ethereumAddress(),
+ decimals: 18,
+ type: 'ERC20',
+ name: 'Safe Token',
+ symbol: 'SAFE',
+ },
+ out: [
+ {
+ raw_value: parseUnits('1', 18).toString(),
+ value: '1',
+ },
+ ],
+ in: [],
+ } as AssetDiff
+
+ const mockBlockaidResponse: TransactionScanResponse = {
+ chain: '1',
+ block: faker.number.int().toString(),
+ simulation: {
+ status: 'Success',
+ assets_diffs: {
+ [mockSafeInfo.address.value]: [accountDiff],
+ },
+ account_summary: {
+ assets_diffs: [accountDiff],
+ exposures: [],
+ total_usd_diff: {
+ in: '0',
+ out: '0',
+ total: '0',
+ },
+ total_usd_exposure: {},
+ },
+ address_details: {},
+ exposures: {},
+ total_usd_diff: {},
+ total_usd_exposure: {},
+ },
+ validation: {
+ status: 'Success',
+ features: [],
+ result_type: 'Malicious',
+ classification: Object.keys(CLASSIFICATION_MAPPING)[0],
+ description: 'Malicious tx detected',
+ reason: Object.keys(REASON_MAPPING)[0],
+ },
+ }
+
+ mockUseWallet.mockImplementation(() => ({
+ address: walletAddress,
+ chainId: '1',
+ label: 'Testwallet',
+ provider: {} as any,
+ }))
+
+ jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
+
+ global.fetch = jest.fn().mockImplementation(setupFetchStub(mockBlockaidResponse))
+
+ const mockFetch = jest.spyOn(global, 'fetch')
+ const { result } = renderHook(() => useBlockaid(mockPayload))
+
+ await waitFor(() => {
+ expect(result.current[1]).toBeUndefined()
+ expect(result.current[0]).toBeDefined()
+ const response = result.current[0]
+ expect(response?.severity).toEqual(SecuritySeverity.HIGH)
+ expect(response?.payload?.issues).toHaveLength(0)
+ expect(response?.payload?.balanceChange[0]).toEqual(accountDiff)
+ expect(result.current[2]).toBeFalsy()
+ expect(mockFetch).toHaveBeenCalledTimes(1)
+ })
+ })
+})
diff --git a/src/components/tx/security/blockaid/index.tsx b/src/components/tx/security/blockaid/index.tsx
new file mode 100644
index 000000000..79cfe8460
--- /dev/null
+++ b/src/components/tx/security/blockaid/index.tsx
@@ -0,0 +1,223 @@
+import { useContext } from 'react'
+import { TxSecurityContext, type TxSecurityContextProps } from '@/components/tx/security/shared/TxSecurityContext'
+import groupBy from 'lodash/groupBy'
+import { Alert, AlertTitle, Box, Checkbox, FormControlLabel, Stack, Typography } from '@mui/material'
+import { FEATURES } from '@/utils/chains'
+import { useHasFeature } from '@/hooks/useChains'
+import { ErrorBoundary } from '@sentry/react'
+import css from './styles.module.css'
+
+import Track from '@/components/common/Track'
+import { MODALS_EVENTS } from '@/services/analytics'
+
+import BlockaidIcon from '@/public/images/transactions/blockaid-icon.svg'
+import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
+import { type SecurityWarningProps, mapSecuritySeverity } from '../utils'
+import { BlockaidHint } from './BlockaidHint'
+import Warning from '@/public/images/notifications/alert.svg'
+import { SecuritySeverity } from '@/services/security/modules/types'
+
+export const REASON_MAPPING: Record = {
+ raw_ether_transfer: 'transfers native currency',
+ signature_farming: 'is a raw signed transaction',
+ transfer_farming: 'transfers tokens',
+ approval_farming: 'approves erc20 tokens',
+ set_approval_for_all: 'approves all tokens of the account',
+ permit_farming: 'authorizes access or permissions',
+ seaport_farming: 'authorizes transfer of assets via Opeansea marketplace',
+ blur_farming: 'authorizes transfer of assets via Blur marketplace',
+ delegatecall_execution: 'involves a delegate call',
+}
+
+export const CLASSIFICATION_MAPPING: Record = {
+ known_malicious: 'to a known malicious address',
+ unverified_contract: 'to an unverified contract',
+ new_address: 'to a new address',
+ untrusted_address: 'to an untrusted address',
+ address_poisoning: 'to a poisoned address',
+ losing_mint: 'resulting in a mint for a new token with a significantly higher price than the known price',
+ losing_assets: 'resulting in a loss of assets without any compensation',
+ losing_trade: 'resulting in a losing trade',
+ drainer_contract: 'to a known drainer contract',
+ user_mistake: 'resulting in a loss of assets due to an innocent mistake',
+ gas_farming_attack: 'resulting in a waste of the account addressโ gas to generate tokens for a scammer',
+ other: 'resulting in a malicious outcome',
+}
+
+const BlockaidResultWarning = ({
+ blockaidResponse,
+ severityProps,
+ needsRiskConfirmation,
+ isRiskConfirmed,
+ isTransaction,
+ toggleConfirmation,
+}: {
+ blockaidResponse?: TxSecurityContextProps['blockaidResponse']
+ severityProps?: SecurityWarningProps
+ needsRiskConfirmation: boolean
+ isRiskConfirmed: boolean
+ isTransaction: boolean
+ toggleConfirmation: () => void
+}) => {
+ return (
+
+ {blockaidResponse && blockaidResponse.severity !== SecuritySeverity.NONE && (
+ <>
+ }
+ className={css.customAlert}
+ sx={
+ needsRiskConfirmation
+ ? {
+ borderBottomLeftRadius: '0px',
+ borderBottomRightRadius: '0px',
+ }
+ : undefined
+ }
+ >
+
+
+
+
+
+ {needsRiskConfirmation && (
+
+
+
+ I understand the risks and would like to sign this {isTransaction ? 'transaction' : 'message'}
+
+ }
+ control={ }
+ />
+
+
+ )}
+
+
+ Powered by
+
+
+
+ >
+ )}
+
+ )
+}
+
+const ResultDescription = ({
+ description,
+ reason,
+ classification,
+}: {
+ description: string | undefined
+ reason: string | undefined
+ classification: string | undefined
+}) => {
+ let text: string | undefined = ''
+ if (reason && classification && REASON_MAPPING[reason] && CLASSIFICATION_MAPPING[classification]) {
+ text = `The transaction ${REASON_MAPPING[reason]} ${CLASSIFICATION_MAPPING[classification]}.`
+ } else {
+ text = description
+ }
+
+ return (
+
+ {text ?? 'The transaction is malicious.'}
+
+ )
+}
+
+const BlockaidError = () => {
+ return (
+ } className={css.customAlert}>
+
+
+ Proceed with caution
+
+
+
+ The transaction could not be checked for security alerts. Verify the details and addresses before proceeding.
+
+
+
+ )
+}
+
+export const Blockaid = () => {
+ const isFeatureEnabled = useHasFeature(FEATURES.RISK_MITIGATION)
+
+ if (!isFeatureEnabled) {
+ return null
+ }
+
+ return (
+ Error showing scan result}>
+
+
+ )
+}
+
+const BlockaidWarning = () => {
+ const { blockaidResponse, setIsRiskConfirmed, needsRiskConfirmation, isRiskConfirmed } = useContext(TxSecurityContext)
+ const { severity, isLoading, error } = blockaidResponse ?? {}
+
+ const { safeTx } = useContext(SafeTxContext)
+
+ // We either scan a tx or a message if tx is undefined
+ const isTransaction = !!safeTx
+
+ const severityProps = severity !== undefined ? mapSecuritySeverity[severity] : undefined
+
+ const toggleConfirmation = () => {
+ setIsRiskConfirmed((prev) => !prev)
+ }
+
+ if (error) {
+ return
+ }
+
+ if (isLoading || !blockaidResponse || !blockaidResponse.severity) {
+ return null
+ }
+
+ return (
+
+ )
+}
+
+export const BlockaidMessage = () => {
+ const { blockaidResponse } = useContext(TxSecurityContext)
+ if (!blockaidResponse) {
+ return null
+ }
+
+ const { warnings } = blockaidResponse
+
+ /* Evaluate security warnings */
+ const groupedShownWarnings = groupBy(warnings, (warning) => warning.severity)
+ const sortedSeverities = Object.keys(groupedShownWarnings).sort((a, b) => (Number(a) < Number(b) ? 1 : -1))
+
+ if (sortedSeverities.length === 0) return null
+
+ return (
+
+ {sortedSeverities.map((key) => (
+ warning.description)} />
+ ))}
+
+ )
+}
diff --git a/src/components/tx/security/redefine/styles.module.css b/src/components/tx/security/blockaid/styles.module.css
similarity index 75%
rename from src/components/tx/security/redefine/styles.module.css
rename to src/components/tx/security/blockaid/styles.module.css
index 75fee7dc1..da6d72406 100644
--- a/src/components/tx/security/redefine/styles.module.css
+++ b/src/components/tx/security/blockaid/styles.module.css
@@ -11,8 +11,9 @@
padding: 0;
}
-.wrapperBox :global .MuiAccordion-root.Mui-expanded {
- border-color: var(--color-border-light) !important;
+.wrapperBox :global .MuiAccordion-root.Mui-expanded,
+.wrapperBox :global .MuiAccordion-root {
+ border: none;
}
.wrapperBox {
@@ -98,3 +99,21 @@
position: absolute;
right: -58px;
}
+
+.resultAccordion :global .Mui-expanded.MuiAccordionSummary-root {
+ background: inherit;
+}
+
+.resultAccordion :global .Mui-expanded {
+ border: var(--color-border-light);
+}
+
+.customAlert {
+ border: none;
+}
+
+.riskConfirmationBlock {
+ background-color: var(--color-error-light);
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
diff --git a/src/components/tx/security/blockaid/useBlockaid.ts b/src/components/tx/security/blockaid/useBlockaid.ts
new file mode 100644
index 000000000..5ea657de2
--- /dev/null
+++ b/src/components/tx/security/blockaid/useBlockaid.ts
@@ -0,0 +1,58 @@
+import useAsync, { type AsyncResult } from '@/hooks/useAsync'
+import { useHasFeature } from '@/hooks/useChains'
+import useSafeInfo from '@/hooks/useSafeInfo'
+import useWallet from '@/hooks/wallets/useWallet'
+import { MODALS_EVENTS, trackEvent } from '@/services/analytics'
+import type { SecurityResponse } from '@/services/security/modules/types'
+import { FEATURES } from '@/utils/chains'
+import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
+import { useEffect, useMemo } from 'react'
+
+import type { EIP712TypedData } from '@safe-global/safe-gateway-typescript-sdk'
+import { BlockaidModule, type BlockaidModuleResponse } from '@/services/security/modules/BlockaidModule'
+
+const BlockaidModuleInstance = new BlockaidModule()
+
+const DEFAULT_ERROR_MESSAGE = 'Unavailable'
+
+export const useBlockaid = (
+ data: SafeTransaction | EIP712TypedData | undefined,
+): AsyncResult> => {
+ const { safe, safeAddress } = useSafeInfo()
+ const wallet = useWallet()
+ const isFeatureEnabled = useHasFeature(FEATURES.RISK_MITIGATION)
+
+ const [blockaidPayload, blockaidErrors, blockaidLoading] = useAsync>(
+ () => {
+ if (!isFeatureEnabled || !data || !wallet?.address) {
+ return
+ }
+
+ return BlockaidModuleInstance.scanTransaction({
+ chainId: Number(safe.chainId),
+ data,
+ safeAddress,
+ walletAddress: wallet.address,
+ threshold: safe.threshold,
+ })
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [safe.chainId, safe.threshold, safeAddress, data, wallet?.address, isFeatureEnabled],
+ false,
+ )
+
+ const loading = blockaidLoading
+
+ useEffect(() => {
+ if (!loading && blockaidPayload) {
+ trackEvent({ ...MODALS_EVENTS.BLOCKAID_RESULT, label: blockaidPayload.severity })
+ }
+ }, [loading, blockaidPayload])
+
+ const errorMsg = useMemo(
+ () => (blockaidErrors ? new Error(DEFAULT_ERROR_MESSAGE) : blockaidPayload?.payload?.error),
+
+ [blockaidErrors, blockaidPayload],
+ )
+ return [blockaidPayload, errorMsg, loading]
+}
diff --git a/src/components/tx/security/redefine/RedefineHint.tsx b/src/components/tx/security/redefine/RedefineHint.tsx
deleted file mode 100644
index 8030ec9ec..000000000
--- a/src/components/tx/security/redefine/RedefineHint.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { SecuritySeverity } from '@/services/security/modules/types'
-import { Alert, Box, List, ListItem, SvgIcon, Typography } from '@mui/material'
-import css from 'src/components/tx/security/redefine/styles.module.css'
-import AlertIcon from '@/public/images/notifications/alert.svg'
-import { mapRedefineSeverity } from '@/components/tx/security/redefine/useRedefine'
-
-export const RedefineHint = ({ severity, warnings }: { severity: SecuritySeverity; warnings: string[] }) => {
- const severityProps = mapRedefineSeverity[severity]
- const pluralizedLabel = (
- <>
- {warnings.length} {severityProps.label}
- {warnings.length > 1 ? 's' : ''}
- >
- )
-
- return (
- <>
- palette[severityProps.color].background }}
- icon={
- palette[severityProps.color].main,
- },
- }}
- />
- }
- >
- {severity !== SecuritySeverity.NONE && (
-
- {pluralizedLabel}
-
- )}
-
-
- {warnings.map((warning) => (
-
- {warning}
-
- ))}
-
-
-
- >
- )
-}
diff --git a/src/components/tx/security/redefine/index.tsx b/src/components/tx/security/redefine/index.tsx
deleted file mode 100644
index a237f6ba3..000000000
--- a/src/components/tx/security/redefine/index.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-import { useContext, useEffect, useRef } from 'react'
-import { mapRedefineSeverity } from '@/components/tx/security/redefine/useRedefine'
-import { TxSecurityContext } from '@/components/tx/security/shared/TxSecurityContext'
-import { SecuritySeverity } from '@/services/security/modules/types'
-import groupBy from 'lodash/groupBy'
-import { Alert, Box, Checkbox, FormControlLabel, Paper, SvgIcon, Tooltip, Typography } from '@mui/material'
-import ExternalLink from '@/components/common/ExternalLink'
-import { FEATURES } from '@/utils/chains'
-import { useHasFeature } from '@/hooks/useChains'
-import { ErrorBoundary } from '@sentry/react'
-import { REDEFINE_ARTICLE, REDEFINE_SIMULATION_URL } from '@/config/constants'
-import css from 'src/components/tx/security/redefine/styles.module.css'
-import sharedCss from '@/components/tx/security/shared/styles.module.css'
-import RedefineLogoDark from '@/public/images/transactions/redefine-dark-mode.png'
-import RedefineLogo from '@/public/images/transactions/redefine.png'
-import Track from '@/components/common/Track'
-import { MODALS_EVENTS } from '@/services/analytics'
-import { useDarkMode } from '@/hooks/useDarkMode'
-import CircularProgress from '@mui/material/CircularProgress'
-import { RedefineHint } from '@/components/tx/security/redefine/RedefineHint'
-import InfoIcon from '@/public/images/notifications/info.svg'
-import Image from 'next/image'
-import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
-
-const MAX_SHOWN_WARNINGS = 3
-
-const RedefineBlock = () => {
- const { severity, isLoading, error, needsRiskConfirmation, isRiskConfirmed, setIsRiskConfirmed, isRiskIgnored } =
- useContext(TxSecurityContext)
-
- const { safeTx } = useContext(SafeTxContext)
-
- // We either scan a tx or a message if tx is undefined
- const isTransaction = !!safeTx
- const checkboxRef = useRef(null)
-
- const isDarkMode = useDarkMode()
- const severityProps = severity !== undefined ? mapRedefineSeverity[severity] : undefined
-
- const toggleConfirmation = () => {
- setIsRiskConfirmed((prev) => !prev)
- }
-
- // Highlight checkbox if user tries to submit transaction without confirming risks
- useEffect(() => {
- if (isRiskIgnored) {
- checkboxRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' })
- }
- }, [isRiskIgnored, checkboxRef])
-
- return (
-
-
-
-
- Scan for risks
-
- This {isTransaction ? 'transaction' : 'message'} has been automatically scanned for risks to help
- prevent scams.
-
- Learn more about security scans
-
- .
- >
- }
- arrow
- placement="top"
- >
-
-
-
-
-
-
-
- Powered by{' '}
-
-
-
-
-
-
-
- {isLoading ? (
- palette.text.secondary,
- }}
- />
- ) : severityProps ? (
-
-
- {severityProps.label}
-
- ) : error ? (
-
- {error.message}
-
- ) : null}
-
-
-
- {needsRiskConfirmation && (
-
-
- }
- className={isRiskIgnored ? css.checkboxError : ''}
- />
-
-
- )}
-
-
- )
-}
-
-export const Redefine = () => {
- const isFeatureEnabled = useHasFeature(FEATURES.RISK_MITIGATION)
-
- if (!isFeatureEnabled) {
- return null
- }
-
- return (
- Error showing scan result}>
-
-
- )
-}
-
-export const RedefineMessage = () => {
- const { severity, warnings, simulationUuid } = useContext(TxSecurityContext)
-
- /* Evaluate security warnings */
- const relevantWarnings = warnings.filter((warning) => warning.severity !== SecuritySeverity.NONE)
- const shownWarnings = relevantWarnings.slice(0, MAX_SHOWN_WARNINGS)
- const hiddenWarningCount = warnings.length - shownWarnings.length
- const hiddenMaxSeverity =
- hiddenWarningCount > 0 ? relevantWarnings[MAX_SHOWN_WARNINGS]?.severity : SecuritySeverity.NONE
-
- const groupedShownWarnings = groupBy(shownWarnings, (warning) => warning.severity)
- const sortedSeverities = Object.keys(groupedShownWarnings).sort((a, b) => (Number(a) < Number(b) ? 1 : -1))
-
- if (sortedSeverities.length === 0 && hiddenWarningCount === 0 && !simulationUuid) return null
-
- return (
-
- {sortedSeverities.map((key) => (
- warning.description.short)}
- />
- ))}
-
- {hiddenWarningCount > 0 && (
- 1 ? 's' : ''}`]}
- />
- )}
-
- {simulationUuid && (
-
- {severity === SecuritySeverity.NONE && (
-
- {mapRedefineSeverity[severity].label}
-
- )}
- For a comprehensive risk overview,{' '}
-
- see the full report on Redefine
-
-
- )}
-
- )
-}
diff --git a/src/components/tx/security/redefine/useRedefine.test.ts b/src/components/tx/security/redefine/useRedefine.test.ts
deleted file mode 100644
index e77f7cc91..000000000
--- a/src/components/tx/security/redefine/useRedefine.test.ts
+++ /dev/null
@@ -1,330 +0,0 @@
-import { act, renderHook, waitFor } from '@/tests/test-utils'
-import { REDEFINE_RETRY_TIMEOUT, useRedefine } from './useRedefine'
-import * as useWallet from '@/hooks/wallets/useWallet'
-import * as useChains from '@/hooks/useChains'
-import type { ConnectedWallet } from '@/hooks/wallets/useOnboard'
-import { toBeHex } from 'ethers'
-import { type RedefineResponse, REDEFINE_ERROR_CODES } from '@/services/security/modules/RedefineModule'
-import { SecuritySeverity } from '@/services/security/modules/types'
-import { safeTxBuilder } from '@/tests/builders/safeTx'
-import { eip712TypedDataBuilder } from '@/tests/builders/messages'
-
-const setupFetchStub = (data: any) => (_url: string) => {
- return Promise.resolve({
- json: () => Promise.resolve(data),
- status: 200,
- ok: true,
- })
-}
-
-enum TEST_CASES {
- MESSAGE = 'EIP 712 Message',
- TRANSACTION = 'SafeTransaction',
-}
-
-// Mock REDEFINE_API
-jest.mock('@/config/constants', () => ({
- ...jest.requireActual('@/config/constants'),
- REDEFINE_API: 'https://redefine-api.test',
-}))
-
-describe.each([TEST_CASES.MESSAGE, TEST_CASES.TRANSACTION])('useRedefine for %s', (testCase) => {
- let mockUseWallet: jest.SpyInstance
-
- const mockPayload = testCase === TEST_CASES.TRANSACTION ? safeTxBuilder().build() : eip712TypedDataBuilder().build()
-
- beforeEach(() => {
- jest.resetAllMocks()
- jest.useFakeTimers()
- mockUseWallet = jest.spyOn(useWallet, 'default')
- mockUseWallet.mockImplementation(() => null)
-
- global.fetch = jest.fn()
- })
-
- it('should return undefined without data', async () => {
- const { result } = renderHook(() => useRedefine(undefined))
-
- await waitFor(() => {
- expect(result.current[0]).toBeUndefined()
- expect(result.current[1]).toBeUndefined()
- expect(result.current[2]).toBeFalsy()
- })
- })
-
- it('should return undefined without connected wallet', async () => {
- const { result } = renderHook(() => useRedefine(mockPayload))
-
- await waitFor(() => {
- expect(result.current[0]).toBeUndefined()
- expect(result.current[1]).toBeUndefined()
- expect(result.current[2]).toBeFalsy()
- })
- })
-
- it('should return undefined without feature enabled', async () => {
- const walletAddress = toBeHex('0x1', 20)
-
- mockUseWallet.mockImplementation(() => ({
- address: walletAddress,
- chainId: '1',
- label: 'Testwallet',
- provider: {} as any,
- }))
-
- jest.spyOn(useChains, 'useHasFeature').mockReturnValue(false)
-
- const { result } = renderHook(() => useRedefine(mockPayload))
-
- await waitFor(() => {
- expect(result.current[0]).toBeUndefined()
- expect(result.current[1]).toEqual(undefined)
- expect(result.current[2]).toBeFalsy()
- })
- })
-
- it('should handle request errors', async () => {
- const walletAddress = toBeHex('0x1', 20)
-
- mockUseWallet.mockImplementation(() => ({
- address: walletAddress,
- chainId: '1',
- label: 'Testwallet',
- provider: {} as any,
- }))
-
- jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
-
- const mockFetch = jest.spyOn(global, 'fetch')
- mockFetch.mockImplementation(() => Promise.reject({ message: '403 not authorized' }))
-
- const { result } = renderHook(() => useRedefine(mockPayload))
-
- await waitFor(() => {
- expect(result.current[0]).toBeUndefined()
- expect(result.current[1]).toEqual(new Error('Unavailable'))
- expect(result.current[2]).toBeFalsy()
- })
- })
-
- it('should return the redefine issues', async () => {
- const walletAddress = toBeHex('0x1', 20)
-
- const mockRedefineResponse: RedefineResponse = {
- data: {
- insights: {
- verdict: {
- code: 1,
- label: 'LOW',
- },
- issues: [
- {
- category: 'SOME_FAKE_WARNING',
- description: {
- short: 'Test',
- long: 'Just a test',
- },
- severity: {
- code: 1,
- label: 'LOW',
- },
- },
- ],
- },
- simulation: {
- block: '123',
- time: '2023-01-01-23:00',
- uuid: '123-456-789',
- },
- balanceChange: {
- in: [
- {
- address: toBeHex('0x2', 20),
- amount: {
- normalizedValue: '0.1',
- value: '100000000000000000',
- },
- decimals: 18,
- name: 'Test',
- symbol: 'TST',
- type: 'ERC20',
- },
- ],
- out: [],
- },
- },
- errors: [],
- }
-
- mockUseWallet.mockImplementation(() => ({
- address: walletAddress,
- chainId: '1',
- label: 'Testwallet',
- provider: {} as any,
- }))
-
- jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
-
- global.fetch = jest.fn().mockImplementation(setupFetchStub(mockRedefineResponse))
-
- const mockFetch = jest.spyOn(global, 'fetch')
- const { result } = renderHook(() => useRedefine(mockPayload))
-
- await waitFor(() => {
- expect(result.current[1]).toBeUndefined()
- expect(result.current[0]).toBeDefined()
- const response = result.current[0]
- expect(response?.severity).toEqual(SecuritySeverity.LOW)
- expect(response?.payload?.issues).toHaveLength(1)
- expect(response?.payload?.balanceChange?.in).toHaveLength(1)
- expect(result.current[2]).toBeFalsy()
- expect(mockFetch).toHaveBeenCalledTimes(1)
- })
-
- // Should not poll again without error 1000
- await act(() => {
- jest.advanceTimersByTime(REDEFINE_RETRY_TIMEOUT)
- })
-
- expect(mockFetch).toHaveBeenCalledTimes(1)
- })
-
- it('should poll again on error code 1000', async () => {
- const walletAddress = toBeHex('0x1', 20)
-
- const mockPartialRedefineResponse: RedefineResponse = {
- data: {
- insights: {
- verdict: {
- code: 1,
- label: 'LOW',
- },
- issues: [
- {
- category: 'SOME_FAKE_WARNING',
- description: {
- short: 'Test',
- long: 'Just a test',
- },
- severity: {
- code: 1,
- label: 'LOW',
- },
- },
- ],
- },
- simulation: {
- block: '123',
- time: '2023-01-01-23:00',
- uuid: '123-456-789',
- },
- },
- errors: [
- {
- code: REDEFINE_ERROR_CODES.ANALYSIS_IN_PROGRESS,
- message: 'Analysis still in progress.',
- },
- ],
- }
-
- const mockFullRedefineResponse: RedefineResponse = {
- data: {
- balanceChange: {
- in: [
- {
- address: toBeHex('0x2', 20),
- amount: {
- normalizedValue: '0.1',
- value: '100000000000000000',
- },
- decimals: 18,
- name: 'Test',
- symbol: 'TST',
- type: 'ERC20',
- },
- ],
- out: [],
- },
- simulation: {
- block: '123',
- time: '2023-01-01-23:00',
- uuid: '123-456-789',
- },
- insights: {
- verdict: {
- code: 1,
- label: 'LOW',
- },
- issues: [
- {
- category: 'SOME_FAKE_WARNING',
- description: {
- short: 'Test',
- long: 'Just a test',
- },
- severity: {
- code: 1,
- label: 'LOW',
- },
- },
- ],
- },
- },
- errors: [],
- }
-
- mockUseWallet.mockImplementation(() => ({
- address: walletAddress,
- chainId: '1',
- label: 'Testwallet',
- provider: {} as any,
- }))
-
- jest.spyOn(useChains, 'useHasFeature').mockReturnValue(true)
-
- global.fetch = jest.fn().mockImplementation(setupFetchStub(mockPartialRedefineResponse))
-
- let mockFetch = jest.spyOn(global, 'fetch')
- const { result } = renderHook(() => useRedefine(mockPayload))
-
- await waitFor(() => {
- expect(result.current[0]).toBeDefined()
- const response = result.current[0]
- expect(response?.severity).toEqual(SecuritySeverity.LOW)
- expect(response?.payload?.issues).toHaveLength(1)
- expect(response?.payload?.balanceChange).toBeUndefined()
- expect(result.current[1]).toBeUndefined()
- expect(result.current[2]).toBeTruthy()
-
- expect(mockFetch).toHaveBeenCalledTimes(1)
- })
-
- global.fetch = jest.fn().mockImplementation(setupFetchStub(mockFullRedefineResponse))
-
- mockFetch = jest.spyOn(global, 'fetch')
-
- // Should poll again on error 1000
- await act(() => {
- jest.advanceTimersByTime(REDEFINE_RETRY_TIMEOUT)
- })
-
- await waitFor(() => {
- expect(result.current[0]).toBeDefined()
- const response = result.current[0]
- expect(response?.severity).toEqual(SecuritySeverity.LOW)
- expect(response?.payload?.issues).toHaveLength(1)
- expect(response?.payload?.balanceChange?.in).toHaveLength(1)
- expect(result.current[1]).toBeUndefined()
- expect(result.current[2]).toBeFalsy()
-
- expect(mockFetch).toHaveBeenCalledTimes(1)
- })
-
- // Should not poll again after full result without error 1000
- // Should not poll again without error 1000
- await act(() => {
- jest.advanceTimersByTime(REDEFINE_RETRY_TIMEOUT)
- })
- expect(mockFetch).toHaveBeenCalledTimes(1)
- })
-})
diff --git a/src/components/tx/security/redefine/useRedefine.ts b/src/components/tx/security/redefine/useRedefine.ts
deleted file mode 100644
index d1f6d3b62..000000000
--- a/src/components/tx/security/redefine/useRedefine.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import useAsync, { type AsyncResult } from '@/hooks/useAsync'
-import { useHasFeature } from '@/hooks/useChains'
-import useSafeInfo from '@/hooks/useSafeInfo'
-import useWallet from '@/hooks/wallets/useWallet'
-import { MODALS_EVENTS, trackEvent } from '@/services/analytics'
-import {
- RedefineModule,
- type RedefineModuleResponse,
- REDEFINE_ERROR_CODES,
-} from '@/services/security/modules/RedefineModule'
-import type { SecurityResponse } from '@/services/security/modules/types'
-import { FEATURES } from '@/utils/chains'
-import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
-import { useState, useEffect, useMemo, type ComponentType } from 'react'
-import { type AlertColor } from '@mui/material'
-import { SecuritySeverity } from '@/services/security/modules/types'
-import CloseIcon from '@/public/images/common/close.svg'
-import InfoIcon from '@/public/images/notifications/info.svg'
-import CheckIcon from '@/public/images/common/check.svg'
-import type { EIP712TypedData } from '@safe-global/safe-gateway-typescript-sdk'
-
-export const REDEFINE_RETRY_TIMEOUT = 2_000
-const RedefineModuleInstance = new RedefineModule()
-
-const DEFAULT_ERROR_MESSAGE = 'Unavailable'
-
-const CRITICAL_ERRORS: Record = {
- [1001]: 'Simulation failed',
- [2000]: DEFAULT_ERROR_MESSAGE,
- [3000]: DEFAULT_ERROR_MESSAGE,
-}
-
-type SecurityWarningProps = {
- color: AlertColor
- icon: ComponentType
- label: string
- action?: string
-}
-
-const ACTION_REJECT = 'Reject this transaction'
-const ACTION_REVIEW = 'Review before processing'
-
-export const mapRedefineSeverity: Record = {
- [SecuritySeverity.CRITICAL]: {
- action: ACTION_REJECT,
- color: 'error',
- icon: CloseIcon,
- label: 'critical risk',
- },
- [SecuritySeverity.HIGH]: {
- action: ACTION_REJECT,
- color: 'error',
- icon: CloseIcon,
- label: 'high risk',
- },
- [SecuritySeverity.MEDIUM]: {
- action: ACTION_REVIEW,
- color: 'warning',
- icon: InfoIcon,
- label: 'medium risk',
- },
- [SecuritySeverity.LOW]: {
- action: ACTION_REVIEW,
- color: 'warning',
- icon: InfoIcon,
- label: 'small risk',
- },
- [SecuritySeverity.NONE]: {
- color: 'success',
- icon: CheckIcon,
- label: 'No issues found',
- },
-}
-
-export const useRedefine = (
- data: SafeTransaction | EIP712TypedData | undefined,
-): AsyncResult> => {
- const { safe, safeAddress } = useSafeInfo()
- const wallet = useWallet()
- const [retryCounter, setRetryCounter] = useState(0)
- const isFeatureEnabled = useHasFeature(FEATURES.RISK_MITIGATION)
-
- // Memoized JSON data to avoid unnecessary requests
- const jsonData = useMemo(() => {
- if (!data) return ''
- let adjustedData = data
- if ('data' in data) {
- // We need to set nonce to 0 to avoid repeated requests with an updated nonce
- adjustedData = { ...data, data: { ...data.data, nonce: 0 } }
- }
- return JSON.stringify(adjustedData)
- }, [data])
-
- const [redefinePayload, redefineErrors, redefineLoading] = useAsync>(
- () => {
- if (!isFeatureEnabled || !jsonData || !wallet?.address) {
- return
- }
-
- return RedefineModuleInstance.scanTransaction({
- chainId: Number(safe.chainId),
- data: JSON.parse(jsonData),
- safeAddress,
- walletAddress: wallet.address,
- threshold: safe.threshold,
- })
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [safe.chainId, safe.threshold, safeAddress, jsonData, wallet?.address, retryCounter, isFeatureEnabled],
- false,
- )
-
- const isAnalyzing = !!redefinePayload?.payload?.errors.some(
- (error) => error.code === REDEFINE_ERROR_CODES.ANALYSIS_IN_PROGRESS,
- )
-
- const loading = redefineLoading || isAnalyzing
-
- const error = useMemo(() => {
- const simulationErrors =
- redefinePayload?.payload?.errors.filter((error) => CRITICAL_ERRORS[error.code] !== undefined) ?? []
- const errorMessage = redefineErrors
- ? DEFAULT_ERROR_MESSAGE
- : simulationErrors.length > 0
- ? CRITICAL_ERRORS[simulationErrors[0].code]
- : undefined
- return errorMessage ? new Error(errorMessage) : undefined
- }, [redefineErrors, redefinePayload?.payload?.errors])
-
- useEffect(() => {
- if (!isAnalyzing) {
- return
- }
-
- let timeoutId = setTimeout(() => setRetryCounter((prev) => prev + 1), REDEFINE_RETRY_TIMEOUT)
- return () => clearTimeout(timeoutId)
- }, [redefinePayload, isAnalyzing])
-
- useEffect(() => {
- if (!loading && !error && redefinePayload) {
- trackEvent({ ...MODALS_EVENTS.REDEFINE_RESULT, label: redefinePayload.severity })
- }
- }, [error, loading, redefinePayload])
-
- return [redefinePayload, error, loading]
-}
diff --git a/src/components/tx/security/shared/TxSecurityContext.tsx b/src/components/tx/security/shared/TxSecurityContext.tsx
index 62f267b83..edcaa570a 100644
--- a/src/components/tx/security/shared/TxSecurityContext.tsx
+++ b/src/components/tx/security/shared/TxSecurityContext.tsx
@@ -1,16 +1,20 @@
-import { type RedefineModuleResponse } from '@/services/security/modules/RedefineModule'
import { SecuritySeverity } from '@/services/security/modules/types'
import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
import { createContext, type Dispatch, type SetStateAction, useContext, useMemo, useState } from 'react'
-import { useRedefine } from '../redefine/useRedefine'
+import type { BlockaidModuleResponse } from '@/services/security/modules/BlockaidModule'
+import { useBlockaid } from '../blockaid/useBlockaid'
export const defaultSecurityContextValues = {
- warnings: [],
- simulationUuid: undefined,
- balanceChange: undefined,
- severity: SecuritySeverity.NONE,
- isLoading: false,
- error: undefined,
+ blockaidResponse: {
+ warnings: [],
+ description: undefined,
+ classification: undefined,
+ reason: undefined,
+ balanceChange: undefined,
+ severity: SecuritySeverity.NONE,
+ isLoading: false,
+ error: undefined,
+ },
needsRiskConfirmation: false,
isRiskConfirmed: false,
setIsRiskConfirmed: () => {},
@@ -18,41 +22,54 @@ export const defaultSecurityContextValues = {
setIsRiskIgnored: () => {},
}
-export const TxSecurityContext = createContext<{
- warnings: NonNullable
- simulationUuid: string | undefined
- balanceChange: RedefineModuleResponse['balanceChange']
- severity: SecuritySeverity | undefined
- isLoading: boolean
- error: Error | undefined
+export type TxSecurityContextProps = {
+ blockaidResponse:
+ | {
+ description: BlockaidModuleResponse['description']
+ classification: BlockaidModuleResponse['classification']
+ reason: BlockaidModuleResponse['reason']
+ warnings: NonNullable
+ balanceChange: BlockaidModuleResponse['balanceChange'] | undefined
+ severity: SecuritySeverity | undefined
+ isLoading: boolean
+ error: Error | undefined
+ }
+ | undefined
needsRiskConfirmation: boolean
isRiskConfirmed: boolean
setIsRiskConfirmed: Dispatch>
isRiskIgnored: boolean
setIsRiskIgnored: Dispatch>
-}>(defaultSecurityContextValues)
+}
+
+export const TxSecurityContext = createContext(defaultSecurityContextValues)
export const TxSecurityProvider = ({ children }: { children: JSX.Element }) => {
const { safeTx, safeMessage } = useContext(SafeTxContext)
- const [redefineResponse, redefineError, redefineLoading] = useRedefine(safeTx ?? safeMessage)
+ const [blockaidResponse, blockaidError, blockaidLoading] = useBlockaid(safeTx ?? safeMessage)
+
const [isRiskConfirmed, setIsRiskConfirmed] = useState(false)
const [isRiskIgnored, setIsRiskIgnored] = useState(false)
const providedValue = useMemo(
() => ({
- severity: redefineResponse?.severity,
- simulationUuid: redefineResponse?.payload?.simulation?.uuid,
- warnings: redefineResponse?.payload?.issues || [],
- balanceChange: redefineResponse?.payload?.balanceChange,
- error: redefineError,
- isLoading: redefineLoading,
- needsRiskConfirmation: !!redefineResponse && redefineResponse.severity >= SecuritySeverity.HIGH,
+ blockaidResponse: {
+ description: blockaidResponse?.payload?.description,
+ reason: blockaidResponse?.payload?.reason,
+ classification: blockaidResponse?.payload?.classification,
+ severity: blockaidResponse?.severity,
+ warnings: blockaidResponse?.payload?.issues || [],
+ balanceChange: blockaidResponse?.payload?.balanceChange,
+ error: blockaidError,
+ isLoading: blockaidLoading,
+ },
+ needsRiskConfirmation: !!blockaidResponse && blockaidResponse.severity >= SecuritySeverity.HIGH,
isRiskConfirmed,
setIsRiskConfirmed,
isRiskIgnored: isRiskIgnored && !isRiskConfirmed,
setIsRiskIgnored,
}),
- [isRiskConfirmed, isRiskIgnored, redefineError, redefineLoading, redefineResponse],
+ [blockaidError, blockaidLoading, blockaidResponse, isRiskConfirmed, isRiskIgnored],
)
return {children}
diff --git a/src/components/tx/security/tenderly/__tests__/useSimulation.test.ts b/src/components/tx/security/tenderly/__tests__/useSimulation.test.ts
index 9e671ee8f..3280269dc 100644
--- a/src/components/tx/security/tenderly/__tests__/useSimulation.test.ts
+++ b/src/components/tx/security/tenderly/__tests__/useSimulation.test.ts
@@ -1,11 +1,12 @@
+import { act } from 'react'
import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import { act, renderHook, waitFor } from '@/tests/test-utils'
+import { renderHook, waitFor } from '@/tests/test-utils'
import { useSimulation } from '@/components/tx/security/tenderly/useSimulation'
import * as utils from '@/components/tx/security/tenderly/utils'
import { FETCH_STATUS, type TenderlySimulation } from '@/components/tx/security/tenderly/types'
-const setupFetchStub = (data: any) => (_url: string) => {
+const setupFetchStub = (data: any) => () => {
return Promise.resolve({
json: () => Promise.resolve(data),
status: 200,
diff --git a/src/components/tx/security/tenderly/utils.ts b/src/components/tx/security/tenderly/utils.ts
index 96834a9c0..cb0203662 100644
--- a/src/components/tx/security/tenderly/utils.ts
+++ b/src/components/tx/security/tenderly/utils.ts
@@ -122,7 +122,7 @@ export const _getMultiSendCallOnlyPayload = async (
params: MultiSendTransactionSimulationParams,
): Promise> => {
const data = encodeMultiSendData(params.transactions)
- const readOnlyMultiSendContract = await getReadOnlyMultiSendCallOnlyContract(params.safe.chainId, params.safe.version)
+ const readOnlyMultiSendContract = await getReadOnlyMultiSendCallOnlyContract(params.safe.version)
return {
to: await readOnlyMultiSendContract.getAddress(),
diff --git a/src/components/tx/security/useDelegateCallModule.ts b/src/components/tx/security/useDelegateCallModule.ts
index a838a6440..4d0f7c11b 100644
--- a/src/components/tx/security/useDelegateCallModule.ts
+++ b/src/components/tx/security/useDelegateCallModule.ts
@@ -5,22 +5,24 @@ import useSafeInfo from '@/hooks/useSafeInfo'
import { DelegateCallModule } from '@/services/security/modules/DelegateCallModule'
import type { DelegateCallModuleResponse } from '@/services/security/modules/DelegateCallModule'
import type { SecurityResponse } from '@/services/security/modules/types'
+import { useCurrentChain } from '@/hooks/useChains'
const DelegateCallModuleInstance = new DelegateCallModule()
// TODO: Not being used right now
export const useDelegateCallModule = (safeTransaction: SafeTransaction | undefined) => {
const { safe, safeLoaded } = useSafeInfo()
+ const currentChain = useCurrentChain()
return useAsync>(() => {
- if (!safeTransaction || !safeLoaded) {
+ if (!safeTransaction || !safeLoaded || !currentChain) {
return
}
return DelegateCallModuleInstance.scanTransaction({
safeTransaction,
safeVersion: safe.version,
- chainId: safe.chainId,
+ chain: currentChain,
})
- }, [safeTransaction, safeLoaded, safe.version, safe.chainId])
+ }, [safeTransaction, safeLoaded, safe.version, currentChain])
}
diff --git a/src/components/tx/security/utils.ts b/src/components/tx/security/utils.ts
new file mode 100644
index 000000000..cf4b91fa8
--- /dev/null
+++ b/src/components/tx/security/utils.ts
@@ -0,0 +1,47 @@
+import { SecuritySeverity } from '@/services/security/modules/types'
+import CloseIcon from '@/public/images/common/close.svg'
+import InfoIcon from '@/public/images/notifications/info.svg'
+import type { ComponentType } from 'react'
+import type { AlertColor } from '@mui/material'
+
+const ACTION_REJECT = 'Reject this transaction'
+const ACTION_REVIEW = 'Review before processing'
+
+export type SecurityWarningProps = {
+ color: AlertColor
+ icon: ComponentType
+ label: string
+ action?: string
+}
+
+export const mapSecuritySeverity: Record = {
+ [SecuritySeverity.CRITICAL]: {
+ action: ACTION_REJECT,
+ color: 'error',
+ icon: CloseIcon,
+ label: 'critical risk',
+ },
+ [SecuritySeverity.HIGH]: {
+ action: ACTION_REJECT,
+ color: 'error',
+ icon: CloseIcon,
+ label: 'high risk',
+ },
+ [SecuritySeverity.MEDIUM]: {
+ action: ACTION_REVIEW,
+ color: 'warning',
+ icon: InfoIcon,
+ label: 'warning',
+ },
+ [SecuritySeverity.LOW]: {
+ action: ACTION_REVIEW,
+ color: 'warning',
+ icon: InfoIcon,
+ label: 'warning',
+ },
+ [SecuritySeverity.NONE]: {
+ color: 'info',
+ icon: InfoIcon,
+ label: 'info',
+ },
+}
diff --git a/src/components/welcome/MyAccounts/AccountItem.tsx b/src/components/welcome/MyAccounts/AccountItem.tsx
index 57874126e..49e426bfb 100644
--- a/src/components/welcome/MyAccounts/AccountItem.tsx
+++ b/src/components/welcome/MyAccounts/AccountItem.tsx
@@ -84,7 +84,7 @@ const AccountItem = ({ onLinkClick, safeItem, safeOverview }: AccountItemProps)
{name && (
-
+
{name}
)}
@@ -113,7 +113,11 @@ const AccountItem = ({ onLinkClick, safeItem, safeOverview }: AccountItemProps)
- {safeOverview ? : }
+ {safeOverview ? (
+
+ ) : undeployedSafe ? null : (
+
+ )}
diff --git a/src/components/welcome/MyAccounts/styles.module.css b/src/components/welcome/MyAccounts/styles.module.css
index 60854d02b..4b97a1534 100644
--- a/src/components/welcome/MyAccounts/styles.module.css
+++ b/src/components/welcome/MyAccounts/styles.module.css
@@ -72,12 +72,14 @@
}
.safeLink :nth-child(3) {
grid-area: c;
+ text-align: left;
}
.safeLink :nth-child(4) {
grid-area: d;
}
}
+.safeName,
.safeAddress {
white-space: nowrap;
overflow: hidden;
diff --git a/src/components/welcome/WelcomeLogin/WalletLogin.tsx b/src/components/welcome/WelcomeLogin/WalletLogin.tsx
index 97a5d78e0..9486b34ed 100644
--- a/src/components/welcome/WelcomeLogin/WalletLogin.tsx
+++ b/src/components/welcome/WelcomeLogin/WalletLogin.tsx
@@ -4,7 +4,7 @@ import { Box, Button, Typography } from '@mui/material'
import EthHashInfo from '@/components/common/EthHashInfo'
import WalletIcon from '@/components/common/WalletIcon'
-const WalletLogin = ({ onLogin }: { onLogin: () => void }) => {
+const WalletLogin = ({ onLogin, onContinue }: { onLogin: () => void; onContinue: () => void }) => {
const wallet = useWallet()
const connectWallet = useConnectWallet()
@@ -16,7 +16,7 @@ const WalletLogin = ({ onLogin }: { onLogin: () => void }) => {
if (wallet !== null) {
return (
-
+
{
it('should render continue with connected wallet', async () => {
const mockOnLogin = jest.fn()
+ const mockOnContinue = jest.fn()
const walletAddress = toBeHex('0x1', 20)
jest.spyOn(useWallet, 'default').mockReturnValue({
address: walletAddress,
@@ -22,7 +23,7 @@ describe('WalletLogin', () => {
})
jest.spyOn(useConnectWallet, 'default').mockReturnValue(jest.fn())
- const result = render( )
+ const result = render( )
await waitFor(() => {
expect(result.findByText(shortenAddress(walletAddress))).resolves.toBeDefined()
@@ -34,16 +35,17 @@ describe('WalletLogin', () => {
const button = await result.findByRole('button')
button.click()
- expect(mockOnLogin).toHaveBeenCalled()
+ expect(mockOnContinue).toHaveBeenCalled()
})
it('should render connect wallet if no wallet is connected', async () => {
const mockOnLogin = jest.fn()
+ const mockOnContinue = jest.fn()
const walletAddress = toBeHex('0x1', 20)
const mockUseWallet = jest.spyOn(useWallet, 'default').mockReturnValue(null)
jest.spyOn(useConnectWallet, 'default').mockReturnValue(jest.fn().mockReturnValue([{}]))
- const result = render( )
+ const result = render( )
await waitFor(() => {
expect(result.findByText('Connect wallet')).resolves.toBeDefined()
@@ -71,11 +73,12 @@ describe('WalletLogin', () => {
it('should invoke the callback if user actively connects', async () => {
const mockOnLogin = jest.fn()
+ const mockOnContinue = jest.fn()
jest.spyOn(useWallet, 'default').mockReturnValue(null)
jest.spyOn(useConnectWallet, 'default').mockReturnValue(jest.fn().mockReturnValue([]))
- const result = render( )
+ const result = render( )
await waitFor(() => {
expect(result.findByText('Connect wallet')).resolves.toBeDefined()
diff --git a/src/components/welcome/WelcomeLogin/index.tsx b/src/components/welcome/WelcomeLogin/index.tsx
index c2fc28b73..1b7fe9864 100644
--- a/src/components/welcome/WelcomeLogin/index.tsx
+++ b/src/components/welcome/WelcomeLogin/index.tsx
@@ -17,22 +17,25 @@ const WelcomeLogin = () => {
const { isLoaded, hasSafes } = useHasSafes()
const [shouldRedirect, setShouldRedirect] = useState(false)
+ const redirect = useCallback(() => {
+ if (wallet) {
+ if (isLoaded && !hasSafes) {
+ trackEvent(CREATE_SAFE_EVENTS.OPEN_SAFE_CREATION)
+ router.push({ pathname: AppRoutes.newSafe.create, query: router.query })
+ } else {
+ router.push({ pathname: AppRoutes.welcome.accounts, query: router.query })
+ }
+ }
+ }, [hasSafes, isLoaded, router, wallet])
+
const onLogin = useCallback(() => {
setShouldRedirect(true)
}, [])
useEffect(() => {
if (!shouldRedirect) return
-
- if (wallet && isLoaded) {
- if (hasSafes) {
- router.push({ pathname: AppRoutes.welcome.accounts, query: router.query })
- } else {
- trackEvent(CREATE_SAFE_EVENTS.OPEN_SAFE_CREATION)
- router.push({ pathname: AppRoutes.newSafe.create, query: router.query })
- }
- }
- }, [hasSafes, isLoaded, router, wallet, shouldRedirect])
+ redirect()
+ }, [redirect, shouldRedirect])
return (
@@ -50,7 +53,7 @@ const WelcomeLogin = () => {
-
+
{!wallet && (
diff --git a/src/config/chains.ts b/src/config/chains.ts
index 6093b0e62..05243aaad 100644
--- a/src/config/chains.ts
+++ b/src/config/chains.ts
@@ -1,4 +1,5 @@
import { networks } from '@safe-global/protocol-kit/dist/src/utils/eip-3770/config'
+
/**
* A static shortName<->chainId dictionary
* E.g.:
diff --git a/src/config/constants.ts b/src/config/constants.ts
index 24ef14915..1d11695ac 100644
--- a/src/config/constants.ts
+++ b/src/config/constants.ts
@@ -12,7 +12,7 @@ export const GATEWAY_URL_STAGING =
export const POLLING_INTERVAL = 15_000
export const BASE_TX_GAS = 21_000
export const LS_NAMESPACE = 'SAFE_v2__'
-export const LATEST_SAFE_VERSION = process.env.NEXT_PUBLIC_SAFE_VERSION || '1.3.0'
+export const LATEST_SAFE_VERSION = process.env.NEXT_PUBLIC_SAFE_VERSION || '1.4.1'
// Access keys
export const INFURA_TOKEN = process.env.NEXT_PUBLIC_INFURA_TOKEN || ''
@@ -60,14 +60,13 @@ export const TENDERLY_ORG_NAME = process.env.NEXT_PUBLIC_TENDERLY_ORG_NAME || ''
export enum SafeAppsTag {
NFT = 'nft',
TX_BUILDER = 'transaction-builder',
- DASHBOARD_FEATURED = 'dashboard-widgets',
SAFE_GOVERNANCE_APP = 'safe-governance-app',
- WALLET_CONNECT = 'wallet-connect',
ONRAMP = 'onramp',
+ RECOVERY_SYGNUM = 'recovery-sygnum',
}
// Help Center
-export const HELP_CENTER_URL = 'https://help.safe.global'
+export const HELP_CENTER_URL = 'https://safe-support.protofire.io'
export const HelpCenterArticle = {
ADDRESS_BOOK_DATA: `${HELP_CENTER_URL}/en/articles/40811-address-book-export-and-import`,
ADVANCED_PARAMS: `${HELP_CENTER_URL}/en/articles/40837-advanced-transaction-parameters`,
@@ -91,7 +90,9 @@ export const HelpCenterArticle = {
export const HelperCenterArticleTitles = {
RECOVERY: 'Learn more about the Account recovery process',
}
-
+//Feedback
+export const NEW_SUGGESTION_FORM =
+ 'https://docs.google.com/forms/d/e/1FAIpQLSfojsADYCiWq9AqbLqsUTzCDSpA8FMgdAQp0Pyl0BOeurlq9A/viewform'
export const RECOVERY_FEEDBACK_FORM =
'https://noteforms.com/forms/safe-feedback-form-hk16ds?notionforms=1&utm_source=notionforms'
@@ -102,7 +103,12 @@ export const TWITTER_URL = 'https://twitter.com/safe'
// Legal
export const IS_OFFICIAL_HOST = process.env.NEXT_PUBLIC_IS_OFFICIAL_HOST === 'true'
-// Risk mitigation (Redefine)
-export const REDEFINE_SIMULATION_URL = 'https://dashboard.redefine.net/reports/'
-export const REDEFINE_API = process.env.NEXT_PUBLIC_REDEFINE_API
+// Risk mitigation (Blockaid)
+export const BLOCKAID_API = 'https://client.blockaid.io'
+export const BLOCKAID_CLIENT_ID = process.env.NEXT_PUBLIC_BLOCKAID_CLIENT_ID
export const REDEFINE_ARTICLE = 'https://safe.mirror.xyz/rInLWZwD_sf7enjoFerj6FIzCYmVMGrrV8Nhg4THdwI'
+
+export const CHAINALYSIS_OFAC_CONTRACT = '0x40c57923924b5c5c5455c48d93317139addac8fb'
+
+export const ECOSYSTEM_ID_ADDRESS =
+ process.env.NEXT_PUBLIC_ECOSYSTEM_ID_ADDRESS || '0x0000000000000000000000000000000000000000'
diff --git a/src/config/routes.ts b/src/config/routes.ts
index 11c78d471..8f65fa5bb 100644
--- a/src/config/routes.ts
+++ b/src/config/routes.ts
@@ -1,9 +1,10 @@
export const AppRoutes = {
- '404': '/404',
'403': '/403',
+ '404': '/404',
wc: '/wc',
terms: '/terms',
swap: '/swap',
+ stake: '/stake',
privacy: '/privacy',
licenses: '/licenses',
index: '/',
@@ -26,6 +27,7 @@ export const AppRoutes = {
newSafe: {
load: '/new-safe/load',
create: '/new-safe/create',
+ advancedCreate: '/new-safe/advanced-create',
},
settings: {
setup: '/settings/setup',
@@ -46,8 +48,8 @@ export const AppRoutes = {
},
transactions: {
tx: '/transactions/tx',
- msg: '/transactions/msg',
queue: '/transactions/queue',
+ msg: '/transactions/msg',
messages: '/transactions/messages',
index: '/transactions',
history: '/transactions/history',
diff --git a/src/config/securityHeaders.ts b/src/config/securityHeaders.ts
index 8d3faa3bb..5cd722ad6 100644
--- a/src/config/securityHeaders.ts
+++ b/src/config/securityHeaders.ts
@@ -13,11 +13,11 @@ export const ContentSecurityPolicy = `
default-src 'self';
connect-src 'self' *;
script-src 'self' https://www.google-analytics.com https://ssl.google-analytics.com 'unsafe-inline' https://*.getbeamer.com https://www.googletagmanager.com https://*.ingest.sentry.io https://sentry.io ${
- !IS_PRODUCTION || /* TODO: remove after moving cypress to gรถrli and testing in staging again!! */ CYPRESS_MNEMONIC
- ? "'unsafe-eval'"
+ !IS_PRODUCTION || CYPRESS_MNEMONIC
+ ? "'unsafe-eval'" // Dev server and cypress need unsafe-eval
: "'wasm-unsafe-eval'"
};
- frame-src *;
+ frame-src http: https:;
style-src 'self' 'unsafe-inline' https://*.getbeamer.com https://*.googleapis.com;
font-src 'self' data:;
worker-src 'self' blob:;
diff --git a/src/features/counterfactual/ActivateAccountButton.tsx b/src/features/counterfactual/ActivateAccountButton.tsx
index 0c84bb8d3..7b2c4efa4 100644
--- a/src/features/counterfactual/ActivateAccountButton.tsx
+++ b/src/features/counterfactual/ActivateAccountButton.tsx
@@ -6,6 +6,7 @@ import { TxModalContext } from '@/components/tx-flow'
import { PendingSafeStatus, selectUndeployedSafe } from '@/features/counterfactual/store/undeployedSafesSlice'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useAppSelector } from '@/store'
+import CheckWallet from '@/components/common/CheckWallet'
const ActivateAccountFlow = dynamic(() => import('./ActivateAccountFlow'))
@@ -24,24 +25,29 @@ const ActivateAccountButton = () => {
return (
-
- {isProcessing ? (
- <>
-
- Processing
-
-
- >
- ) : (
- 'Activate now'
+
+ {(isOk) => (
+
+ {isProcessing ? (
+ <>
+
+ Processing
+
+
+ >
+ ) : (
+ 'Activate now'
+ )}
+
)}
-
+
)
diff --git a/src/features/counterfactual/ActivateAccountFlow.tsx b/src/features/counterfactual/ActivateAccountFlow.tsx
index 4de9d8883..15eecb5e7 100644
--- a/src/features/counterfactual/ActivateAccountFlow.tsx
+++ b/src/features/counterfactual/ActivateAccountFlow.tsx
@@ -1,5 +1,4 @@
import { createNewSafe, relaySafeCreation } from '@/components/new-safe/create/logic'
-import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
import { NetworkFee, SafeSetupOverview } from '@/components/new-safe/create/steps/ReviewStep'
import ReviewRow from '@/components/new-safe/ReviewRow'
import { TxModalContext } from '@/components/tx-flow'
@@ -20,7 +19,6 @@ import { useLeastRemainingRelays } from '@/hooks/useRemainingRelays'
import useSafeInfo from '@/hooks/useSafeInfo'
import useWalletCanPay from '@/hooks/useWalletCanPay'
import useWallet from '@/hooks/wallets/useWallet'
-import { useWeb3 } from '@/hooks/wallets/web3'
import { OVERVIEW_EVENTS, trackEvent, WALLET_EVENTS } from '@/services/analytics'
import { TX_EVENTS, TX_TYPES } from '@/services/analytics/events/transactions'
import { asError } from '@/services/exceptions/utils'
@@ -31,6 +29,9 @@ import { Box, Button, CircularProgress, Divider, Grid, Typography } from '@mui/m
import type { DeploySafeProps } from '@safe-global/protocol-kit'
import { FEATURES } from '@/utils/chains'
import React, { useContext, useState } from 'react'
+import CheckWallet from '@/components/common/CheckWallet'
+import { getLatestSafeVersion } from '@/utils/chains'
+import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
const useActivateAccount = () => {
const chain = useCurrentChain()
@@ -50,7 +51,7 @@ const useActivateAccount = () => {
: { gasPrice: maxFeePerGas?.toString(), gasLimit: gasLimit?.totalGas.toString() }
const totalFee = getTotalFeeFormatted(maxFeePerGas, gasLimit?.totalGas, chain)
- const walletCanPay = useWalletCanPay({ gasLimit: gasLimit?.totalGas, maxFeePerGas, maxPriorityFeePerGas })
+ const walletCanPay = useWalletCanPay({ gasLimit: gasLimit?.totalGas, maxFeePerGas })
return { options, totalFee, walletCanPay }
}
@@ -64,7 +65,6 @@ const ActivateAccountFlow = () => {
const chain = useCurrentChain()
const chainId = useChainId()
const { safeAddress } = useSafeInfo()
- const provider = useWeb3()
const undeployedSafe = useAppSelector((state) => selectUndeployedSafe(state, chainId, safeAddress))
const { setTxFlow } = useContext(TxModalContext)
const wallet = useWallet()
@@ -94,7 +94,7 @@ const ActivateAccountFlow = () => {
}
const createSafe = async () => {
- if (!provider || !chain) return
+ if (!wallet || !chain) return
trackEvent({ ...OVERVIEW_EVENTS.PROCEED_WITH_TX, label: TX_TYPES.activate_without_tx })
@@ -109,14 +109,14 @@ const ActivateAccountFlow = () => {
onSubmit()
} else {
await createNewSafe(
- provider,
+ wallet.provider,
{
safeAccountConfig: undeployedSafe.props.safeAccountConfig,
saltNonce,
options,
callback: onSubmit,
},
- safeVersion,
+ safeVersion ?? getLatestSafeVersion(chain),
)
}
} catch (_err) {
@@ -163,11 +163,13 @@ const ActivateAccountFlow = () => {
name="Est. network fee"
value={
<>
-
+
{!willRelay && (
- You will have to confirm a transaction with your connected wallet.
+ {isWrongChain
+ ? `Switch your connected wallet to ${chain?.chainName} to see the correct estimated network fee`
+ : 'You will have to confirm a transaction with your connected wallet.'}
)}
>
@@ -181,7 +183,7 @@ const ActivateAccountFlow = () => {
)}
- {isWrongChain && }
+
{!walletCanPay && !willRelay && (
@@ -193,15 +195,19 @@ const ActivateAccountFlow = () => {
-
- {!isSubmittable ? : 'Activate'}
-
+
+ {(isOk) => (
+
+ {!isSubmittable ? : 'Activate'}
+
+ )}
+
diff --git a/src/features/counterfactual/CounterfactualForm.tsx b/src/features/counterfactual/CounterfactualForm.tsx
index cf20d005e..98d59a571 100644
--- a/src/features/counterfactual/CounterfactualForm.tsx
+++ b/src/features/counterfactual/CounterfactualForm.tsx
@@ -1,15 +1,12 @@
import { TxModalContext } from '@/components/tx-flow'
import useDeployGasLimit from '@/features/counterfactual/hooks/useDeployGasLimit'
import { deploySafeAndExecuteTx } from '@/features/counterfactual/utils'
-import useChainId from '@/hooks/useChainId'
import { getTotalFeeFormatted } from '@/hooks/useGasPrice'
import useSafeInfo from '@/hooks/useSafeInfo'
import useWalletCanPay from '@/hooks/useWalletCanPay'
-import useOnboard from '@/hooks/wallets/useOnboard'
import useWallet from '@/hooks/wallets/useWallet'
import { OVERVIEW_EVENTS, trackEvent, WALLET_EVENTS } from '@/services/analytics'
import { TX_EVENTS, TX_TYPES } from '@/services/analytics/events/transactions'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import madProps from '@/utils/mad-props'
import React, { type ReactElement, type SyntheticEvent, useContext, useState } from 'react'
import { CircularProgress, Box, Button, CardActions, Divider, Alert } from '@mui/material'
@@ -48,9 +45,7 @@ export const CounterfactualForm = ({
safeTx?: SafeTransaction
}): ReactElement => {
const wallet = useWallet()
- const onboard = useOnboard()
const chain = useCurrentChain()
- const chainId = useChainId()
const { safeAddress } = useSafeInfo()
// Form state
@@ -84,7 +79,6 @@ export const CounterfactualForm = ({
try {
trackEvent({ ...OVERVIEW_EVENTS.PROCEED_WITH_TX, label: TX_TYPES.activate_with_tx })
- onboard && (await assertWalletChain(onboard, chainId))
await deploySafeAndExecuteTx(txOptions, wallet, safeAddress, safeTx, wallet?.provider)
trackEvent({ ...TX_EVENTS.CREATE, label: TX_TYPES.activate_with_tx })
@@ -104,7 +98,6 @@ export const CounterfactualForm = ({
const walletCanPay = useWalletCanPay({
gasLimit: gasLimit?.totalGas,
maxFeePerGas: advancedParams.maxFeePerGas,
- maxPriorityFeePerGas: advancedParams.maxPriorityFeePerGas,
})
const cannotPropose = !isOwner && !onlyExecute
@@ -179,7 +172,7 @@ export const CounterfactualForm = ({
{/* Submit button */}
-
+
{(isOk) => (
{!isSubmittable ? : 'Execute'}
diff --git a/src/features/counterfactual/CounterfactualSuccessScreen.tsx b/src/features/counterfactual/CounterfactualSuccessScreen.tsx
index f798e5b99..55dcd1cbf 100644
--- a/src/features/counterfactual/CounterfactualSuccessScreen.tsx
+++ b/src/features/counterfactual/CounterfactualSuccessScreen.tsx
@@ -1,7 +1,7 @@
import EthHashInfo from '@/components/common/EthHashInfo'
import { safeCreationPendingStatuses } from '@/features/counterfactual/hooks/usePendingSafeStatuses'
import { SafeCreationEvent, safeCreationSubscribe } from '@/features/counterfactual/services/safeCreationEvents'
-import { useCurrentChain } from '@/hooks/useChains'
+import { useChain, useCurrentChain } from '@/hooks/useChains'
import { useEffect, useState } from 'react'
import { Box, Button, Dialog, DialogContent, Typography } from '@mui/material'
import CheckRoundedIcon from '@mui/icons-material/CheckRounded'
@@ -9,12 +9,18 @@ import CheckRoundedIcon from '@mui/icons-material/CheckRounded'
const CounterfactualSuccessScreen = () => {
const [open, setOpen] = useState(false)
const [safeAddress, setSafeAddress] = useState()
- const chain = useCurrentChain()
+ const [chainId, setChainId] = useState()
+ const currentChain = useCurrentChain()
+ const chain = useChain(chainId || currentChain?.chainId || '')
useEffect(() => {
const unsubFns = Object.entries(safeCreationPendingStatuses).map(([event]) =>
safeCreationSubscribe(event as SafeCreationEvent, async (detail) => {
if (event === SafeCreationEvent.INDEXED) {
+ if ('chainId' in detail) {
+ setChainId(detail.chainId)
+ }
+
setSafeAddress(detail.safeAddress)
setOpen(true)
}
@@ -60,7 +66,7 @@ const CounterfactualSuccessScreen = () => {
{safeAddress && (
-
+
)}
diff --git a/src/features/counterfactual/__tests__/useDeployGasLimit.test.ts b/src/features/counterfactual/__tests__/useDeployGasLimit.test.ts
index 0579fc2a6..c5ef2d818 100644
--- a/src/features/counterfactual/__tests__/useDeployGasLimit.test.ts
+++ b/src/features/counterfactual/__tests__/useDeployGasLimit.test.ts
@@ -9,17 +9,19 @@ import * as protocolKitContracts from '@safe-global/protocol-kit/dist/src/contra
import type Safe from '@safe-global/protocol-kit'
import { renderHook } from '@/tests/test-utils'
+import type {
+ CompatibilityFallbackHandlerContractImplementationType,
+ SimulateTxAccessorContractImplementationType,
+} from '@safe-global/protocol-kit/dist/src/types'
import { waitFor } from '@testing-library/react'
import type { OnboardAPI } from '@web3-onboard/core'
import { faker } from '@faker-js/faker'
-import type { CompatibilityFallbackHandlerContract, SimulateTxAccessorContract } from '@safe-global/safe-core-sdk-types'
describe('useDeployGasLimit hook', () => {
beforeEach(() => {
jest.resetAllMocks()
jest.spyOn(useWallet, 'default').mockReturnValue({} as ConnectedWallet)
- jest.spyOn(sdk, 'assertWalletChain').mockImplementation(jest.fn())
})
it('returns undefined in onboard is not initialized', () => {
@@ -75,15 +77,18 @@ describe('useDeployGasLimit hook', () => {
const mockOnboard = {} as OnboardAPI
jest.spyOn(onboard, 'default').mockReturnValue(mockOnboard)
jest.spyOn(sdk, 'getSafeSDKWithSigner').mockResolvedValue({
+ getThreshold: jest.fn(),
+ getNonce: jest.fn(),
+ getSafeProvider: () => ({
+ estimateGas: () => Promise.resolve('420000'),
+ getSignerAddress: () => Promise.resolve(faker.finance.ethereumAddress()),
+ }),
+ getChainId: jest.fn(),
getContractManager: () =>
({
contractNetworks: {},
} as any),
getContractVersion: () => Promise.resolve('1.3.0'),
- getEthAdapter: () => ({
- estimateGas: () => Promise.resolve('420000'),
- getSignerAddress: () => Promise.resolve(faker.finance.ethereumAddress()),
- }),
createSafeDeploymentTransaction: () =>
Promise.resolve({
to: faker.finance.ethereumAddress(),
@@ -100,11 +105,11 @@ describe('useDeployGasLimit hook', () => {
} as unknown as Safe)
jest.spyOn(protocolKitContracts, 'getCompatibilityFallbackHandlerContract').mockResolvedValue({
encode: () => '0x3456',
- } as unknown as CompatibilityFallbackHandlerContract)
+ } as unknown as CompatibilityFallbackHandlerContractImplementationType)
jest.spyOn(protocolKitContracts, 'getSimulateTxAccessorContract').mockResolvedValue({
encode: () => '0x4567',
getAddress: () => Promise.resolve(faker.finance.ethereumAddress()),
- } as unknown as SimulateTxAccessorContract)
+ } as unknown as SimulateTxAccessorContractImplementationType)
jest.spyOn(protocolKit, 'estimateSafeDeploymentGas').mockReturnValue(Promise.resolve('100'))
jest.spyOn(protocolKit, 'estimateTxBaseGas').mockReturnValue(Promise.resolve('21000'))
diff --git a/src/features/counterfactual/__tests__/utils.test.ts b/src/features/counterfactual/__tests__/utils.test.ts
index 88e61f8ce..fc033c167 100644
--- a/src/features/counterfactual/__tests__/utils.test.ts
+++ b/src/features/counterfactual/__tests__/utils.test.ts
@@ -25,7 +25,7 @@ describe('Counterfactual utils', () => {
const mockAddress = faker.finance.ethereumAddress()
const mockChainId = '1'
- const result = getUndeployedSafeInfo(undeployedSafe, mockAddress, mockChainId)
+ const result = getUndeployedSafeInfo(undeployedSafe, mockAddress, chainBuilder().with({ chainId: '1' }).build())
expect(result.nonce).toEqual(0)
expect(result.deployed).toEqual(false)
diff --git a/src/features/counterfactual/hooks/useDeployGasLimit.ts b/src/features/counterfactual/hooks/useDeployGasLimit.ts
index 45e406100..673186935 100644
--- a/src/features/counterfactual/hooks/useDeployGasLimit.ts
+++ b/src/features/counterfactual/hooks/useDeployGasLimit.ts
@@ -2,7 +2,7 @@ import useAsync from '@/hooks/useAsync'
import useChainId from '@/hooks/useChainId'
import useOnboard from '@/hooks/wallets/useOnboard'
import useWallet from '@/hooks/wallets/useWallet'
-import { assertWalletChain, getSafeSDKWithSigner } from '@/services/tx/tx-sender/sdk'
+import { getSafeSDKWithSigner } from '@/services/tx/tx-sender/sdk'
import { estimateSafeDeploymentGas, estimateTxBaseGas } from '@safe-global/protocol-kit'
import type Safe from '@safe-global/protocol-kit'
@@ -28,7 +28,6 @@ const useDeployGasLimit = (safeTx?: SafeTransaction) => {
const [gasLimit, gasLimitError, gasLimitLoading] = useAsync(async () => {
if (!wallet || !onboard) return
- await assertWalletChain(onboard, chainId)
const sdk = await getSafeSDKWithSigner(wallet.provider)
const [baseGas, batchTxGas, safeDeploymentGas] = await Promise.all([
@@ -67,15 +66,15 @@ export const estimateBatchDeploymentTransaction = async (
) => {
const customContracts = sdk.getContractManager().contractNetworks?.[chainId]
const safeVersion = await sdk.getContractVersion()
- const ethAdapter = sdk.getEthAdapter()
+ const safeProvider = sdk.getSafeProvider()
const fallbackHandlerContract = await getCompatibilityFallbackHandlerContract({
- ethAdapter,
+ safeProvider,
safeVersion,
customContracts,
})
const simulateTxAccessorContract = await getSimulateTxAccessorContract({
- ethAdapter,
+ safeProvider,
safeVersion,
customContracts,
})
@@ -92,7 +91,7 @@ export const estimateBatchDeploymentTransaction = async (
// 2. Add a simulate call to the predicted SafeProxy as second transaction
const transactionDataToEstimate: string = simulateTxAccessorContract.encode('simulate', [
safeTransaction.data.to,
- safeTransaction.data.value,
+ BigInt(safeTransaction.data.value),
safeTransaction.data.data,
safeTransaction.data.operation,
])
@@ -114,10 +113,10 @@ export const estimateBatchDeploymentTransaction = async (
simulateBatchTransaction,
])
- const signerAddress = await ethAdapter.getSignerAddress()
+ const signerAddress = await safeProvider.getSignerAddress()
// estimate the entire batch
- const safeTxGas = await ethAdapter.estimateGas({
+ const safeTxGas = await safeProvider.estimateGas({
...safeDeploymentBatch,
from: signerAddress || ZERO_ADDRESS, // This address should not really matter
})
diff --git a/src/features/counterfactual/hooks/usePendingSafeStatuses.ts b/src/features/counterfactual/hooks/usePendingSafeStatuses.ts
index d9f607773..39d525922 100644
--- a/src/features/counterfactual/hooks/usePendingSafeStatuses.ts
+++ b/src/features/counterfactual/hooks/usePendingSafeStatuses.ts
@@ -1,4 +1,5 @@
import { pollSafeInfo } from '@/components/new-safe/create/logic'
+import { PayMethod } from '@/features/counterfactual/PayNowPayLater'
import {
safeCreationDispatch,
SafeCreationEvent,
@@ -13,10 +14,12 @@ import {
import { checkSafeActionViaRelay, checkSafeActivation } from '@/features/counterfactual/utils'
import useChainId from '@/hooks/useChainId'
import useSafeInfo from '@/hooks/useSafeInfo'
-import { isSmartContract, useWeb3ReadOnly } from '@/hooks/wallets/web3'
+import { useWeb3ReadOnly } from '@/hooks/wallets/web3'
import { CREATE_SAFE_EVENTS, trackEvent } from '@/services/analytics'
import { useAppDispatch, useAppSelector } from '@/store'
import { useEffect, useRef } from 'react'
+import { isSmartContract } from '@/utils/wallets'
+import { gtmSetSafeAddress } from '@/services/analytics/gtm'
export const safeCreationPendingStatuses: Partial> = {
[SafeCreationEvent.PROCESSING]: PendingSafeStatus.PROCESSING,
@@ -49,7 +52,7 @@ const usePendingSafeMonitor = (): void => {
const monitorPendingSafe = async () => {
const {
- status: { status, txHash, taskId, startBlock },
+ status: { status, txHash, taskId, startBlock, type },
} = undeployedSafe
const isProcessing = status === PendingSafeStatus.PROCESSING && txHash !== undefined
@@ -61,11 +64,11 @@ const usePendingSafeMonitor = (): void => {
monitoredSafes.current[safeAddress] = true
if (isProcessing) {
- checkSafeActivation(provider, txHash, safeAddress, startBlock)
+ checkSafeActivation(provider, txHash, safeAddress, type, chainId, startBlock)
}
if (isRelaying) {
- checkSafeActionViaRelay(taskId, safeAddress)
+ checkSafeActionViaRelay(taskId, safeAddress, type, chainId)
}
}
@@ -92,7 +95,7 @@ const usePendingSafeStatus = (): void => {
const { chainId } = await provider.getNetwork()
if (chainId !== BigInt(safe.chainId)) return
- const isContractDeployed = await isSmartContract(provider, safeAddress)
+ const isContractDeployed = await isSmartContract(safeAddress)
if (isContractDeployed) {
dispatch(removeUndeployedSafe({ chainId: safe.chainId, address: safeAddress }))
@@ -106,26 +109,37 @@ const usePendingSafeStatus = (): void => {
useEffect(() => {
const unsubFns = Object.entries(safeCreationPendingStatuses).map(([event, status]) =>
safeCreationSubscribe(event as SafeCreationEvent, async (detail) => {
+ const creationChainId = 'chainId' in detail ? detail.chainId : chainId
+
if (event === SafeCreationEvent.SUCCESS) {
+ gtmSetSafeAddress(detail.safeAddress)
+
// TODO: Possible to add a label with_tx, without_tx?
trackEvent(CREATE_SAFE_EVENTS.ACTIVATED_SAFE)
- pollSafeInfo(chainId, detail.safeAddress).finally(() => {
+
+ // Not a counterfactual deployment
+ if ('type' in detail && detail.type === PayMethod.PayNow) {
+ trackEvent(CREATE_SAFE_EVENTS.CREATED_SAFE)
+ }
+
+ pollSafeInfo(creationChainId, detail.safeAddress).finally(() => {
safeCreationDispatch(SafeCreationEvent.INDEXED, {
groupKey: detail.groupKey,
safeAddress: detail.safeAddress,
+ chainId: creationChainId,
})
})
return
}
if (event === SafeCreationEvent.INDEXED) {
- dispatch(removeUndeployedSafe({ chainId, address: detail.safeAddress }))
+ dispatch(removeUndeployedSafe({ chainId: creationChainId, address: detail.safeAddress }))
}
if (status === null) {
dispatch(
updateUndeployedSafeStatus({
- chainId,
+ chainId: creationChainId,
address: detail.safeAddress,
status: {
status: PendingSafeStatus.AWAITING_EXECUTION,
@@ -140,7 +154,7 @@ const usePendingSafeStatus = (): void => {
dispatch(
updateUndeployedSafeStatus({
- chainId,
+ chainId: creationChainId,
address: detail.safeAddress,
status: {
status,
diff --git a/src/features/counterfactual/services/safeCreationEvents.ts b/src/features/counterfactual/services/safeCreationEvents.ts
index 883f60156..668d185c3 100644
--- a/src/features/counterfactual/services/safeCreationEvents.ts
+++ b/src/features/counterfactual/services/safeCreationEvents.ts
@@ -1,3 +1,4 @@
+import type { PayMethod } from '@/features/counterfactual/PayNowPayLater'
import EventBus from '@/services/EventBus'
export enum SafeCreationEvent {
@@ -23,10 +24,13 @@ export interface SafeCreationEvents {
[SafeCreationEvent.SUCCESS]: {
groupKey: string
safeAddress: string
+ type: PayMethod
+ chainId: string
}
[SafeCreationEvent.INDEXED]: {
groupKey: string
safeAddress: string
+ chainId: string
}
[SafeCreationEvent.FAILED]: {
groupKey: string
diff --git a/src/features/counterfactual/store/undeployedSafesSlice.ts b/src/features/counterfactual/store/undeployedSafesSlice.ts
index 0196052e9..de3a66c53 100644
--- a/src/features/counterfactual/store/undeployedSafesSlice.ts
+++ b/src/features/counterfactual/store/undeployedSafesSlice.ts
@@ -2,6 +2,7 @@ import type { PayMethod } from '@/features/counterfactual/PayNowPayLater'
import { type RootState } from '@/store'
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit'
import type { PredictedSafeProps } from '@safe-global/protocol-kit'
+import { selectChainIdAndSafeAddress } from '@/store/common'
export enum PendingSafeStatus {
AWAITING_EXECUTION = 'AWAITING_EXECUTION',
@@ -54,6 +55,11 @@ export const undeployedSafesSlice = createSlice({
}
},
+ addUndeployedSafes: (_, { payload }: PayloadAction) => {
+ // We must return as we are overwriting the entire state
+ return payload
+ },
+
updateUndeployedSafeStatus: (
state,
action: PayloadAction<{ chainId: string; address: string; status: Omit }>,
@@ -91,7 +97,7 @@ export const selectUndeployedSafes = (state: RootState): UndeployedSafesState =>
}
export const selectUndeployedSafe = createSelector(
- [selectUndeployedSafes, (_, chainId: string, address: string) => [chainId, address]],
+ [selectUndeployedSafes, selectChainIdAndSafeAddress],
(undeployedSafes, [chainId, address]): UndeployedSafe | undefined => {
return undeployedSafes[chainId]?.[address]
},
diff --git a/src/features/counterfactual/utils.ts b/src/features/counterfactual/utils.ts
index 8b3a16d6d..1042177a6 100644
--- a/src/features/counterfactual/utils.ts
+++ b/src/features/counterfactual/utils.ts
@@ -1,14 +1,15 @@
import type { NewSafeFormData } from '@/components/new-safe/create'
-import { LATEST_SAFE_VERSION, POLLING_INTERVAL } from '@/config/constants'
+import { getLatestSafeVersion } from '@/utils/chains'
+import { POLLING_INTERVAL } from '@/config/constants'
import { AppRoutes } from '@/config/routes'
-import { PayMethod } from '@/features/counterfactual/PayNowPayLater'
+import type { PayMethod } from '@/features/counterfactual/PayNowPayLater'
import { safeCreationDispatch, SafeCreationEvent } from '@/features/counterfactual/services/safeCreationEvents'
import { addUndeployedSafe } from '@/features/counterfactual/store/undeployedSafesSlice'
import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
-import { createWeb3, getWeb3ReadOnly } from '@/hooks/wallets/web3'
+import { getWeb3ReadOnly } from '@/hooks/wallets/web3'
import { asError } from '@/services/exceptions/utils'
import ExternalStore from '@/services/ExternalStore'
-import { getUncheckedSafeSDK, tryOffChainTxSigning } from '@/services/tx/tx-sender/sdk'
+import { getSafeSDKWithSigner, getUncheckedSigner, tryOffChainTxSigning } from '@/services/tx/tx-sender/sdk'
import { getRelayTxStatus, TaskState } from '@/services/tx/txMonitor'
import type { AppDispatch } from '@/store'
import { addOrUpdateSafe } from '@/store/addedSafesSlice'
@@ -18,7 +19,7 @@ import { didRevert, type EthersError } from '@/utils/ethers-utils'
import { assertProvider, assertTx, assertWallet } from '@/utils/helpers'
import type { DeploySafeProps, PredictedSafeProps } from '@safe-global/protocol-kit'
import { ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants'
-import type { SafeTransaction, SafeVersion, TransactionOptions } from '@safe-global/safe-core-sdk-types'
+import type { SafeTransaction, TransactionOptions } from '@safe-global/safe-core-sdk-types'
import {
type ChainInfo,
ImplementationVersionState,
@@ -28,17 +29,19 @@ import {
import type { BrowserProvider, ContractTransactionResponse, Eip1193Provider, Provider } from 'ethers'
import type { NextRouter } from 'next/router'
-export const getUndeployedSafeInfo = (undeployedSafe: PredictedSafeProps, address: string, chainId: string) => {
+export const getUndeployedSafeInfo = (undeployedSafe: PredictedSafeProps, address: string, chain: ChainInfo) => {
+ const latestSafeVersion = getLatestSafeVersion(chain)
+
return {
...defaultSafeInfo,
address: { value: address },
- chainId,
+ chainId: chain.chainId,
owners: undeployedSafe.safeAccountConfig.owners.map((owner) => ({ value: owner })),
nonce: 0,
threshold: undeployedSafe.safeAccountConfig.threshold,
implementationVersionState: ImplementationVersionState.UP_TO_DATE,
fallbackHandler: { value: undeployedSafe.safeAccountConfig.fallbackHandler! },
- version: undeployedSafe.safeDeploymentConfig?.safeVersion || LATEST_SAFE_VERSION,
+ version: undeployedSafe.safeDeploymentConfig?.safeVersion || latestSafeVersion,
deployed: false,
}
}
@@ -51,17 +54,15 @@ export const dispatchTxExecutionAndDeploySafe = async (
provider: Eip1193Provider,
safeAddress: string,
) => {
- const sdkUnchecked = await getUncheckedSafeSDK(provider)
+ const sdk = await getSafeSDKWithSigner(provider)
const eventParams = { groupKey: CF_TX_GROUP_KEY }
let result: ContractTransactionResponse | undefined
try {
- const signedTx = await tryOffChainTxSigning(safeTx, await sdkUnchecked.getContractVersion(), sdkUnchecked)
+ const signedTx = await tryOffChainTxSigning(safeTx, await sdk.getContractVersion(), sdk)
+ const signer = await getUncheckedSigner(provider)
- const browserProvider = createWeb3(provider)
- const signer = await browserProvider.getSigner()
-
- const deploymentTx = await sdkUnchecked.wrapSafeTransactionIntoDeploymentBatch(signedTx, txOptions)
+ const deploymentTx = await sdk.wrapSafeTransactionIntoDeploymentBatch(signedTx, txOptions)
// We need to estimate the actual gasLimit after the user has signed since it is more accurate than what useDeployGasLimit returns
const gas = await signer.estimateGas({ data: deploymentTx.data, value: deploymentTx.value, to: deploymentTx.to })
@@ -139,17 +140,18 @@ export const createCounterfactualSafe = (
data: NewSafeFormData,
dispatch: AppDispatch,
props: DeploySafeProps,
- router: NextRouter,
+ payMethod: PayMethod,
+ router?: NextRouter,
) => {
const undeployedSafe = {
chainId: chain.chainId,
address: safeAddress,
- type: PayMethod.PayLater,
+ type: payMethod,
safeProps: {
safeAccountConfig: props.safeAccountConfig,
safeDeploymentConfig: {
saltNonce,
- safeVersion: LATEST_SAFE_VERSION as SafeVersion,
+ safeVersion: data.safeVersion,
},
},
}
@@ -170,7 +172,8 @@ export const createCounterfactualSafe = (
},
}),
)
- return router.push({
+
+ router?.push({
pathname: AppRoutes.home,
query: { safe: `${chain.shortName}:${safeAddress}` },
})
@@ -186,7 +189,7 @@ const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
* @param txHash
* @param maxAttempts
*/
-async function retryGetTransaction(provider: Provider, txHash: string, maxAttempts = 6) {
+async function retryGetTransaction(provider: Provider, txHash: string, maxAttempts = 8) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const txResponse = await provider.getTransaction(txHash)
if (txResponse !== null) {
@@ -204,6 +207,8 @@ export const checkSafeActivation = async (
provider: Provider,
txHash: string,
safeAddress: string,
+ type: PayMethod,
+ chainId: string,
startBlock?: number,
) => {
try {
@@ -228,6 +233,8 @@ export const checkSafeActivation = async (
safeCreationDispatch(SafeCreationEvent.SUCCESS, {
groupKey: CF_TX_GROUP_KEY,
safeAddress,
+ type,
+ chainId,
})
} catch (err) {
const _err = err as EthersError
@@ -236,6 +243,8 @@ export const checkSafeActivation = async (
safeCreationDispatch(SafeCreationEvent.SUCCESS, {
groupKey: CF_TX_GROUP_KEY,
safeAddress,
+ type,
+ chainId,
})
return
}
@@ -257,7 +266,7 @@ export const checkSafeActivation = async (
}
}
-export const checkSafeActionViaRelay = (taskId: string, safeAddress: string) => {
+export const checkSafeActionViaRelay = (taskId: string, safeAddress: string, type: PayMethod, chainId: string) => {
const TIMEOUT_TIME = 2 * 60 * 1000 // 2 minutes
let intervalId: NodeJS.Timeout
@@ -274,6 +283,8 @@ export const checkSafeActionViaRelay = (taskId: string, safeAddress: string) =>
safeCreationDispatch(SafeCreationEvent.SUCCESS, {
groupKey: CF_TX_GROUP_KEY,
safeAddress,
+ type,
+ chainId,
})
break
case TaskState.ExecReverted:
diff --git a/src/features/recovery/components/CancelRecoveryButton/index.tsx b/src/features/recovery/components/CancelRecoveryButton/index.tsx
index 0bb427872..7247d79ce 100644
--- a/src/features/recovery/components/CancelRecoveryButton/index.tsx
+++ b/src/features/recovery/components/CancelRecoveryButton/index.tsx
@@ -1,7 +1,6 @@
import useWallet from '@/hooks/wallets/useWallet'
import { trackEvent } from '@/services/analytics'
import { RECOVERY_EVENTS } from '@/services/analytics/events/recovery'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import { Button } from '@mui/material'
import { useContext } from 'react'
import type { SyntheticEvent, ReactElement } from 'react'
@@ -12,7 +11,6 @@ import { CancelRecoveryFlow } from '@/components/tx-flow/flows'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import { dispatchRecoverySkipExpired } from '@/features/recovery/services/recovery-sender'
import useSafeInfo from '@/hooks/useSafeInfo'
-import useOnboard from '@/hooks/wallets/useOnboard'
import { trackError, Errors } from '@/services/exceptions'
import { asError } from '@/services/exceptions/utils'
import { useRecoveryTxState } from '@/features/recovery/hooks/useRecoveryTxState'
@@ -30,7 +28,6 @@ export function CancelRecoveryButton({
const isOwner = useIsSafeOwner()
const { isExpired, isPending } = useRecoveryTxState(recovery)
const { setTxFlow } = useContext(TxModalContext)
- const onboard = useOnboard()
const wallet = useWallet()
const { safe } = useSafeInfo()
@@ -41,10 +38,8 @@ export function CancelRecoveryButton({
trackEvent(RECOVERY_EVENTS.CANCEL_RECOVERY)
if (isOwner) {
setTxFlow( )
- } else if (onboard && wallet) {
+ } else if (wallet) {
try {
- await assertWalletChain(onboard, safe.chainId)
-
await dispatchRecoverySkipExpired({
provider: wallet.provider,
chainId: safe.chainId,
@@ -62,7 +57,7 @@ export function CancelRecoveryButton({
}
return (
-
+
{(isOk) => {
const isDisabled = isPending || (isOwner ? !isOk : !isOk || !isExpired)
diff --git a/src/features/recovery/components/ExecuteRecoveryButton/index.tsx b/src/features/recovery/components/ExecuteRecoveryButton/index.tsx
index 5b3662d2f..c71ef9ffc 100644
--- a/src/features/recovery/components/ExecuteRecoveryButton/index.tsx
+++ b/src/features/recovery/components/ExecuteRecoveryButton/index.tsx
@@ -1,18 +1,14 @@
-import useWallet from '@/hooks/wallets/useWallet'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import { Button, Tooltip } from '@mui/material'
import { useContext } from 'react'
import type { SyntheticEvent, ReactElement } from 'react'
import CheckWallet from '@/components/common/CheckWallet'
-import { dispatchRecoveryExecution } from '@/features/recovery/services/recovery-sender'
-import useOnboard from '@/hooks/wallets/useOnboard'
-import useSafeInfo from '@/hooks/useSafeInfo'
import { useRecoveryTxState } from '@/features/recovery/hooks/useRecoveryTxState'
-import { Errors, trackError } from '@/services/exceptions'
-import { asError } from '@/services/exceptions/utils'
-import { RecoveryListItemContext } from '../RecoveryListItem/RecoveryListItemContext'
import type { RecoveryQueueItem } from '@/features/recovery/services/recovery-state'
+import useIsWrongChain from '@/hooks/useIsWrongChain'
+import { useCurrentChain } from '@/hooks/useChains'
+import { TxModalContext } from '@/components/tx-flow'
+import { RecoveryAttemptFlow } from '@/components/tx-flow/flows'
export function ExecuteRecoveryButton({
recovery,
@@ -21,48 +17,29 @@ export function ExecuteRecoveryButton({
recovery: RecoveryQueueItem
compact?: boolean
}): ReactElement {
- const { setSubmitError } = useContext(RecoveryListItemContext)
const { isExecutable, isNext, isPending } = useRecoveryTxState(recovery)
- const onboard = useOnboard()
- const wallet = useWallet()
- const { safe } = useSafeInfo()
+ const isDisabled = !isExecutable || isPending
+ const isWrongChain = useIsWrongChain()
+ const chain = useCurrentChain()
+ const { setTxFlow } = useContext(TxModalContext)
const onClick = async (e: SyntheticEvent) => {
e.stopPropagation()
e.preventDefault()
- if (!onboard || !wallet) {
- return
- }
-
- try {
- await assertWalletChain(onboard, safe.chainId)
-
- await dispatchRecoveryExecution({
- provider: wallet.provider,
- chainId: safe.chainId,
- args: recovery.args,
- delayModifierAddress: recovery.address,
- signerAddress: wallet.address,
- })
- } catch (_err) {
- const err = asError(_err)
-
- trackError(Errors._812, e)
- setSubmitError(err)
- }
+ setTxFlow( )
}
return (
-
+
{(isOk) => {
- const isDisabled = !isOk || !isExecutable || isPending
-
return (
diff --git a/src/features/recovery/components/RecoveryCards/RecoveryProposalCard.tsx b/src/features/recovery/components/RecoveryCards/RecoveryProposalCard.tsx
index 72c08a5e4..ce153d1da 100644
--- a/src/features/recovery/components/RecoveryCards/RecoveryProposalCard.tsx
+++ b/src/features/recovery/components/RecoveryCards/RecoveryProposalCard.tsx
@@ -67,7 +67,7 @@ export function _RecoveryProposalCard({ orientation = 'vertical', onClose, safe,
if (orientation === 'horizontal') {
return (
-
+
+
{icon}
diff --git a/src/features/recovery/components/RecoverySettings/ChooseRecoveryMethodModal.tsx b/src/features/recovery/components/RecoverySettings/ChooseRecoveryMethodModal.tsx
index a8d6d03a5..54f2c98d2 100644
--- a/src/features/recovery/components/RecoverySettings/ChooseRecoveryMethodModal.tsx
+++ b/src/features/recovery/components/RecoverySettings/ChooseRecoveryMethodModal.tsx
@@ -1,5 +1,5 @@
import Track from '@/components/common/Track'
-import { RECOVERY_FEEDBACK_FORM, HelpCenterArticle } from '@/config/constants'
+import { RECOVERY_FEEDBACK_FORM, HelpCenterArticle, SafeAppsTag } from '@/config/constants'
import { trackEvent } from '@/services/analytics'
import { RECOVERY_EVENTS } from '@/services/analytics/events/recovery'
import { type ChangeEvent, type ReactElement, useContext } from 'react'
@@ -26,18 +26,17 @@ import { UpsertRecoveryFlow } from '@/components/tx-flow/flows'
import ExternalLink from '@/components/common/ExternalLink'
import RecoveryCustomIcon from '@/public/images/common/recovery_custom.svg'
import RecoverySygnumIcon from '@/public/images/common/recovery_sygnum.svg'
-import RecoveryCoincoverIcon from '@/public/images/common/recovery_coincover.svg'
import { TxModalContext } from '@/components/tx-flow'
import css from './styles.module.css'
import CheckIcon from '@/public/images/common/check.svg'
-
-const SYGNUM_WAITLIST_LINK = 'https://wn2n6ocviur.typeform.com/to/cJbJW0KR'
-const COINCOVER_WAITLIST_LINK = 'https://wn2n6ocviur.typeform.com/to/ijqSzOkr'
+import { AppRoutes } from '@/config/routes'
+import { useSearchParams } from 'next/navigation'
+import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps'
+import TxStatusChip from '@/components/transactions/TxStatusChip'
enum RecoveryMethod {
SelfCustody = 'SelfCustody',
Sygnum = 'Sygnum',
- Coincover = 'Coincover',
}
enum FieldNames {
@@ -50,6 +49,9 @@ type Fields = {
export function ChooseRecoveryMethodModal({ open, onClose }: { open: boolean; onClose: () => void }): ReactElement {
const { setTxFlow } = useContext(TxModalContext)
+ const querySafe = useSearchParams().get('safe')
+ const [matchingApps] = useRemoteSafeApps(SafeAppsTag.RECOVERY_SYGNUM)
+ const hasSygnumApp = Boolean(matchingApps?.length)
const methods = useForm({
defaultValues: {
@@ -124,53 +126,34 @@ export function ChooseRecoveryMethodModal({ open, onClose }: { open: boolean; on
}
+ disabled={!hasSygnumApp}
label={
-
- Sygnum
-
-
-
-
- Regulated Swiss digital asset bank
-
-
-
- Ensure you (and only you) can recover
-
-
-
- Simple and efficient
-
-
-
- }
- />
- }
- label={
-
-
- Coincover
+ Sygnum Web3 Recovery
- World's #1 Recovery Solution
+ Your key. Your crypto. Your recovery
- Protected by Biometrics
+ Account recovery by your identity
- Lloydโs of London Backed Tech
+ Regulated Swiss digital asset bank
+
+ {!hasSygnumApp && (
+
+ Not available on this network
+
+ )}
}
/>
@@ -199,13 +182,18 @@ export function ChooseRecoveryMethodModal({ open, onClose }: { open: boolean; on
) : (
-
+
- Join waitlist
+ Open App
)}
diff --git a/src/features/recovery/components/RecoverySettings/DelayModifierRow.tsx b/src/features/recovery/components/RecoverySettings/DelayModifierRow.tsx
index ddd4da961..ed4614b87 100644
--- a/src/features/recovery/components/RecoverySettings/DelayModifierRow.tsx
+++ b/src/features/recovery/components/RecoverySettings/DelayModifierRow.tsx
@@ -35,7 +35,7 @@ export function DelayModifierRow({ delayModifier }: { delayModifier: RecoverySta
-
+
diff --git a/src/features/recovery/components/RecoverySigners/index.tsx b/src/features/recovery/components/RecoverySigners/index.tsx
index 2e428a07a..af0715eee 100644
--- a/src/features/recovery/components/RecoverySigners/index.tsx
+++ b/src/features/recovery/components/RecoverySigners/index.tsx
@@ -8,7 +8,6 @@ import { Countdown } from '@/components/common/Countdown'
import { ExecuteRecoveryButton } from '../ExecuteRecoveryButton'
import { CancelRecoveryButton } from '../CancelRecoveryButton'
import { useRecoveryTxState } from '@/features/recovery/hooks/useRecoveryTxState'
-import { RecoveryValidationErrors } from '../RecoveryValidationErrors'
import { formatDateTime } from '@/utils/date'
import type { RecoveryQueueItem } from '@/features/recovery/services/recovery-state'
@@ -70,8 +69,6 @@ export function RecoverySigners({ item }: { item: RecoveryQueueItem }): ReactEle
{isNext && }
-
-
diff --git a/src/features/recovery/hooks/__tests__/useIsValidExecution.test.ts b/src/features/recovery/hooks/__tests__/useIsValidExecution.test.ts
index 501a7157f..4fb0e7c5f 100644
--- a/src/features/recovery/hooks/__tests__/useIsValidExecution.test.ts
+++ b/src/features/recovery/hooks/__tests__/useIsValidExecution.test.ts
@@ -1,3 +1,4 @@
+import type { SafeContractImplementationType } from '@safe-global/protocol-kit'
import type { SafeTransaction, SafeSignature } from '@safe-global/safe-core-sdk-types'
import * as useWallet from '@/hooks/wallets/useWallet'
import { act, renderHook } from '@/tests/test-utils'
@@ -9,7 +10,6 @@ import type { Eip1193Provider } from 'ethers'
import { JsonRpcProvider, BrowserProvider } from 'ethers'
import * as contracts from '@/services/contracts/safeContracts'
-import type { SafeContractEthers } from '@safe-global/protocol-kit'
import { MockEip1193Provider } from '@/tests/mocks/providers'
const createSafeTx = (data = '0x'): SafeTransaction => {
@@ -69,7 +69,7 @@ describe('useIsValidExecution', () => {
},
},
},
- } as unknown as SafeContractEthers),
+ } as unknown as SafeContractImplementationType),
)
const mockTx = createSafeTx()
diff --git a/src/features/recovery/hooks/useIsRecoverySupported.ts b/src/features/recovery/hooks/useIsRecoverySupported.ts
index 099578df0..549070dea 100644
--- a/src/features/recovery/hooks/useIsRecoverySupported.ts
+++ b/src/features/recovery/hooks/useIsRecoverySupported.ts
@@ -2,5 +2,5 @@ import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
export function useIsRecoverySupported(): boolean {
- return useHasFeature(FEATURES.RECOVERY)
+ return useHasFeature(FEATURES.RECOVERY) ?? false
}
diff --git a/src/features/recovery/services/__tests__/recovery-state.test.ts b/src/features/recovery/services/__tests__/recovery-state.test.ts
index b70a2d7a0..b9826bd0f 100644
--- a/src/features/recovery/services/__tests__/recovery-state.test.ts
+++ b/src/features/recovery/services/__tests__/recovery-state.test.ts
@@ -15,17 +15,24 @@ import { useWeb3ReadOnly } from '@/hooks/wallets/web3'
import { encodeMultiSendData } from '@safe-global/protocol-kit/dist/src/utils/transactions/utils'
import { getMultiSendCallOnlyDeployment, getSafeSingletonDeployment } from '@safe-global/safe-deployments'
import { Interface } from 'ethers'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
+import { FEATURES, getLatestSafeVersion } from '@/utils/chains'
+import { type FEATURES as GatewayFeatures } from '@safe-global/safe-gateway-typescript-sdk'
+import { chainBuilder } from '@/tests/builders/chains'
jest.mock('@/hooks/wallets/web3')
const mockUseWeb3ReadOnly = useWeb3ReadOnly as jest.MockedFunction
+const latestSafeVersion = getLatestSafeVersion(
+ chainBuilder()
+ .with({ chainId: '1', features: [FEATURES.SAFE_141 as unknown as GatewayFeatures] })
+ .build(),
+)
const PRE_MULTI_SEND_CALL_ONLY_VERSIONS = ['1.0.0', '1.1.1']
const SUPPORTED_MULTI_SEND_CALL_ONLY_VERSIONS = [
'1.3.0',
// '1.4.1', TODO: Uncomment when safe-deployments is updated >1.25.0
- LATEST_SAFE_VERSION,
+ latestSafeVersion,
]
describe('recovery-state', () => {
@@ -37,8 +44,8 @@ describe('recovery-state', () => {
describe('isMaliciousRecovery', () => {
describe('non-MultiSend', () => {
it('should return true if the transaction is not calling the Safe itself', () => {
- const chainId = '5'
- const version = LATEST_SAFE_VERSION
+ const chainId = '1'
+ const version = latestSafeVersion
const safeAddress = faker.finance.ethereumAddress()
const transaction = {
@@ -50,8 +57,8 @@ describe('recovery-state', () => {
})
it('should return false if the transaction is calling the Safe itself', () => {
- const chainId = '5'
- const version = LATEST_SAFE_VERSION
+ const chainId = '1'
+ const version = latestSafeVersion
const safeAddress = faker.finance.ethereumAddress()
const transaction = {
@@ -66,7 +73,7 @@ describe('recovery-state', () => {
describe('MultiSend', () => {
;[...PRE_MULTI_SEND_CALL_ONLY_VERSIONS, ...SUPPORTED_MULTI_SEND_CALL_ONLY_VERSIONS].forEach((version) => {
it(`should return true if the transaction is not an official MultiSend address for Safe version ${version}`, () => {
- const chainId = '5'
+ const chainId = '1'
const safeAddress = faker.finance.ethereumAddress()
const safeAbi = getSafeSingletonDeployment({ network: chainId, version })!.abi
@@ -102,8 +109,8 @@ describe('recovery-state', () => {
})
;[...PRE_MULTI_SEND_CALL_ONLY_VERSIONS, ...SUPPORTED_MULTI_SEND_CALL_ONLY_VERSIONS].forEach((version) => {
it(`should return true if the transaction is an official MultiSend call and not every transaction in the batch calls the Safe itself for Safe version ${version}`, () => {
- const chainId = '5'
- const version = LATEST_SAFE_VERSION
+ const chainId = '1'
+ const version = latestSafeVersion
const safeAddress = faker.finance.ethereumAddress()
const safeAbi = getSafeSingletonDeployment({ network: chainId, version })!.abi
@@ -140,7 +147,7 @@ describe('recovery-state', () => {
SUPPORTED_MULTI_SEND_CALL_ONLY_VERSIONS.forEach((version) => {
it(`should return false if the transaction is an official MultiSend call and every transaction in the batch calls the Safe itself for Safe version ${version}`, () => {
- const chainId = '5'
+ const chainId = '1'
const safeAddress = faker.finance.ethereumAddress()
const safeAbi = getSafeSingletonDeployment({ network: chainId, version })!.abi
@@ -175,7 +182,7 @@ describe('recovery-state', () => {
PRE_MULTI_SEND_CALL_ONLY_VERSIONS.forEach((version) => {
it(`should return false if the transaction is an official MultiSend call for Safe version ${version} (below the initial MultiSend contract version)`, () => {
- const chainId = '5'
+ const chainId = '1'
const safeAddress = faker.finance.ethereumAddress()
const safeAbi = getSafeSingletonDeployment({ network: chainId, version })!.abi
@@ -271,7 +278,7 @@ describe('recovery-state', () => {
blockHash: faker.string.alphanumeric(),
} as TransactionReceipt
- global.fetch = jest.fn().mockImplementation((_url: string) => {
+ global.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
json: () => Promise.resolve({ transactionHash }),
status: 200,
@@ -301,7 +308,7 @@ describe('recovery-state', () => {
blockHash: faker.string.alphanumeric(),
} as TransactionReceipt
- global.fetch = jest.fn().mockImplementation((_url: string) => {
+ global.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
json: () => Promise.resolve({ transactionHash }),
status: 200,
@@ -329,7 +336,7 @@ describe('recovery-state', () => {
const transactionService = faker.internet.url({ appendSlash: false })
const safeAddress = faker.finance.ethereumAddress()
- global.fetch = jest.fn().mockImplementation((_url: string) => {
+ global.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
status: 500,
ok: false,
@@ -352,7 +359,7 @@ describe('recovery-state', () => {
describe('getRecoveryState', () => {
it('should return the recovery state from the Safe creation block', async () => {
const safeAddress = faker.finance.ethereumAddress()
- const chainId = '5'
+ const chainId = '1'
const version = '1.3.0'
const transactionService = faker.internet.url({ appendSlash: false })
const transactionHash = `0x${faker.string.hexadecimal()}`
@@ -369,7 +376,7 @@ describe('recovery-state', () => {
.mockResolvedValue(transactionAddedReceipt),
} as unknown as JsonRpcProvider
- global.fetch = jest.fn().mockImplementation((_url: string) => {
+ global.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
json: () => Promise.resolve({ transactionHash }),
status: 200,
@@ -477,7 +484,7 @@ describe('recovery-state', () => {
it('should not query data if the queueNonce equals the txNonce', async () => {
const safeAddress = faker.finance.ethereumAddress()
- const chainId = '5'
+ const chainId = '1'
const version = '1.3.0'
const transactionService = faker.internet.url({ appendSlash: true })
const provider = {} as unknown as JsonRpcProvider
diff --git a/src/features/siweAccounts/components/EnableAccountBanner/index.stories.tsx b/src/features/siweAccounts/components/EnableAccountBanner/index.stories.tsx
new file mode 100644
index 000000000..223aae84c
--- /dev/null
+++ b/src/features/siweAccounts/components/EnableAccountBanner/index.stories.tsx
@@ -0,0 +1,37 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import EnableAccountBanner from './index'
+import { Paper } from '@mui/material'
+import type { Eip1193Provider } from 'ethers'
+import { BrowserProvider } from 'ethers'
+
+const mockBrowserProvider = new BrowserProvider({
+ request: () => {},
+} as unknown as Eip1193Provider)
+
+const meta = {
+ component: EnableAccountBanner,
+ parameters: {
+ layout: 'centered',
+ },
+ decorators: [
+ (Story) => {
+ return (
+
+
+
+ )
+ },
+ ],
+
+ tags: ['autodocs'],
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
+export const Default: Story = {
+ args: {
+ provider: mockBrowserProvider,
+ },
+}
diff --git a/src/features/siweAccounts/components/EnableAccountBanner/index.tsx b/src/features/siweAccounts/components/EnableAccountBanner/index.tsx
new file mode 100644
index 000000000..126c3d750
--- /dev/null
+++ b/src/features/siweAccounts/components/EnableAccountBanner/index.tsx
@@ -0,0 +1,73 @@
+import { signInWithEthereum } from '@/services/siwe'
+import { Alert, Box, Button, IconButton, Typography } from '@mui/material'
+import useWallet from '@/hooks/wallets/useWallet'
+import CloseIcon from '@mui/icons-material/Close'
+import DownloadCloud from '@/public/images/common/download-cloud.svg'
+import ChevronRightIcon from '@mui/icons-material/ChevronRight'
+
+import css from './style.module.css'
+import { useState } from 'react'
+import { createAccount, getAccount } from '@safe-global/safe-gateway-typescript-sdk'
+import type { BrowserProvider } from 'ethers'
+import { logError } from '@/services/exceptions'
+import ErrorCodes from '@/services/exceptions/ErrorCodes'
+
+const SignInBanner = ({ provider }: { provider: BrowserProvider | undefined }) => {
+ const { address = '' } = useWallet() || {}
+ const [isDismissed, setIsDismissed] = useState(false)
+
+ if (!provider || isDismissed) return null
+
+ const signIn = async () => {
+ let account
+ try {
+ await signInWithEthereum(provider)
+ account = await getAccount(address)
+ } catch (error) {
+ logError(ErrorCodes._640, error)
+ }
+ if (!account) {
+ try {
+ account = await createAccount({ address: address as `0x${string}` })
+ } catch (error) {
+ logError(ErrorCodes._641, error)
+ }
+ }
+ }
+
+ return (
+
+
}
+ action={
+ <>
+
} sx={{ padding: '2px', minWidth: '130px' }}>
+
+ Enable now
+
+
+
{
+ setIsDismissed(true)
+ }}
+ >
+
+
+ >
+ }
+ >
+
+
+ Access your accounts on any device! Enable cloud storage to switch devices effortlessly and keep your
+ data secure.
+
+
+
+
+ )
+}
+
+export default SignInBanner
diff --git a/src/features/siweAccounts/components/EnableAccountBanner/style.module.css b/src/features/siweAccounts/components/EnableAccountBanner/style.module.css
new file mode 100644
index 000000000..075c49900
--- /dev/null
+++ b/src/features/siweAccounts/components/EnableAccountBanner/style.module.css
@@ -0,0 +1,11 @@
+.container :global .MuiAlert-message {
+ padding: 4px;
+}
+
+.container :global .MuiAlert-action {
+ padding-top: 2px;
+}
+
+.container :global .MuiAlert-root {
+ border: 1px solid var(--color-secondary-light);
+}
diff --git a/src/features/speedup/components/SpeedUpModal.tsx b/src/features/speedup/components/SpeedUpModal.tsx
index d4a054a93..50a641b1b 100644
--- a/src/features/speedup/components/SpeedUpModal.tsx
+++ b/src/features/speedup/components/SpeedUpModal.tsx
@@ -1,8 +1,7 @@
import useGasPrice from '@/hooks/useGasPrice'
import ModalDialog from '@/components/common/ModalDialog'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import DialogContent from '@mui/material/DialogContent'
-import { Box, Button, SvgIcon, Tooltip, Typography } from '@mui/material'
+import { Box, Button, CircularProgress, SvgIcon, Tooltip, Typography } from '@mui/material'
import RocketSpeedup from '@/public/images/common/ic-rocket-speedup.svg'
import DialogActions from '@mui/material/DialogActions'
import useWallet from '@/hooks/wallets/useWallet'
@@ -27,6 +26,9 @@ import { TX_EVENTS } from '@/services/analytics/events/transactions'
import { getTransactionTrackingType } from '@/services/analytics/tx-tracking'
import { trackError } from '@/services/exceptions'
import ErrorCodes from '@/services/exceptions/ErrorCodes'
+import CheckWallet from '@/components/common/CheckWallet'
+import { useLazyGetTransactionDetailsQuery } from '@/store/gateway'
+import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
type Props = {
open: boolean
@@ -58,7 +60,7 @@ export const SpeedUpModal = ({
const safeAddress = useSafeAddress()
const hasActions = signerAddress && signerAddress === wallet?.address
const dispatch = useAppDispatch()
-
+ const [trigger] = useLazyGetTransactionDetailsQuery()
const isDisabled = waitingForConfirmation || !wallet || !speedUpFee || !onboard
const [safeTx] = useAsync(async () => {
if (!chainInfo?.chainId || !safeAddress) {
@@ -90,7 +92,6 @@ export const SpeedUpModal = ({
try {
setWaitingForConfirmation(true)
- await assertWalletChain(onboard, chainInfo.chainId)
if (pendingTx.txType === PendingTxType.SAFE_TX) {
await dispatchSafeTxSpeedUp(
@@ -100,8 +101,10 @@ export const SpeedUpModal = ({
chainInfo.chainId,
wallet.address,
safeAddress,
+ safeTx.data.nonce,
)
- const txType = await getTransactionTrackingType(chainInfo.chainId, txId)
+ const { data: details } = await trigger({ chainId: chainInfo.chainId, txId })
+ const txType = getTransactionTrackingType(details)
trackEvent({ ...TX_EVENTS.SPEED_UP, label: txType })
} else {
await dispatchCustomTxSpeedUp(
@@ -112,6 +115,7 @@ export const SpeedUpModal = ({
wallet.provider,
wallet.address,
safeAddress,
+ pendingTx.nonce,
)
// Currently all custom txs are batch executes
trackEvent({ ...TX_EVENTS.SPEED_UP, label: 'batch' })
@@ -152,6 +156,7 @@ export const SpeedUpModal = ({
txId,
wallet,
safeTx,
+ trigger,
])
if (!hasActions) {
@@ -190,15 +195,28 @@ export const SpeedUpModal = ({
/>
)}
+
+
+
Cancel
-
- {isDisabled ? 'Waiting on confirmation in wallet...' : 'Confirm'}
-
+
+ {(isOk) => (
+
+ {isDisabled ? : 'Confirm'}
+
+ )}
+
diff --git a/src/features/speedup/components/SpeedUpMonitor.tsx b/src/features/speedup/components/SpeedUpMonitor.tsx
index 8f36af7f8..8aa9e2dac 100644
--- a/src/features/speedup/components/SpeedUpMonitor.tsx
+++ b/src/features/speedup/components/SpeedUpMonitor.tsx
@@ -6,7 +6,8 @@ import type { MouseEventHandler } from 'react'
import { useState } from 'react'
import type { PendingProcessingTx } from '@/store/pendingTxsSlice'
import useAsync from '@/hooks/useAsync'
-import { isSmartContract, useWeb3ReadOnly } from '@/hooks/wallets/web3'
+import { useWeb3ReadOnly } from '@/hooks/wallets/web3'
+import { isSmartContract } from '@/utils/wallets'
import useWallet from '@/hooks/wallets/useWallet'
import { isSpeedableTx } from '@/features/speedup/utils/IsSpeedableTx'
import { MODALS_EVENTS, trackEvent } from '@/services/analytics'
@@ -30,7 +31,7 @@ export const SpeedUpMonitor = ({ txId, pendingTx, modalTrigger = 'alertBox' }: S
const [smartContract] = useAsync(async () => {
if (!pendingTx.signerAddress || !web3ReadOnly) return false
- return isSmartContract(web3ReadOnly, pendingTx.signerAddress)
+ return isSmartContract(pendingTx.signerAddress)
}, [pendingTx.signerAddress, web3ReadOnly])
if (!isFeatureEnabled || !isSpeedableTx(pendingTx, smartContract, wallet?.address ?? '')) {
diff --git a/src/features/speedup/utils/__tests__/IsSpeedableTx.test.ts b/src/features/speedup/utils/__tests__/IsSpeedableTx.test.ts
index a8089531f..9f3f84add 100644
--- a/src/features/speedup/utils/__tests__/IsSpeedableTx.test.ts
+++ b/src/features/speedup/utils/__tests__/IsSpeedableTx.test.ts
@@ -1,19 +1,14 @@
-import { isSpeedableTx } from '../IsSpeedableTx'
import { PendingStatus, type PendingTx, PendingTxType } from '@/store/pendingTxsSlice'
-import { faker } from '@faker-js/faker'
+import { pendingTxBuilder } from '@/tests/builders/pendingTx'
+import { isSpeedableTx } from '../IsSpeedableTx'
describe('isSpeedableTx', () => {
it('returns true when all conditions are met', () => {
const pendingTx: PendingTx = {
- status: PendingStatus.PROCESSING,
+ ...pendingTxBuilder().with({ status: PendingStatus.PROCESSING }).build(),
txHash: '0x123',
signerAddress: '0xabc',
- chainId: '1',
- safeAddress: '0xdef',
txType: PendingTxType.SAFE_TX,
- signerNonce: 0,
- submittedAt: Date.now(),
- gasLimit: 45_000,
}
const isSmartContract = false
@@ -26,16 +21,10 @@ describe('isSpeedableTx', () => {
it('returns false when one of the conditions is not met', () => {
const pendingTx: PendingTx = {
- status: PendingStatus.PROCESSING,
+ ...pendingTxBuilder().with({ status: PendingStatus.PROCESSING }).build(),
txHash: '0x123',
signerAddress: '0xabc',
- chainId: '1',
- safeAddress: '0xdef',
- txType: PendingTxType.CUSTOM_TX,
- signerNonce: 0,
- submittedAt: Date.now(),
- data: '0x1234',
- to: faker.finance.ethereumAddress(),
+ txType: PendingTxType.SAFE_TX,
}
const isSmartContract = true
diff --git a/src/features/stake/components/InfoTooltip/index.tsx b/src/features/stake/components/InfoTooltip/index.tsx
new file mode 100644
index 000000000..5dae2afa2
--- /dev/null
+++ b/src/features/stake/components/InfoTooltip/index.tsx
@@ -0,0 +1,22 @@
+import { SvgIcon, Tooltip } from '@mui/material'
+import InfoIcon from '@/public/images/notifications/info.svg'
+import type { ReactNode } from 'react'
+
+export function InfoTooltip({ title }: { title: string | ReactNode }) {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/src/features/stake/components/StakeButton/index.tsx b/src/features/stake/components/StakeButton/index.tsx
new file mode 100644
index 000000000..efe0b3822
--- /dev/null
+++ b/src/features/stake/components/StakeButton/index.tsx
@@ -0,0 +1,58 @@
+import CheckWallet from '@/components/common/CheckWallet'
+import Track from '@/components/common/Track'
+import { AppRoutes } from '@/config/routes'
+import useSpendingLimit from '@/hooks/useSpendingLimit'
+import { Button } from '@mui/material'
+import type { TokenInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { TokenType } from '@safe-global/safe-gateway-typescript-sdk'
+import { useRouter } from 'next/router'
+import type { ReactElement } from 'react'
+import StakeIcon from '@/public/images/common/stake.svg'
+import type { STAKE_LABELS } from '@/services/analytics/events/stake'
+import { STAKE_EVENTS } from '@/services/analytics/events/stake'
+import { useCurrentChain } from '@/hooks/useChains'
+
+const StakeButton = ({
+ tokenInfo,
+ trackingLabel,
+}: {
+ tokenInfo: TokenInfo
+ trackingLabel: STAKE_LABELS
+}): ReactElement => {
+ const spendingLimit = useSpendingLimit(tokenInfo)
+ const chain = useCurrentChain()
+ const router = useRouter()
+
+ return (
+
+ {(isOk) => (
+
+ }
+ onClick={() => {
+ router.push({
+ pathname: AppRoutes.stake,
+ query: {
+ ...router.query,
+ asset: `${chain?.shortName}_${
+ tokenInfo.type === TokenType.NATIVE_TOKEN ? 'NATIVE_TOKEN' : tokenInfo.address
+ }`,
+ },
+ })
+ }}
+ disabled={!isOk}
+ >
+ Stake
+
+
+ )}
+
+ )
+}
+
+export default StakeButton
diff --git a/src/features/stake/components/StakePage/index.tsx b/src/features/stake/components/StakePage/index.tsx
new file mode 100644
index 000000000..b42b388f4
--- /dev/null
+++ b/src/features/stake/components/StakePage/index.tsx
@@ -0,0 +1,57 @@
+import { Stack } from '@mui/material'
+import Disclaimer from '@/components/common/Disclaimer'
+import WidgetDisclaimer from '@/components/common/WidgetDisclaimer'
+import useStakeConsent from '@/features/stake/useStakeConsent'
+import StakingWidget from '../StakingWidget'
+import { useRouter } from 'next/router'
+import { useGetIsSanctionedQuery } from '@/store/ofac'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+import useWallet from '@/hooks/wallets/useWallet'
+import useSafeInfo from '@/hooks/useSafeInfo'
+import { getKeyWithTrueValue } from '@/utils/helpers'
+import BlockedAddress from '@/components/common/BlockedAddress'
+
+const StakePage = () => {
+ const { isConsentAccepted, onAccept } = useStakeConsent()
+ const router = useRouter()
+ const { asset } = router.query
+
+ const { safeAddress } = useSafeInfo()
+ const wallet = useWallet()
+
+ const { data: isSafeAddressBlocked } = useGetIsSanctionedQuery(safeAddress || skipToken)
+ const { data: isWalletAddressBlocked } = useGetIsSanctionedQuery(wallet?.address || skipToken)
+ const blockedAddresses = {
+ [safeAddress]: !!isSafeAddressBlocked,
+ [wallet?.address || '']: !!isWalletAddressBlocked,
+ }
+
+ const blockedAddress = getKeyWithTrueValue(blockedAddresses)
+
+ if (blockedAddress) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+ <>
+ {isConsentAccepted === undefined ? null : isConsentAccepted ? (
+
+ ) : (
+
+ }
+ onAccept={onAccept}
+ buttonText="Continue"
+ />
+
+ )}
+ >
+ )
+}
+
+export default StakePage
diff --git a/src/features/stake/components/StakingConfirmationTx/Deposit.tsx b/src/features/stake/components/StakingConfirmationTx/Deposit.tsx
new file mode 100644
index 000000000..902434751
--- /dev/null
+++ b/src/features/stake/components/StakingConfirmationTx/Deposit.tsx
@@ -0,0 +1,104 @@
+import { Box, Stack, Typography } from '@mui/material'
+import FieldsGrid from '@/components/tx/FieldsGrid'
+import type { StakingTxDepositInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import {
+ ConfirmationViewTypes,
+ type NativeStakingDepositConfirmationView,
+} from '@safe-global/safe-gateway-typescript-sdk'
+import ConfirmationOrderHeader from '@/components/tx/ConfirmationOrder/ConfirmationOrderHeader'
+import { formatDurationFromMilliseconds, formatVisualAmount } from '@/utils/formatters'
+import { formatCurrency } from '@/utils/formatNumber'
+import StakingStatus from '@/features/stake/components/StakingStatus'
+import { InfoTooltip } from '@/features/stake/components/InfoTooltip'
+
+type StakingOrderConfirmationViewProps = {
+ order: NativeStakingDepositConfirmationView | StakingTxDepositInfo
+}
+
+const CURRENCY = 'USD'
+
+const StakingConfirmationTxDeposit = ({ order }: StakingOrderConfirmationViewProps) => {
+ const isOrder = order.type === ConfirmationViewTypes.KILN_NATIVE_STAKING_DEPOSIT
+
+ // the fee is returned in decimal format, so we multiply by 100 to get the percentage
+ const fee = (order.fee * 100).toFixed(2)
+ return (
+
+ {isOrder && (
+
+ )}
+
+
+ {formatVisualAmount(order.expectedAnnualReward, order.tokenInfo.decimals)} {order.tokenInfo.symbol}
+ {' ('}
+ {formatCurrency(order.expectedFiatAnnualReward, CURRENCY)})
+
+
+
+ {formatVisualAmount(order.expectedMonthlyReward, order.tokenInfo.decimals)} {order.tokenInfo.symbol}
+ {' ('}
+ {formatCurrency(order.expectedFiatMonthlyReward, CURRENCY)})
+
+
+
+ Fee
+
+ >
+ }
+ >
+ {fee} %
+
+
+
+ {isOrder ? (
+
+ You will own{' '}
+
+ {order.numValidators} Ethereum validator{order.numValidators === 1 ? '' : 's'}
+
+
+ ) : (
+ {order.numValidators}
+ )}
+
+ {formatDurationFromMilliseconds(order.estimatedEntryTime)}
+
+ Approx. every 5 days after activation
+
+ {!isOrder && (
+
+
+
+ )}
+
+ {isOrder && (
+
+ Earn ETH rewards with dedicated validators. Rewards must be withdrawn manually, and you can request a
+ withdrawal at any time.
+
+ )}
+
+
+ )
+}
+
+export default StakingConfirmationTxDeposit
diff --git a/src/features/stake/components/StakingConfirmationTx/Exit.tsx b/src/features/stake/components/StakingConfirmationTx/Exit.tsx
new file mode 100644
index 000000000..03a8012da
--- /dev/null
+++ b/src/features/stake/components/StakingConfirmationTx/Exit.tsx
@@ -0,0 +1,67 @@
+import { Alert, Stack, Typography } from '@mui/material'
+import FieldsGrid from '@/components/tx/FieldsGrid'
+import { formatDurationFromMilliseconds } from '@/utils/formatters'
+import { type NativeStakingValidatorsExitConfirmationView } from '@safe-global/safe-gateway-typescript-sdk/dist/types/decoded-data'
+import ConfirmationOrderHeader from '@/components/tx/ConfirmationOrder/ConfirmationOrderHeader'
+import { InfoTooltip } from '@/features/stake/components/InfoTooltip'
+
+type StakingOrderConfirmationViewProps = {
+ order: NativeStakingValidatorsExitConfirmationView
+}
+
+const StakingConfirmationTxExit = ({ order }: StakingOrderConfirmationViewProps) => {
+ const withdrawIn = formatDurationFromMilliseconds(order.estimatedExitTime + order.estimatedWithdrawalTime, [
+ 'days',
+ 'hours',
+ ])
+
+ return (
+
+
+
+
+ Withdraw in
+
+ Withdrawal time is the sum of:
+
+ Time until your validator is successfully exited after the withdraw request
+ Time for a stake to receive Consensus rewards on the execution layer
+
+ >
+ }
+ />
+ >
+ }
+ >
+ Up to {withdrawIn}
+
+
+
+ The selected amount and any rewards will be withdrawn from Dedicated Staking for ETH after the validator exit.
+
+
+
+ This transaction is a withdrawal request. After it's executed, you'll need to complete a separate
+ withdrawal transaction.
+
+
+ )
+}
+
+export default StakingConfirmationTxExit
diff --git a/src/features/stake/components/StakingConfirmationTx/Withdraw.tsx b/src/features/stake/components/StakingConfirmationTx/Withdraw.tsx
new file mode 100644
index 000000000..08011e6e7
--- /dev/null
+++ b/src/features/stake/components/StakingConfirmationTx/Withdraw.tsx
@@ -0,0 +1,29 @@
+import { Stack } from '@mui/material'
+import FieldsGrid from '@/components/tx/FieldsGrid'
+import {
+ type NativeStakingWithdrawConfirmationView,
+ type StakingTxWithdrawInfo,
+} from '@safe-global/safe-gateway-typescript-sdk'
+import TokenAmount from '@/components/common/TokenAmount'
+
+type StakingOrderConfirmationViewProps = {
+ order: NativeStakingWithdrawConfirmationView | StakingTxWithdrawInfo
+}
+
+const StakingConfirmationTxWithdraw = ({ order }: StakingOrderConfirmationViewProps) => {
+ return (
+
+
+ {' '}
+
+
+
+ )
+}
+
+export default StakingConfirmationTxWithdraw
diff --git a/src/features/stake/components/StakingConfirmationTx/index.tsx b/src/features/stake/components/StakingConfirmationTx/index.tsx
new file mode 100644
index 000000000..c1309fccb
--- /dev/null
+++ b/src/features/stake/components/StakingConfirmationTx/index.tsx
@@ -0,0 +1,31 @@
+import type { AnyStakingConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
+import { ConfirmationViewTypes, type StakingTxInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import StakingConfirmationTxDeposit from '@/features/stake/components/StakingConfirmationTx/Deposit'
+import StakingConfirmationTxExit from '@/features/stake/components/StakingConfirmationTx/Exit'
+import StakingConfirmationTxWithdraw from '@/features/stake/components/StakingConfirmationTx/Withdraw'
+
+type StakingOrderConfirmationViewProps = {
+ order: AnyStakingConfirmationView | StakingTxInfo
+}
+
+const StrakingConfirmationTx = ({ order }: StakingOrderConfirmationViewProps) => {
+ const isDeposit = order.type === ConfirmationViewTypes.KILN_NATIVE_STAKING_DEPOSIT
+ const isExit = order.type === ConfirmationViewTypes.KILN_NATIVE_STAKING_VALIDATORS_EXIT
+ const isWithdraw = order.type === ConfirmationViewTypes.KILN_NATIVE_STAKING_WITHDRAW
+
+ if (isDeposit) {
+ return
+ }
+
+ if (isExit) {
+ return
+ }
+
+ if (isWithdraw) {
+ return
+ }
+
+ return null
+}
+
+export default StrakingConfirmationTx
diff --git a/src/features/stake/components/StakingStatus/index.tsx b/src/features/stake/components/StakingStatus/index.tsx
new file mode 100644
index 000000000..9efc1f667
--- /dev/null
+++ b/src/features/stake/components/StakingStatus/index.tsx
@@ -0,0 +1,77 @@
+import { NativeStakingStatus } from '@safe-global/safe-gateway-typescript-sdk'
+import { SvgIcon } from '@mui/material'
+import CheckIcon from '@/public/images/common/circle-check.svg'
+import ClockIcon from '@/public/images/common/clock.svg'
+import SlashShield from '@/public/images/common/shield-off.svg'
+import SignatureIcon from '@/public/images/common/document_signature.svg'
+import TxStatusChip, { type TxStatusChipProps } from '@/components/transactions/TxStatusChip'
+
+const ColorIcons: Record<
+ NativeStakingStatus,
+ | {
+ color: TxStatusChipProps['color']
+ icon?: React.ComponentType
+ text: string
+ }
+ | undefined
+> = {
+ [NativeStakingStatus.NOT_STAKED]: {
+ color: 'warning',
+ icon: SignatureIcon,
+ text: 'Inactive',
+ },
+ [NativeStakingStatus.ACTIVATING]: {
+ color: 'info',
+ icon: ClockIcon,
+ text: 'Activating',
+ },
+ [NativeStakingStatus.DEPOSIT_IN_PROGRESS]: {
+ color: 'info',
+ icon: ClockIcon,
+ text: 'Awaiting entry',
+ },
+ [NativeStakingStatus.ACTIVE]: {
+ color: 'success',
+ icon: CheckIcon,
+ text: 'Validating',
+ },
+ [NativeStakingStatus.EXIT_REQUESTED]: {
+ color: 'info',
+ icon: ClockIcon,
+ text: 'Requested exit',
+ },
+ [NativeStakingStatus.EXITING]: {
+ color: 'info',
+ icon: ClockIcon,
+ text: 'Request pending',
+ },
+ [NativeStakingStatus.EXITED]: {
+ color: 'success',
+ icon: CheckIcon,
+ text: 'Withdrawn',
+ },
+ [NativeStakingStatus.SLASHED]: {
+ color: 'warning',
+ icon: SlashShield,
+ text: 'Slashed',
+ },
+}
+
+const capitalizedStatus = (status: string) =>
+ status
+ .toLowerCase()
+ .replace(/_/g, ' ')
+ .replace(/^\w/g, (l) => l.toUpperCase())
+
+const StakingStatus = ({ status }: { status: NativeStakingStatus }) => {
+ const config = ColorIcons[status]
+
+ return (
+
+ {config?.icon && }
+ {config?.text || capitalizedStatus(status)}
+
+ )
+}
+
+export default StakingStatus
diff --git a/src/features/stake/components/StakingTxDepositDetails/index.tsx b/src/features/stake/components/StakingTxDepositDetails/index.tsx
new file mode 100644
index 000000000..1a3e3c9d0
--- /dev/null
+++ b/src/features/stake/components/StakingTxDepositDetails/index.tsx
@@ -0,0 +1,21 @@
+import { Box } from '@mui/material'
+import type { StakingTxDepositInfo, TransactionData } from '@safe-global/safe-gateway-typescript-sdk'
+import FieldsGrid from '@/components/tx/FieldsGrid'
+import SendAmountBlock from '@/components/tx-flow/flows/TokenTransfer/SendAmountBlock'
+import StakingConfirmationTxDeposit from '@/features/stake/components/StakingConfirmationTx/Deposit'
+
+const StakingTxDepositDetails = ({ info, txData }: { info: StakingTxDepositInfo; txData?: TransactionData }) => {
+ return (
+
+ {txData && (
+
+ )}
+
+ {info.annualNrr.toFixed(3)}%
+
+
+
+ )
+}
+
+export default StakingTxDepositDetails
diff --git a/src/features/stake/components/StakingTxDepositInfo/index.tsx b/src/features/stake/components/StakingTxDepositInfo/index.tsx
new file mode 100644
index 000000000..ef18cb93d
--- /dev/null
+++ b/src/features/stake/components/StakingTxDepositInfo/index.tsx
@@ -0,0 +1,17 @@
+import type { StakingTxDepositInfo as StakingTxDepositInfoType } from '@safe-global/safe-gateway-typescript-sdk'
+import TokenAmount from '@/components/common/TokenAmount'
+
+export const StakingTxDepositInfo = ({ info }: { info: StakingTxDepositInfoType }) => {
+ return (
+ <>
+
+ >
+ )
+}
+
+export default StakingTxDepositInfo
diff --git a/src/features/stake/components/StakingTxExitDetails/index.tsx b/src/features/stake/components/StakingTxExitDetails/index.tsx
new file mode 100644
index 000000000..d4cdb8072
--- /dev/null
+++ b/src/features/stake/components/StakingTxExitDetails/index.tsx
@@ -0,0 +1,51 @@
+import { Box, Link } from '@mui/material'
+import type { StakingTxExitInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { NativeStakingStatus } from '@safe-global/safe-gateway-typescript-sdk'
+import FieldsGrid from '@/components/tx/FieldsGrid'
+import StakingStatus from '@/features/stake/components/StakingStatus'
+import { formatDurationFromMilliseconds } from '@/utils/formatters'
+import { BEACON_CHAIN_EXPLORERS } from '@/features/stake/constants'
+import useChainId from '@/hooks/useChainId'
+
+const StakingTxExitDetails = ({ info }: { info: StakingTxExitInfo }) => {
+ const withdrawIn = formatDurationFromMilliseconds(info.estimatedExitTime + info.estimatedWithdrawalTime, [
+ 'days',
+ 'hours',
+ ])
+
+ return (
+
+
+ {info.validators.map((validator: string, index: number) => {
+ return (
+ <>
+
+ {index < info.validators.length - 1 && ' | '}
+ >
+ )
+ })}
+
+ {info.status !== NativeStakingStatus.EXITED && Up to {withdrawIn} }
+
+
+
+
+
+ )
+}
+
+export const BeaconChainLink = ({ validator, name }: { validator: string; name: string }) => {
+ const chainId = useChainId()
+ return (
+
+ {name}
+
+ )
+}
+export default StakingTxExitDetails
diff --git a/src/features/stake/components/StakingTxExitInfo/index.tsx b/src/features/stake/components/StakingTxExitInfo/index.tsx
new file mode 100644
index 000000000..989bb04d3
--- /dev/null
+++ b/src/features/stake/components/StakingTxExitInfo/index.tsx
@@ -0,0 +1,11 @@
+import type { StakingTxExitInfo } from '@safe-global/safe-gateway-typescript-sdk'
+
+const StakingTxExitInfo = ({ info }: { info: StakingTxExitInfo }) => {
+ return (
+ <>
+ {info.numValidators} Validator{info.numValidators > 1 ? 's' : ''}
+ >
+ )
+}
+
+export default StakingTxExitInfo
diff --git a/src/features/stake/components/StakingTxWithdrawDetails/index.tsx b/src/features/stake/components/StakingTxWithdrawDetails/index.tsx
new file mode 100644
index 000000000..78681f19d
--- /dev/null
+++ b/src/features/stake/components/StakingTxWithdrawDetails/index.tsx
@@ -0,0 +1,13 @@
+import { Box } from '@mui/material'
+import type { StakingTxWithdrawInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import StakingConfirmationTxWithdraw from '@/features/stake/components/StakingConfirmationTx/Withdraw'
+
+const StakingTxWithdrawDetails = ({ info }: { info: StakingTxWithdrawInfo }) => {
+ return (
+
+
+
+ )
+}
+
+export default StakingTxWithdrawDetails
diff --git a/src/features/stake/components/StakingTxWithdrawInfo/index.tsx b/src/features/stake/components/StakingTxWithdrawInfo/index.tsx
new file mode 100644
index 000000000..13fee40c8
--- /dev/null
+++ b/src/features/stake/components/StakingTxWithdrawInfo/index.tsx
@@ -0,0 +1,17 @@
+import type { StakingTxWithdrawInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import TokenAmount from '@/components/common/TokenAmount'
+
+const StakingTxWithdrawInfo = ({ info }: { info: StakingTxWithdrawInfo }) => {
+ return (
+ <>
+
+ >
+ )
+}
+
+export default StakingTxWithdrawInfo
diff --git a/src/features/stake/components/StakingWidget/index.tsx b/src/features/stake/components/StakingWidget/index.tsx
new file mode 100644
index 000000000..e06c911b0
--- /dev/null
+++ b/src/features/stake/components/StakingWidget/index.tsx
@@ -0,0 +1,29 @@
+import { useMemo } from 'react'
+import AppFrame from '@/components/safe-apps/AppFrame'
+import { getEmptySafeApp } from '@/components/safe-apps/utils'
+import { useGetStakeWidgetUrl } from '@/features/stake/hooks/useGetStakeWidgetUrl'
+import { widgetAppData } from '@/features/stake/constants'
+
+const StakingWidget = ({ asset }: { asset?: string }) => {
+ const url = useGetStakeWidgetUrl(asset)
+
+ const appData = useMemo(
+ () => ({
+ ...getEmptySafeApp(),
+ ...widgetAppData,
+ url,
+ }),
+ [url],
+ )
+
+ return (
+
+ )
+}
+
+export default StakingWidget
diff --git a/src/features/stake/constants.ts b/src/features/stake/constants.ts
new file mode 100644
index 000000000..f0efac218
--- /dev/null
+++ b/src/features/stake/constants.ts
@@ -0,0 +1,17 @@
+export const STAKE_TITLE = 'Stake'
+
+export const WIDGET_PRODUCTION_URL = 'https://safe.widget.kiln.fi/overview'
+export const WIDGET_TESTNET_URL = 'https://safe.widget.testnet.kiln.fi/overview'
+
+export const widgetAppData = {
+ url: WIDGET_PRODUCTION_URL,
+ name: STAKE_TITLE,
+ iconUrl: '/images/common/stake.svg',
+ chainIds: ['17000', '11155111', '1', '42161', '137', '56', '8453', '10'],
+}
+
+// TODO: move this to the config service
+export const BEACON_CHAIN_EXPLORERS = {
+ '1': 'https://beaconcha.in',
+ '17000': 'https://holesky.beaconcha.in',
+}
diff --git a/src/features/stake/helpers/utils.ts b/src/features/stake/helpers/utils.ts
new file mode 100644
index 000000000..5eae4b383
--- /dev/null
+++ b/src/features/stake/helpers/utils.ts
@@ -0,0 +1,19 @@
+import { id } from 'ethers'
+import type { BaseTransaction } from '@safe-global/safe-apps-sdk'
+
+const WITHDRAW_SIGHASH = id('requestValidatorsExit(bytes)').slice(0, 10)
+const CLAIM_SIGHASH = id('batchWithdrawCLFee(bytes)').slice(0, 10)
+
+export const getStakeTitle = (txs: BaseTransaction[] | undefined) => {
+ const hashToLabel = {
+ [WITHDRAW_SIGHASH]: 'Withdraw request',
+ [CLAIM_SIGHASH]: 'Claim',
+ }
+
+ const stakeTitle = txs
+ ?.map((tx) => hashToLabel[tx.data.slice(0, 10)])
+ .filter(Boolean)
+ .join(' and ')
+
+ return stakeTitle
+}
diff --git a/src/features/stake/hooks/useGetStakeWidgetUrl.ts b/src/features/stake/hooks/useGetStakeWidgetUrl.ts
new file mode 100644
index 000000000..f14c4ef1b
--- /dev/null
+++ b/src/features/stake/hooks/useGetStakeWidgetUrl.ts
@@ -0,0 +1,24 @@
+import { useDarkMode } from '@/hooks/useDarkMode'
+import useChainId from '@/hooks/useChainId'
+import useChains from '@/hooks/useChains'
+import { useMemo } from 'react'
+import { WIDGET_PRODUCTION_URL, WIDGET_TESTNET_URL } from '@/features/stake/constants'
+
+export const useGetStakeWidgetUrl = (asset?: string) => {
+ let url = WIDGET_PRODUCTION_URL
+ const isDarkMode = useDarkMode()
+ const currentChainId = useChainId()
+ const { configs } = useChains()
+ const testChains = useMemo(() => configs.filter((chain) => chain.isTestnet), [configs])
+ if (testChains.some((chain) => chain.chainId === currentChainId)) {
+ url = WIDGET_TESTNET_URL
+ }
+ const params = new URLSearchParams()
+ params.append('theme', isDarkMode ? 'dark' : 'light')
+
+ if (asset) {
+ params.append('asset', asset)
+ }
+
+ return url + '?' + params.toString()
+}
diff --git a/src/features/stake/hooks/useIsSwapFeatureEnabled.ts b/src/features/stake/hooks/useIsSwapFeatureEnabled.ts
new file mode 100644
index 000000000..964072036
--- /dev/null
+++ b/src/features/stake/hooks/useIsSwapFeatureEnabled.ts
@@ -0,0 +1,11 @@
+import { GeoblockingContext } from '@/components/common/GeoblockingProvider'
+import { useHasFeature } from '@/hooks/useChains'
+import { FEATURES } from '@/utils/chains'
+import { useContext } from 'react'
+
+const useIsStakingFeatureEnabled = () => {
+ const isBlockedCountry = useContext(GeoblockingContext)
+ return useHasFeature(FEATURES.STAKING) && !isBlockedCountry
+}
+
+export default useIsStakingFeatureEnabled
diff --git a/src/features/stake/useStakeConsent.ts b/src/features/stake/useStakeConsent.ts
new file mode 100644
index 000000000..456612655
--- /dev/null
+++ b/src/features/stake/useStakeConsent.ts
@@ -0,0 +1,28 @@
+import { localItem } from '@/services/local-storage/local'
+import { useCallback, useEffect, useState } from 'react'
+
+const STAKE_CONSENT_STORAGE_KEY = 'stakeDisclaimerAcceptedV1'
+const stakeConsentStorage = localItem(STAKE_CONSENT_STORAGE_KEY)
+
+const useStakeConsent = (): {
+ isConsentAccepted: boolean | undefined
+ onAccept: () => void
+} => {
+ const [isConsentAccepted, setIsConsentAccepted] = useState()
+
+ const onAccept = useCallback(() => {
+ setIsConsentAccepted(true)
+ stakeConsentStorage.set(true)
+ }, [setIsConsentAccepted])
+
+ useEffect(() => {
+ setIsConsentAccepted(stakeConsentStorage.get() || false)
+ }, [setIsConsentAccepted])
+
+ return {
+ isConsentAccepted,
+ onAccept,
+ }
+}
+
+export default useStakeConsent
diff --git a/src/features/swap/components/StatusLabel/index.tsx b/src/features/swap/components/StatusLabel/index.tsx
index e8a186714..67910f4f2 100644
--- a/src/features/swap/components/StatusLabel/index.tsx
+++ b/src/features/swap/components/StatusLabel/index.tsx
@@ -1,4 +1,4 @@
-import { Chip as MuiChip, SvgIcon } from '@mui/material'
+import { SvgIcon } from '@mui/material'
import type { OrderStatuses } from '@safe-global/safe-gateway-typescript-sdk'
import type { ReactElement } from 'react'
import CheckIcon from '@/public/images/common/circle-check.svg'
@@ -6,6 +6,7 @@ import ClockIcon from '@/public/images/common/clock.svg'
import BlockIcon from '@/public/images/common/block.svg'
import SignatureIcon from '@/public/images/common/document_signature.svg'
import CircleIPartialFillcon from '@/public/images/common/circle-partial-fill.svg'
+import TxStatusChip, { type TxStatusChipProps } from '@/components/transactions/TxStatusChip'
type CustomOrderStatuses = OrderStatuses | 'partiallyFilled'
type Props = {
@@ -14,72 +15,51 @@ type Props = {
type StatusProps = {
label: string
- color: string
- backgroundColor: string
- iconColor: string
- icon: any
+ color: TxStatusChipProps['color']
+ icon: React.ComponentType
}
const statusMap: Record = {
presignaturePending: {
label: 'Execution needed',
- color: 'warning.main',
- backgroundColor: 'warning.background',
- iconColor: 'warning.main',
+ color: 'warning',
icon: SignatureIcon,
},
fulfilled: {
label: 'Filled',
- color: 'success.dark',
- backgroundColor: 'secondary.background',
- iconColor: 'success.dark',
+ color: 'success',
icon: CheckIcon,
},
open: {
label: 'Open',
- color: 'warning.main',
- backgroundColor: 'warning.background',
- iconColor: 'warning.main',
+ color: 'warning',
icon: ClockIcon,
},
cancelled: {
label: 'Cancelled',
- color: 'error.main',
- backgroundColor: 'error.background',
- iconColor: 'error.main',
+ color: 'error',
icon: BlockIcon,
},
expired: {
label: 'Expired',
- color: 'primary.light',
- backgroundColor: 'background.main',
- iconColor: 'border.main',
+ color: 'primary',
icon: ClockIcon,
},
partiallyFilled: {
label: 'Partially filled',
- color: 'success.dark',
- backgroundColor: 'secondary.background',
- iconColor: 'success.dark',
+ color: 'success',
icon: CircleIPartialFillcon,
},
}
export const StatusLabel = (props: Props): ReactElement => {
const { status } = props
- const { label, color, icon, iconColor, backgroundColor } = statusMap[status]
+ const { label, color, icon } = statusMap[status]
return (
- }
- />
+
+
+ {label}
+
)
}
diff --git a/src/features/swap/components/SwapButton/index.tsx b/src/features/swap/components/SwapButton/index.tsx
index 13f4c9fb1..70610da8c 100644
--- a/src/features/swap/components/SwapButton/index.tsx
+++ b/src/features/swap/components/SwapButton/index.tsx
@@ -37,7 +37,7 @@ const SwapButton = ({
pathname: AppRoutes.swap,
query: {
...router.query,
- token: tokenInfo.symbol,
+ token: tokenInfo.address,
amount,
},
})
diff --git a/src/features/swap/components/SwapOrder/index.tsx b/src/features/swap/components/SwapOrder/index.tsx
index 8610f9707..dd12b48a6 100644
--- a/src/features/swap/components/SwapOrder/index.tsx
+++ b/src/features/swap/components/SwapOrder/index.tsx
@@ -1,4 +1,4 @@
-import TokenIcon from '@/components/common/TokenIcon'
+import { Fragment } from 'react'
import OrderId from '@/features/swap/components/OrderId'
import StatusLabel from '@/features/swap/components/StatusLabel'
import SwapProgress from '@/features/swap/components/SwapProgress'
@@ -18,7 +18,6 @@ import { compareAsc } from 'date-fns'
import css from './styles.module.css'
import { Typography } from '@mui/material'
import { formatAmount } from '@/utils/formatNumber'
-import { formatVisualAmount } from '@/utils/formatters'
import {
getExecutionPrice,
getLimitPrice,
@@ -28,6 +27,7 @@ import {
isOrderPartiallyFilled,
} from '@/features/swap/helpers/utils'
import EthHashInfo from '@/components/common/EthHashInfo'
+import TokenAmount from '@/components/common/TokenAmount'
import useSafeInfo from '@/hooks/useSafeInfo'
import { isSwapOrderTxInfo, isSwapTransferOrderTxInfo, isTwapOrderTxInfo } from '@/utils/transaction-guards'
import { EmptyRow } from '@/components/common/Table/EmptyRow'
@@ -41,6 +41,8 @@ type SwapOrderProps = {
txInfo?: Order
}
+const TWAP_PARTS_STATUS_THRESHOLD = 10
+
const AmountRow = ({ order }: { order: Order }) => {
const { sellToken, buyToken, sellAmount, buyAmount, kind } = order
const isSellOrder = kind === 'sell'
@@ -50,19 +52,23 @@ const AmountRow = ({ order }: { order: Order }) => {
{isSellOrder ? 'Sell' : 'For at most'}{' '}
- {sellToken.logoUri && }{' '}
-
- {formatVisualAmount(sellAmount, sellToken.decimals)} {sellToken.symbol}
-
+
{isSellOrder ? 'for at least' : 'Buy'}{' '}
- {buyToken.logoUri && }
-
- {formatVisualAmount(buyAmount, buyToken.decimals)} {buyToken.symbol}
-
+
@@ -213,9 +219,9 @@ export const TwapOrder = ({ order }: { order: SwapTwapOrder }) => {
const isPartiallyFilled = isOrderPartiallyFilled(order)
const expires = new Date(validUntil * 1000)
const now = new Date()
-
const orderKindLabel = capitalize(kind)
+ const isStatusKnown = Number(numberOfParts) <= TWAP_PARTS_STATUS_THRESHOLD
return (
{
,
,
,
- ,
+ order.executedSellAmount !== null && order.executedBuyAmount !== null ? (
+
+ ) : (
+
+ ),
,
,
status !== 'fulfilled' && compareAsc(now, expires) !== 1 ? (
@@ -248,9 +258,13 @@ export const TwapOrder = ({ order }: { order: SwapTwapOrder }) => {
{formatDateTime(validUntil * 1000)}
),
-
-
- ,
+ isStatusKnown ? (
+
+
+
+ ) : (
+
+ ),
]}
/>
)
diff --git a/src/features/swap/components/SwapOrderConfirmationView/OrderFeeConfirmationView.tsx b/src/features/swap/components/SwapOrderConfirmationView/OrderFeeConfirmationView.tsx
index 4cc8020a8..8fcf7d3db 100644
--- a/src/features/swap/components/SwapOrderConfirmationView/OrderFeeConfirmationView.tsx
+++ b/src/features/swap/components/SwapOrderConfirmationView/OrderFeeConfirmationView.tsx
@@ -1,11 +1,15 @@
-import type { OrderConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
+import type { SwapOrderConfirmationView, TwapOrderConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
import { getOrderFeeBps } from '@/features/swap/helpers/utils'
import { DataRow } from '@/components/common/Table/DataRow'
import { HelpCenterArticle } from '@/config/constants'
import { HelpIconTooltip } from '@/features/swap/components/HelpIconTooltip'
import MUILink from '@mui/material/Link'
-export const OrderFeeConfirmationView = ({ order }: { order: Pick }) => {
+export const OrderFeeConfirmationView = ({
+ order,
+}: {
+ order: Pick
+}) => {
const bps = getOrderFeeBps(order)
if (Number(bps) === 0) {
diff --git a/src/features/swap/components/SwapOrderConfirmationView/index.tsx b/src/features/swap/components/SwapOrderConfirmationView/index.tsx
index 8c34512b6..2c986286b 100644
--- a/src/features/swap/components/SwapOrderConfirmationView/index.tsx
+++ b/src/features/swap/components/SwapOrderConfirmationView/index.tsx
@@ -6,9 +6,8 @@ import { DataTable } from '@/components/common/Table/DataTable'
import { compareAsc } from 'date-fns'
import { Alert, Typography } from '@mui/material'
import { formatAmount } from '@/utils/formatNumber'
-import { formatVisualAmount } from '@/utils/formatters'
import { getLimitPrice, getOrderClass, getSlippageInPercent } from '@/features/swap/helpers/utils'
-import type { OrderConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
+import type { AnySwapOrderConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
import { StartTimeValue } from '@safe-global/safe-gateway-typescript-sdk'
import { ConfirmationViewTypes } from '@safe-global/safe-gateway-typescript-sdk'
import SwapTokens from '@/features/swap/components/SwapTokens'
@@ -20,13 +19,15 @@ import { PartDuration } from '@/features/swap/components/SwapOrder/rows/PartDura
import { PartSellAmount } from '@/features/swap/components/SwapOrder/rows/PartSellAmount'
import { PartBuyAmount } from '@/features/swap/components/SwapOrder/rows/PartBuyAmount'
import { OrderFeeConfirmationView } from '@/features/swap/components/SwapOrderConfirmationView/OrderFeeConfirmationView'
+import { isSettingTwapFallbackHandler } from '@/features/swap/helpers/utils'
+import { TwapFallbackHandlerWarning } from '@/features/swap/components/TwapFallbackHandlerWarning'
type SwapOrderProps = {
- order: OrderConfirmationView
+ order: AnySwapOrderConfirmationView
settlementContract: string
}
-export const SwapOrderConfirmationView = ({ order, settlementContract }: SwapOrderProps): ReactElement => {
+export const SwapOrderConfirmation = ({ order, settlementContract }: SwapOrderProps): ReactElement => {
const { owner, kind, validUntil, sellToken, buyToken, sellAmount, buyAmount, explorerUrl, receiver } = order
const isTwapOrder = order.type === ConfirmationViewTypes.COW_SWAP_TWAP_ORDER
@@ -38,25 +39,26 @@ export const SwapOrderConfirmationView = ({ order, settlementContract }: SwapOrd
const slippage = getSlippageInPercent(order)
const isSellOrder = kind === 'sell'
+ const isChangingFallbackHandler = isSettingTwapFallbackHandler(order)
return (
-
+ <>
+ {isChangingFallbackHandler && }
+
,
@@ -143,8 +145,8 @@ export const SwapOrderConfirmationView = ({ order, settlementContract }: SwapOrd
/>
)}
-
+ >
)
}
-export default SwapOrderConfirmationView
+export default SwapOrderConfirmation
diff --git a/src/features/swap/components/SwapTokens/index.stories.tsx b/src/features/swap/components/SwapTokens/index.stories.tsx
index 7f5e9b29d..06d9b077a 100644
--- a/src/features/swap/components/SwapTokens/index.stories.tsx
+++ b/src/features/swap/components/SwapTokens/index.stories.tsx
@@ -28,16 +28,22 @@ export const Default: Story = {
first: {
value: '100',
label: 'Sell',
- logoUri:
- 'https://safe-transaction-assets.staging.5afe.dev/tokens/logos/0x0625aFB445C3B6B7B929342a04A22599fd5dBB59.png',
- tokenSymbol: 'COW',
+ tokenInfo: {
+ decimals: 18,
+ logoUri:
+ 'https://safe-transaction-assets.staging.5afe.dev/tokens/logos/0x0625aFB445C3B6B7B929342a04A22599fd5dBB59.png',
+ symbol: 'COW',
+ },
},
second: {
value: '86',
label: 'For at least',
- logoUri:
- 'https://safe-transaction-assets.staging.5afe.dev/tokens/logos/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984.png',
- tokenSymbol: 'UNI',
+ tokenInfo: {
+ decimals: 18,
+ logoUri:
+ 'https://safe-transaction-assets.staging.5afe.dev/tokens/logos/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984.png',
+ symbol: 'UNI',
+ },
},
},
}
diff --git a/src/features/swap/components/SwapTokens/index.tsx b/src/features/swap/components/SwapTokens/index.tsx
index d42d87649..15d02327a 100644
--- a/src/features/swap/components/SwapTokens/index.tsx
+++ b/src/features/swap/components/SwapTokens/index.tsx
@@ -1,53 +1,7 @@
-import type { ReactElement } from 'react'
-import TokenIcon from '@/components/common/TokenIcon'
-import { SvgIcon, Typography } from '@mui/material'
-import Stack from '@mui/material/Stack'
-import css from './styles.module.css'
-import EastRoundedIcon from '@mui/icons-material/EastRounded'
+import ConfirmationOrderHeader, { type InfoBlock } from '@/components/tx/ConfirmationOrder/ConfirmationOrderHeader'
-const SwapToken = ({
- value,
- tokenSymbol,
- label,
- logoUri,
-}: {
- value: string
- tokenSymbol: string
- label?: string
- logoUri?: string
-}): ReactElement => {
- return (
-
-
-
-
- {label}
-
-
- {value} {tokenSymbol}
-
-
-
- )
-}
-
-type SwapToken = {
- value: string
- tokenSymbol: string
- label: string
- logoUri: string
-}
-
-const SwapTokens = ({ first, second }: { first: SwapToken; second: SwapToken }) => {
- return (
-
- )
+const SwapTokens = ({ first, second }: { first: InfoBlock; second: InfoBlock }) => {
+ return
}
export default SwapTokens
diff --git a/src/features/swap/components/SwapTokens/styles.module.css b/src/features/swap/components/SwapTokens/styles.module.css
deleted file mode 100644
index 5dd2ad675..000000000
--- a/src/features/swap/components/SwapTokens/styles.module.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.container {
- display: flex;
- gap: var(--space-1);
- position: relative;
-}
-
-.swapToken {
- display: inline-flex;
- align-items: center;
- flex-wrap: wrap;
- gap: var(--space-2);
- color: var(--color-text-primary);
- background: var(--color-background-main);
- padding: var(--space-2) var(--space-3);
- border-radius: 6px;
- flex: 1;
-}
-
-.icon {
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--color-background-paper);
- border-radius: 50%;
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- width: 40px;
- height: 40px;
- padding: var(--space-1);
-}
diff --git a/src/features/swap/components/SwapTxInfo/SwapTx.tsx b/src/features/swap/components/SwapTxInfo/SwapTx.tsx
index 08f861a8d..c06651486 100644
--- a/src/features/swap/components/SwapTxInfo/SwapTx.tsx
+++ b/src/features/swap/components/SwapTxInfo/SwapTx.tsx
@@ -1,67 +1,52 @@
-import type { Order } from '@safe-global/safe-gateway-typescript-sdk'
+import type { Order, OrderToken } from '@safe-global/safe-gateway-typescript-sdk'
import type { ReactElement } from 'react'
-import { Box, Typography } from '@mui/material'
+import { Typography } from '@mui/material'
+import TokenAmount from '@/components/common/TokenAmount'
import TokenIcon from '@/components/common/TokenIcon'
-import { formatVisualAmount } from '@/utils/formatters'
+
+const Amount = ({ value, token }: { value: string; token: OrderToken }) => (
+
+)
+
+const OnlyToken = ({ token }: { token: OrderToken }) => (
+
+
+ {token.symbol}
+
+)
export const SwapTx = ({ info }: { info: Order }): ReactElement => {
const { kind, sellToken, sellAmount, buyToken, buyAmount } = info
const isSellOrder = kind === 'sell'
- let from = (
- <>
-
-
-
-
- {formatVisualAmount(sellAmount, sellToken.decimals)} {sellToken.symbol}{' '}
-
- >
- )
-
- let to = (
- <>
-
-
- {' '}
-
- {buyToken.symbol}
-
- >
- )
+ let from =
+ let to =
if (!isSellOrder) {
- from = (
- <>
-
-
-
-
- {sellToken.symbol}
-
- >
- )
- to = (
- <>
-
-
- {' '}
-
- {formatVisualAmount(buyAmount, buyToken.decimals)} {buyToken.symbol}{' '}
-
- >
- )
+ from =
+ to =
}
return (
-
-
- {from}
-
- to
-
- {to}
+
+ {from}
+
+ to
-
+ {to}
+
)
}
diff --git a/src/features/swap/components/SwapTxInfo/interactWith.tsx b/src/features/swap/components/SwapTxInfo/interactWith.tsx
deleted file mode 100644
index 92f3a822d..000000000
--- a/src/features/swap/components/SwapTxInfo/interactWith.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
-import { Box, Typography } from '@mui/material'
-import EthHashInfo from '@/components/common/EthHashInfo'
-
-const InteractWith = ({ txData }: { txData: TransactionDetails['txData'] }) => {
- if (!txData) return null
-
- return (
-
- Interact with
-
-
- )
-}
-export default InteractWith
diff --git a/src/features/swap/components/TwapFallbackHandlerWarning/index.tsx b/src/features/swap/components/TwapFallbackHandlerWarning/index.tsx
index 470bb515b..7c9234a3c 100644
--- a/src/features/swap/components/TwapFallbackHandlerWarning/index.tsx
+++ b/src/features/swap/components/TwapFallbackHandlerWarning/index.tsx
@@ -1,15 +1,17 @@
-import { Alert, Box, SvgIcon } from '@mui/material'
+import { Alert, SvgIcon } from '@mui/material'
import InfoOutlinedIcon from '@/public/images/notifications/info.svg'
export const TwapFallbackHandlerWarning = () => {
return (
-
- }>
- Enable TWAPs and submit order.
- {` `}
- To enable TWAP orders you need to set a custom fallback handler. This software is developed by CoW Swap and Safe
- will not be responsible for any possible issues with it.
-
-
+ }
+ sx={{ mb: 1 }}
+ >
+ Enable TWAPs and submit order.
+ {` `}
+ To enable TWAP orders you need to set a custom fallback handler. This software is developed by CoW Swap and Safe
+ will not be responsible for any possible issues with it.
+
)
}
diff --git a/src/features/swap/helpers/__tests__/utils.test.ts b/src/features/swap/helpers/__tests__/utils.test.ts
index 17ebb380f..506a96ce8 100644
--- a/src/features/swap/helpers/__tests__/utils.test.ts
+++ b/src/features/swap/helpers/__tests__/utils.test.ts
@@ -8,7 +8,7 @@ import {
isSettingTwapFallbackHandler,
TWAP_FALLBACK_HANDLER,
} from '../utils'
-import type { DecodedDataResponse } from '@safe-global/safe-gateway-typescript-sdk'
+import type { DecodedDataResponse, TwapOrder } from '@safe-global/safe-gateway-typescript-sdk'
import { type SwapOrder } from '@safe-global/safe-gateway-typescript-sdk'
describe('Swap helpers', () => {
@@ -88,6 +88,25 @@ describe('Swap helpers', () => {
expect(surplusPrice).toBe(0)
})
+ test('twap order with unknown executed sell and buy amounts', () => {
+ const mockOrder = {
+ executedSellAmount: null,
+ executedBuyAmount: null,
+ buyToken: { decimals: 8 },
+ sellToken: { decimals: 18 },
+ sellAmount: '100000000000000000000',
+ buyAmount: '5000000000',
+ } as unknown as TwapOrder
+
+ const executionPrice = getExecutionPrice(mockOrder)
+ const limitPrice = getLimitPrice(mockOrder)
+ const surplusPrice = getSurplusPrice(mockOrder)
+
+ expect(executionPrice).toBe(0)
+ expect(limitPrice).toBe(2)
+ expect(surplusPrice).toBe(0)
+ })
+
describe('getFilledPercentage', () => {
it('returns 0 if no amount was executed', () => {
const mockOrder = {
diff --git a/src/features/swap/helpers/data/stablecoins.ts b/src/features/swap/helpers/data/stablecoins.ts
index b818b11d6..6ce272f1d 100644
--- a/src/features/swap/helpers/data/stablecoins.ts
+++ b/src/features/swap/helpers/data/stablecoins.ts
@@ -506,4 +506,39 @@ export const stableCoinAddresses: {
symbol: 'USDT',
chains: ['sepolia'],
},
+ '0xaf204776c7245bf4147c2612bf6e5972ee483701': {
+ name: 'Savings xDai',
+ symbol: 'SDAI',
+ chains: ['gnosis'],
+ },
+ '0x83f20f44975d03b1b09e64809b757c47f942beea': {
+ name: 'Savings xDai',
+ symbol: 'SDAI',
+ chains: ['ethereum'],
+ },
+ '0x4c612e3b15b96ff9a6faed838f8d07d479a8dd4c': {
+ name: 'Aave v3 sDai',
+ symbol: 'ASDAI',
+ chains: ['ethereum'],
+ },
+ '0x7a5c3860a77a8dc1b225bd46d0fb2ac1c6d191bc': {
+ name: 'Aave v3 sDai',
+ symbol: 'ASDAI',
+ chains: ['gnosis'],
+ },
+ '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0': {
+ name: 'Gnosis xDAI Bridged USDC',
+ symbol: 'USDC.e',
+ chains: ['gnosis'],
+ },
+ '0xcb444e90d8198415266c6a2724b7900fb12fc56e': {
+ name: 'Monerium EUR emoney',
+ symbol: 'EURE',
+ chains: ['gnosis'],
+ },
+ '0xdc035d45d973e3ec169d2276ddab16f1e407384f': {
+ name: 'Sky dollar',
+ symbol: 'USDS',
+ chains: ['ethereum'],
+ },
}
diff --git a/src/features/swap/helpers/utils.ts b/src/features/swap/helpers/utils.ts
index 318fad0b7..ad745ea18 100644
--- a/src/features/swap/helpers/utils.ts
+++ b/src/features/swap/helpers/utils.ts
@@ -30,9 +30,9 @@ export const getExecutionPrice = (
const { executedSellAmount, executedBuyAmount, buyToken, sellToken } = order
const ratio = calculateRatio(
- { amount: executedSellAmount, decimals: sellToken.decimals },
+ { amount: executedSellAmount || '0', decimals: sellToken.decimals },
{
- amount: executedBuyAmount,
+ amount: executedBuyAmount || '0',
decimals: buyToken.decimals,
},
)
@@ -54,6 +54,9 @@ export const getLimitPrice = (
}
const calculateRatio = (a: Quantity, b: Quantity) => {
+ if (BigInt(b.amount) === 0n) {
+ return 0
+ }
return asDecimal(BigInt(a.amount), a.decimals) / asDecimal(BigInt(b.amount), b.decimals)
}
@@ -65,9 +68,9 @@ export const getSurplusPrice = (
): number => {
const { kind, executedSellAmount, sellAmount, sellToken, executedBuyAmount, buyAmount, buyToken } = order
if (kind === OrderKind.BUY) {
- return calculateDifference(sellAmount, executedSellAmount, sellToken.decimals)
+ return calculateDifference(sellAmount, executedSellAmount || '', sellToken.decimals)
} else if (kind === OrderKind.SELL) {
- return calculateDifference(executedBuyAmount, buyAmount, buyToken.decimals)
+ return calculateDifference(executedBuyAmount || '', buyAmount, buyToken.decimals)
} else {
return 0
}
@@ -95,8 +98,8 @@ const getPartiallyFilledBuySurplus = (
{ amount: sellAmount, decimals: sellToken.decimals },
{ amount: buyAmount, decimals: buyToken.decimals },
)
- const maximumSellAmount = asDecimal(BigInt(executedBuyAmount), buyToken.decimals) * limitPrice
- return maximumSellAmount - asDecimal(BigInt(executedSellAmount), sellToken.decimals)
+ const maximumSellAmount = asDecimal(BigInt(executedBuyAmount || 0n), buyToken.decimals) * limitPrice
+ return maximumSellAmount - asDecimal(BigInt(executedSellAmount || 0n), sellToken.decimals)
}
const getPartiallyFilledSellSurplus = (
@@ -112,8 +115,8 @@ const getPartiallyFilledSellSurplus = (
{ amount: sellAmount, decimals: sellToken.decimals },
)
- const minimumBuyAmount = asDecimal(BigInt(executedSellAmount), sellToken.decimals) * limitPrice
- return asDecimal(BigInt(executedBuyAmount), buyToken.decimals) - minimumBuyAmount
+ const minimumBuyAmount = asDecimal(BigInt(executedSellAmount || 0n), sellToken.decimals) * limitPrice
+ return asDecimal(BigInt(executedBuyAmount || 0n), buyToken.decimals) - minimumBuyAmount
}
export const getFilledPercentage = (
@@ -139,9 +142,9 @@ export const getFilledAmount = (
order: Pick,
): string => {
if (order.kind === OrderKind.BUY) {
- return formatUnits(order.executedBuyAmount, order.buyToken.decimals)
+ return formatUnits(order.executedBuyAmount || 0n, order.buyToken.decimals)
} else if (order.kind === OrderKind.SELL) {
- return formatUnits(order.executedSellAmount, order.sellToken.decimals)
+ return formatUnits(order.executedSellAmount || 0n, order.sellToken.decimals)
} else {
return '0'
}
@@ -171,9 +174,9 @@ export const getOrderFeeBps = (order: Pick): number =>
export const isOrderPartiallyFilled = (
order: Pick,
): boolean => {
- const executedBuyAmount = BigInt(order.executedBuyAmount)
+ const executedBuyAmount = BigInt(order.executedBuyAmount || 0)
const buyAmount = BigInt(order.buyAmount)
- const executedSellAmount = BigInt(order.executedSellAmount)
+ const executedSellAmount = BigInt(order.executedSellAmount || 0)
const sellAmount = BigInt(order.sellAmount)
if (order.kind === OrderKind.BUY) {
diff --git a/src/features/swap/hooks/useIsExpiredSwap.ts b/src/features/swap/hooks/useIsExpiredSwap.ts
index bb563d99a..82915d7e7 100644
--- a/src/features/swap/hooks/useIsExpiredSwap.ts
+++ b/src/features/swap/hooks/useIsExpiredSwap.ts
@@ -1,24 +1,41 @@
-import { useEffect, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
import type { TransactionInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { isSwapOrderTxInfo } from '@/utils/transaction-guards'
-import useIntervalCounter from '@/hooks/useIntervalCounter'
-
-const INTERVAL_IN_MS = 10_000
/**
- * Checks the expiry time of a swap every 10s
- * and returns true if the swap expired
+ * Checks whether a swap has expired and if it hasn't it sets a timeout
+ * for the exact moment it will expire
* @param txInfo
*/
const useIsExpiredSwap = (txInfo: TransactionInfo) => {
const [isExpired, setIsExpired] = useState(false)
- const [counter] = useIntervalCounter(INTERVAL_IN_MS)
+ const timerRef = useRef(null)
useEffect(() => {
if (!isSwapOrderTxInfo(txInfo)) return
- setIsExpired(Date.now() > txInfo.validUntil * 1000)
- }, [counter, txInfo])
+ const checkExpiry = () => {
+ const now = Date.now()
+ const expiryTime = txInfo.validUntil * 1000
+
+ if (now > expiryTime) {
+ setIsExpired(true)
+ } else {
+ // Set a timeout for the exact moment it will expire
+ timerRef.current = setTimeout(() => {
+ setIsExpired(true)
+ }, expiryTime - now)
+ }
+ }
+
+ checkExpiry()
+
+ return () => {
+ if (timerRef.current) {
+ clearTimeout(timerRef.current)
+ }
+ }
+ }, [txInfo])
return isExpired
}
diff --git a/src/features/swap/index.tsx b/src/features/swap/index.tsx
index 295da8ec6..9ac20f238 100644
--- a/src/features/swap/index.tsx
+++ b/src/features/swap/index.tsx
@@ -3,7 +3,7 @@ import { type CowSwapWidgetParams, TradeType } from '@cowprotocol/widget-lib'
import type { OnTradeParamsPayload } from '@cowprotocol/events'
import { type CowEventListeners, CowEvents } from '@cowprotocol/events'
import { type MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
-import { Box, Container, Grid, useTheme } from '@mui/material'
+import { Box, useTheme } from '@mui/material'
import {
SafeAppAccessPolicyTypes,
type SafeAppData,
@@ -20,15 +20,13 @@ import useWallet from '@/hooks/wallets/useWallet'
import BlockedAddress from '@/components/common/BlockedAddress'
import useSwapConsent from './useSwapConsent'
import Disclaimer from '@/components/common/Disclaimer'
-import LegalDisclaimerContent from '@/features/swap/components/LegalDisclaimer'
-import { isBlockedAddress } from '@/services/ofac'
+import WidgetDisclaimer from '@/components/common/WidgetDisclaimer'
import { selectSwapParams, setSwapParams, type SwapState } from './store/swapParamsSlice'
import { setSwapOrder } from '@/store/swapOrderSlice'
import useChainId from '@/hooks/useChainId'
import { type BaseTransaction } from '@safe-global/safe-apps-sdk'
import { APPROVAL_SIGNATURE_HASH } from '@/components/tx/ApprovalEditor/utils/approvals'
import { id } from 'ethers'
-import useIsSwapFeatureEnabled from './hooks/useIsSwapFeatureEnabled'
import {
LIMIT_ORDER_TITLE,
SWAP_TITLE,
@@ -39,6 +37,9 @@ import {
import { calculateFeePercentageInBps } from '@/features/swap/helpers/fee'
import { UiOrderTypeToOrderType } from '@/features/swap/helpers/utils'
import { FEATURES } from '@/utils/chains'
+import { useGetIsSanctionedQuery } from '@/store/ofac'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+import { getKeyWithTrueValue } from '@/utils/helpers'
const BASE_URL = typeof window !== 'undefined' && window.location.origin ? window.location.origin : ''
@@ -50,6 +51,7 @@ const CANCEL_ORDER_SIGHASH = id('invalidateOrder(bytes)').slice(0, 10)
type Params = {
sell?: {
+ // The token address
asset: string
amount: string
}
@@ -78,15 +80,25 @@ const SwapWidget = ({ sell }: Params) => {
const darkMode = useDarkMode()
const chainId = useChainId()
const dispatch = useAppDispatch()
- const isSwapFeatureEnabled = useIsSwapFeatureEnabled()
const swapParams = useAppSelector(selectSwapParams)
const { safeAddress, safeLoading } = useSafeInfo()
- const [blockedAddress, setBlockedAddress] = useState('')
+ const [recipientAddress, setRecipientAddress] = useState('')
const wallet = useWallet()
const { isConsentAccepted, onAccept } = useSwapConsent()
const feeEnabled = useHasFeature(FEATURES.NATIVE_SWAPS_FEE_ENABLED)
const useStagingCowServer = useHasFeature(FEATURES.NATIVE_SWAPS_USE_COW_STAGING_SERVER)
+ const { data: isSafeAddressBlocked } = useGetIsSanctionedQuery(safeAddress || skipToken)
+ const { data: isWalletAddressBlocked } = useGetIsSanctionedQuery(wallet?.address || skipToken)
+ const { data: isRecipientAddressBlocked } = useGetIsSanctionedQuery(recipientAddress || skipToken)
+ const blockedAddresses = {
+ [safeAddress]: !!isSafeAddressBlocked,
+ [wallet?.address || '']: !!isWalletAddressBlocked,
+ [recipientAddress]: !!isRecipientAddressBlocked,
+ }
+
+ const blockedAddress = getKeyWithTrueValue(blockedAddresses)
+
const [params, setParams] = useState({
appCode: 'Safe Wallet Swaps', // Name of your app (max 50 characters)
width: '100%', // Width in pixels (or 100% to use all available space)
@@ -141,15 +153,6 @@ const SwapWidget = ({ sell }: Params) => {
},
})
- useEffect(() => {
- if (isBlockedAddress(safeAddress)) {
- setBlockedAddress(safeAddress)
- }
- if (wallet?.address && isBlockedAddress(wallet.address)) {
- setBlockedAddress(wallet.address)
- }
- }, [safeAddress, wallet?.address])
-
const appData: SafeAppData = useMemo(
() => ({
id: 1,
@@ -233,15 +236,15 @@ const SwapWidget = ({ sell }: Params) => {
bps: newFeeBps,
},
sell: {
- asset: sellToken?.symbol,
+ asset: sellToken?.address,
},
buy: {
- asset: buyToken?.symbol,
+ asset: buyToken?.address,
},
}))
- if (recipient && isBlockedAddress(recipient)) {
- setBlockedAddress(recipient)
+ if (recipient) {
+ setRecipientAddress(recipient)
}
dispatch(setSwapParams({ tradeType }))
@@ -269,6 +272,14 @@ const SwapWidget = ({ sell }: Params) => {
}))
}, [palette, darkMode, chainId])
+ useEffect(() => {
+ if (!sell) return
+ setParams((params) => ({
+ ...params,
+ sell,
+ }))
+ }, [sell])
+
const chain = useCurrentChain()
const iframeRef: MutableRefObject = useRef(null)
@@ -283,20 +294,17 @@ const SwapWidget = ({ sell }: Params) => {
useCustomAppCommunicator(iframeRef, appData, chain)
if (blockedAddress) {
- return
+ return
}
if (!isConsentAccepted) {
- return } onAccept={onAccept} buttonText="Continue" />
- }
-
- if (!isSwapFeatureEnabled) {
return (
-
-
- Swaps are not supported on this chain
-
-
+ }
+ onAccept={onAccept}
+ buttonText="Continue"
+ />
)
}
diff --git a/src/features/walletconnect/WalletConnectContext.tsx b/src/features/walletconnect/WalletConnectContext.tsx
index c17820132..757f021fb 100644
--- a/src/features/walletconnect/WalletConnectContext.tsx
+++ b/src/features/walletconnect/WalletConnectContext.tsx
@@ -17,7 +17,7 @@ export const WalletConnectContext = createContext({
error: null,
setError: () => {},
open: false,
- setOpen: (_open: boolean) => {},
+ setOpen: () => {},
isLoading: undefined,
setIsLoading: () => {},
})
diff --git a/src/features/walletconnect/components/WalletConnectProvider/index.tsx b/src/features/walletconnect/components/WalletConnectProvider/index.tsx
index fd9b0442f..a9787fc85 100644
--- a/src/features/walletconnect/components/WalletConnectProvider/index.tsx
+++ b/src/features/walletconnect/components/WalletConnectProvider/index.tsx
@@ -23,6 +23,10 @@ export enum WCLoadingState {
DISCONNECT = 'Disconnect',
}
+// The URL of the former WalletConnect Safe App
+// This is still used to differentiate these txs from Safe App txs in the analytics
+const LEGACY_WC_APP_URL = 'https://apps-portal.safe.global/wallet-connect'
+
const walletConnectSingleton = new WalletConnectWallet()
const getWrongChainError = (dappName: string): Error => {
@@ -88,7 +92,7 @@ export const WalletConnectProvider = ({ children }: { children: ReactNode }) =>
// Get response from Safe Wallet Provider
return safeWalletProvider.request(event.id, event.params.request, {
- url: session.peer.metadata.url,
+ url: LEGACY_WC_APP_URL, // required for server-side analytics
name: getPeerName(session.peer) || 'WalletConnect',
description: session.peer.metadata.description,
iconUrl: session.peer.metadata.icons[0],
diff --git a/src/features/walletconnect/components/WcProposalForm/ProposalVerification.tsx b/src/features/walletconnect/components/WcProposalForm/ProposalVerification.tsx
index 15b7a37d6..869c10bcb 100644
--- a/src/features/walletconnect/components/WcProposalForm/ProposalVerification.tsx
+++ b/src/features/walletconnect/components/WcProposalForm/ProposalVerification.tsx
@@ -1,72 +1,42 @@
import type { Web3WalletTypes } from '@walletconnect/web3wallet'
import { Alert, SvgIcon } from '@mui/material'
-import type { AlertColor } from '@mui/material'
import AlertIcon from '@/public/images/notifications/alert.svg'
-import type { Verify } from '@walletconnect/types'
-import type { ComponentType, ReactElement } from 'react'
-import CloseIcon from '@/public/images/common/close.svg'
-import InfoIcon from '@/public/images/notifications/info.svg'
-import CheckIcon from '@/public/images/common/check.svg'
+import type { ReactElement } from 'react'
import { getPeerName } from '@/features/walletconnect/services/utils'
import css from './styles.module.css'
-const Validation: {
- [key in Verify.Context['verified']['validation']]: {
- color: AlertColor
- desc: string
- Icon: ComponentType
- }
-} = {
- VALID: {
- color: 'success',
- desc: 'has been verified by WalletConnect.',
- Icon: CheckIcon,
- },
- UNKNOWN: {
- color: 'warning',
- desc: 'has not been verified by WalletConnect.',
- Icon: InfoIcon,
- },
- INVALID: {
- color: 'error',
- desc: 'has a domain that does not match the sender of this request. Approving it may result in a loss of funds.',
- Icon: CloseIcon,
- },
-}
-
const ProposalVerification = ({ proposal }: { proposal: Web3WalletTypes.SessionProposal }): ReactElement | null => {
- const { proposer } = proposal.params
const { isScam, validation } = proposal.verifyContext.verified
- if (validation === 'UNKNOWN') {
+ if (validation === 'UNKNOWN' || validation === 'VALID') {
return null
}
- const _validation = Validation[validation]
- const color = isScam ? 'error' : _validation.color
- const Icon = isScam ? AlertIcon : _validation.Icon
+ const appName = getPeerName(proposal.params.proposer)
return (
palette[color].background }}
+ severity="error"
+ sx={{ bgcolor: 'error.background' }}
className={css.alert}
icon={
palette[color].main,
+ fill: 'error.main',
},
}}
/>
}
>
{isScam
- ? `We prevent connecting to ${getPeerName(proposer) || 'this dApp'} as they are a known scam.`
- : `${getPeerName(proposer) || 'This dApp'} ${_validation.desc}`}
+ ? `We prevent connecting to ${appName || 'this dApp'} as they are a known scam.`
+ : `${
+ appName || 'This dApp'
+ } has a domain that does not match the sender of this request. Approving it may result in a loss of funds.`}
)
}
diff --git a/src/hooks/Beamer/useBeamer.ts b/src/hooks/Beamer/useBeamer.ts
index 52afabba1..f97a74b9d 100644
--- a/src/hooks/Beamer/useBeamer.ts
+++ b/src/hooks/Beamer/useBeamer.ts
@@ -1,13 +1,12 @@
import { useEffect } from 'react'
import { useAppSelector } from '@/store'
-import { CookieAndTermType, selectCookies } from '@/store/cookiesAndTermsSlice'
+import { CookieAndTermType, hasConsentFor } from '@/store/cookiesAndTermsSlice'
import { loadBeamer, unloadBeamer, updateBeamer } from '@/services/beamer'
import { useCurrentChain } from '@/hooks/useChains'
const useBeamer = () => {
- const cookies = useAppSelector(selectCookies)
- const isBeamerEnabled = cookies[CookieAndTermType.UPDATES]
+ const isBeamerEnabled = useAppSelector((state) => hasConsentFor(state, CookieAndTermType.UPDATES))
const chain = useCurrentChain()
useEffect(() => {
diff --git a/src/hooks/__tests__/useDecodeTx.test.ts b/src/hooks/__tests__/useDecodeTx.test.ts
index f8c7fd791..4127c2ce0 100644
--- a/src/hooks/__tests__/useDecodeTx.test.ts
+++ b/src/hooks/__tests__/useDecodeTx.test.ts
@@ -83,13 +83,14 @@ describe('useDecodeTx', () => {
const safeTx = createMockSafeTransaction({
data: '0x1234567890abcdef', // non-empty data
to: faker.finance.ethereumAddress(),
+ value: '1000000',
})
- const { result } = renderHook(() => useDecodeTx(safeTx))
+ renderHook(() => useDecodeTx(safeTx))
await waitFor(async () => {
expect(getConfirmationView).toHaveBeenCalledTimes(1)
- expect(getConfirmationView).toHaveBeenCalledWith('5', '0x789', '0x1234567890abcdef', safeTx.data.to)
+ expect(getConfirmationView).toHaveBeenCalledWith('5', '0x789', '0x1234567890abcdef', safeTx.data.to, '1000000')
})
})
diff --git a/src/hooks/__tests__/useLoadChains.test.ts b/src/hooks/__tests__/useLoadChains.test.ts
index 5188de75d..0b4563cdf 100644
--- a/src/hooks/__tests__/useLoadChains.test.ts
+++ b/src/hooks/__tests__/useLoadChains.test.ts
@@ -1,20 +1,18 @@
-import { getChainsConfig } from '@safe-global/safe-gateway-typescript-sdk'
import useLoadChains from '@/hooks/loadables/useLoadChains'
import { act, renderHook } from '@/tests/test-utils'
+import { getConfigs } from '@/hooks/loadables/helpers/config'
// Mock getChainsConfig
-jest.mock('@safe-global/safe-gateway-typescript-sdk', () => {
+jest.mock('@/hooks/loadables/helpers/config.ts', () => {
return {
- getChainsConfig: jest.fn(() => {
+ getConfigs: jest.fn(() => {
return new Promise((resolve) => {
setTimeout(() => {
- resolve({
- results: [
- {
- chainId: '4',
- },
- ],
- })
+ resolve([
+ {
+ chainId: '4',
+ },
+ ])
}, 100)
})
}),
@@ -61,7 +59,7 @@ describe('useLoadChains hook', () => {
it('should set the error state when the promise rejects', async () => {
// Change the getChainsConfig mock to reject
- ;(getChainsConfig as jest.Mock).mockImplementation(() => Promise.reject(new Error('Something went wrong')))
+ ;(getConfigs as jest.Mock).mockImplementation(() => Promise.reject(new Error('Something went wrong')))
const { result } = renderHook(() => useLoadChains())
diff --git a/src/hooks/__tests__/useRankedSafeApps.test.ts b/src/hooks/__tests__/useRankedSafeApps.test.ts
index 669a5c0fa..0ce767eba 100644
--- a/src/hooks/__tests__/useRankedSafeApps.test.ts
+++ b/src/hooks/__tests__/useRankedSafeApps.test.ts
@@ -1,7 +1,6 @@
import { renderHook } from '@/tests/test-utils'
import { useRankedSafeApps } from '@/hooks/safe-apps/useRankedSafeApps'
import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk'
-import { SafeAppsTag } from '@/config/constants'
const getMockSafeApp = (props: Partial) => {
return {
@@ -32,20 +31,6 @@ describe('useRankedSafeApps', () => {
expect(result.current.length).toEqual(5)
})
- it('excludes featured safe apps', () => {
- const mockSafeApp1 = getMockSafeApp({ id: 1, tags: [SafeAppsTag.DASHBOARD_FEATURED] })
- const mockSafeApp2 = getMockSafeApp({ id: 2, tags: [SafeAppsTag.DASHBOARD_FEATURED] })
- const mockSafeApp3 = getMockSafeApp({ id: 3 })
- const mockSafeApp4 = getMockSafeApp({ id: 4 })
- const mockSafeApp5 = getMockSafeApp({ id: 5 })
-
- const { result } = renderHook(() =>
- useRankedSafeApps([mockSafeApp1, mockSafeApp2, mockSafeApp3, mockSafeApp4, mockSafeApp5], []),
- )
-
- expect(result.current.length).toEqual(3)
- })
-
it('returns pinned apps first', () => {
const mockSafeApp1 = getMockSafeApp({ id: 1 })
const mockSafeApp2 = getMockSafeApp({ id: 2 })
diff --git a/src/hooks/__tests__/useRemainingRelays.test.ts b/src/hooks/__tests__/useRemainingRelays.test.ts
index e167618f9..9be3ed1f4 100644
--- a/src/hooks/__tests__/useRemainingRelays.test.ts
+++ b/src/hooks/__tests__/useRemainingRelays.test.ts
@@ -98,7 +98,7 @@ describe('fetch remaining relays hooks', () => {
const ownerAddresses = ['0x00', '0x01', '0x02']
it('should return 0 if one of the owners has no remaining relays', async () => {
- const mockFetch = jest
+ jest
.spyOn(gateway, 'getRelayCount')
.mockResolvedValue({ limit: 5, remaining: 3 })
.mockResolvedValueOnce({ limit: 5, remaining: 0 })
@@ -112,7 +112,7 @@ describe('fetch remaining relays hooks', () => {
})
it('should return the minimum number of relays amongst owners', async () => {
- const mockFetch = jest
+ jest
.spyOn(gateway, 'getRelayCount')
.mockResolvedValue({ limit: 5, remaining: 3 })
.mockResolvedValueOnce({ limit: 5, remaining: 2 })
@@ -126,7 +126,7 @@ describe('fetch remaining relays hooks', () => {
})
it('should return 0 if there is an error fetching the remaining relays', async () => {
- const mockFetch = jest
+ jest
.spyOn(gateway, 'getRelayCount')
.mockResolvedValue({ limit: 5, remaining: 3 })
.mockRejectedValueOnce('Failed to fetch')
diff --git a/src/hooks/__tests__/useSafeTokenAllocation.test.ts b/src/hooks/__tests__/useSafeTokenAllocation.test.ts
index e029f067a..6b66b3c44 100644
--- a/src/hooks/__tests__/useSafeTokenAllocation.test.ts
+++ b/src/hooks/__tests__/useSafeTokenAllocation.test.ts
@@ -12,7 +12,7 @@ import { ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants
const setupFetchStub =
(data: any, status: number = 200) =>
- (_url: string) => {
+ () => {
return Promise.resolve({
json: () => Promise.resolve(data),
status,
@@ -32,8 +32,8 @@ describe('_getRedeemDeadline', () => {
jest.clearAllMocks()
})
- it('should should only call the provider once per address on a chain', async () => {
- for await (const _ of Array.from({ length: 10 })) {
+ it('should only call the provider once per address on a chain', async () => {
+ for (let i = 0; i < 10; i++) {
await _getRedeemDeadline({ chainId: 1, contract: toBeHex('0x1', 20) } as VestingData, mockProvider)
}
diff --git a/src/hooks/__tests__/useTxPendingStatus.test.ts b/src/hooks/__tests__/useTxPendingStatus.test.ts
new file mode 100644
index 000000000..1a3981cc0
--- /dev/null
+++ b/src/hooks/__tests__/useTxPendingStatus.test.ts
@@ -0,0 +1,330 @@
+import * as useChainIdHook from '@/hooks/useChainId'
+import * as useSafeInfoHook from '@/hooks/useSafeInfo'
+import useTxPendingStatuses, { useTxMonitor } from '@/hooks/useTxPendingStatuses'
+import * as web3 from '@/hooks/wallets/web3'
+import { txDispatch, TxEvent } from '@/services/tx/txEvents'
+import * as txMonitor from '@/services/tx/txMonitor'
+import {
+ clearPendingTx,
+ PendingStatus,
+ type PendingTxsState,
+ PendingTxType,
+ setPendingTx,
+} from '@/store/pendingTxsSlice'
+import { pendingTxBuilder } from '@/tests/builders/pendingTx'
+import { extendedSafeInfoBuilder } from '@/tests/builders/safe'
+import { renderHook } from '@/tests/test-utils'
+import { faker } from '@faker-js/faker'
+import type { JsonRpcProvider } from 'ethers'
+
+describe('useTxMonitor', () => {
+ let mockProvider
+ beforeEach(() => {
+ jest.clearAllMocks()
+
+ jest.spyOn(useChainIdHook, 'default').mockReturnValue('11155111')
+
+ mockProvider = jest.fn() as unknown as JsonRpcProvider
+ jest.spyOn(web3, 'useWeb3ReadOnly').mockReturnValue(mockProvider)
+ })
+
+ it('should not monitor transactions if provider is not available', () => {
+ jest.spyOn(web3, 'useWeb3ReadOnly').mockReturnValue(undefined)
+ const mockWaitForTx = jest.spyOn(txMonitor, 'waitForTx')
+ const mockWaitForRelayedTx = jest.spyOn(txMonitor, 'waitForRelayedTx')
+
+ renderHook(() => useTxMonitor())
+
+ expect(mockWaitForTx).not.toHaveBeenCalled()
+ expect(mockWaitForRelayedTx).not.toHaveBeenCalled()
+ })
+
+ it('should not monitor transactions if there are no pending transactions', () => {
+ const mockWaitForTx = jest.spyOn(txMonitor, 'waitForTx')
+ const mockWaitForRelayedTx = jest.spyOn(txMonitor, 'waitForRelayedTx')
+
+ renderHook(() => useTxMonitor, { initialReduxState: { pendingTxs: {} } })
+
+ expect(mockWaitForTx).not.toHaveBeenCalled()
+ expect(mockWaitForRelayedTx).not.toHaveBeenCalled()
+ })
+
+ it('should monitor processing transactions', () => {
+ const mockWaitForTx = jest.spyOn(txMonitor, 'waitForTx')
+ const mockWaitForRelayedTx = jest.spyOn(txMonitor, 'waitForRelayedTx')
+
+ const pendingTx: PendingTxsState = {
+ '123': pendingTxBuilder().with({ chainId: '11155111', status: PendingStatus.PROCESSING }).build(),
+ }
+
+ renderHook(() => useTxMonitor(), { initialReduxState: { pendingTxs: pendingTx } })
+
+ expect(mockWaitForTx).toHaveBeenCalled()
+ expect(mockWaitForRelayedTx).not.toHaveBeenCalled()
+ })
+
+ it('should monitor relaying transactions', () => {
+ const mockWaitForTx = jest.spyOn(txMonitor, 'waitForTx')
+ const mockWaitForRelayedTx = jest.spyOn(txMonitor, 'waitForRelayedTx')
+
+ const pendingTx: PendingTxsState = {
+ '123': pendingTxBuilder().with({ chainId: '11155111', status: PendingStatus.RELAYING }).build(),
+ }
+
+ renderHook(() => useTxMonitor(), { initialReduxState: { pendingTxs: pendingTx } })
+
+ expect(mockWaitForRelayedTx).toHaveBeenCalled()
+ expect(mockWaitForTx).not.toHaveBeenCalled()
+ })
+
+ it('should not monitor already monitored transactions', () => {
+ const mockWaitForTx = jest.spyOn(txMonitor, 'waitForTx')
+
+ const pendingTxs: PendingTxsState = {
+ '123': pendingTxBuilder().with({ chainId: '11155111', status: PendingStatus.PROCESSING }).build(),
+ }
+
+ const { rerender } = renderHook(() => useTxMonitor(), { initialReduxState: { pendingTxs } })
+
+ rerender()
+
+ expect(mockWaitForTx).toHaveBeenCalledTimes(1)
+ })
+})
+
+jest.mock('@/store/pendingTxsSlice', () => {
+ const original = jest.requireActual('@/store/pendingTxsSlice')
+ return {
+ ...original,
+ setPendingTx: jest.fn(original.setPendingTx),
+ clearPendingTx: jest.fn(original.clearPendingTx),
+ }
+})
+
+const extendedSafeInfo = extendedSafeInfoBuilder().build()
+
+describe('useTxPendingStatuses', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+
+ jest.spyOn(useChainIdHook, 'default').mockReturnValue('11155111')
+ jest.spyOn(useSafeInfoHook, 'default').mockReturnValue({
+ safe: extendedSafeInfo,
+ safeAddress: faker.finance.ethereumAddress(),
+ safeError: undefined,
+ safeLoaded: true,
+ safeLoading: false,
+ })
+ })
+
+ it('should update pending tx when SIGNATURE_PROPOSED', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+ const mockSignerAddress = faker.finance.ethereumAddress()
+
+ txDispatch(TxEvent.SIGNATURE_PROPOSED, {
+ nonce: 1,
+ txId: mockTxId,
+ signerAddress: mockSignerAddress,
+ })
+
+ expect(setPendingTx).toHaveBeenCalledWith({
+ nonce: 1,
+ chainId: expect.anything(),
+ safeAddress: expect.anything(),
+ signerAddress: mockSignerAddress,
+ status: PendingStatus.SIGNING,
+ txId: mockTxId,
+ })
+ })
+
+ it('should update custom pending tx when PROCESSING', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+ const mockTxHash = '0x123'
+ const mockNonce = 1
+ const mockData = '0x456'
+ const mockSignerAddress = faker.finance.ethereumAddress()
+ const mockTo = faker.finance.ethereumAddress()
+
+ txDispatch(TxEvent.PROCESSING, {
+ nonce: 1,
+ txId: mockTxId,
+ txHash: mockTxHash,
+ signerNonce: mockNonce,
+ signerAddress: mockSignerAddress,
+ txType: 'Custom',
+ data: mockData,
+ to: mockTo,
+ })
+
+ expect(setPendingTx).toHaveBeenCalledWith({
+ nonce: 1,
+ chainId: expect.anything(),
+ safeAddress: expect.anything(),
+ submittedAt: expect.anything(),
+ signerAddress: mockSignerAddress,
+ signerNonce: mockNonce,
+ to: mockTo,
+ data: mockData,
+ status: PendingStatus.PROCESSING,
+ txId: mockTxId,
+ txHash: mockTxHash,
+ txType: PendingTxType.CUSTOM_TX,
+ })
+ })
+
+ it('should update pending safe tx when PROCESSING', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+ const mockTxHash = '0x123'
+ const mockNonce = 1
+ const mockGasLimit = '80000'
+ const mockSignerAddress = faker.finance.ethereumAddress()
+
+ txDispatch(TxEvent.PROCESSING, {
+ nonce: 1,
+ txId: mockTxId,
+ txHash: mockTxHash,
+ signerNonce: mockNonce,
+ signerAddress: mockSignerAddress,
+ txType: 'SafeTx',
+ gasLimit: mockGasLimit,
+ })
+
+ expect(setPendingTx).toHaveBeenCalledWith({
+ nonce: 1,
+ chainId: expect.anything(),
+ safeAddress: expect.anything(),
+ submittedAt: expect.anything(),
+ signerAddress: mockSignerAddress,
+ signerNonce: mockNonce,
+ gasLimit: mockGasLimit,
+ status: PendingStatus.PROCESSING,
+ txId: mockTxId,
+ txHash: mockTxHash,
+ txType: PendingTxType.SAFE_TX,
+ })
+ })
+
+ it('should update pending tx when EXECUTING', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+
+ txDispatch(TxEvent.EXECUTING, {
+ nonce: 1,
+ txId: mockTxId,
+ })
+
+ expect(setPendingTx).toHaveBeenCalledWith({
+ nonce: 1,
+ chainId: expect.anything(),
+ safeAddress: expect.anything(),
+ status: PendingStatus.SUBMITTING,
+ txId: mockTxId,
+ })
+ })
+
+ it('should update pending tx when PROCESSED', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+
+ txDispatch(TxEvent.PROCESSED, {
+ nonce: 1,
+ txId: mockTxId,
+ safeAddress: faker.finance.ethereumAddress(),
+ })
+
+ expect(setPendingTx).toHaveBeenCalledWith({
+ nonce: 1,
+ chainId: expect.anything(),
+ safeAddress: expect.anything(),
+ status: PendingStatus.INDEXING,
+ txId: mockTxId,
+ })
+ })
+
+ it('should update pending tx when RELAYING', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+ const mockTaskId = '0x123'
+
+ txDispatch(TxEvent.RELAYING, {
+ nonce: 1,
+ txId: mockTxId,
+ taskId: mockTaskId,
+ })
+
+ expect(setPendingTx).toHaveBeenCalledWith({
+ nonce: 1,
+ chainId: expect.anything(),
+ safeAddress: expect.anything(),
+ status: PendingStatus.RELAYING,
+ txId: mockTxId,
+ taskId: mockTaskId,
+ })
+ })
+
+ it('should clear the pending tx on SUCCESS', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+
+ txDispatch(TxEvent.SUCCESS, {
+ nonce: 1,
+ txId: mockTxId,
+ })
+
+ expect(setPendingTx).not.toHaveBeenCalled()
+ expect(clearPendingTx).toHaveBeenCalled()
+ })
+
+ it('should clear the pending tx on SIGNATURE_INDEXED', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+
+ txDispatch(TxEvent.SIGNATURE_INDEXED, {
+ txId: mockTxId,
+ })
+
+ expect(setPendingTx).not.toHaveBeenCalled()
+ expect(clearPendingTx).toHaveBeenCalled()
+ })
+
+ it('should clear the pending tx on REVERTED', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+
+ txDispatch(TxEvent.REVERTED, {
+ nonce: 1,
+ txId: mockTxId,
+ error: new Error('Transaction reverted'),
+ })
+
+ expect(setPendingTx).not.toHaveBeenCalled()
+ expect(clearPendingTx).toHaveBeenCalled()
+ })
+
+ it('should clear the pending tx on FAILED', () => {
+ renderHook(() => useTxPendingStatuses())
+
+ const mockTxId = '123'
+
+ txDispatch(TxEvent.FAILED, {
+ nonce: 1,
+ txId: mockTxId,
+ error: new Error('Transaction failed'),
+ })
+
+ expect(setPendingTx).not.toHaveBeenCalled()
+ expect(clearPendingTx).toHaveBeenCalled()
+ })
+})
diff --git a/src/hooks/__tests__/useTxTracking.test.ts b/src/hooks/__tests__/useTxTracking.test.ts
index bd7834e8a..1042a254a 100644
--- a/src/hooks/__tests__/useTxTracking.test.ts
+++ b/src/hooks/__tests__/useTxTracking.test.ts
@@ -2,7 +2,7 @@ import { act, renderHook } from '@/tests/test-utils'
import { txDispatch, TxEvent } from '@/services/tx/txEvents'
import { useTxTracking } from '../useTxTracking'
import { trackEvent, WALLET_EVENTS } from '@/services/analytics'
-import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
+import { getTransactionDetails, type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { faker } from '@faker-js/faker'
jest.mock('@/services/analytics', () => ({
@@ -10,15 +10,22 @@ jest.mock('@/services/analytics', () => ({
trackEvent: jest.fn(),
}))
-jest.mock('@/services/transactions', () => ({
- getTxDetails: jest.fn(() => Promise.resolve({ safeAppInfo: { url: 'google.com' } } as unknown as TransactionDetails)),
+jest.mock('@safe-global/safe-gateway-typescript-sdk', () => ({
+ ...jest.requireActual('@safe-global/safe-gateway-typescript-sdk'),
+ getTransactionDetails: jest.fn(),
}))
describe('useTxTracking', () => {
+ beforeEach(() => {
+ ;(getTransactionDetails as jest.Mock).mockResolvedValue({
+ safeAppInfo: { url: 'google.com' },
+ } as unknown as TransactionDetails)
+ })
it('should track the ONCHAIN_INTERACTION event', async () => {
renderHook(() => useTxTracking())
txDispatch(TxEvent.PROCESSING, {
+ nonce: 1,
txId: '123',
txHash: '0x123',
signerAddress: faker.finance.ethereumAddress(),
@@ -35,10 +42,6 @@ describe('useTxTracking', () => {
})
})
- beforeEach(() => {
- // jest.resetAllMocks()
- })
-
it('should track relayed executions', () => {
renderHook(() => useTxTracking())
@@ -64,6 +67,7 @@ describe('useTxTracking', () => {
renderHook(() => useTxTracking())
txDispatch(TxEvent.PROCESSING, {
+ nonce: 1,
txId: '0x123',
txHash: '0x234',
signerAddress: faker.finance.ethereumAddress(),
diff --git a/src/hooks/__tests__/useWalletCanPay.test.ts b/src/hooks/__tests__/useWalletCanPay.test.ts
index 565a6828c..954145ab5 100644
--- a/src/hooks/__tests__/useWalletCanPay.test.ts
+++ b/src/hooks/__tests__/useWalletCanPay.test.ts
@@ -8,13 +8,13 @@ describe('useWalletCanPay', () => {
})
it('should return true if gasLimit is missing', () => {
- const { result } = renderHook(() => useWalletCanPay({ maxFeePerGas: BigInt(1), maxPriorityFeePerGas: BigInt(1) }))
+ const { result } = renderHook(() => useWalletCanPay({ maxFeePerGas: BigInt(1) }))
expect(result.current).toEqual(true)
})
it('should return true if maxFeePerGas is missing', () => {
- const { result } = renderHook(() => useWalletCanPay({ gasLimit: BigInt(21000), maxPriorityFeePerGas: BigInt(1) }))
+ const { result } = renderHook(() => useWalletCanPay({ gasLimit: BigInt(21000) }))
expect(result.current).toEqual(true)
})
@@ -66,7 +66,6 @@ describe('useWalletCanPay', () => {
useWalletCanPay({
gasLimit: BigInt(21000),
maxFeePerGas: BigInt(1),
- maxPriorityFeePerGas: BigInt(1),
}),
)
diff --git a/src/hooks/coreSDK/__tests__/safeCoreSDK.test.ts b/src/hooks/coreSDK/__tests__/safeCoreSDK.test.ts
index 1e831139f..0e77d1088 100644
--- a/src/hooks/coreSDK/__tests__/safeCoreSDK.test.ts
+++ b/src/hooks/coreSDK/__tests__/safeCoreSDK.test.ts
@@ -1,5 +1,5 @@
-import { toBeHex } from 'ethers'
-import { BrowserProvider, id, AbiCoder, type Eip1193Provider } from 'ethers'
+import { Gnosis_safe__factory } from '@/types/contracts'
+import { JsonRpcProvider, toBeHex } from 'ethers'
import Safe from '@safe-global/protocol-kit'
import {
getProxyFactoryContract,
@@ -25,17 +25,22 @@ jest.mock('@/types/contracts', () => {
})
jest.mock('@safe-global/protocol-kit', () => {
- const originalModule = jest.requireActual('@safe-global/protocol-kit')
-
- // Mock class
- class MockEthersAdapter extends originalModule.EthersAdapter {
- getChainId = jest.fn().mockImplementation(() => Promise.resolve(BigInt(1)))
+ return {
+ ...jest.requireActual('@safe-global/protocol-kit'),
+ __esModule: true,
+ default: {
+ init: jest.fn(),
+ },
}
+})
+jest.mock('@/types/contracts', () => {
return {
- __esModule: true,
- ...originalModule,
- EthersAdapter: MockEthersAdapter,
+ ...jest.requireActual('@/types/contracts'),
+ _esModule: true,
+ Gnosis_safe__factory: {
+ connect: jest.fn().mockReturnValue({ VERSION: jest.fn() }),
+ },
}
})
@@ -99,36 +104,15 @@ describe('safeCoreSDK', () => {
})
})
- const getMockProvider = (chainId: string, version: string) => {
- const mockProvider: Eip1193Provider = {
- request: jest.fn((request: { method: string; params?: Array | Record }) => {
- const { method, params } = request
- const VERSION_SIG_HASH = id('VERSION()').slice(0, 10)
-
- if (method === 'eth_chainId') {
- return Promise.resolve(+chainId)
- }
-
- if (method === 'eth_call' && Array.isArray(params) && params?.[0].data.startsWith(VERSION_SIG_HASH)) {
- const encodedVersion = AbiCoder.defaultAbiCoder().encode(['string'], [version])
- return Promise.resolve(encodedVersion)
- }
-
- return Promise.resolve()
- }),
- }
-
- return new BrowserProvider(mockProvider)
- }
-
describe('Supported contracts', () => {
it('should return an SDK instance', async () => {
const chainId = '1'
const version = '1.3.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
- const sdk = await initSafeSDK({
+ await initSafeSDK({
provider: mockProvider,
chainId,
address: toBeHex('0x1', 20),
@@ -137,16 +121,17 @@ describe('safeCoreSDK', () => {
implementationVersionState: ImplementationVersionState.UP_TO_DATE,
})
- expect(sdk).toBeInstanceOf(Safe)
+ expect(Safe.init).toHaveBeenCalled()
})
it('should return an L1 SDK instance for mainnet', async () => {
const chainId = '1'
const version = '1.3.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
- const sdk = await initSafeSDK({
+ await initSafeSDK({
provider: mockProvider,
chainId,
address: toBeHex('0x1', 20),
@@ -155,17 +140,21 @@ describe('safeCoreSDK', () => {
implementationVersionState: ImplementationVersionState.UP_TO_DATE,
})
- expect(sdk).toBeInstanceOf(Safe)
- expect(sdk?.getContractManager().isL1SafeSingleton).toBe(true)
+ expect(Safe.init).toHaveBeenCalledWith({
+ isL1SafeSingleton: true,
+ provider: expect.anything(),
+ safeAddress: expect.anything(),
+ })
})
it('should return an L2 SDK instance for L2 chain', async () => {
const chainId = '137' // Polygon
const version = '1.3.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
- const sdk = await initSafeSDK({
+ await initSafeSDK({
provider: mockProvider,
chainId,
address: toBeHex('0x1', 20),
@@ -174,17 +163,21 @@ describe('safeCoreSDK', () => {
implementationVersionState: ImplementationVersionState.UP_TO_DATE,
})
- expect(sdk).toBeInstanceOf(Safe)
- expect(sdk?.getContractManager().isL1SafeSingleton).toBe(false)
+ expect(Safe.init).toHaveBeenCalledWith({
+ isL1SafeSingleton: false,
+ provider: expect.anything(),
+ safeAddress: expect.anything(),
+ })
})
it('should return an L1 SDK instance for legacy Safes, regardless of chain', async () => {
const chainId = '137' // Polygon
const version = '1.0.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
- const sdk = await initSafeSDK({
+ await initSafeSDK({
provider: mockProvider,
chainId,
address: toBeHex('0x1', 20),
@@ -193,8 +186,34 @@ describe('safeCoreSDK', () => {
implementationVersionState: ImplementationVersionState.OUTDATED,
})
- expect(sdk).toBeInstanceOf(Safe)
- expect(sdk?.getContractManager().isL1SafeSingleton).toBe(true)
+ expect(Safe.init).toHaveBeenCalledWith({
+ isL1SafeSingleton: true,
+ provider: expect.anything(),
+ safeAddress: expect.anything(),
+ })
+ })
+
+ it('should return an L1 SDK instance for a canonical mastercopy on optimism', async () => {
+ const chainId = '10' // Optimism mainnet
+ const version = '1.3.0'
+
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
+
+ await initSafeSDK({
+ provider: mockProvider,
+ chainId,
+ address: toBeHex('0x1', 20),
+ version,
+ implementation: MAINNET_MASTER_COPY,
+ implementationVersionState: ImplementationVersionState.UNKNOWN,
+ })
+
+ expect(Safe.init).toHaveBeenCalledWith({
+ isL1SafeSingleton: true,
+ provider: expect.anything(),
+ safeAddress: expect.anything(),
+ })
})
})
@@ -202,11 +221,11 @@ describe('safeCoreSDK', () => {
// Note: backend returns a null version for unsupported contracts
it('should retrieve the Safe version from the contract if not provided', async () => {
const chainId = '1'
- const version = '1.3.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
- const sdk = await initSafeSDK({
+ await initSafeSDK({
provider: mockProvider,
chainId: '1',
address: toBeHex('0x1', 20),
@@ -215,16 +234,16 @@ describe('safeCoreSDK', () => {
implementationVersionState: ImplementationVersionState.UNKNOWN,
})
- expect(sdk).toBeInstanceOf(Safe)
+ expect(Gnosis_safe__factory.connect(MAINNET_MASTER_COPY, mockProvider).VERSION).toHaveBeenCalled()
})
it('should return an L1 SDK instance for L1 contracts not deployed on mainnet', async () => {
const chainId = '137' // Polygon
- const version = '1.3.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
- const sdk = await initSafeSDK({
+ await initSafeSDK({
provider: mockProvider,
chainId,
address: toBeHex('0x1', 20),
@@ -233,20 +252,23 @@ describe('safeCoreSDK', () => {
implementationVersionState: ImplementationVersionState.UNKNOWN,
})
- expect(sdk).toBeInstanceOf(Safe)
- expect(sdk?.getContractManager().isL1SafeSingleton).toBe(true)
+ expect(Safe.init).toHaveBeenCalledWith({
+ isL1SafeSingleton: true,
+ provider: expect.anything(),
+ safeAddress: expect.anything(),
+ })
})
it('should return undefined for unsupported mastercopies', async () => {
const chainId = '1'
- const version = '1.3.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
const sdk = await initSafeSDK({
provider: mockProvider,
chainId,
- address: toBeHex('0x1', 20),
+ address: MAINNET_MASTER_COPY,
version: null,
implementation: '0xinvalid',
implementationVersionState: ImplementationVersionState.UNKNOWN,
@@ -259,9 +281,9 @@ describe('safeCoreSDK', () => {
it('should return undefined if provider does not match safe', async () => {
const chainId = '1'
const safeChainId = '100'
- const version = '1.3.0'
- const mockProvider = getMockProvider(chainId, version)
+ const mockProvider = new JsonRpcProvider()
+ mockProvider.getNetwork = jest.fn().mockReturnValue({ chainId: BigInt(chainId) })
const sdk = await initSafeSDK({
provider: mockProvider,
diff --git a/src/hooks/coreSDK/safeCoreSDK.ts b/src/hooks/coreSDK/safeCoreSDK.ts
index eeaaaf563..87a6f130b 100644
--- a/src/hooks/coreSDK/safeCoreSDK.ts
+++ b/src/hooks/coreSDK/safeCoreSDK.ts
@@ -1,19 +1,16 @@
import chains from '@/config/chains'
import type { UndeployedSafe } from '@/features/counterfactual/store/undeployedSafesSlice'
-import { getWeb3ReadOnly } from '@/hooks/wallets/web3'
-import { UncheckedJsonRpcSigner } from '@/utils/providers/UncheckedJsonRpcSigner'
-import { getSafeSingletonDeployment, getSafeL2SingletonDeployment } from '@safe-global/safe-deployments'
+import { getSafeSingletonDeployments, getSafeL2SingletonDeployments } from '@safe-global/safe-deployments'
import ExternalStore from '@/services/ExternalStore'
import { Gnosis_safe__factory } from '@/types/contracts'
import { invariant } from '@/utils/helpers'
-import type { BrowserProvider, Provider } from 'ethers'
+import type { JsonRpcProvider } from 'ethers'
import Safe from '@safe-global/protocol-kit'
import type { SafeVersion } from '@safe-global/safe-core-sdk-types'
-import { EthersAdapter } from '@safe-global/protocol-kit'
import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import { ethers } from 'ethers'
import semverSatisfies from 'semver/functions/satisfies'
import { isValidMasterCopy } from '@/services/contracts/safeContracts'
+import { sameAddress } from '@/utils/addresses'
export const isLegacyVersion = (safeVersion: string): boolean => {
const LEGACY_VERSION = '<1.3.0'
@@ -30,27 +27,8 @@ export function assertValidSafeVersion(safeVersio
return invariant(isValidSafeVersion(safeVersion), `${safeVersion} is not a valid Safe Account version`)
}
-export const createEthersAdapter = async (provider: BrowserProvider) => {
- const signer = new UncheckedJsonRpcSigner(provider, (await provider.getSigner()).address)
- return new EthersAdapter({
- ethers,
- signerOrProvider: signer,
- })
-}
-
-export const createReadOnlyEthersAdapter = (provider: Provider | undefined = getWeb3ReadOnly()) => {
- if (!provider) {
- throw new Error('Unable to create `EthersAdapter` without a provider')
- }
-
- return new EthersAdapter({
- ethers,
- signerOrProvider: provider,
- })
-}
-
type SafeCoreSDKProps = {
- provider: Provider
+ provider: JsonRpcProvider
chainId: SafeInfo['chainId']
address: SafeInfo['address']['value']
version: SafeInfo['version']
@@ -59,6 +37,13 @@ type SafeCoreSDKProps = {
undeployedSafe?: UndeployedSafe
}
+const isInDeployments = (address: string, deployments: string | string[] | undefined): boolean => {
+ if (Array.isArray(deployments)) {
+ return deployments.some((deployment) => sameAddress(deployment, address))
+ }
+ return sameAddress(address, deployments)
+}
+
// Safe Core SDK
export const initSafeSDK = async ({
provider,
@@ -79,11 +64,11 @@ export const initSafeSDK = async ({
if (!isValidMasterCopy(implementationVersionState)) {
const masterCopy = implementation
- const safeL1Deployment = getSafeSingletonDeployment({ network: chainId, version: safeVersion })
- const safeL2Deployment = getSafeL2SingletonDeployment({ network: chainId, version: safeVersion })
+ const safeL1Deployment = getSafeSingletonDeployments({ network: chainId, version: safeVersion })
+ const safeL2Deployment = getSafeL2SingletonDeployments({ network: chainId, version: safeVersion })
- isL1SafeSingleton = masterCopy === safeL1Deployment?.networkAddresses[chainId]
- const isL2SafeMasterCopy = masterCopy === safeL2Deployment?.networkAddresses[chainId]
+ isL1SafeSingleton = isInDeployments(masterCopy, safeL1Deployment?.networkAddresses[chainId])
+ const isL2SafeMasterCopy = isInDeployments(masterCopy, safeL2Deployment?.networkAddresses[chainId])
// Unknown deployment, which we do not want to support
if (!isL1SafeSingleton && !isL2SafeMasterCopy) {
@@ -96,15 +81,14 @@ export const initSafeSDK = async ({
}
if (undeployedSafe) {
- return Safe.create({
- ethAdapter: createReadOnlyEthersAdapter(provider),
+ return Safe.init({
+ provider: provider._getConnection().url,
isL1SafeSingleton,
predictedSafe: undeployedSafe.props,
})
}
-
- return Safe.create({
- ethAdapter: createReadOnlyEthersAdapter(provider),
+ return Safe.init({
+ provider: provider._getConnection().url,
safeAddress: address,
isL1SafeSingleton,
})
diff --git a/src/hooks/loadables/helpers/__tests__/config.test.ts b/src/hooks/loadables/helpers/__tests__/config.test.ts
new file mode 100644
index 000000000..977bac90e
--- /dev/null
+++ b/src/hooks/loadables/helpers/__tests__/config.test.ts
@@ -0,0 +1,150 @@
+import { getConfigs } from '@/hooks/loadables/helpers/config'
+
+jest.mock('@/pages/_app', () => ({
+ ...jest.requireActual('@/pages/_app'),
+ GATEWAY_URL: 'https://safe-client.safe.global',
+ __esModule: true,
+}))
+import { chainBuilder } from '@/tests/builders/chains'
+
+// Mock data for testing
+const mockDataPage1 = {
+ results: [chainBuilder().build(), chainBuilder().build()],
+ next: 'https://safe-client.safe.global/v1/chains?cursor=limit%3D2%26offset%3D20',
+}
+
+const mockDataPage2 = {
+ results: [chainBuilder().build(), chainBuilder().build()],
+ next: null,
+}
+
+describe('getConfigs', () => {
+ beforeEach(() => {
+ // Clear all instances and calls to fetch
+ jest.clearAllMocks()
+ })
+
+ it('should fetch data from the API and concatenate results', async () => {
+ // Mock fetch responses for each call
+ global.fetch = jest
+ .fn()
+ // First call returns mockDataPage1
+ .mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockDataPage1,
+ })
+ // Second call returns mockDataPage2
+ .mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockDataPage2,
+ })
+
+ const results = await getConfigs()
+
+ expect(results).toEqual([...mockDataPage1.results, ...mockDataPage2.results])
+
+ // Ensure fetch was called twice
+ expect(fetch).toHaveBeenCalledTimes(2)
+
+ // Check that fetch was called with the correct URLs
+ expect(fetch).toHaveBeenCalledWith('https://safe-client.safe.global/v1/chains')
+ expect(fetch).toHaveBeenCalledWith('https://safe-client.safe.global/v1/chains?cursor=limit%3D2%26offset%3D20')
+ })
+
+ it('should handle a single page of results', async () => {
+ // Mock fetch response
+ global.fetch = jest.fn().mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ results: [mockDataPage1.results[0]],
+ next: null,
+ }),
+ })
+
+ const results = await getConfigs()
+
+ expect(results).toEqual([mockDataPage1.results[0]])
+
+ // Ensure fetch was called once
+ expect(fetch).toHaveBeenCalledTimes(1)
+
+ // Check that fetch was called with the correct URL
+ expect(fetch).toHaveBeenCalledWith('https://safe-client.safe.global/v1/chains')
+ })
+
+ it('should handle network errors', async () => {
+ // Mock fetch to throw an error
+ global.fetch = jest.fn().mockImplementation(() => {
+ throw new Error('Network error')
+ })
+
+ let error: Error | undefined = undefined
+ try {
+ await getConfigs()
+ } catch (e) {
+ error = e as Error
+ }
+
+ expect(error).toBeDefined()
+ expect(error!.message).toBe('Network error')
+
+ // Ensure fetch was called once
+ expect(fetch).toHaveBeenCalledTimes(1)
+ })
+
+ it('should handle HTTP errors (non-200 responses)', async () => {
+ // Mock fetch to return a non-OK response
+ global.fetch = jest.fn().mockResolvedValueOnce({
+ ok: false,
+ status: 500,
+ })
+
+ let error: Error | undefined = undefined
+ try {
+ await getConfigs()
+ } catch (e) {
+ error = e as Error
+ }
+
+ expect(error).toBeDefined()
+ expect(error!.message).toBe('HTTP error! status: 500')
+
+ // Ensure fetch was called once
+ expect(fetch).toHaveBeenCalledTimes(1)
+ })
+
+ it('should handle empty results', async () => {
+ // Mock fetch response with empty results
+ global.fetch = jest.fn().mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ results: [],
+ next: null,
+ }),
+ })
+
+ const results = await getConfigs()
+
+ expect(results).toEqual([])
+
+ // Ensure fetch was called once
+ expect(fetch).toHaveBeenCalledTimes(1)
+ })
+
+ it('should handle missing next property', async () => {
+ // Mock fetch response without next property
+ global.fetch = jest.fn().mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ results: [mockDataPage1.results[0]],
+ }),
+ })
+
+ const results = await getConfigs()
+
+ expect(results).toEqual([mockDataPage1.results[0]])
+
+ // Ensure fetch was called once
+ expect(fetch).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/src/hooks/loadables/helpers/config.ts b/src/hooks/loadables/helpers/config.ts
new file mode 100644
index 000000000..42ca7f918
--- /dev/null
+++ b/src/hooks/loadables/helpers/config.ts
@@ -0,0 +1,20 @@
+import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { GATEWAY_URL } from '@/pages/_app'
+
+export const getConfigs = async (): Promise => {
+ let allResults: ChainInfo[] = []
+ let url = `${GATEWAY_URL}/v1/chains`
+
+ while (url) {
+ const response = await fetch(url)
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+ const data = await response.json()
+ const results = data.results || []
+ allResults = allResults.concat(results)
+ url = data.next
+ }
+
+ return allResults
+}
diff --git a/src/hooks/loadables/useLoadChains.ts b/src/hooks/loadables/useLoadChains.ts
index 2a57315b3..7784578cf 100644
--- a/src/hooks/loadables/useLoadChains.ts
+++ b/src/hooks/loadables/useLoadChains.ts
@@ -1,12 +1,8 @@
import { useEffect } from 'react'
-import { getChainsConfig, type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import useAsync, { type AsyncResult } from '../useAsync'
-import { logError, Errors } from '@/services/exceptions'
-
-const getConfigs = async (): Promise => {
- const data = await getChainsConfig()
- return data.results || []
-}
+import { Errors, logError } from '@/services/exceptions'
+import { getConfigs } from '@/hooks/loadables/helpers/config'
export const useLoadChains = (): AsyncResult => {
const [data, error, loading] = useAsync(getConfigs, [])
diff --git a/src/hooks/loadables/useLoadSafeInfo.ts b/src/hooks/loadables/useLoadSafeInfo.ts
index b8bfece18..11321bf6b 100644
--- a/src/hooks/loadables/useLoadSafeInfo.ts
+++ b/src/hooks/loadables/useLoadSafeInfo.ts
@@ -11,29 +11,31 @@ import useSafeInfo from '../useSafeInfo'
import { Errors, logError } from '@/services/exceptions'
import { POLLING_INTERVAL } from '@/config/constants'
import { checksumAddress, sameAddress } from '@/utils/addresses'
+import { useCurrentChain } from '../useChains'
export const useLoadSafeInfo = (): AsyncResult => {
const address = useSafeAddress()
const chainId = useChainId()
+ const chain = useCurrentChain()
const [pollCount, resetPolling] = useIntervalCounter(POLLING_INTERVAL)
const { safe } = useSafeInfo()
const isStoredSafeValid = safe.chainId === chainId && sameAddress(safe.address.value, checksumAddress(address))
const undeployedSafe = useAppSelector((state) => selectUndeployedSafe(state, chainId, checksumAddress(address)))
const [data, error, loading] = useAsync(async () => {
- if (!chainId || !address) return
+ if (!chainId || !address || !chain) return
/**
* This is the one place where we can't check for `safe.deployed` as we want to update that value
* when the local storage is cleared, so we have to check undeployedSafe
*/
- if (undeployedSafe) return getUndeployedSafeInfo(undeployedSafe.props, checksumAddress(address), chainId)
+ if (undeployedSafe) return getUndeployedSafeInfo(undeployedSafe.props, checksumAddress(address), chain)
const safeInfo = await getSafeInfo(chainId, checksumAddress(address))
return { ...safeInfo, deployed: true }
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [chainId, address, pollCount, undeployedSafe])
+ }, [chainId, address, pollCount, undeployedSafe, chain])
// Reset the counter when safe address/chainId changes
useEffect(() => {
diff --git a/src/hooks/loadables/useLoadSpendingLimits.ts b/src/hooks/loadables/useLoadSpendingLimits.ts
index c0f3deea5..c372a8977 100644
--- a/src/hooks/loadables/useLoadSpendingLimits.ts
+++ b/src/hooks/loadables/useLoadSpendingLimits.ts
@@ -103,7 +103,7 @@ export const useLoadSpendingLimits = (): AsyncResult => {
const [data, error, loading] = useAsync(
() => {
- if (!provider || !safeLoaded || !safe.modules || !tokenInfoFromBalances) return
+ if (!provider || !safeLoaded || !safe.modules || tokenInfoFromBalances.length === 0) return
return getSpendingLimits(provider, safe.modules, safeAddress, chainId, tokenInfoFromBalances)
},
diff --git a/src/hooks/loadables/useLoadTxHistory.ts b/src/hooks/loadables/useLoadTxHistory.ts
index 284e13961..f656fad7a 100644
--- a/src/hooks/loadables/useLoadTxHistory.ts
+++ b/src/hooks/loadables/useLoadTxHistory.ts
@@ -12,19 +12,21 @@ import { FEATURES } from '@/utils/chains'
export const useLoadTxHistory = (): AsyncResult => {
const { safe, safeAddress, safeLoaded } = useSafeInfo()
const { chainId, txHistoryTag } = safe
- const { showOnlyTrustedTransactions } = useAppSelector(selectSettings)
+ const { hideSuspiciousTransactions } = useAppSelector(selectSettings)
const hasDefaultTokenlist = useHasFeature(FEATURES.DEFAULT_TOKENLIST)
+ const hideUntrustedTxs = (hasDefaultTokenlist && hideSuspiciousTransactions) || false
+ const hideImitationTxs = hideSuspiciousTransactions || false
- // Re-fetch when chainId, address, showOnlyTrustedTransactions, or txHistoryTag changes
+ // Re-fetch when chainId, address, hideSuspiciousTransactions, or txHistoryTag changes
const [data, error, loading] = useAsync(
() => {
if (!safeLoaded) return
if (!safe.deployed) return Promise.resolve({ results: [] })
- return getTxHistory(chainId, safeAddress, hasDefaultTokenlist && showOnlyTrustedTransactions)
+ return getTxHistory(chainId, safeAddress, hideUntrustedTxs, hideImitationTxs)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
- [safeLoaded, chainId, safeAddress, showOnlyTrustedTransactions, hasDefaultTokenlist, txHistoryTag, safe.deployed],
+ [safeLoaded, chainId, safeAddress, hideSuspiciousTransactions, hasDefaultTokenlist, txHistoryTag, safe.deployed],
false,
)
diff --git a/src/hooks/messages/useSyncSafeMessageSigner.ts b/src/hooks/messages/useSyncSafeMessageSigner.ts
index a22a2014b..cc6e4ca95 100644
--- a/src/hooks/messages/useSyncSafeMessageSigner.ts
+++ b/src/hooks/messages/useSyncSafeMessageSigner.ts
@@ -3,7 +3,6 @@ import { Errors, logError } from '@/services/exceptions'
import { asError } from '@/services/exceptions/utils'
import { dispatchPreparedSignature } from '@/services/safe-messages/safeMsgNotifications'
import { dispatchSafeMsgProposal, dispatchSafeMsgConfirmation } from '@/services/safe-messages/safeMsgSender'
-import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import {
getSafeMessage,
SafeMessageListItemType,
@@ -12,7 +11,6 @@ import {
} from '@safe-global/safe-gateway-typescript-sdk'
import { useEffect, useCallback, useState } from 'react'
import useSafeInfo from '../useSafeInfo'
-import useOnboard from '../wallets/useOnboard'
const HIDE_DELAY = 3000
@@ -39,7 +37,6 @@ const useSyncSafeMessageSigner = (
onClose: () => void,
) => {
const [submitError, setSubmitError] = useState()
- const onboard = useOnboard()
const wallet = useWallet()
const { safe } = useSafeInfo()
@@ -54,15 +51,13 @@ const useSyncSafeMessageSigner = (
const onSign = useCallback(async () => {
// Error is shown when no wallet is connected, this appeases TypeScript
- if (!onboard || !wallet) {
+ if (!wallet) {
return
}
setSubmitError(undefined)
try {
- await assertWalletChain(onboard, safe.chainId)
-
// When collecting the first signature
if (!message) {
await dispatchSafeMsgProposal({ provider: wallet.provider, safe, message: decodedMessage, safeAppId })
@@ -91,7 +86,7 @@ const useSyncSafeMessageSigner = (
} catch (e) {
setSubmitError(asError(e))
}
- }, [onboard, wallet, safe, message, decodedMessage, safeAppId, safeMessageHash, onClose, requestId])
+ }, [wallet, safe, message, decodedMessage, safeAppId, safeMessageHash, onClose, requestId])
return { submitError, onSign }
}
diff --git a/src/hooks/safe-apps/useAppsSearch.ts b/src/hooks/safe-apps/useAppsSearch.ts
index f2f57ad41..900b8e18e 100644
--- a/src/hooks/safe-apps/useAppsSearch.ts
+++ b/src/hooks/safe-apps/useAppsSearch.ts
@@ -15,6 +15,10 @@ const useAppsSearch = (apps: SafeAppData[], query: string): SafeAppData[] => {
name: 'description',
weight: 0.5,
},
+ {
+ name: 'tags',
+ weight: 0.99,
+ },
],
// https://fusejs.io/api/options.html#threshold
// Very naive explanation: threshold represents how accurate the search results should be. The default is 0.6
diff --git a/src/hooks/safe-apps/useCategoryFilter.ts b/src/hooks/safe-apps/useCategoryFilter.ts
index 9e7099177..4757252ff 100644
--- a/src/hooks/safe-apps/useCategoryFilter.ts
+++ b/src/hooks/safe-apps/useCategoryFilter.ts
@@ -31,7 +31,7 @@ const useCategoryFilter = ({
}, [router.isReady, router.query.categories, safeAppsList, selectedCategories.length, setSelectedCategories])
const onSelectCategories = async (selectedCategories: string[]) => {
- const { categories, ...restProps } = router.query
+ const { categories: _, ...restProps } = router.query
await router.push(
{
diff --git a/src/hooks/safe-apps/useRankedSafeApps.ts b/src/hooks/safe-apps/useRankedSafeApps.ts
index 7fe4a0849..f598b8654 100644
--- a/src/hooks/safe-apps/useRankedSafeApps.ts
+++ b/src/hooks/safe-apps/useRankedSafeApps.ts
@@ -1,7 +1,6 @@
import { useMemo } from 'react'
import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk'
import { rankSafeApps } from '@/services/safe-apps/track-app-usage-count'
-import { SafeAppsTag } from '@/config/constants'
// number of ranked Safe Apps that we want to display
const NUMBER_OF_SAFE_APPS = 5
@@ -14,10 +13,7 @@ const useRankedSafeApps = (safeApps: SafeAppData[], pinnedSafeApps: SafeAppData[
const rankedPinnedApps = rankSafeApps(pinnedSafeApps)
const randomApps = safeApps.slice().sort(() => Math.random() - 0.5)
- const allRankedApps = rankedPinnedApps
- .concat(pinnedSafeApps, mostUsedApps, randomApps)
- // Filter out Featured Apps because they are in their own section
- .filter((app) => !app.tags.includes(SafeAppsTag.DASHBOARD_FEATURED))
+ const allRankedApps = rankedPinnedApps.concat(pinnedSafeApps, mostUsedApps, randomApps)
// Use a Set to remove duplicates
return [...new Set(allRankedApps)].slice(0, NUMBER_OF_SAFE_APPS)
diff --git a/src/hooks/useAsync.ts b/src/hooks/useAsync.ts
index 494518a0e..594183afb 100644
--- a/src/hooks/useAsync.ts
+++ b/src/hooks/useAsync.ts
@@ -51,3 +51,43 @@ const useAsync = (
}
export default useAsync
+
+export const useAsyncCallback = Promise>(
+ callback: T,
+): {
+ asyncCallback: (...args: Parameters) => Promise> | undefined
+ error: Error | undefined
+ isLoading: boolean
+} => {
+ const [error, setError] = useState()
+ const [isLoading, setLoading] = useState(false)
+
+ const asyncCallback = useCallback(
+ async (...args: Parameters) => {
+ setError(undefined)
+
+ const result = callback(...args)
+
+ // Not a promise, exit early
+ if (!result) {
+ setLoading(false)
+ return result
+ }
+
+ setLoading(true)
+
+ result
+ .catch((err) => {
+ setError(asError(err))
+ })
+ .finally(() => {
+ setLoading(false)
+ })
+
+ return result
+ },
+ [callback],
+ )
+
+ return { asyncCallback, error, isLoading }
+}
diff --git a/src/hooks/useChains.ts b/src/hooks/useChains.ts
index 669c99865..c04e848e0 100644
--- a/src/hooks/useChains.ts
+++ b/src/hooks/useChains.ts
@@ -37,7 +37,7 @@ export const useCurrentChain = (): ChainInfo | undefined => {
* @param feature name of the feature to check for
* @returns `true`, if the feature is enabled on the current chain. Otherwise `false`
*/
-export const useHasFeature = (feature: FEATURES): boolean => {
+export const useHasFeature = (feature: FEATURES): boolean | undefined => {
const currentChain = useCurrentChain()
- return !!currentChain && hasFeature(currentChain, feature)
+ return currentChain ? hasFeature(currentChain, feature) : undefined
}
diff --git a/src/hooks/useDecodeTx.ts b/src/hooks/useDecodeTx.ts
index f4fbfd012..25d7c5cc0 100644
--- a/src/hooks/useDecodeTx.ts
+++ b/src/hooks/useDecodeTx.ts
@@ -1,10 +1,5 @@
import { type SafeTransaction } from '@safe-global/safe-core-sdk-types'
-import {
- getConfirmationView,
- type BaselineConfirmationView,
- type OrderConfirmationView,
- type DecodedDataResponse,
-} from '@safe-global/safe-gateway-typescript-sdk'
+import { getConfirmationView, type AnyConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
import { getNativeTransferData } from '@/services/tx/tokenTransferParams'
import { isEmptyHexData } from '@/utils/hex'
import type { AsyncResult } from './useAsync'
@@ -12,26 +7,27 @@ import useAsync from './useAsync'
import useChainId from './useChainId'
import useSafeAddress from '@/hooks/useSafeAddress'
-const useDecodeTx = (
- tx?: SafeTransaction,
-): AsyncResult => {
+const useDecodeTx = (tx?: SafeTransaction): AsyncResult => {
const chainId = useChainId()
const safeAddress = useSafeAddress()
- const encodedData = tx?.data.data
- const isEmptyData = !!encodedData && isEmptyHexData(encodedData)
- const isRejection = isEmptyData && tx?.data.value === '0'
+ const { to, value, data } = tx?.data || {}
- const [data, error, loading] = useAsync<
- DecodedDataResponse | BaselineConfirmationView | OrderConfirmationView | undefined
- >(() => {
- if (!encodedData || isEmptyData) {
- const nativeTransfer = isEmptyData && !isRejection ? getNativeTransferData(tx?.data) : undefined
- return Promise.resolve(nativeTransfer)
- }
- return getConfirmationView(chainId, safeAddress, encodedData, tx.data.to)
- }, [chainId, encodedData, isEmptyData, tx?.data, isRejection, safeAddress])
+ return useAsync(
+ () => {
+ if (to === undefined || value === undefined) return
- return [data, error, loading]
+ const isEmptyData = !!data && isEmptyHexData(data)
+ if (!data || isEmptyData) {
+ const isRejection = isEmptyData && value === '0'
+ const nativeTransfer = isEmptyData && !isRejection ? getNativeTransferData({ to, value }) : undefined
+ return Promise.resolve(nativeTransfer)
+ }
+
+ return getConfirmationView(chainId, safeAddress, data, to, value)
+ },
+ [chainId, safeAddress, to, value, data],
+ false,
+ )
}
export default useDecodeTx
diff --git a/src/hooks/useDelegates.ts b/src/hooks/useDelegates.ts
new file mode 100644
index 000000000..74c643ce3
--- /dev/null
+++ b/src/hooks/useDelegates.ts
@@ -0,0 +1,22 @@
+import useSafeInfo from '@/hooks/useSafeInfo'
+import useWallet from '@/hooks/wallets/useWallet'
+import { useGetDelegatesQuery } from '@/store/gateway'
+import { skipToken } from '@reduxjs/toolkit/query/react'
+
+const useDelegates = () => {
+ const {
+ safe: { chainId },
+ safeAddress,
+ } = useSafeInfo()
+
+ return useGetDelegatesQuery(chainId && safeAddress ? { chainId, safeAddress } : skipToken)
+}
+
+export const useIsWalletDelegate = () => {
+ const wallet = useWallet()
+ const delegates = useDelegates()
+
+ return delegates.data?.results.some((delegate) => delegate.delegate === wallet?.address)
+}
+
+export default useDelegates
diff --git a/src/hooks/useDraftBatch.ts b/src/hooks/useDraftBatch.ts
index a4794a24e..451fc4160 100644
--- a/src/hooks/useDraftBatch.ts
+++ b/src/hooks/useDraftBatch.ts
@@ -1,3 +1,4 @@
+import { isMultisigExecutionInfo } from '@/utils/transaction-guards'
import { useCallback } from 'react'
import { useAppDispatch, useAppSelector } from '@/store'
import useChainId from './useChainId'
@@ -7,6 +8,7 @@ import { selectBatchBySafe, addTx, removeTx } from '@/store/batchSlice'
import { type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { BATCH_EVENTS, trackEvent } from '@/services/analytics'
import { txDispatch, TxEvent } from '@/services/tx/txEvents'
+import { shallowEqual } from 'react-redux'
export const useUpdateBatch = () => {
const chainId = useChainId()
@@ -23,7 +25,9 @@ export const useUpdateBatch = () => {
}),
)
- txDispatch(TxEvent.BATCH_ADD, { txId: txDetails.txId })
+ if (isMultisigExecutionInfo(txDetails.detailedExecutionInfo)) {
+ txDispatch(TxEvent.BATCH_ADD, { txId: txDetails.txId, nonce: txDetails.detailedExecutionInfo.nonce })
+ }
trackEvent({ ...BATCH_EVENTS.BATCH_TX_APPENDED, label: txDetails.txInfo.type })
},
@@ -49,6 +53,6 @@ export const useUpdateBatch = () => {
export const useDraftBatch = (): DraftBatchItem[] => {
const chainId = useChainId()
const safeAddress = useSafeAddress()
- const batch = useAppSelector((state) => selectBatchBySafe(state, chainId, safeAddress))
+ const batch = useAppSelector((state) => selectBatchBySafe(state, chainId, safeAddress), shallowEqual)
return batch
}
diff --git a/src/hooks/useGasLimit.ts b/src/hooks/useGasLimit.ts
index d7b6ce66f..45abd0fc9 100644
--- a/src/hooks/useGasLimit.ts
+++ b/src/hooks/useGasLimit.ts
@@ -1,3 +1,4 @@
+import { SafeProvider } from '@safe-global/protocol-kit'
import { useEffect } from 'react'
import type Safe from '@safe-global/protocol-kit'
import { encodeSignatures } from '@/services/tx/encodeSignatures'
@@ -28,7 +29,7 @@ const getEncodedSafeTx = (
): string | undefined => {
const EXEC_TX_METHOD = 'execTransaction'
- //UPDATE: pass lowercase to ethers library due to unsupported EIP-1191
+ // @ts-ignore union type is too complex
return safeSDK
.getContractManager()
.safeContract?.encode(EXEC_TX_METHOD, [
@@ -89,15 +90,15 @@ const getGasLimitForZkSync = async (
const fakeEOAFromAddress = '0x330d9F4906EDA1f73f668660d1946bea71f48827'
const customContracts = safeSDK.getContractManager().contractNetworks?.[safe.chainId]
const safeVersion = await safeSDK.getContractVersion()
- const ethAdapter = safeSDK.getEthAdapter()
+ const safeProvider = new SafeProvider({ provider: web3._getConnection().url })
const fallbackHandlerContract = await getCompatibilityFallbackHandlerContract({
- ethAdapter,
+ safeProvider,
safeVersion,
customContracts,
})
const simulateTxAccessorContract = await getSimulateTxAccessorContract({
- ethAdapter,
+ safeProvider,
safeVersion,
customContracts,
})
@@ -105,6 +106,7 @@ const getGasLimitForZkSync = async (
// 2. Add a simulate call to the predicted SafeProxy as second transaction
const transactionDataToEstimate: string = simulateTxAccessorContract.encode('simulate', [
safeTx.data.to,
+ // @ts-ignore
safeTx.data.value,
safeTx.data.data,
safeTx.data.operation,
diff --git a/src/hooks/useIsValidExecution.ts b/src/hooks/useIsValidExecution.ts
index 69e0c84df..49b2e83ad 100644
--- a/src/hooks/useIsValidExecution.ts
+++ b/src/hooks/useIsValidExecution.ts
@@ -70,8 +70,7 @@ const useIsValidExecution = (
}
try {
- const provider = getPatchedSignerProvider(wallet, safe.chainId, readOnlyProvider)
- const safeContract = await getCurrentGnosisSafeContract(safe, provider)
+ const safeContract = await getCurrentGnosisSafeContract(safe, readOnlyProvider._getConnection().url)
/**
* We need to call the contract directly instead of using `sdk.isValidTransaction`
diff --git a/src/hooks/usePendingTxs.ts b/src/hooks/usePendingTxs.ts
index db7a46c62..23689c392 100644
--- a/src/hooks/usePendingTxs.ts
+++ b/src/hooks/usePendingTxs.ts
@@ -15,11 +15,12 @@ import {
isTransactionListItem,
} from '@/utils/transaction-guards'
import useSafeInfo from './useSafeInfo'
+import { shallowEqual } from 'react-redux'
const usePendingTxIds = (): Array => {
const { safe, safeAddress } = useSafeInfo()
const { chainId } = safe
- return useAppSelector((state) => selectPendingTxIdsBySafe(state, chainId, safeAddress))
+ return useAppSelector((state) => selectPendingTxIdsBySafe(state, chainId, safeAddress), shallowEqual)
}
export const useHasPendingTxs = (): boolean => {
diff --git a/src/hooks/useSafeTokenEnabled.ts b/src/hooks/useSafeTokenEnabled.ts
new file mode 100644
index 000000000..de25364c6
--- /dev/null
+++ b/src/hooks/useSafeTokenEnabled.ts
@@ -0,0 +1,10 @@
+import { useContext } from 'react'
+import { GeoblockingContext } from '@/components/common/GeoblockingProvider'
+import useSafeInfo from './useSafeInfo'
+import { getSafeTokenAddress } from '@/components/common/SafeTokenWidget'
+
+export function useSafeTokenEnabled(): boolean {
+ const isBlockedCountry = useContext(GeoblockingContext)
+ const { safe, safeLoaded } = useSafeInfo()
+ return !isBlockedCountry && safeLoaded && !!getSafeTokenAddress(safe.chainId)
+}
diff --git a/src/hooks/useTransactionType.ts b/src/hooks/useTransactionType.tsx
similarity index 69%
rename from src/hooks/useTransactionType.ts
rename to src/hooks/useTransactionType.tsx
index a688c1975..8ea2e2042 100644
--- a/src/hooks/useTransactionType.ts
+++ b/src/hooks/useTransactionType.tsx
@@ -1,4 +1,5 @@
import { getOrderClass } from '@/features/swap/helpers/utils'
+import type { ReactElement } from 'react'
import { useMemo } from 'react'
import {
type AddressEx,
@@ -6,11 +7,20 @@ import {
TransactionInfoType,
type TransactionSummary,
} from '@safe-global/safe-gateway-typescript-sdk'
+import SwapIcon from '@/public/images/common/swap.svg'
+import StakeIcon from '@/public/images/common/stake.svg'
-import { isCancellationTxInfo, isModuleExecutionInfo, isOutgoingTransfer, isTxQueued } from '@/utils/transaction-guards'
+import {
+ isCancellationTxInfo,
+ isModuleExecutionInfo,
+ isMultiSendTxInfo,
+ isOutgoingTransfer,
+ isTxQueued,
+} from '@/utils/transaction-guards'
import useAddressBook from './useAddressBook'
import type { AddressBook } from '@/store/addressBookSlice'
import { TWAP_ORDER_TITLE } from '@/features/swap/constants'
+import { SvgIcon } from '@mui/material'
const getTxTo = ({ txInfo }: Pick): AddressEx | undefined => {
switch (txInfo.type) {
@@ -30,7 +40,7 @@ const getTxTo = ({ txInfo }: Pick): AddressEx | un
}
type TxType = {
- icon: string
+ icon: string | ReactElement
text: string
}
@@ -66,19 +76,45 @@ export const getTransactionType = (tx: TransactionSummary, addressBook: AddressB
}
case TransactionInfoType.SWAP_ORDER: {
const orderClass = getOrderClass(tx.txInfo)
+ const altText = orderClass === 'limit' ? 'Limit order' : 'Swap order'
return {
- icon: '/images/common/swap.svg',
- text: orderClass === 'limit' ? 'Limit order' : 'Swap order',
+ icon: ,
+ text: altText,
}
}
case TransactionInfoType.TWAP_ORDER: {
return {
- icon: '/images/common/swap.svg',
+ icon: ,
text: TWAP_ORDER_TITLE,
}
}
+ case TransactionInfoType.NATIVE_STAKING_DEPOSIT: {
+ return {
+ icon: ,
+ text: 'Stake',
+ }
+ }
+ case TransactionInfoType.NATIVE_STAKING_VALIDATORS_EXIT: {
+ return {
+ icon: ,
+ text: 'Withdraw request',
+ }
+ }
+ case TransactionInfoType.NATIVE_STAKING_WITHDRAW: {
+ return {
+ icon: ,
+ text: 'Claim',
+ }
+ }
case TransactionInfoType.CUSTOM: {
+ if (isMultiSendTxInfo(tx.txInfo) && !tx.safeAppInfo) {
+ return {
+ icon: '/images/common/multisend.svg',
+ text: 'Batch',
+ }
+ }
+
if (isModuleExecutionInfo(tx.executionInfo)) {
return {
icon: toAddress?.logoUri || '/images/transactions/custom.svg',
@@ -93,6 +129,12 @@ export const getTransactionType = (tx: TransactionSummary, addressBook: AddressB
}
}
+ return {
+ icon: toAddress?.logoUri || '/images/transactions/custom.svg',
+ text: addressBookName || toAddress?.name || 'Contract interaction',
+ }
+ }
+ default: {
if (tx.safeAppInfo) {
return {
icon: tx.safeAppInfo.logoUri,
@@ -100,12 +142,6 @@ export const getTransactionType = (tx: TransactionSummary, addressBook: AddressB
}
}
- return {
- icon: toAddress?.logoUri || '/images/transactions/custom.svg',
- text: addressBookName || toAddress?.name || 'Contract interaction',
- }
- }
- default: {
return {
icon: '/images/transactions/custom.svg',
text: addressBookName || 'Contract interaction',
diff --git a/src/hooks/useTxDetails.ts b/src/hooks/useTxDetails.ts
new file mode 100644
index 000000000..c62b23ab0
--- /dev/null
+++ b/src/hooks/useTxDetails.ts
@@ -0,0 +1,14 @@
+import { getTransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
+import useAsync from '@/hooks/useAsync'
+import useChainId from './useChainId'
+
+function useTxDetails(txId?: string) {
+ const chainId = useChainId()
+
+ return useAsync(() => {
+ if (!txId) return
+ return getTransactionDetails(chainId, txId)
+ }, [chainId, txId])
+}
+
+export default useTxDetails
diff --git a/src/hooks/useTxHistory.ts b/src/hooks/useTxHistory.ts
index 9fdefccd3..ac260c1fb 100644
--- a/src/hooks/useTxHistory.ts
+++ b/src/hooks/useTxHistory.ts
@@ -20,9 +20,10 @@ const useTxHistory = (
// The latest page of the history is always in the store
const historyState = useAppSelector(selectTxHistory)
const [filter] = useTxFilter()
- const { showOnlyTrustedTransactions } = useAppSelector(selectSettings)
+ const { hideSuspiciousTransactions } = useAppSelector(selectSettings)
const hasDefaultTokenlist = useHasFeature(FEATURES.DEFAULT_TOKENLIST)
- const onlyTrusted = (hasDefaultTokenlist && showOnlyTrustedTransactions) || false
+ const hideUntrustedTxs = (hasDefaultTokenlist && hideSuspiciousTransactions) || false
+ const hideImitationTxs = hideSuspiciousTransactions || false
const {
safe: { chainId },
@@ -35,10 +36,10 @@ const useTxHistory = (
if (!(filter || pageUrl)) return
return filter
- ? fetchFilteredTxHistory(chainId, safeAddress, filter, onlyTrusted, pageUrl)
- : getTxHistory(chainId, safeAddress, onlyTrusted, pageUrl)
+ ? fetchFilteredTxHistory(chainId, safeAddress, filter, hideUntrustedTxs, hideImitationTxs, pageUrl)
+ : getTxHistory(chainId, safeAddress, hideUntrustedTxs, hideImitationTxs, pageUrl)
},
- [chainId, safeAddress, pageUrl, filter, onlyTrusted],
+ [filter, pageUrl, chainId, safeAddress, hideUntrustedTxs, hideImitationTxs],
false,
)
diff --git a/src/hooks/useTxNotifications.ts b/src/hooks/useTxNotifications.ts
index 98967e59e..5311a8e8a 100644
--- a/src/hooks/useTxNotifications.ts
+++ b/src/hooks/useTxNotifications.ts
@@ -12,9 +12,9 @@ import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import useWallet from './wallets/useWallet'
import useSafeAddress from './useSafeAddress'
import { getExplorerLink } from '@/utils/gateway'
-import { getTxDetails } from '@/services/transactions'
import { isWalletRejection } from '@/utils/wallets'
import { getTxLink } from '@/utils/tx-link'
+import { useLazyGetTransactionDetailsQuery } from '@/store/gateway'
const TxNotifications = {
[TxEvent.SIGN_FAILED]: 'Failed to sign. Please try again.',
@@ -46,6 +46,7 @@ const useTxNotifications = (): void => {
const dispatch = useAppDispatch()
const chain = useCurrentChain()
const safeAddress = useSafeAddress()
+ const [trigger] = useLazyGetTransactionDetailsQuery()
/**
* Show notifications of a transaction's lifecycle
@@ -70,8 +71,8 @@ const useTxNotifications = (): void => {
const id = txId || txHash
if (id) {
try {
- const txDetails = await getTxDetails(chain.chainId, id)
- humanDescription = txDetails.txInfo.humanDescription || humanDescription
+ const { data: txDetails } = await trigger({ chainId: chain.chainId, txId: id })
+ humanDescription = txDetails?.txInfo.humanDescription || humanDescription
} catch {}
}
@@ -95,7 +96,7 @@ const useTxNotifications = (): void => {
return () => {
unsubFns.forEach((unsub) => unsub())
}
- }, [dispatch, safeAddress, chain])
+ }, [dispatch, safeAddress, chain, trigger])
/**
* If there's at least one transaction awaiting confirmations, show a notification for it
diff --git a/src/hooks/useTxPendingStatuses.ts b/src/hooks/useTxPendingStatuses.ts
index aaffbe761..776fce6d4 100644
--- a/src/hooks/useTxPendingStatuses.ts
+++ b/src/hooks/useTxPendingStatuses.ts
@@ -19,7 +19,7 @@ import { SimpleTxWatcher } from '@/utils/SimpleTxWatcher'
const FINAL_PENDING_STATUSES = [TxEvent.SIGNATURE_INDEXED, TxEvent.SUCCESS, TxEvent.REVERTED, TxEvent.FAILED]
-const useTxMonitor = (): void => {
+export const useTxMonitor = (): void => {
const chainId = useChainId()
const pendingTxs = useAppSelector(selectPendingTxs)
const pendingTxEntriesOnChain = Object.entries(pendingTxs).filter(([, pendingTx]) => pendingTx.chainId === chainId)
@@ -53,17 +53,19 @@ const useTxMonitor = (): void => {
pendingTx.safeAddress,
pendingTx.signerAddress,
pendingTx.signerNonce,
+ pendingTx.nonce,
+ chainId,
)
continue
}
if (isRelaying) {
- waitForRelayedTx(pendingTx.taskId, [txId], pendingTx.safeAddress)
+ waitForRelayedTx(pendingTx.taskId, [txId], pendingTx.safeAddress, pendingTx.nonce)
}
}
// `provider` is updated when switching chains, re-running this effect
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [provider])
+ }, [pendingTxEntriesOnChain.length, provider])
}
const useTxPendingStatuses = (): void => {
@@ -82,7 +84,9 @@ const useTxPendingStatuses = (): void => {
const unsubSignatureProposing = txSubscribe(TxEvent.SIGNATURE_PROPOSED, (detail) => {
// All pending txns should have a txId
const txId = 'txId' in detail && detail.txId
- if (!txId) return
+ const nonce = 'nonce' in detail ? detail.nonce : undefined
+
+ if (!txId || nonce === undefined) return
// If we have future issues with statuses, we should refactor `useTxPendingStatuses`
// @see https://github.com/safe-global/safe-wallet-web/issues/1754
@@ -94,6 +98,7 @@ const useTxPendingStatuses = (): void => {
// Update pendingTx
dispatch(
setPendingTx({
+ nonce,
chainId,
safeAddress,
txId,
@@ -106,7 +111,9 @@ const useTxPendingStatuses = (): void => {
const unsubProcessing = txSubscribe(TxEvent.PROCESSING, (detail) => {
// All pending txns should have a txId
const txId = 'txId' in detail && detail.txId
- if (!txId) return
+ const nonce = 'nonce' in detail ? detail.nonce : undefined
+
+ if (!txId || nonce === undefined) return
// If we have future issues with statuses, we should refactor `useTxPendingStatuses`
// @see https://github.com/safe-global/safe-wallet-web/issues/1754
@@ -118,6 +125,7 @@ const useTxPendingStatuses = (): void => {
const pendingTx: PendingProcessingTx & { txId: string } =
detail.txType === 'Custom'
? {
+ nonce,
chainId,
safeAddress,
txId,
@@ -131,6 +139,7 @@ const useTxPendingStatuses = (): void => {
to: detail.to,
}
: {
+ nonce,
chainId,
safeAddress,
txId,
@@ -148,7 +157,9 @@ const useTxPendingStatuses = (): void => {
const unsubExecuting = txSubscribe(TxEvent.EXECUTING, (detail) => {
// All pending txns should have a txId
const txId = 'txId' in detail && detail.txId
- if (!txId) return
+ const nonce = 'nonce' in detail ? detail.nonce : undefined
+
+ if (!txId || nonce === undefined) return
// If we have future issues with statuses, we should refactor `useTxPendingStatuses`
// @see https://github.com/safe-global/safe-wallet-web/issues/1754
@@ -160,6 +171,7 @@ const useTxPendingStatuses = (): void => {
// Update pendingTx
dispatch(
setPendingTx({
+ nonce,
chainId,
safeAddress,
txId,
@@ -171,7 +183,9 @@ const useTxPendingStatuses = (): void => {
const unsubProcessed = txSubscribe(TxEvent.PROCESSED, (detail) => {
// All pending txns should have a txId
const txId = 'txId' in detail && detail.txId
- if (!txId) return
+ const nonce = 'nonce' in detail ? detail.nonce : undefined
+
+ if (!txId || nonce === undefined) return
// If we have future issues with statuses, we should refactor `useTxPendingStatuses`
// @see https://github.com/safe-global/safe-wallet-web/issues/1754
@@ -183,6 +197,7 @@ const useTxPendingStatuses = (): void => {
// Update pendingTx
dispatch(
setPendingTx({
+ nonce,
chainId,
safeAddress,
txId,
@@ -194,7 +209,9 @@ const useTxPendingStatuses = (): void => {
const unsubRelaying = txSubscribe(TxEvent.RELAYING, (detail) => {
// All pending txns should have a txId
const txId = 'txId' in detail && detail.txId
- if (!txId) return
+ const nonce = 'nonce' in detail ? detail.nonce : undefined
+
+ if (!txId || nonce === undefined) return
// If we have future issues with statuses, we should refactor `useTxPendingStatuses`
// @see https://github.com/safe-global/safe-wallet-web/issues/1754
@@ -206,6 +223,7 @@ const useTxPendingStatuses = (): void => {
// Update pendingTx
dispatch(
setPendingTx({
+ nonce,
chainId,
safeAddress,
txId,
diff --git a/src/hooks/useTxTracking.ts b/src/hooks/useTxTracking.ts
index 857e89f28..44a1e570d 100644
--- a/src/hooks/useTxTracking.ts
+++ b/src/hooks/useTxTracking.ts
@@ -1,8 +1,8 @@
import { trackEvent, WALLET_EVENTS } from '@/services/analytics'
-import { getTxDetails } from '@/services/transactions'
import { TxEvent, txSubscribe } from '@/services/tx/txEvents'
import { useEffect } from 'react'
import useChainId from './useChainId'
+import { useLazyGetTransactionDetailsQuery } from '@/store/gateway'
const events = {
[TxEvent.SIGNED]: WALLET_EVENTS.OFFCHAIN_SIGNATURE,
@@ -14,6 +14,8 @@ const events = {
export const useTxTracking = (): void => {
const chainId = useChainId()
+ const [trigger] = useLazyGetTransactionDetailsQuery()
+
useEffect(() => {
const unsubFns = Object.entries(events).map(([txEvent, analyticsEvent]) =>
txSubscribe(txEvent as TxEvent, async (detail) => {
@@ -25,8 +27,8 @@ export const useTxTracking = (): void => {
if (id) {
try {
- const txDetails = await getTxDetails(chainId, id)
- origin = txDetails.safeAppInfo?.url || ''
+ const { data: txDetails } = await trigger({ chainId, txId: id })
+ origin = txDetails?.safeAppInfo?.url || ''
} catch {}
}
@@ -40,5 +42,5 @@ export const useTxTracking = (): void => {
return () => {
unsubFns.forEach((unsub) => unsub())
}
- }, [chainId])
+ }, [chainId, trigger])
}
diff --git a/src/hooks/useWalletCanPay.ts b/src/hooks/useWalletCanPay.ts
index 58a65094c..8d32c5f9c 100644
--- a/src/hooks/useWalletCanPay.ts
+++ b/src/hooks/useWalletCanPay.ts
@@ -1,15 +1,7 @@
import { getTotalFee } from '@/hooks/useGasPrice'
import useWalletBalance from '@/hooks/wallets/useWalletBalance'
-const useWalletCanPay = ({
- gasLimit,
- maxFeePerGas,
- maxPriorityFeePerGas,
-}: {
- gasLimit?: bigint
- maxFeePerGas?: bigint | null
- maxPriorityFeePerGas?: bigint | null
-}) => {
+const useWalletCanPay = ({ gasLimit, maxFeePerGas }: { gasLimit?: bigint; maxFeePerGas?: bigint | null }) => {
const [walletBalance] = useWalletBalance()
// Take an optimistic approach and assume the wallet can pay
diff --git a/src/hooks/wallets/__tests__/useOnboard.test.ts b/src/hooks/wallets/__tests__/useOnboard.test.ts
index 7fa1602c3..a885e21af 100644
--- a/src/hooks/wallets/__tests__/useOnboard.test.ts
+++ b/src/hooks/wallets/__tests__/useOnboard.test.ts
@@ -51,6 +51,7 @@ describe('useOnboard', () => {
chainId: '4',
ens: 'test.eth',
balance: '0.00235 ETH',
+ isDelegate: false,
})
})
diff --git a/src/hooks/wallets/useOnboard.ts b/src/hooks/wallets/useOnboard.ts
index 452068219..583976d5e 100644
--- a/src/hooks/wallets/useOnboard.ts
+++ b/src/hooks/wallets/useOnboard.ts
@@ -21,6 +21,7 @@ export type ConnectedWallet = {
provider: Eip1193Provider
icon?: string
balance?: string
+ isDelegate?: boolean
}
const { getStore, setStore, useStore } = new ExternalStore()
@@ -70,6 +71,7 @@ export const getConnectedWallet = (wallets: WalletState[]): ConnectedWallet | nu
provider: primaryWallet.provider,
icon: primaryWallet.icon,
balance,
+ isDelegate: false,
}
} catch (e) {
logError(Errors._106, e)
@@ -176,13 +178,6 @@ export const useInitOnboard = () => {
}
enableWallets().then(() => {
- // e2e wallet
- if (typeof window !== 'undefined' && window.Cypress) {
- connectWallet(onboard, {
- autoSelect: { label: 'e2e wallet', disableModals: true },
- })
- }
-
// Reconnect last wallet
connectLastWallet(onboard)
})
diff --git a/src/hooks/wallets/wallets.ts b/src/hooks/wallets/wallets.ts
index 852b6ccf4..e7098be87 100644
--- a/src/hooks/wallets/wallets.ts
+++ b/src/hooks/wallets/wallets.ts
@@ -1,4 +1,4 @@
-import { IS_PRODUCTION, TREZOR_APP_URL, TREZOR_EMAIL, WC_PROJECT_ID } from '@/config/constants'
+import { TREZOR_APP_URL, TREZOR_EMAIL, WC_PROJECT_ID } from '@/config/constants'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import type { InitOptions } from '@web3-onboard/core'
import coinbaseModule from '@web3-onboard/coinbase'
@@ -45,11 +45,7 @@ const WALLET_MODULES: Partial<{ [key in WALLET_KEYS]: (chain: ChainInfo) => Wall
[WALLET_KEYS.LEDGER]: () => ledgerModule() as WalletInit,
[WALLET_KEYS.TREZOR]: () => trezorModule({ appUrl: TREZOR_APP_URL, email: TREZOR_EMAIL }) as WalletInit,
[WALLET_KEYS.KEYSTONE]: () => keystoneModule() as WalletInit,
-}
-
-// Testing wallet module
-if (!IS_PRODUCTION) {
- WALLET_MODULES[WALLET_KEYS.PK] = (chain) => pkModule(chain.chainId, chain.rpcUri) as WalletInit
+ [WALLET_KEYS.PK]: (chain) => pkModule(chain.chainId, chain.rpcUri) as WalletInit,
}
export const getAllWallets = (chain: ChainInfo): WalletInits => {
diff --git a/src/hooks/wallets/web3.ts b/src/hooks/wallets/web3.ts
index ae31d511c..40975d4b8 100644
--- a/src/hooks/wallets/web3.ts
+++ b/src/hooks/wallets/web3.ts
@@ -1,14 +1,15 @@
import { type ChainInfo, RPC_AUTHENTICATION, type RpcUri } from '@safe-global/safe-gateway-typescript-sdk'
import { INFURA_TOKEN, SAFE_APPS_INFURA_TOKEN } from '@/config/constants'
-import { JsonRpcProvider, BrowserProvider, type Eip1193Provider, type Provider } from 'ethers'
+import { JsonRpcProvider, BrowserProvider, type Eip1193Provider } from 'ethers'
import ExternalStore from '@/services/ExternalStore'
-import { EMPTY_DATA } from '@safe-global/protocol-kit/dist/src/utils/constants'
/**
* Infura and other RPC providers limit the max amount included in a batch RPC call.
* Ethers uses 100 by default which is too high for i.e. Infura.
+ *
+ * Some networks like Scroll only support a batch size of 3.
*/
-const BATCH_MAX_COUNT = 10
+const BATCH_MAX_COUNT = 3
// RPC helpers
const formatRpcServiceUrl = ({ authentication, value }: RpcUri, token: string): string => {
@@ -65,9 +66,3 @@ export const getUserNonce = async (userAddress: string): Promise => {
return Promise.reject(error)
}
}
-
-export const isSmartContract = async (provider: Provider, address: string): Promise => {
- const code = await provider.getCode(address)
-
- return code !== EMPTY_DATA
-}
diff --git a/src/markdown/terms/terms.md b/src/markdown/terms/terms.md
new file mode 100644
index 000000000..e0b9433cb
--- /dev/null
+++ b/src/markdown/terms/terms.md
@@ -0,0 +1,389 @@
+---
+version: 1.2
+last_update_date: September, 2024
+---
+
+# Terms and Conditions
+
+Last updated: September, 2024
+
+[1\. What is the scope of the Terms?](#1.-what-is-the-scope-of-the-terms?)
+
+[2\. What do some of the capitalized terms mean in the Agreement?](#2.-what-do-some-of-the-capitalized-terms-mean-in-the-agreement?)
+
+[3\. What are the Services offered?](#3.-what-are-the-services-offered?)
+
+[4\. What do the Services not consist of?](#4.-what-do-the-services-not-consist-of?)
+
+[5\. What do you need to know about Third-Party Services?](#5.-what-do-you-need-to-know-about-third-party-safe-apps-and-third-party-services?)
+
+[6\. What are the fees for the Services?](#6.-what-are-the-fees-for-the-services?)
+
+[7\. Are we responsible for the security of your Private Keys, Recovery Phrase or other credentials?](#7.-are-we-responsible-for-the-security-of-your-private-keys,-recovery-phrase-or-other-credentials?)
+
+[8\. Are we responsible for recovering your Safe Account?](#8.-are-we-responsible-for-recovering-your-safe-account?)
+
+[9\. Are we responsible for notifying you about events occuring in your Safe Account?](#9.-are-we-responsible-for-notifying-you-about-events-occuring-in-your-safe-account?)
+
+[10\. Are we responsible for flagging malicious transactions?](#10.-are-we-responsible-for-flagging-malicious-transactions?)
+
+[11\. Are we responsible for the issuance of the Safe Token and any related functionalities or reward programs?](#11.-are-we-responsible-for-the-issuance-of-the-safe-token-and-any-related-functionalities-or-reward-programs?)
+
+[12\. Are we responsible for third-party content and services?](#12.-are-we-responsible-for-third-party-content-and-services?)
+
+[14\. Can you terminate your Agreement with us?](#14.-can-you-terminate-your-agreement-with-us?)
+
+[15\. What licenses and access do we grant to you?](#15.-what-licenses-and-access-do-we-grant-to-you?)
+
+[16\. What can you expect from the Services and can we make changes to them?](#16.-what-can-you-expect-from-the-services-and-can-we-make-changes-to-them?)
+
+[17\. What do you agree, warrant and represent?](#17.-what-do-you-agree,-warrant-and-represent?)
+
+[18\. What about our liability to you?](#18.-what-about-our-liability-to-you?)
+
+[19\. What about viruses, bugs and security vulnerabilities?](#19.-what-about-viruses,-bugs-and-security-vulnerabilities?)
+
+[20\. What if an event outside our control happens that affects our Services?](#20.-what-if-an-event-outside-our-control-happens-that-affects-our-services?)
+
+[21\. Who is responsible for your tax liabilities?](#21.-who-is-responsible-for-your-tax-liabilities?)
+
+[22\. What if a court disagrees with part of this Agreement?](#22.-what-if-a-court-disagrees-with-part-of-this-agreement?)
+
+[23\. What if we do not enforce certain rights under this Agreement?](#23.-what-if-we-do-not-enforce-certain-rights-under-this-agreement?)
+
+[24\. Do third parties have rights?](#24.-do-third-parties-have-rights?)
+
+[25\. Can this Agreement be assigned?](#25.-can-this-agreement-be-assigned?)
+
+[26\. Which Clauses of this Agreement survive termination?](#26.-which-clauses-of-this-agreement-survive-termination?)
+
+[27\. Data Protection](#27.-data-protection)
+
+[28\. Which laws apply to the Agreement?](#28.-which-laws-apply-to-the-agreement?)
+
+[29\. How can you get support for Safe Accounts and tell us about any problems?](#29.-how-can-you-get-support-for-safe-accounts-and-tell-us-about-any-problems?)
+
+[30\. Where is the place of legal proceedings?](#30.-where-is-the-place-of-legal-proceedings?)
+
+[31\. Is this all?](#31.-is-this-all?)
+
+# 1\. What is the scope of the Terms? {#1.-what-is-the-scope-of-the-terms?}
+
+These Terms and Conditions (โ**Terms**โ) become part of any contract (โ**Agreement**โ) between you (โ**you**โ, โ**yours**โ or โ**User**โ) and Core Contributors GmbH (โ**CC**โ, โ**we**โ, โ**our**โ or โ**us**โ) provided we made these Terms accessible to you prior to entering into the Agreement and you consent to these Terms. We are a limited liability company registered with the commercial register of Berlin Charlottenburg under company number HRB 240421 B, with its registered office at Gontardstraรe 11, 10178 Berlin, Germany. You can contact us by writing to info@cc0x.dev.
+
+The Agreement is concluded by using the Mobile App, Web App and/or Browser Extension subject to these Terms. The use of our Services is only permitted to legal entities, partnerships and natural persons with unlimited legal capacity. In particular, minors are prohibited from using our Services.
+
+The application of your general terms and conditions is excluded. Your deviating, conflicting or supplementary general terms and conditions shall only become part of the Agreement if and to the extent that CC has expressly agreed to their application in writing. This consent requirement shall apply in any case, even if for example CC, being aware of your general terms and conditions, accepts payments by the contractual partner without reservations.
+
+We reserve the right to change these Terms at any time and without giving reasons, while considering and weighing your interests. The new Terms will be communicated to you in advance. If you do not accept the new Terms, you are no longer entitled to use the Services.
+
+# 2\. What do some of the capitalized terms mean in the Agreement? {#2.-what-do-some-of-the-capitalized-terms-mean-in-the-agreement?}
+
+โ**Blockchain**โ means a mathematically secured consensus ledger such as the Ethereum Virtual Machine, an Ethereum Virtual Machine compatible validation mechanism, or other decentralized validation mechanisms.
+
+โ**Transaction**โ means a change to the data set through a new entry in the continuous Blockchain.
+
+โ**Smart Contract**โ means a piece of source code deployed as an application on the Blockchain which can be executed, including self-execution of Transactions as well as execution triggered by 3rd parties.
+
+โ**Token**โ means a digital asset transferred in a Transaction, including ETH, ERC20, ERC721 and ERC1155 tokens.
+
+โ**Wallet**โ means a cryptographic storage solution permitting you to store cryptographic assets by correlation of a (i) Public Key and (ii) a Private Key, or a Smart Contract to receive, manage and send Tokens.
+
+โ**Recovery Phrase**โ means a series of secret words used to generate one or more Private Keys and derived Public Keys.
+
+โ**Public Key**โ means a unique sequence of numbers and letters within the Blockchain to distinguish the network participants from each other.
+
+โ**Private Key**โ means a unique sequence of numbers and/or letters required to initiate a Blockchain Transaction and should only be known by the legal owner of the Wallet.
+
+# 3\. What are the Services offered? {#3.-what-are-the-services-offered?}
+
+Our services (โ**Services**โ) primarily consist of enabling users to create their Safe Accounts and ongoing interaction with it on the Blockchain.
+
+1. โ**Safe Account**โ
+
+A Safe Account is a modular, self-custodial (i.e. not supervised by us) smart contract-based wallet not provided by CC. Safe Accounts are open-source released under LGPL-3.0.
+
+Smart contract wallet means, unlike a standard private key Wallet, that access control for authorizing any Transaction is defined in code. An example are multi-signature wallets which require that any Transaction must be signed by a minimum number of signing wallets whereby the specifics of the requirements to authorize a Transaction can be configured in code.
+
+Owners need to connect a signing wallet with a Safe Account. Safe Accounts are compatible inter alia with standard private key Wallets such as hardware wallets, browser extension wallets and mobile wallets that support WalletConnect.
+
+2. โ**Safe{Wallet} App**โ
+
+You may access Safe Accounts using the Safe{Wallet} web app, mobile app for iOS and android, or the browser extension (each a โSafe{Wallet} Appโ). The Safe{Wallet} App may be used to manage your personal digital assets on Ethereum and other common EVM chains when you connect a Safe Account with third-party services (as defined below). The Safe{Wallet} App provides certain features that may be amended from time to time.
+
+3. โ**Third-Party Safe Apps**โ
+
+The Safe{Wallet} App allows you to connect Safe Accounts to third-party applications (โThird-Party Safe Appsโ) and use third-party services such as from the decentralized finance sector, DAO tools or services related to NFTs (โThird-Party Services"). The Third-Party Safe Apps are integrated in the user interface of the Safe{Wallet} App via inline framing. The provider of the Third-Party Safe App and/or related Third-Party Services is responsible for the operation of the service and the correctness, completeness and actuality of any information provided therein. We make a pre-selection of Third-Party Safe Apps that we show in the Safe{Wallet} App. However, we only perform a rough triage in advance for obvious problems and functionality in terms of loading time and resolution capability of the transactions. Accordingly, in the event of any (technical) issues concerning the Third-Party Services, the user must only contact the respective service provider directly. The terms of service, if any, shall be governed by the applicable contractual provisions between the User and the respective provider of the Third-Party Safe Apps or Third-Party Services. Accordingly, we are not liable in the event of a breach of contract, damage or loss related to the use of such Third-Party Safe Apps or Third-Party Services.
+
+# 4\. What do the Services not consist of? {#4.-what-do-the-services-not-consist-of?}
+
+Our Services do not consist of:
+
+1. activity regulated by the Federal Financial Supervisory Authority (BaFin) or any other regulatory agency in any jurisdiction;
+
+2. coverage underwritten by any regulatory agencyโs compensation scheme;
+
+3. custody of your Recovery Phrase, Private Keys, Tokens or the ability to remove or freeze your Tokens, i.e. a Safe Account is a self-custodial wallet;
+
+4. the storage or transmission of fiat currencies;
+
+5. back-up services to recover your Recovery Phrase or Private Keys, for whose safekeeping you are solely responsible; CC has no means to recover your access to your Tokens, when you lose access to your Safe Account;
+
+6. any form of legal, financial, investment, accounting, tax or other professional advice regarding Transactions and their suitability to you;
+
+7. the responsibility to monitor authorized Transactions or to check the correctness or completeness of Transactions before you are authorizing them;
+
+8. notifications about events occurring in or connection with your Safe Account;
+
+9. recovery of your Safe Account;
+
+10. flagging malicious transactions;
+
+11. issuance of the Safe Token and any related functionalities or reward programs.
+
+# 5\. What do you need to know about Third-Party Safe Apps and Third-Party Services? {#5.-what-do-you-need-to-know-about-third-party-safe-apps-and-third-party-services?}
+
+1. We provide you the possibility to interact with your Safe Account through Third-Party Services. Any activities you engage in with, or services you receive from a third party is between you and that third party directly. The conditions of service provisions, if any, shall be governed by the applicable contractual provisions between you and the respective provider of the Third-Party Service.
+
+2. The Services rely in part on third-party and open-source software, including the Blockchain, and the continued development and support by third parties. There is no assurance or guarantee that those third parties will maintain their support of their software or that open-source software will continue to be maintained. This may have a material adverse effect on the Services.
+
+3. This means specifically:
+
+* We do not have any oversight over your activities with Third-Party Services especially by using Third-Party Safe Apps, and therefore we do not and cannot make any representation regarding their appropriateness and suitability for you.
+
+* Third-Party Safe Apps and Third-Party Services are not hosted, owned, controlled or maintained by us. We also do not participate in the Transaction and will not and cannot monitor, verify, censor or edit the functioning or content of any Third-Party Safe Apps and Third-Party Services.
+
+* We have not conducted any security audit, bug bounty or formal verification (whether internal or external) of the Third-Party Safe Apps and Third-Party Services.
+
+* We have no control over, do not recommend, endorse, or otherwise take a position on the integrity, functioning of, content and your use of Third-Party Safe Apps and Third-Party Services, whose sole responsibility lies with the person from whom such services or content originated.
+
+* When you access or use Third-Party Safe Apps and Third-Party Services you accept that there are risks in doing so and that you alone assume any such risks when choosing to interact with them. We are not liable for any errors or omissions or for any damages or loss you might suffer through interacting with those Third-Party Safe Apps and Third-Party Services.
+
+* You know of the inherent risks of cryptographic and Blockchain-based systems and the high volatility of Token markets. Transactions undertaken in the Blockchain are irrevocable and irreversible and there is no possibility to refund Token that have been deployed.
+
+* You should read the license requirements, terms and conditions as well as privacy policy of each Third-Party Safe Appz and Third-Party Service that you access or use. Certain Third-Party Safe Apps and Third-Party Services may involve complex Transactions that entail a high degree of risk.
+
+* If you contribute integrations to Third-Party Safe Apps and Third-Party Services, you are responsible for all content you contribute, in any manner, and you must have all rights necessary to do so, in the manner in which you contribute it. You are responsible for all your activity in connection with any such Third-Party Safe Apps and Third-Party Services.
+
+* Your interactions with persons found on or through the Third-Party Safe Apps and and Third-Party Services, including payment and delivery of goods and services, financial transactions, and any other terms associated with such dealings, are solely between you and such persons. You agree that we shall not be responsible or liable for any loss or damage of any sort incurred as the result of any such dealings.
+
+* If there is a dispute between you and the Third-Party Safe Apps or Third-Party Services provider or/and other users of the Third-Party Safe Apps or Third-Party Service, you agree that we are under no obligation to become involved. In the event that you have a dispute with one or more other users, you release us, our officers, employees, agents, contractors and successors from claims, demands, and damages of every kind or nature, known or unknown, suspected or unsuspected, disclosed or undisclosed, arising out of or in any way related to such disputes and/or our Services.
+
+# 6\. What are the fees for the Services? {#6.-what-are-the-fees-for-the-services?}
+
+1. The use of the Safe{Wallet} App, Third-Party Safe Apps or Third-Party Services may cause fees, including network fees, as indicated in the respective app. CC has no control over the fees charged by the Third-Party Safe Apps or Third Party Services. CC may change its own fees at any time. Price changes will be communicated to the User in due time before taking effect.
+
+2. The User is only entitled to offset and/or assert rights of retention if his counterclaims are legally established, undisputed or recognized by CC.
+
+# 7\. Are we responsible for the security of your Private Keys, Recovery Phrase or other credentials? {#7.-are-we-responsible-for-the-security-of-your-private-keys,-recovery-phrase-or-other-credentials?}
+
+1. We shall not be responsible to secure your Private Keys, Recovery Phrase, credentials or other means of authorization of your wallet(s).
+
+2. You must own and control any wallet you use in connection with our Services. You are responsible for implementing all appropriate measures for securing any wallet you use, including any Private Key(s), Recovery Phrase, credentials or other means of authorization necessary to access such storage mechanism(s).
+
+3. We exclude any and all liability for any security breaches or other acts or omissions, which result in your loss of access or custody of any cryptographic assets stored thereon.
+
+# 8\. Are we responsible for recovering your Safe Account? {#8.-are-we-responsible-for-recovering-your-safe-account?}
+
+1. We shall not be responsible for recovering your Safe Account.
+
+2. You are solely responsible for securing a back-up of your Safe Account access as you see fit.
+
+3. Any recovery feature we provide access to within the Safe{Wallet} App is a mechanism controlled by your Safe Account on the Blockchain, both of which we donโt have any influence over once you have set it up. We will never act as a recoverer ourselves and donโt offer recovery services. The Self Custodial Recovery feature allows you to determine your own recovery setup and nominate anyone including yourself as your recoverer. The recoverer can start the recovery process at any time. Please note that we are not responsible for notifying you of this process (see Section 7 above). Furthermore we reserve the right to cease the access to the Self Custodial Recovery feature via our Safe{Wallet} App taking the userโs reasonable interests into account and providing due notification.
+
+4. The recovery feature is provided free of charge and liability is limited pursuant to Section 18 below.
+
+# 9\. Are we responsible for notifying you about events occuring in your Safe Account? {#9.-are-we-responsible-for-notifying-you-about-events-occuring-in-your-safe-account?}
+
+1. We shall not be responsible for notifying you of any interactions or events occurring in your Safe Account, be it on the Blockchain, third-party interfaces, within any other infrastructure, or our Services.
+
+2. You are responsible for monitoring Safe Account as you see fit.
+
+3. Any notification service we provide or offer for subscription within the Safe{Wallet} App via e-mail or push notifications or any other means of communication is provided free of charge and liability is limited pursuant to Section 18 below. Furthermore we reserve the right to change the notification feature from time to time or cease to provide them without notice.
+
+# 10\. Are we responsible for flagging malicious transactions? {#10.-are-we-responsible-for-flagging-malicious-transactions?}
+
+1. We shall not be responsible for flagging malicious transactions in our Safe{Wallet} App.
+
+2. You are solely responsible for checking any transaction, address, Token or other item you interact with via your Smart Account in our Safe{Wallet} App.
+
+5. Any security flagging or warning service we provide or offer for subscription within the Safe{Wallet} App is provided free of charge and liability is limited pursuant to Section 18 below. Furthermore we reserve the right to change the feature from time to time or cease to provide them without notice.
+
+# 11\. Are we responsible for the issuance of the Safe Token and any related functionalities or reward programs? {#11.-are-we-responsible-for-the-issuance-of-the-safe-token-and-any-related-functionalities-or-reward-programs?}
+
+1. The Safe Token is issued by the Safe Ecosystem Foundation. We are not the issuer or in any way responsible for the Safe Token. Furthermore, we do not provide any functionalities to the Safe Token or Safe Token reward programs.
+
+2. You are solely responsible for managing your Safe Tokens just like any other Token in your Safe Account and solely responsible for your eligibility for any reward programs.
+
+3. Any interface we provide that allows you to claim or delegate your Safe Tokens or to participate in any third party program related to Safe Tokens is provided free of charge and we exclude any and all liability for the correctness, completeness, speed or timeliness of these services. Furthermore we reserve the right to change the feature from time to time or cease to provide them without notice.
+
+# 12\. Are we responsible for third-party content and services? {#12.-are-we-responsible-for-third-party-content-and-services?}
+
+1. You may view, have access to, and may use third-party content and services, for example widget integrations, within the Safe{Wallet} App (โThird-Party Featuresโ). You view, access, or use Third-Party Features at your own election. Your reliance on Third-Party Features is subject to separate terms and conditions set forth by the applicable third party content and/or service provider (โThird-Party Termsโ). Third-Party Terms may, amongst other things,
+
+ 1. involve separate fees and charges,
+
+ 2. include disclaimers or risk warnings,
+
+ 3. apply a different terms and privacy policy.
+
+ It is your responsibility to understand the Third-Party Terms, including how Third-Party Features use any of your information under their privacy policies.
+
+2. Third Party Features are provided for your convenience only. We do not verify, curate, or control Third Party Features.
+
+3. If we offer access to Third-Party Features in the Safe{Wallet} App free of charge by us (Third-Parties may charge separate fees), the liability for providing access to such Third-Party Feature is limited pursuant to Section 18 below. Furthermore we reserve the right to cease to provide access to those Third-Party Features through the Safe{Wallet} App without notice.
+
+13\. Can we terminate or limit your right to use our Services?
+
+1. We may cease offering our Services and/or terminate the Agreement and refuse access to the Safe{Wallet} App at any time. The right of the parties to terminate the Agreement at any time for cause remains unaffected. In case of our termination of the Agreement, you may no longer access your Safe Account via our Services. However, you may continue to access your Safe Account and any Tokens via a third-party wallet provider using your Recovery Phrase and Private Keys.
+
+2. We reserve the right to limit the use of the Safe{Wallet} App to a specified number of Users if necessary to protect or ensure the stability and integrity of the Services. We will only be able to limit access to the Services. At no time will we be able to limit or block access to or transfer your funds without your consent.
+
+# 14\. Can you terminate your Agreement with us? {#14.-can-you-terminate-your-agreement-with-us?}
+
+You may terminate the Agreement at any time without notice.
+
+# 15\. What licenses and access do we grant to you? {#15.-what-licenses-and-access-do-we-grant-to-you?}
+
+1. All intellectual property rights in Safe Accounts and the Services throughout the world belong to us as owner or our licensors. Nothing in these Terms gives you any rights in respect of any intellectual property owned by us or our licensors and you acknowledge that you do not acquire any ownership rights by downloading the Safe{Wallet} App or any content from the Safe{Wallet} App.
+
+2. If you are a consumer we grant you a simple, limited license, but do not sell, to you the Services you download solely for your own personal, non-commercial use.
+
+# 16\. What can you expect from the Services and can we make changes to them? {#16.-what-can-you-expect-from-the-services-and-can-we-make-changes-to-them?}
+
+1. Without limiting your mandatory warranties, we provide the Services to you โas isโ and โas availableโ in relation to merchantability, fitness for a particular purpose, availability, security, title or non-infringement.
+
+2. If you use the Safe{Wallet} App via web browser, the strict liability of CC for damages (sec. 536a German Civil Code) for defects existing at the time of conclusion of the contract is precluded.
+
+3. The foregoing provisions will not limit CCโs liability as defined in Clause 18\.
+
+4. We reserve the right to change the format and features of the Services by making any updates to Services available for you to download or, where your device settings permit it, by automatic delivery of updates.
+
+5. You are not obliged to download the updated Services, but we may cease to provide and/or update prior versions of the Services and, depending on the nature of the update, in some circumstances you may not be able to continue using the Services until you have downloaded the updated version.
+
+6. We may cease to provide and/or update content to the Services, with or without notice to you, if it improves the Services we provide to you, or we need to do so for security, legal or any other reasons.
+
+# 17\. What do you agree, warrant and represent? {#17.-what-do-you-agree,-warrant-and-represent?}
+
+By using our Services you hereby agree, represent and warrant that:
+
+1. You are not a citizen, resident, or member of any jurisdiction or group that is subject to economic sanctions by the European Union or the United States or any other relevant jurisdiction.
+
+2. You do not appear on HMT Sanctions List, the U.S. Treasury Departmentโs Office of Foreign Asset Controlโs sanctions lists, the U.S. commerce department's consolidated screening list, the EU consolidated list of persons, groups or entities subject to EU Financial Sanctions, nor do you act on behalf of a person sanctioned thereunder.
+
+3. You have read and understood these Terms and agree to be bound by its terms.
+
+4. Your usage of our Services is legal under the laws of your jurisdiction or under the laws of any other jurisdiction to which you may be subject.
+
+5. You wonโt use the Services or interact with the Services in a manner that violates any law or regulation, including, without limitation, any applicable export control laws.
+
+6. You understand the functionality, usage, storage, transmission mechanisms and intricacies associated with Tokens as well as wallet (including Safe Account) and Blockchains.
+
+7. You understand that Transactions on the Blockchain are irreversible and may not be erased and that your Safe Account address and Transactions are displayed permanently and publicly.
+
+8. You will comply with any applicable tax obligations in your jurisdiction arising from your use of the Services.
+
+9. You will not misuse or gain unauthorized access to our Services by knowingly introducing viruses, cross-site scripting, Trojan horses, worms, time-bombs, keystroke loggers, spyware, adware or any other harmful programs or similar computer code designed to adversely affect our Services and that in the event you do so or otherwise attack our Services, we reserve the right to report any such activity to the relevant law enforcement authorities and we will cooperate with those authorities as required.
+
+10. You wonโt access without authority, interfere with, damage or disrupt any part of our Services, any equipment or network on which our Services is stored, any software used in the provision of our Services or any equipment or network or software owned or used by any third party.
+
+11. You wonโt use our Services for activities that are unlawful or fraudulent or have such purpose or effect or otherwise support any activities that breach applicable local, national or international law or regulations.
+
+12. You wonโt use our Services to store, trade or transmit Tokens that are proceeds of criminal or fraudulent activity.
+
+13. You understand that the Services and the underlying Blockchain are in an early development stage and we accordingly do not guarantee an error-free process and give no price or liquidity guarantee.
+
+14. You are using the Services at your own risk.
+
+# 18\. What about our liability to you? {#18.-what-about-our-liability-to-you?}
+
+1. If the Safe{Wallet} App or Services are provided to the User free of charge (please note, in this context, that any service, network, and/or transaction fees may be charged by third parties via the Blockchain and not necessarily by us), CC shall be liable only in cases of intent, gross negligence, or if CC has fraudulently concealed a possible material or legal defect of the Safe{Wallet} App or Services.
+2. If the Safe{Wallet} App or Services are not provided to the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 18.1 as well as (ii) in cases of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the performance of which enables the proper execution of this Agreement in the first place and on the compliance of which the User regularly relies and may rely, whereby CCโs liability shall be limited to the compensation of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages resulting from the breach of a non-essential contractual duty are excluded.
+3. The limitations of liability according to Clause 18.1 and Clause 18.2 do not apply (i) to damages resulting from injury to life, body or health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product Liability Act and (iv) to claims of the User according to the applicable data protection law.
+4. The limitation of liability also applies to the personal liability of the organs, legal representatives, employees and vicarious agents of CC.
+5. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the damage would have been avoided by a regular and complete backup of all relevant data by the User.
+6. In the event of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be economically expected of CC.
+
+# 19\. What about viruses, bugs and security vulnerabilities? {#19.-what-about-viruses,-bugs-and-security-vulnerabilities?}
+
+1. We endeavor to provide our Service free from material bugs, security vulnerabilities or viruses.
+
+2. You are responsible for configuring your information technology and computer programmes to access our Services and to use your own virus protection software.
+
+3. If you become aware of any exploits, bugs or vulnerabilities, please inform bounty@safe.global.
+
+4. You must not misuse our Services by knowingly introducing material that is malicious or technologically harmful. If you do, your right to use our Services will cease immediately.
+
+# 20\. What if an event outside our control happens that affects our Services? {#20.-what-if-an-event-outside-our-control-happens-that-affects-our-services?}
+
+1. We may update and change our Services from time to time. We may suspend or withdraw or restrict the availability of all or any part of our Services for business, operational or regulatory reasons or because of a Force Majeure Event at no notice.
+
+2. A โForce Majeure Eventโ shall mean any event, circumstance or cause beyond our reasonable control, which prevents, hinders or delays the provision of our Services or makes their provision impossible or onerous, including, without limitation:
+
+* acts of God, flood, storm, drought, earthquake or other natural disaster;
+
+* epidemic or pandemic (for the avoidance of doubt, including the 2020 Coronavirus Pandemic);
+
+* terrorist attack, hacking or cyber threats, civil war, civil commotion or riots, war, threat of or preparation for war, armed conflict, imposition of sanctions, embargo, or breaking off of diplomatic relations;
+
+* equipment or software malfunction or bugs including network splits or forks or unexpected changes in the Blockchain, as well as hacks, phishing attacks, distributed denials of service or any other security attacks;
+
+* nuclear, chemical or biological contamination;
+
+* any law statutes, ordinances, rules, regulations, judgments, injunctions, orders and decrees or any action taken by a government or public authority, including without limitation imposing a prohibition, or failing to grant a necessary license or consent;
+
+* collapse of buildings, breakdown of plant or machinery, fire, explosion or accident; and
+
+* strike, industrial action or lockout.
+
+3. We shall not be liable or responsible to you, or be deemed to have defaulted under or breached this Agreement, for any failure or delay in the provision of the Services or the performance of this Agreement, if and to the extent such failure or delay is caused by or results from or is connected to acts beyond our reasonable control, including the occurrence of a Force Majeure Event.
+
+# 21\. Who is responsible for your tax liabilities? {#21.-who-is-responsible-for-your-tax-liabilities?}
+
+You are solely responsible to determine if your use of the Services have tax implications, in particular income tax and capital gains tax relating to the purchase or sale of Tokens, for you. By using the Services you agree not to hold us liable for any tax liability associated with or arising from the operation of the Services or any other action or transaction related thereto.
+
+# 22\. What if a court disagrees with part of this Agreement? {#22.-what-if-a-court-disagrees-with-part-of-this-agreement?}
+
+Should individual provisions of these Terms be or become invalid or unenforceable in whole or in part, this shall not affect the validity of the remaining provisions. The invalid or unenforceable provision shall be replaced by the statutory provision. If there is no statutory provision or if the statutory provision would lead to an unacceptable result, the parties shall enter negotiations to replace the invalid or unenforceable provision with a valid provision that comes as close as possible to the economic purpose of the invalid or unenforceable provision.
+
+# 23\. What if we do not enforce certain rights under this Agreement? {#23.-what-if-we-do-not-enforce-certain-rights-under-this-agreement?}
+
+Our failure to exercise or enforce any right or remedy provided under this Agreement or by law shall not constitute a waiver of that or any other right or remedy, nor shall it prevent or restrict any further exercise of that or any other right or remedy.
+
+# 24\. Do third parties have rights? {#24.-do-third-parties-have-rights?}
+
+Unless it expressly states otherwise, this Agreement does not give rise to any third-party rights, which may be enforced against us.
+
+# 25\. Can this Agreement be assigned? {#25.-can-this-agreement-be-assigned?}
+
+1. We are entitled to transfer our rights and obligations under the Agreement in whole or in part to third parties with a notice period of four weeks. In this case, you have the right to terminate the Agreement without notice.
+
+2. You shall not be entitled to assign this Agreement to any third party without our express prior written consent.
+
+# 26\. Which Clauses of this Agreement survive termination? {#26.-which-clauses-of-this-agreement-survive-termination?}
+
+All covenants, agreements, representations and warranties made in this Agreement shall survive your acceptance of this Agreement and its termination.
+
+# 27\. Data Protection {#27.-data-protection}
+
+We inform you about our processing of personal data, including the disclosure to third parties and your rights as an affected party, in the Privacy Policy.
+
+# 28\. Which laws apply to the Agreement? {#28.-which-laws-apply-to-the-agreement?}
+
+The Agreement including these Terms shall be governed by German law. The application of the UN Convention on Contracts for the International Sale of Goods is excluded. For consumers domiciled in another European country but Germany, the mandatory provisions of the consumer protection laws of the member state in which the consumer is domiciled shall also apply, provided that these are more advantageous for the consumer than the provisions of the German law.
+
+# 29\. How can you get support for Safe Accounts and tell us about any problems? {#29.-how-can-you-get-support-for-safe-accounts-and-tell-us-about-any-problems?}
+
+If you want to learn more about Safe Accounts or the Service or have any problems using them or have any complaints please get in touch via any of the following channels:
+
+1. Intercom: https://help.safe.global
+2. Discord: https://chat.safe.global
+3. Twitter: https://twitter.com/safe
+
+# 30\. Where is the place of legal proceedings? {#30.-where-is-the-place-of-legal-proceedings?}
+
+For users who are merchants within the meaning of the German Commercial Code (Handelsgesetzbuch), a special fund (Sondervermรถgen) under public law or a legal person under public law, Berlin shall be the exclusive place of jurisdiction for all disputes arising from the contractual relationship.
+
+# 31\. Is this all? {#31.-is-this-all?}
+
+These Terms constitute the entire agreement between you and us in relation to the Agreementโs subject matter. It replaces and extinguishes any and all prior agreements, draft agreements, arrangements, warranties, statements, assurances, representations and undertakings of any nature made by, or on behalf of either of us, whether oral or written, public or private, in relation to that subject matter.
diff --git a/src/markdown/terms/terms.md.d.ts b/src/markdown/terms/terms.md.d.ts
new file mode 100644
index 000000000..a89eca793
--- /dev/null
+++ b/src/markdown/terms/terms.md.d.ts
@@ -0,0 +1,5 @@
+export { default } from '*.md'
+export const metadata = {
+ version: string,
+ last_update_date: string,
+}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 916175595..915cf8a98 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -45,7 +45,7 @@ import CounterfactualHooks from '@/features/counterfactual/CounterfactualHooks'
import PkModulePopup from '@/services/private-key-module/PkModulePopup'
import GeoblockingProvider from '@/components/common/GeoblockingProvider'
-const GATEWAY_URL = IS_PRODUCTION || cgwDebugStorage.get() ? GATEWAY_URL_PRODUCTION : GATEWAY_URL_STAGING
+export const GATEWAY_URL = IS_PRODUCTION || cgwDebugStorage.get() ? GATEWAY_URL_PRODUCTION : GATEWAY_URL_STAGING
const reduxStore = makeStore()
diff --git a/src/pages/cookie.tsx b/src/pages/cookie.tsx
index 84244bd99..ef4999431 100644
--- a/src/pages/cookie.tsx
+++ b/src/pages/cookie.tsx
@@ -1,603 +1,163 @@
-import type { NextPage } from 'next'
-import Head from 'next/head'
-import Link from 'next/link'
-import MUILink from '@mui/material/Link'
-import { AppRoutes } from '@/config/routes'
-import { IS_OFFICIAL_HOST } from '@/config/constants'
+import { Typography, Link } from '@mui/material'
-const SafeCookiePolicy = () => (
-
-
-
-
Cookie Policy
-
Last updated: January 2024.
-
- As described in our{' '}
-
- Privacy Policy
-
- , for general web-browsing of this website, your personal data is not revealed to us, although certain statistical
- information is available to us via our internet service provider as well as through the use of special tracking
- technologies. Such information tells us about the pages you are clicking on or the hardware you are using, but not
- your name, age, address or anything we can use to identify you personally. We exclusively process your personal
- data in pseudonymised form.
-
-
- This Cookie Policy applies to our website at{' '}
-
- https://app.safe.global
-
- and sets out some further detail on how and why we use these technologies on our website.{' '}
-
-
- In this policy, "we", "us" and "our" refers to Core Contributors GmbH a company
- incorporated in Germany with its registered address at Gontardstraรe 11, 10178 Berlin, Germany. The terms
- “you” and “your” includes our clients, business partners and users of this website.{' '}
-
-
- By using our website, you consent to storage and access to cookies and other technologies on your device, in
- accordance with this Cookie Policy.
-
-
What are cookies?
-
- Cookies are a feature of web browser software that allows web servers to recognize the computer or device used to
- access a website. A cookie is a small text file that a website saves on your computer or mobile device when you
- visit the site. It enables the website to remember your actions and preferences (such as login, language, font
- size and other display preferences) over a period of time, so you don't have to keep re-entering them
- whenever you come back to the site or browse from one page to another.
-
-
What are the different types of cookies?
-
A cookie can be classified by its lifespan and the domain to which it belongs.
-
By lifespan, a cookie is either a:
-
- session cookie which is erased when the user closes the browser; or
-
- persistent cookie which is saved to the hard drive and remains on the user's computer/device for a
- pre-defined period of time. As for the domain to which it belongs, cookies are either:
-
-
-
-
- first-party cookies which are set by the web server of the visited page and share the same domain (i.e. set by
- us); or
-
- third-party cookies stored by a different domain to the visited page's domain.
-
-
What cookies do we use and why?
-
We list all the cookies we use on this website in the APPENDIX below.
-
- We do not use cookies set by ourselves via our web developers (first-party cookies). We only have those set by
- others (third-party cookies).
-
-
- Cookies are also sometimes classified by reference to their purpose. We use the following cookies for the
- following purposes:
-
-
-
- Analytical/performance cookies: They allow us to recognize and count the number of visitors and to see how
- visitors move around our website when they are using it, as well as dates and times they visit. This helps us to
- improve the way our website works, for example, by ensuring that users are finding what they are looking for
- easily.
-
-
- Targeting cookies: These cookies record your visit to our website, the pages you have visited and the links you
- have followed, as well as time spent on our website, and the websites visited just before and just after our
- website. We will use this information to make our website and the advertising displayed on it more relevant to
- your interests. We may also share this information with third parties for this purpose.
-
-
-
- In general, we use cookies and other technologies (such as web server logs) on our website to enhance your
- experience and to collect information about how our website is used.{' '}
-
-
- We will retain and evaluate information on your recent visits to our website and how you move around different
- sections of our website for analytics purposes to understand how people use our website so that we can make it
- more intuitive. The information also helps us to understand which parts of this website are most popular and
- generally to assess user behavior and characteristics to measure interest in and use of the various areas of our
- website. This then allows us to improve our website and the way we market our business.
-
-
- This information may also be used to help us to improve, administer and diagnose problems with our server and
- website. The information also helps us monitor traffic on our website so that we can manage our website's
- capacity and efficiency.
-
-
Other Technologies
-
- We may allow others to provide analytics services and serve advertisements on our behalf. In addition to the uses
- of cookies described above, these entities may use other methods, such as the technologies described below, to
- collect information about your use of our website and other websites and online services.
-
-
- Pixels tags. Pixel tags (which are also called clear GIFs, web beacons, or pixels), are small pieces of code that
- can be embedded on websites and emails. Pixels tags may be used to learn how you interact with our website pages
- and emails, and this information helps us, and our partners provide you with a more tailored experience.
-
-
- Device Identifiers. A device identifier is a unique label that can be used to identify a mobile device. Device
- identifiers may be used to track, analyze and improve the performance of the website and ads delivered.
-
-
What data is collected by cookies and other technologies on our website?
-
This information may include:
-
-
- the IP and logical address of the server you are using (but the last digits are anonymized so we cannot identify
- you).
-
- the top level domain name from which you access the internet (for example .ie, .com, etc)
- the type of browser you are using,
- the date and time you access our website
- the internet address linking to our website.
-
-
This website also uses cookies to:
-
- remember you and your actions while navigating between pages;
- remember if you have agreed (or not) to our use of cookies on our website;
- ensure the security of the website;
- monitor and improve the performance of servers hosting the site;
- distinguish users and sessions;
- Improving the speed of the site when you access content repeatedly;
- determine new sessions and visits;
- show the traffic source or campaign that explains how you may have reached our website; and
- allow us to store any customization preferences where our website allows this
-
-
- We may also use other services, such as{' '}
-
-
- Google Analytics
-
-
- (described below) or other third-party cookies, to assist with analyzing performance on our website. As part
- of providing these services, these service providers may use cookies and the technologies described below to
- collect and store information about your device, such as time of visit, pages visited, time spent on each page of
- our website, links clicked and conversion information, IP address, browser, mobile network information, and type
- of operating system used.
-
-
Google Analytics Cookies
-
- This website uses{' '}
-
-
- Google Analytics
-
-
- , a web analytics service provided by Google, Inc. ("Google").
-
-
- We use Google Analytics to track your preferences and also to identify popular sections of our website. Use of
- Google Analytics in this way, enables us to adapt the content of our website more specifically to your needs and
- thereby improve what we can offer to you.
-
-
- Google will use this information for the purpose of evaluating your use of our website, compiling reports on
- website activity for website operators and providing other services relating to website activity and internet
- usage. Google may also transfer this information to third parties where required to do so by law, or where such
- third parties process the information on Google's behalf. Google will not associate your IP address with any
- other data held by Google.
-
-
In particular Google Analytics tells us
-
- your IP address (last 3 digits are masked);
- the number of pages visited;
- the time and duration of the visit;
- your location;
- the website you came from (if any);
- the type of hardware you use (i.e. whether you are browsing from a desktop or a mobile device);
- the software used (type of browser); and
- your general interaction with our website.
-
-
- As stated above, cookie-related information is not used to identify you personally, and what is compiled is only
- aggregate data that tells us, for example, what countries we are most popular in, but not that you live in a
- particular country or your precise location when you visited our website (this is because we have only half the
- information- we know the country the person is browsing from, but not the name of person who is browsing). In such
- an example Google will analyze the number of users for us, but the relevant cookies do not reveal their
- identities.
-
-
- By using this website, you consent to the processing of data about you by Google in the manner and for the
- purposes set out above. Google Analytics, its purpose and function is further explained on the{' '}
-
-
- Google Analytics website
-
-
- .
-
-
- For more information about Google Analytics cookies, please see Google's help pages and privacy policy:{' '}
-
-
- Google's Privacy Policy
-
-
- and{' '}
-
-
- Google Analytics Help pages
-
-
- . For further information about the use of these cookies by Google{' '}
-
-
- click here
-
-
- .
-
-
- What if you don’t agree with us monitoring your use of our website (even if we don't collect your
- personal data)?
-
-
- Enabling these cookies is not strictly necessary for our website to work but it will provide you with a better
- browsing experience. You can delete or block the cookies we set, but if you do that, some features of this website
- may not work as intended.
-
-
- Most browsers are initially set to accept cookies. If you prefer, you can set your browser to refuse cookies and
- control and/or delete cookies as you wish – for details, see{' '}
-
-
- https://aboutcookies.org
-
-
- . You can delete all cookies that are already on your device and you can set most browsers to prevent them from
- being placed. You should be aware that if you do this, you may have to manually adjust some preferences every time
- you visit an Internet site and some services and functionalities may not work if you do not accept the cookies
- they send.
-
-
- Advertisers and business partners that you access on or through our website may also send you cookies. We do not
- control any cookies outside of our website.
-
-
- If you have any further questions regarding disabling cookies you should consult with your preferred
- browser’s provider or manufacturer.
-
-
- In order to implement your objection it may be necessary to install an opt-out cookie on your browser. This cookie
- will only indicate that you have opted out. It is important to note, that for technical reasons, the opt-out
- cookie will only affect the browser from which you actively object from. If you delete the cookies in your browser
- or use a different end device or browser, you will need to opt out again.
-
-
- To opt out of being tracked by Google Analytics across all websites, Google has developed Google Analytics opt-out
- browser add-on. If you would like to opt out of Google Analytics, you have the option of downloading and
- installing this browser add-on which can be found under the link:{' '}
-
-
- https://tools.google.com/dlpage/gaoptout
-
-
- .
-
-
Revisions to this Cookie Policy
-
- On this website, you can always view the latest version of our Privacy Policy and our Cookie Policy. We may modify
- this Cookie Policy from time to time. If we make changes to this Cookie Policy, we will provide notice of such
- changes, such as by sending an email notification, providing notice through our website or updating the
- ‘Last Updated’ date at the beginning of this Cookie Policy. The amended Cookie Policy will be
- effective immediately after the date it is posted. By continuing to access or use our website after the effective
- date, you confirm your acceptance of the revised Cookie Policy and all of the terms incorporated therein by
- reference. We encourage you to review our Privacy Policy and our Cookie Policy whenever you access or use our
- website to stay informed about our information practices and the choices available to you.
-
-
- If you do not accept changes which are made to this Cookie Policy, or take any measures described above to opt-out
- by removing or rejecting cookies, you may continue to use this website but accept that it may not display and/or
- function as intended by us. Any social media channels connected to us and third party applications will be subject
- to the privacy and cookie policies and practices of the relevant platform providers which, unless otherwise
- indicated, are not affiliated or associated with us Your exercise of any rights to opt-out may also impact how our
- information and content is displayed and/or accessible to you on this website and on other websites.
-
-
APPENDIX
-
Overview of cookies placed and the consequences if the cookies are not placed.
-
First-party cookies
-
-
-
-
- #
-
-
- Name of cookie
-
-
- Domain
-
-
- Purpose(s) of cookie
-
-
- Storage period of cookie
-
-
- Consequences is cookie is not accepted
-
-
-
-
- 1
-
-
- _BEAMER_FILTER_BY_URL_{'{productID}'}
-
-
- app.safe.global
-
-
- Stores whether to apply URL filtering on the feed.
-
-
- 20 minutes
-
-
- User activity won't be tracked
-
-
-
-
- 2
-
-
- _BEAMER_DATE_{'{productID}'}
-
-
- app.safe.global
-
-
- Stores the latest date in which the feed was opened.
-
-
- 300 days
-
-
- User activity won't be tracked
-
-
-
-
- 3
-
-
- _BEAMER_LAST_POST_SHOWN_{'{productID}'}
-
-
- app.safe.global
-
-
- Stores the ID of the last post shown as a teaser.
-
-
- Session
-
-
- User activity won't be tracked
-
-
-
-
- 4
-
-
- _BEAMER_BOOSTED_ANNOUNCEMENT_DATE_{'{productID}'}
-
-
- app.safe.global
-
-
- Stores the latest date in which a boosted announcement was displayed.
-
-
- 300 days
-
-
- User activity won't be tracked
-
-
-
-
- 5
-
-
- _BEAMER_FIRST_VISIT_{'{productID}'}
-
-
- app.safe.global
-
-
- Stores the date of this user’s first visit to the site.
-
-
- 300 days
-
-
- User activity won't be tracked
-
-
-
-
- 6
-
-
- _BEAMER_USER_ID_{'{productID}'}
-
-
- app.safe.global
-
-
- Stores an internal ID for this user.
-
-
- 300 days
-
-
- User activity won't be tracked
-
-
-
-
-
Third-party cookies
-
The cookies from this table can be set by third-party wallets.
-
-
-
-
- #
-
-
- Name of cookie
-
-
- Domain
-
-
- Purpose(s) of cookie
-
-
- Storage period of cookie
-
-
- Consequences is cookie is not accepted
-
-
-
-
- 1
-
-
- _ga
-
-
- safe.global
-
-
- Used to distinguish users
-
-
- 2 years from set/update
-
-
- User activity won't be tracked
-
-
-
-
- 2
-
-
- _ga
-
-
- getbeamer.com
-
-
- Used to distinguish users
-
-
- 2 years from set/update
-
-
- User activity won't be tracked
-
-
-
-
- 3
-
-
- _gid
-
-
- getbeamer.com
-
-
- Used to distinguish users
-
-
- 24 hours
-
-
- User activity won't be tracked
-
-
-
-
- 4
-
-
- _BEAMER_USER_ID_{'{productID}'}
-
-
- getbeamer.com
-
-
- Stores an internal ID for this user.
-
-
- 300 days
-
-
- User activity won't be tracked
-
-
-
-
- 5
-
-
- JSESSIONID
-
-
- app.getbeamer.com
-
-
- Stores an internal ID for this user.
-
-
- Session
-
-
- User activity won't be tracked
-
-
-
-
-
-)
-
-const CookiePolicy: NextPage = () => {
+const CookiePolicy = () => {
return (
- <>
-
- {'Rootstock Safe โ Cookie policy'}
-
-
- {IS_OFFICIAL_HOST && }
- >
+
+
+ Cookie Policy
+
+ Last updated: January 2024.
+
+ For general web-browsing of this website, your personal data is not revealed to us, although certain statistical
+ information is available to us via our internet service provider. We exclusively process your personal data in
+ pseudonymised form.
+
+
+ This Cookie Policy applies to our website at{' '}
+
+ https://safe.rootstock.io
+ {' '}
+ and sets out some further detail on how and why we use these technologies on our website.
+
+
+ The terms "you" and "your" includes our clients, business partners and users of this
+ website.
+
+
+ By using our website, you consent to storage and access to cookies and other technologies on your device, in
+ accordance with this Cookie Policy.
+
+ What are cookies?
+
+ Cookies are a feature of web browser software that allows web servers to recognize the computer or device used
+ to access a website. A cookie is a small text file that a website saves on your computer or mobile device when
+ you visit the site. It enables the website to remember your actions and preferences (such as login, language,
+ font size and other display preferences) over a period of time, so you don't have to keep re-entering them
+ whenever you come back to the site or browse from one page to another.
+
+ What are the different types of cookies?
+ A cookie can be classified by its lifespan and the domain to which it belongs.
+ By lifespan, a cookie is either a:
+
+ session cookie which is erased when the user closes the browser; or
+
+ persistent cookie which is saved to the hard drive and remains on the user's computer/device for a
+ pre-defined period of time.
+
+
+ As for the domain to which it belongs, cookies are either:
+
+
+ first-party cookies which are set by the web server of the visited page and share the same domain (i.e. set by
+ us); or
+
+ third-party cookies stored by a different domain to the visited page's domain.
+
+ What cookies do we use and why?
+ We list all the cookies we use on this website in the APPENDIX below.
+
+ We do not use cookies set by ourselves via our web developers (first-party cookies). We only have those set by
+ others (third-party cookies).
+
+
+ Cookies are also sometimes classified by reference to their purpose. We use the following cookies for the
+ following purposes:
+
+
+
+ Analytical/performance cookies: They allow us to recognize and count the number of visitors and to see how
+ visitors move around our website when they are using it, as well as dates and times they visit. This helps us
+ to improve the way our website works, for example, by ensuring that users are finding what they are looking
+ for easily.
+
+
+ Targeting cookies: These cookies record your visit to our website, the pages you have visited and the links
+ you have followed, as well as time spent on our website, and the websites visited just before and just after
+ our website. We will use this information to make our website and the advertising displayed on it more
+ relevant to your interests. We may also share this information with third parties for this purpose.
+
+
+
+ In general, we use cookies and other technologies (such as web server logs) on our website to enhance your
+ experience and to collect information about how our website is used.
+
+ Other Technologies
+
+ We may allow others to provide analytics services and serve advertisements on our behalf. In addition to the
+ uses of cookies described above, these entities may use other methods, such as the technologies described below,
+ to collect information about your use of our website and other websites and online services.
+
+
+
+ Pixels tags. Pixel tags (which are also called clear GIFs, web beacons, or pixels), are small pieces of code
+ that can be embedded on websites and emails. Pixels tags may be used to learn how you interact with our
+ website pages and emails, and this information helps us, and our partners provide you with a more tailored
+ experience.
+
+
+ Device Identifiers. A device identifier is a unique label that can be used to identify a mobile device. Device
+ identifiers may be used to track, analyze and improve the performance of the website and ads delivered.
+
+
+ What data is collected by cookies and other technologies on our website?
+ This information may include:
+
+
+ the IP and logical address of the server you are using (but the last digits are anonymized so we cannot
+ identify you).
+
+ the top level domain name from which you access the internet (for example .ie, .com, etc)
+ the type of browser you are using,
+ the date and time you access our website
+ the internet address linking to our website.
+
+ This website also uses cookies to:
+
+ remember you and your actions while navigating between pages;
+ remember if you have agreed (or not) to our use of cookies on our website;
+ ensure the security of the website;
+ monitor and improve the performance of servers hosting the site;
+ distinguish users and sessions;
+ Improving the speed of the site when you access content repeatedly;
+ determine new sessions and visits;
+ allow us to store any customization preferences where our website allows this
+
+
+ We may also use other services, such as{' '}
+
+ Google Analytics
+ {' '}
+ (described below) or other third-party cookies, to analyzing performance on our website. As part of providing
+ these services, these service providers may use cookies and the technologies described below to collect and
+ store information about your device, such as time of visit, pages visited, time spent on each page of our
+ website, links clicked and conversion information, IP address, browser, mobile network information, and type of
+ operating system used.
+
+ Revisions to this Cookie Policy
+
+ On this website, you can always view the latest version of our Privacy Policy and our Cookie Policy. We may
+ modify this Cookie Policy from time to time. If we make changes to this Cookie Policy, we will provide notice of
+ such changes, such as by sending an email notification, providing notice through our website or updating the
+ 'Last Updated' date at the beginning of this Cookie Policy. The amended Cookie Policy will be
+ effective immediately after the date it is posted. By continuing to access or use our website after the
+ effective date, you confirm your acceptance of the revised Cookie Policy and all of the terms incorporated
+ therein by reference. We encourage you to review our Privacy Policy and our Cookie Policy whenever you access or
+ use our website to stay informed about our information practices and the choices available to you.
+
+
+ If you do not accept changes which are made to this Cookie Policy, or take any measures described above to
+ opt-out by removing or rejecting cookies, you may continue to use this website but accept that it may not
+ display and/or function as intended by us. Any social media channels connected to us and third party
+ applications will be subject to the privacy and cookie policies and practices of the relevant platform providers
+ which, unless otherwise indicated, are not affiliated or associated with us Your exercise of any rights to
+ opt-out may also impact how our information and content is displayed and/or accessible to you on this website
+ and on other websites.
+
+
)
}
diff --git a/src/pages/imprint.tsx b/src/pages/imprint.tsx
deleted file mode 100644
index 9dca94426..000000000
--- a/src/pages/imprint.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import type { NextPage } from 'next'
-import Head from 'next/head'
-import { IS_OFFICIAL_HOST } from '@/config/constants'
-import { Typography } from '@mui/material'
-import Link from 'next/link'
-import MUILink from '@mui/material/Link'
-
-const SafeImprint = () => (
-
-
- Imprint & Disclaimer
-
-
- Information in accordance with section 5 of the Telemedia Act (TMG, Germany):
-
-
- Core Contributors GmbH
-
- Gontardstraรe 11
-
- 10178 Berlin, Germany
-
-
- Managing directors: Richard Meiรner, Tobias Schubotz
-
- Contact:{' '}
-
- info@cc0x.dev
-
-
- District Court: Berlin Charlottenburg
-
- Register Number: HRB 240421 B
-
-
- Disclaimer
-
-
- Accountability for content
-
-
- The contents of our pages have been created with the utmost care. However, we cannot guarantee the contentsโ
- accuracy, completeness or topicality. According to statutory provisions, we are furthermore responsible for our
- own content on these web pages. In this context, please note that we are accordingly not obliged to monitor merely
- the transmitted or saved information of third parties, or investigate circumstances pointing to illegal activity.
- Our obligations to remove or block the use of information under generally applicable laws remain unaffected by
- this as per ยงยง 8 to 10 of the Telemedia Act (TMG).
-
-
- Accountability for links
-
-
- Responsibility for the content of external links (to web pages of third parties) lies solely with the operators of
- the linked pages. No violations were evident to us at the time of linking. Should any legal infringement become
- known to us, we will remove the respective link immediately.
-
-
- Copyright
-
-
- This website and their contents are subject to copyright laws.{' '}
-
-
- The code is open-source, released under GPL-3.0.
-
-
-
-
-)
-
-const Imprint: NextPage = () => {
- return (
- <>
-
- {'Rootstock Safe โ Imprint'}
-
-
- {IS_OFFICIAL_HOST && }
- >
- )
-}
-
-export default Imprint
diff --git a/src/pages/licenses.tsx b/src/pages/licenses.tsx
deleted file mode 100644
index f6180e2cb..000000000
--- a/src/pages/licenses.tsx
+++ /dev/null
@@ -1,720 +0,0 @@
-import type { NextPage } from 'next'
-import Head from 'next/head'
-import { IS_OFFICIAL_HOST } from '@/config/constants'
-import { Typography, Table, TableBody, TableRow, TableCell, TableHead, TableContainer, Box } from '@mui/material'
-import ExternalLink from '@/components/common/ExternalLink'
-import Paper from '@mui/material/Paper'
-
-const SafeLicenses = () => (
- <>
-
- Licenses
-
-
- Libraries we use
-
-
-
- This page contains a list of attribution notices for third party software that may be contained in portions of
- the {'Rootstock Safe'}. We thank the open source community for all of their contributions.
-
-
- Android
-
-
-
-
-
-
- Library
-
-
- License
-
-
-
-
-
- AndroidX
-
-
- https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/LICENSE.txt
-
-
-
-
- Bivrost for Kotlin
-
-
- https://github.com/gnosis/bivrost-kotlin/blob/master/LICENSE
-
-
-
-
- Dagger
-
-
- {' '}
- https://github.com/google/dagger#license{' '}
-
-
-
-
- FloatingActionButton
-
-
- https://github.com/Clans/FloatingActionButton/blob/master/LICENSE
-
-
-
-
- Material Progress Bar
-
-
- https://github.com/DreaminginCodeZH/MaterialProgressBar/blob/master/LICENSE
-
-
-
-
- Kethereum
-
-
- https://github.com/walleth/kethereum/blob/master/LICENSE
-
-
-
-
- Koptional
-
-
- {' '}
- https://github.com/gojuno/koptional#license{' '}
-
-
-
-
- Moshi
-
-
- {' '}
- https://github.com/square/moshi#license{' '}
-
-
-
-
- OkHttp
-
-
- {' '}
- https://github.com/square/okhttp#license{' '}
-
-
-
-
- Okio
-
-
- {' '}
- https://github.com/square/okio#license{' '}
-
-
-
-
- Phrase
-
-
- {' '}
- https://github.com/square/phrase/#license{' '}
-
-
-
-
- Picasso
-
-
- {' '}
- https://github.com/square/picasso#license{' '}
-
-
-
-
- ReTrofit
-
-
- {' '}
- https://github.com/square/reTrofit#license{' '}
-
-
-
-
- RxAndroid
-
-
- https://github.com/ReactiveX/RxAndroid#license
-
-
-
-
- RxBinding
-
-
- https://github.com/JakeWharton/RxBinding#license
-
-
-
-
- RxJava
-
-
- {' '}
- https://github.com/ReactiveX/RxJava#license{' '}
-
-
-
-
- RxKotlin
-
-
- https://github.com/ReactiveX/RxKotlin/blob/2.x/LICENSE
-
-
-
-
- SpongyCastle
-
-
- https://github.com/rtyley/spongycastle/blob/spongy-master/LICENSE.html
-
-
-
-
- Svalinn Android
-
-
- https://github.com/gnosis/svalinn-kotlin/blob/master/LICENSE
-
-
-
-
- Timber
-
-
- https://github.com/JakeWharton/timber#license
-
-
-
-
- Zxing
-
-
- https://github.com/zxing/zxing/blob/master/LICENSE
-
-
-
-
-
-
-
-
-
- iOS
-
-
-
-
-
-
- Library
-
-
- License
-
-
-
-
-
- BigInt
-
-
- https://github.com/attaswift/BigInt/blob/master/LICENSE.md
-
-
-
-
- BlockiesSwift
-
-
- https://github.com/gnosis/BlockiesSwift/blob/master/LICENSE
-
-
-
-
- CryptoEthereumSwift
-
-
- https://github.com/yuzushioh/CryptoEthereumSwift/blob/master/LICENSE
-
-
-
-
- CryptoSwift
-
-
- https://github.com/krzyzanowskim/CryptoSwift#license
-
-
-
-
- DateTools
-
-
- {' '}
- https://github.com/gnosis/DateTools#license{' '}
-
-
-
-
- EthereumKit
-
-
- https://github.com/D-Technologies/EthereumKit#license
-
-
-
-
- Keycard.swift
-
-
- https://github.com/gnosis/Keycard.swift/blob/master/LICENSE
-
-
-
-
- Kingfisher
-
-
- https://github.com/onevcat/Kingfisher#license
-
-
-
-
- SipHash
-
-
- https://github.com/attaswift/SipHash/blob/master/LICENSE.md
-
-
-
-
- Starscream
-
-
- https://github.com/daltoniam/Starscream/blob/master/LICENSE
-
-
-
-
- RsBarcodesSwift
-
-
- https://github.com/yeahdongcn/RSBarcodes_Swift#license
-
-
-
-
- libidn2
-
-
- https://github.com/gnosis/libidn2/blob/master/COPYING.LESSERv3
-
-
-
-
- libunisTring
-
-
- https://github.com/gnosis/libunisTring/blob/master/COPYING.LIB
-
-
-
-
-
-
-
-
-
- Web
-
-
-
-
-
- Library
- License
-
-
-
-
- @emotion/cache
-
-
- https://github.com/emotion-js/emotion/blob/main/LICENSE
-
-
-
-
- @emotion/react
-
-
- https://github.com/emotion-js/emotion/blob/main/LICENSE
-
-
-
-
- @emotion/server
-
-
- https://github.com/emotion-js/emotion/blob/main/LICENSE
-
-
-
-
- @emotion/styled
-
-
- https://github.com/emotion-js/emotion/blob/main/LICENSE
-
-
-
-
- @safe-global/safe-modules-deployments
-
-
- https://github.com/safe-global/safe-modules-deployments/blob/main/LICENSE
-
-
-
-
- @mui/icons-material
-
-
- https://github.com/mui/material-ui/blob/master/LICENSE
-
-
-
-
- @mui/material
-
-
- https://github.com/mui/material-ui/blob/master/LICENSE
-
-
-
-
- @mui/x-date-pickers
-
-
- https://github.com/mui/mui-x#mit-vs-commercial-licenses
-
-
-
-
- @reduxjs/toolkit
-
-
- https://github.com/reduxjs/redux-toolkit/blob/master/LICENSE
-
-
-
-
- @safe-global/safe-apps-sdk
-
-
- https://github.com/safe-global/safe-apps-sdk/blob/main/LICENSE.md
-
-
-
-
- @safe-global/safe-core-sdk
-
-
- https://github.com/safe-global/safe-core-sdk/blob/main/LICENSE.md
-
-
-
-
- @safe-global/safe-deployments
-
-
- https://github.com/safe-global/safe-deployments/blob/main/LICENSE
-
-
-
-
- @safe-global/safe-gateway-typescript-sdk
-
-
- https://github.com/safe-global/safe-gateway-typescript-sdk/blob/main/LICENSE.md
-
-
-
-
- @safe-global/safe-react-components
-
-
- https://github.com/safe-global/safe-react-components/blob/main/LICENSE.md
-
-
-
-
- @sentry/react
-
-
- https://github.com/getsentry/sentry-javascript/blob/develop/LICENSE
-
-
-
-
- @sentry/tracing
-
-
- https://github.com/getsentry/sentry-javascript/blob/develop/LICENSE
-
-
-
-
- @web3-onboard/coinbase
-
-
- https://github.com/blocknative/web3-onboard/blob/main/LICENSE
-
-
-
-
- @web3-onboard/core
-
-
- https://github.com/blocknative/web3-onboard/blob/main/LICENSE
-
-
-
-
- @web3-onboard/injected-wallets
-
-
- https://github.com/blocknative/web3-onboard/blob/main/LICENSE
-
-
-
-
- @web3-onboard/keystone
-
-
- https://github.com/blocknative/web3-onboard/blob/main/LICENSE
-
-
-
-
- @web3-onboard/ledger
-
-
- https://github.com/blocknative/web3-onboard/blob/main/LICENSE
-
-
-
-
- @web3-onboard/trezor
-
-
- https://github.com/blocknative/web3-onboard/blob/main/LICENSE
-
-
-
-
- @web3-onboard/walletconnect
-
-
- https://github.com/blocknative/web3-onboard/blob/main/LICENSE
-
-
-
-
- classnames
-
-
- https://github.com/JedWatson/classnames/blob/main/LICENSE
-
-
-
-
- date-fns
-
-
- https://github.com/date-fns/date-fns/blob/main/LICENSE.md
-
-
-
-
- ethereum-blockies-base64
-
-
- https://github.com/MyCryptoHQ/ethereum-blockies-base64
-
-
-
-
- ethers
-
-
- https://github.com/ethers-io/ethers.js/blob/main/LICENSE.md
-
-
-
-
- exponential-backoff
-
-
- https://github.com/coveo/exponential-backoff/blob/master/LICENSE
-
-
-
-
- fuse.js
-
-
- https://github.com/krisk/Fuse/blob/master/LICENSE
-
-
-
-
- js-cookie
-
-
- https://github.com/js-cookie/js-cookie/blob/main/LICENSE
-
-
-
-
- lodash
-
-
- https://github.com/lodash/lodash/blob/master/LICENSE
-
-
-
-
- next
-
-
- https://github.com/vercel/next.js/blob/canary/LICENSE
-
-
-
-
- next-pwa
-
-
- https://github.com/shadowwalker/next-pwa/blob/master/LICENSE
-
-
-
-
- papaparse
-
-
- https://github.com/mholt/PapaParse/blob/master/LICENSE
-
-
-
-
- qrcode.react
-
-
- https://github.com/zpao/qrcode.react/blob/main/LICENSE
-
-
-
-
- react
-
-
- https://github.com/facebook/react/blob/main/LICENSE
-
-
-
-
- react-dom
-
-
- https://github.com/facebook/react/blob/main/LICENSE
-
-
-
-
- react-dropzone
-
-
- https://github.com/react-dropzone/react-dropzone/blob/master/LICENSE
-
-
-
-
- react-gtm-module
-
-
- https://github.com/alinemorelli/react-gtm/blob/master/LICENSE
-
-
-
-
- react-hook-form
-
-
- https://github.com/react-hook-form/react-hook-form/blob/master/LICENSE
-
-
-
-
- react-papaparse
-
-
- https://github.com/Bunlong/react-papaparse/blob/master/LICENSE
-
-
-
-
- react-redux
-
-
- https://github.com/reduxjs/react-redux/blob/master/LICENSE
-
-
-
-
- semver
-
-
- https://github.com/npm/node-semver/blob/main/LICENSE
-
-
-
-
-
-
-
- >
-)
-
-const Licenses: NextPage = () => {
- return (
- <>
-
- {'Rootstock Safe โ Licenses'}
-
-
- {IS_OFFICIAL_HOST && }
- >
- )
-}
-
-export default Licenses
diff --git a/src/pages/new-safe/advanced-create.tsx b/src/pages/new-safe/advanced-create.tsx
new file mode 100644
index 000000000..a3a1b54d1
--- /dev/null
+++ b/src/pages/new-safe/advanced-create.tsx
@@ -0,0 +1,18 @@
+import Head from 'next/head'
+import type { NextPage } from 'next'
+
+import AdvancedCreateSafe from '@/components/new-safe/create/AdvancedCreateSafe'
+
+const Open: NextPage = () => {
+ return (
+
+
+ {'Safe{Wallet} โ Advanced Safe creation'}
+
+
+
+
+ )
+}
+
+export default Open
diff --git a/src/pages/privacy.tsx b/src/pages/privacy.tsx
deleted file mode 100644
index 57eb8559d..000000000
--- a/src/pages/privacy.tsx
+++ /dev/null
@@ -1,1015 +0,0 @@
-import type { MouseEventHandler, ReactNode } from 'react'
-import type { NextPage } from 'next'
-import Head from 'next/head'
-import { IS_OFFICIAL_HOST } from '@/config/constants'
-
-const SmoothScroll = ({ children }: { children: ReactNode }) => {
- const onClick: MouseEventHandler = (e) => {
- const anchor = (e.target as HTMLAnchorElement).getAttribute('href')
- if (anchor?.startsWith('#')) {
- e.preventDefault()
- const element = document.querySelector(anchor)
- if (element) {
- element.scrollIntoView({ behavior: 'smooth' })
- }
- }
- }
-
- return {children}
-}
-
-const SafePrivacyPolicy = () => (
-
-
-
- Privacy Policy
- Last updated: June 2024.
-
- Your privacy is important to us. It is our policy to respect your privacy and comply with any applicable law and
- regulation regarding any personal information we may collect about you, including across our website,{' '}
- https://app.safe.global
- , and other sites we own and operate as well as mobile applications we offer. Wherever possible, we have
- designed our website so that you may navigate and use our website without having to provide Personal Data.
-
-
- This Privacy Policy describes how we, as a controller, collect, use and share your personal data. It applies to
- personal data you voluntarily provide to us, or is automatically collected by us.{' '}
-
-
- In this policy, "we", "us" and "our" refers to Core Contributors GmbH a company
- incorporated in Germany with its registered address at Gontardstraรe 11, 10178 Berlin, Germany. Any data
- protection related questions you might have about how we handle your personal data or if you wish to exercise your
- data subject rights, please contact us by post or at privacy@cc0x.dev.{' '}
-
-
- In this Policy, “personal data” means any information relating to you as an identified or identifiable
- natural person (“Data Subject”); an identifiable natural person is one who can be identified, directly
- or indirectly, in particular by reference to an identifier such as a name, an online identifier or to one or more
- factors specific to your physical, physiological, genetic, mental, economic, cultural or social identity.
-
-
- In this Policy, “processing” means any operation or set of operations which is performed on personal
- data (as defined in this Privacy Policy) or on sets of personal data, whether or not by automated means, such as
- collection, recording, organization, structuring, storage, adaptation or alteration, retrieval, consultation, use,
- disclosure by transmission, dissemination or otherwise making available, alignment or combination, restriction,
- erasure or destruction.
-
- 1. Navigating this Policy
- If you are viewing this policy online, you can click on the below links to jump to the relevant section:
-
-
- Glossary
-
-
- Your information and the Blockchain
-
-
- How We Use Personal Data
-
-
- Use of Third Party Applications
-
-
- Sharing Your Personal Data
-
-
- Transferring Your data outside of the EU
-
-
- Existence of Automated Decision-making
-
-
- Data Security
-
-
- Your Rights as a Data Subject
-
-
- Storing Personal Data
-
-
- Childrenโs data
-
-
- Changes to this Privacy Policy
-
-
- Contacts us
-
-
- 2. Glossary
- What do some of the capitalized terms mean in this policy?
-
-
- “Blockchain” means a mathematically secured consensus ledger such as the Ethereum Virtual Machine,
- an Ethereum Virtual Machine compatible validation mechanism, or other decentralized validation mechanisms.
-
-
- “Transaction” means a change to the data set through a new entry in the continuous Blockchain.
-
-
- “Smart Contract” is a piece of source code deployed as an application on the Blockchain which can be
- executed, including self-execution of Transactions as well as execution triggered by 3rd parties.
-
-
- “Token” is a digital asset transferred in a Transaction, including ETH, ERC20, ERC721 and ERC1155
- tokens.
-
-
- “Wallet” is a cryptographic storage solution permitting you to store cryptographic assets by
- correlation of a (i) Public Key and (ii) a Private Key or a Smart Contract to receive, manage and send Tokens.
-
-
- “Recovery Phrase” is a series of secret words used to generate one or more Private Keys and derived
- Public Keys.
-
-
- “Public Key” is a unique sequence of numbers and letters within the Blockchain to distinguish the
- network participants from each other.
-
-
- “Private Key” is a unique sequence of numbers and/or letters required to initiate a Blockchain
- Transaction and should only be known by the legal owner of the Wallet.
-
-
- “Safe Account” is a modular, self-custodial (i.e. not supervised by us) smart contract-based
- multi-signature Wallet. Safe Accounts are{' '}
-
- open-source
-
- released under LGPL-3.0.
-
-
- “{'Rootstock Safe'}” refers to a web-based graphical user interface for Safe Accounts as well as a
- mobile application on Android and iOS.
-
-
- “Safe Account Transaction” is a Transaction of a Safe Account, authorized by a user, typically via
- their Wallet.{' '}
-
-
- “Profile” means the Public Key and user provided, human readable label stored locally on the
- user's device.
-
-
- 3. Your information and the Blockchain
-
- Blockchains, also known as distributed ledger technology (or simply ‘DLT’), are made up of digitally
- recorded data in a chain of packages called ‘blocks’. The manner in which these blocks are linked is
- chronological, meaning that the data is very difficult to alter once recorded. Since the ledger may be distributed
- all over the world (across several ‘nodes’ which usually replicate the ledger) this means there is no
- single person making decisions or otherwise administering the system (such as an operator of a cloud computing
- system), and that there is no centralized place where it is located either.
-
-
- Accordingly, by design, records of a Blockchain cannot be changed or deleted and are said to be
- ‘immutable’. This may affect your ability to exercise your rights such as your right to erasure
- (‘right to be forgotten’), or your rights to object or restrict processing of your personal data. Data
- on the Blockchain cannot be erased and cannot be changed. Although smart contracts may be used to revoke certain
- access rights, and some content may be made invisible to others, it is not deleted.
-
-
- In certain circumstances, in order to comply with our contractual obligations to you (such as delivery of Tokens)
- it will be necessary to write certain personal data, such as your Wallet address, onto the Blockchain; this is
- done through a smart contract and requires you to execute such transactions using your Wallet’s Private Key.
-
-
- In most cases ultimate decisions to (i) transact on the Blockchain using your Wallet, as well as (ii) share the
- Public Key relating to your Wallet with anyone (including us) rests with you.
-
-
- IF YOU WANT TO ENSURE YOUR PRIVACY RIGHTS ARE NOT AFFECTED IN ANY WAY, YOU SHOULD NOT TRANSACT ON BLOCKCHAINS AS
- CERTAIN RIGHTS MAY NOT BE FULLY AVAILABLE OR EXERCISABLE BY YOU OR US DUE TO THE TECHNOLOGICAL INFRASTRUCTURE OF
- THE BLOCKCHAIN. IN PARTICULAR THE BLOCKCHAIN IS AVAILABLE TO THE PUBLIC AND ANY PERSONAL DATA SHARED ON THE
- BLOCKCHAIN WILL BECOME PUBLICLY AVAILABLE
-
- 4. How We Use Personal Data
- 4.1. When visiting our website and using {'Rootstock Safe'}
-
- When visiting our website or using {'Rootstock Safe'}, we may collect and process personal data. The data will be
- stored in different instances
-
-
-
- We connect the Wallet to the web app to identify the user via their public Wallet address. For this purpose
- we process:
-
- public Wallet address and
- WalletConnect connection data
-
-
-
- When you create a new Safe Account we process the following data to compose a Transaction based on your entered
- data to be approved by your Wallet:
-
- your public Wallet address,
- account balance,
- smart contract address of the Safe Account,
- addresses of externally owned accounts and
- user activity
-
-
-
-
-
- When you create a Profile for a new Safe Account we process the following data for the purpose of enabling you
- to view your Safe Account after creation as well as enabling you to view all co-owned Safe Accounts:
-
- your public Wallet address and
- account balance
-
-
-
-
-
- When you create a Profile for an existing Safe Account for the purpose of allowing you to view and use them in
- the {'Rootstock Safe'}, we process your
-
- public Wallet address,
- Safe Account balance,
- smart contract address of the Safe Account and
- Safe Account owner's public Wallet addresses
-
-
-
-
-
- When you initiate a Safe Account Transaction we process the following data to compose the Transaction for
- you based on your entered data:{' '}
-
- your public Wallet address and
- smart contract address of the Safe Account
-
-
-
-
-
- When you sign a Safe Account Transaction we process the following data to enable you to sign the
- Transaction using your Wallet:
-
- Safe Account balance,
- smart contract address of Safe Account and
- Safe Account owner's public Wallet addresses
-
-
-
-
-
- To enable you to execute The transaction on the Blockchain we process:
-
- your public Wallet address,
- Safe Account balance,
- smart contract address of the Safe Account,
- Safe Account owner's public Wallet addresses and
- Transactions signed by all Safe Account owners
-
-
-
-
-
- When we collect relevant data from the Blockchain to display context information in the
- {`Rootstock Safe`}
- we process:
-
- your public Wallet address,
- account balance,
- account activity and
- Safe Account owner's Public wallet addresses
-
-
-
-
-
- When we decode Transactions from the Blockchain for the purpose of providing Transaction information in a
- conveniently readable format, we process:
-
- your public Wallet address
- account balance and
- account activity
-
-
-
-
-
- When we maintain a user profile to provide you with a good user experience through Profiles and an address
- book we process:
-
- your public Wallet address,
- label,
- smart contract address of the Safe Account,
- Safe Account owner's public wallet addresses,
- last used Wallet (for automatic reconnect),
- last used chain id,
- selected currency,
- theme and
- address format
-
-
-
- The legal base for all these activities is the performance of the contract we have with you (GDPR Art.6.1b).
-
- THE DATA WILL BE STORED ON THE BLOCKCHAIN. GIVEN THE TECHNOLOGICAL DESIGN OF THE BLOCKCHAIN, AS EXPLAINED IN
- SECTION 2, THIS DATA WILL BECOME PUBLIC AND IT WILL NOT LIKELY BE POSSIBLE TO DELETE OR CHANGE THE DATA AT ANY
- GIVEN TIME.
-
- 4.2. Tracking & Analysis
- 4.2.1 We will process the following personal data to analyze your behavior:
-
- IP address (will not be stored for EU users),
- session tracking,
- user behavior,
- wallet type,
- Safe Account address,
- Signer wallet address,
- device and browser user agent,
- user consent,
- operating system,
- referrers,
- user behavior: subpage, duration, and revisit, the date and time of access,
-
-
- In the case you have given consent, we will additionally store an analytics cookie on your device to identify you
- as a user across browsing sessions. The lawful basis for this processing is your consent (GDPR Art.6.1a) when
- agreeing to accept cookies.
-
-
- The collected data is solely used in the legitimate interest of improving our product and user experience. The
- data is stored only temporarily and is deleted after 14 months.
-
-
- We do not track any of the following:
-
- Wallet signatures
- Granular transaction details
-
-
-
- 4.2.2 For general operational analysis of the {'Safe{Wallet}'} interface, monitoring transaction origins and
- measuring transaction failure rates to ensure improved service performance and reliability, we process information
- which constitutes the transaction service database, such as:
-
-
- signatures
- signature_type
- ethereum_tx_id
- message_hash
- safe_app_id
- safe_message_id
-
-
- We conduct this analysis in our legitimate interest to continuously improve our product and service and ensure
- increased service performance and reliability.
-
-
- 4.2.3 We conduct technical monitoring of your activity on the platform in order to ensure availability, integrity
- and robustness of the service. For this purpose we process your:
-
-
- IP addresses,
- meta and communication data,
- website access and
- log data
-
-
- The lawful basis for this processing is our legitimate interest (GDPR Art.6.1f) in ensuring the correctness of the
- service.
-
- 4.2.4 Anonymized tracking
-
- We will anonymize the following personal data to gather anonymous user statistics on your browsing behavior on our
- website:
-
- daily active users,
- new users acquired from a specific campaign,
- user journeys,
- number of users per country,
- difference in user behavior between mobile vs. web visitors.
-
-
-
- The lawful basis for this processing is our legitimate interest (GDPR Art.6.1f) in improving our product and user
- experience.
-
- 4.3. When Participating in User Experience Research (UXR)
-
- When you participate in our user experience research we may collect and process some personal data. This data may
- include:
-
-
- your name
- your email
- your phone type
- your occupation
- range of managed funds
-
-
- In addition, we may take a recording of you while testing {'Rootstock Safe'} for internal and external use. The
- basis for this collection and processing is our legitimate business interest in monitoring and improving our
- services.
-
-
- The lawful basis for this processing is your consent as provided before participating in user experience research.
-
- 4.4. Publishing the app
- 4.4.1 Publishing the app on Google Play Store.
- We process the following information to enable you to download the app on smartphones running Android:
-
- google account and
- e-mail address
-
- 4.4.2 Publishing the app on Apple App Store
- We process the following information to enable you to download the app on smartphones running iOS:
-
- apple account and
- e-mail address
-
-
- The lawful basis for these two processing activities is the performance of the contract we have with you (GDPR
- Art.6.1b).{' '}
-
- 4.5. Use of the app
- 4.5.1 We provide the app to you to enable you to use it. For this purpose we process your:
-
- mobile device information,
- http request caches and
- http request cookies
-
-
- 4.5.2 In order to update you about changes in the app, we need to send you push notifications. For this purpose we
- process your:
-
-
- Transactions executed and failed,
- assets sent,
- assets received
-
-
- 4.5.3 To provide support to you and notify you about outage resulting in unavailability of the service, we process
- your:
-
-
- pseudonymized user identifier
-
-
- 4.5.4 In order to provide remote client configuration and control whether to inform about, recommend or force you
- to update your app or enable/disable certain app features we process your:
-
-
- User agent,
- app information (version, build number etc.),
- language,
- Country,
- Platform
- operating system
- Browser
- Device category
- User audience
- User property
- User in random percentage
- Imported segment
- date/time
- first open
- installation ID
-
-
- For all these activities (4.5.1-4.54) we rely on the legal base of performance of a contract (GDPR Art.6.1b) with
- you.{' '}
-
- 4.5.5 Finally, to report errors and improve user experience we process your:
-
- User agent info (Browser, OS, device),
- URL that you were on (Can contain Safe Account address) and
- Error info: Time, stacktrace
-
- We rely on our legitimate interest (GDPR Art.6.1f) of ensuring product quality.
-
- 4.5.6 We process your personal data to allow you to authenticate using your gmail account or AppleID and to create
- a signer wallet/owner account . For that purpose following personal data is processed:
-
-
- Anonymised device information and identifiers, e.g. IP address, cookie IDs, device type
- User account authentication information (e.g. username, password)
-
- Unique user identifier (e.g. a random string associated with authentication, at times can be email. If so,
- sensitive strings are processed but hashed and not stored)
-
- Connection and usage Information (e.g. logins to the application)
-
-
- For this processing, we rely on our legitimate interest (GDPR Art.6.1f) of facilitating the onboarding for users
- and ameliorating the user experience with regards to our product.
-
-
- 4.5.7 Providing on and off-ramp services to enable you to top up your Safe Account with e.g. bank transfer, debit
- card, credit card. For this purpose MoonPay may process your:
-
-
- full name
- date of birth
- nationality
- gender
- signature
- utility bills
- photographs
- phone number
- home address
- email
-
- information about the transactions you make via MoonPay services (e.g. name of the recipient, your name, the
- amount, and/or timestamp)
-
- geo location/tracking details
- operating system
- personal IP address
-
-
- To conduct this activity we rely on our legitimate interest (GDPR Art.6.1f) of ameliorating the onboarding process
- and the user experience through providing an easier option to customers to fund their account.
-
- 4.6 Other uses of your Personal Data
-
- We may process any of your Personal Data where it is necessary to establish, exercise, or defend legal claims. The
- legal basis for this is our legitimate interests, namely the protection and assertion of our legal rights, your
- legal rights and the legal rights of others.
-
-
- Further, we may process your Personal data where such processing is necessary in order for us to comply with a
- legal obligation to which we are subject. The legal basis for this processing is our legitimate interests, namely
- the protection and assertion of our legal rights.
-
- 5. Use of Third Party Applications
- 5.1. Blockchain
-
- When using Safe Accounts your smart contract address, Safe Account Transactions, addresses of signer accounts and
- ETH balances and token balances will be stored on the Blockchain. See section 2 of this Policy
-
-
- THE INFORMATION WILL BE DISPLAYED PERMANENTLY AND PUBLIC, THIS IS PART OF THE NATURE OF THE BLOCKCHAIN. IF YOU ARE
- NEW TO THIS FIELD, WE HIGHLY RECOMMEND INFORMING YOURSELF ABOUT THE BLOCKCHAIN TECHNOLOGY BEFORE USING OUR
- SERVICES.
-
- 5.2. Amazon Web Services
-
- We use{' '}
-
- Amazon Web Services (AWS)
-
- to store log and database data as described in section 4.1.
-
- 5.3. Datadog
-
- We use{' '}
-
- Datadog
-
- to store log data as described in section 4.1.
-
- 5.4. Mobile app stores
-
- {'Rootstock Safe'} mobile apps are distributed via{' '}
-
- Apple AppStore
-
- and{' '}
-
- Google Play Store
-
- . They most likely track user behavior when downloading apps from their stores as well as when using apps. We only
- have very limited access to that data. We can view aggregated statistics on installs and uninstalls. Grouping by
- device type, app version, language, carrier and country is possible.
-
- 5.5. Fingerprint/Touch ID/ Face ID
-
- We enable the user to unlock the {'Rootstock Safe'} mobile app via biometrics information (touch ID or face ID).
- This is a feature of the operating system. We do not store any of this data. Instead, the API of the operating
- system is used to validate the user input. If you have any further questions you should consult with your
- preferred mobile device provider or manufacturer.
-
- 5.6. Google Firebase
-
- We use the following{' '}
-
- Google Firebase
-
- services:
-
-
-
- Firebase Cloud Messaging: Provide updates to the user about changes in the mobile apps via push notifications.
-
-
- Firebase remote config: Inform users about, recommend or force user to update their mobile app or
- enabling/disabling certain app features. These settings are global for all users, no personalization is
- happening.
-
- Firebase crash reporting: Report errors and crashes to improve product and user experience.
-
- 5.7. WalletConnect
-
-
- WalletConnect
-
- is used to connect wallets to dapps using end-to-end encryption by scanning a QR code. We do not store any
- information collected by WalletConnect.{' '}
-
- 5.8. Sentry
-
- We use{' '}
-
- Sentry
-
- to collect error reports and crashes to improve product and user experience.{' '}
-
- 5.9. Beamer
-
- We use{' '}
-
- Beamer
-
- providing updates to the user about changes in the app. Beamer's purpose and function are further
- explained under the following link{' '}
-
- https://www.getbeamer.com/showcase/notification-center
-
- .
-
- We do not store any information collected by Beamer.
- 5.10. Node providers
-
- We use{' '}
-
- Infura
-
- and{' '}
-
- Nodereal
-
- to query public blockchain data from our backend services. All Safe Accounts are monitored, no
- personalization is happening and no user IP addresses are forwarded. Personal data processed are:
-
-
- Your smart contract address of the Safe;
- Transaction id/hash
- Transaction data
-
- 5.11. Tenderly
-
- We use{' '}
-
- Tenderly
-
- to simulate blockchain transactions before they are executed. For that we send your smart contract address
- of your Safe Account and transaction data to Tenderly.
-
- 5.12. Internal communication
- We use the following tools for internal communication.
-
- 5.13. MoonPay
-
- We use{' '}
-
- MoonPay
- {' '}
- to offer on-ramp and off-ramp services. For that purpose personal data is required for KYC/AML or other financial
- regulatory requirements. This data is encrypted by MoonPay.
-
- 5.14. Spindl
-
- We use{' '}
-
- Spindl
-
- , a measurement and attribution solution for web3 that assists us in comprehending how users interact with
- different decentralized applications and our app and to enhance your experience with {`Rootstock Safe`}. For
- enhanced privacy, data is stored for a period of 7 days after which it is securely deleted.
-
- 6. Sharing Your Personal Data
-
- We may pass your information to our Business Partners, administration centers, third party service providers,
- agents, subcontractors and other associated organizations for the purposes of completing tasks and providing our
- services to you.
-
-
- In addition, when we use any other third-party service providers, we will disclose only the personal information
- that is necessary to deliver the service required and we will ensure that they keep your information secure and
- not use it for their own direct marketing purposes. In addition, we may transfer your personal information to a
- third party as part of a sale of some, or all, of our business and assets or as part of any business restructuring
- or reorganization, or if we are under a duty to disclose or share your personal data in order to comply with any
- legal obligation. However, we will take steps to ensure that your privacy rights continue to be protected.
-
- 7. Transferring Your data outside of the EU
-
- Wherever possible we will choose service providers based in the EU. For those outside the EU, wherever possible we
- will configure data to be inside the EU. We concluded the new version of the Standard Contractual Clauses with
- these service providers (2021/914).
-
- Service providers in the US:
-
- Amazon Web Service Inc.
- Google LLC
- Data Dog Inc.
- Slack Technologies LLC
- Joincube Inc. (Beamer)
- Functional software Inc. (Sentry)
- Notion Labs Inc.
- ConsenSys Software Inc.
-
- Service providers in other countries outside of the EU:
-
- Tenderly d.o.o. is based in Serbia.
- Node Real PTE Ltd. is based in Singapore.
- Torus Labs PTE. Ltd. is based in Singapore.
- Eighteenth September Limited (โMoonPayโ) in the Seychelles.
-
-
- HOWEVER, WHEN INTERACTING WITH THE BLOCKCHAIN, AS EXPLAINED ABOVE IN THIS POLICY, THE BLOCKCHAIN IS A GLOBAL
- DECENTRALIZED PUBLIC NETWORK AND ACCORDINGLY ANY PERSONAL DATA WRITTEN ONTO THE BLOCKCHAIN MAY BE TRANSFERRED AND
- STORED ACROSS THE GLOBE.
-
- 8. Existence of Automated Decision-making
- We do not use automatic decision-making or profiling when processing Personal Data.
- 9. Data Security
-
- We have put in place appropriate security measures to prevent your personal data from being accidentally lost,
- used or accessed in an unauthorized way, altered or disclosed. In addition, we limit access to your personal data
- to those employees, agents, contractors and other third parties who have a business need to know. They will only
- process your personal data on our instructions and they are subject to a duty of confidentiality.
-
-
- We have put in place procedures to deal with any suspected personal data breach and will notify you and any
- applicable regulator of a breach where we are legally required to do so.
-
- 10. Your Rights as a Data Subject
-
- You have certain rights under applicable legislation, and in particular under Regulation EU 2016/679 (General Data
- Protection Regulation or ‘GDPR’). We explain these below. You can find out more about the GDPR and
- your rights by accessing the{' '}
-
- European Commission’s website
-
- . If you wish to exercise your data subject rights, please contact us by post or at privacy@cc0x.dev.
-
- Right Information and access
-
- You have a right to be informed about the processing of your personal data (and if you did not give it to us,
- information as to the source) and this Privacy Policy intends to provide the information. Of course, if you have
- any further questions you can contact us on the above details.
-
- Right to rectification
-
- You have the right to have any inaccurate personal information about you rectified and to have any incomplete
- personal information about you completed. You may also request that we restrict the processing of that
- information. The accuracy of your information is important to us. If you do not want us to use your Personal
- Information in the manner set out in this Privacy Policy, or need to advise us of any changes to your personal
- information, or would like any more information about the way in which we collect and use your Personal
- Information, please contact us at the above details.
-
- Right to erasure (right to be ‘forgotten’)
-
- You have the general right to request the erasure of your personal information in the following circumstances:
-
-
- the personal information is no longer necessary for the purpose for which it was collected;
-
- you withdraw your consent to consent based processing and no other legal justification for processing applies;
-
- you object to processing for direct marketing purposes;
- we unlawfully processed your personal information; and
- erasure is required to comply with a legal obligation that applies to us.
-
-
- HOWEVER, WHEN INTERACTING WITH THE BLOCKCHAIN WE MAY NOT BE ABLE TO ENSURE THAT YOUR PERSONAL DATA IS DELETED.
- THIS IS BECAUSE THE BLOCKCHAIN IS A PUBLIC DECENTRALIZED NETWORK AND BLOCKCHAIN TECHNOLOGY DOES NOT GENERALLY
- ALLOW FOR DATA TO BE DELETED AND YOUR RIGHT TO ERASURE MAY NOT BE ABLE TO BE FULLY ENFORCED. IN THESE
- CIRCUMSTANCES WE WILL ONLY BE ABLE TO ENSURE THAT ALL PERSONAL DATA THAT IS HELD BY US IS PERMANENTLY DELETED.
-
- We will proceed to comply with an erasure request without delay unless continued retention is necessary for:
-
- Exercising the right of freedom of expression and information;
- Complying with a legal obligation under EU or other applicable law;
- The performance of a task carried out in the public interest;
-
- Archiving purposes in the public interest, scientific or historical research purposes, or statistical purposes,
- under certain circumstances; and/or
-
- The establishment, exercise, or defense of legal claims.
-
- Right to restrict processing and right to object to processing
- You have a right to restrict processing of your personal information, such as where:
-
- you contest the accuracy of the personal information;
-
- where processing is unlawful you may request, instead of requesting erasure, that we restrict the use of the
- unlawfully processed personal information;
-
-
- we no longer need to process your personal information but need to retain your information for the
- establishment, exercise, or defense of legal claims.
-
-
-
- You also have the right to object to processing of your personal information under certain circumstances, such as
- where the processing is based on your consent and you withdraw that consent. This may impact the services we can
- provide and we will explain this to you if you decide to exercise this right.
-
-
- HOWEVER, WHEN INTERACTING WITH THE BLOCKCHAIN, AS IT IS A PUBLIC DECENTRALIZED NETWORK, WE WILL LIKELY NOT BE ABLE
- TO PREVENT EXTERNAL PARTIES FROM PROCESSING ANY PERSONAL DATA WHICH HAS BEEN WRITTEN ONTO THE BLOCKCHAIN. IN THESE
- CIRCUMSTANCES WE WILL USE OUR REASONABLE ENDEAVORS TO ENSURE THAT ALL PROCESSING OF PERSONAL DATA HELD BY US IS
- RESTRICTED, NOTWITHSTANDING THIS, YOUR RIGHT TO RESTRICT TO PROCESSING MAY NOT BE ABLE TO BE FULLY ENFORCED.
-
- Right to data portability
-
- Where the legal basis for our processing is your consent or the processing is necessary for the performance of a
- contract to which you are party or in order to take steps at your request prior to entering into a contract, you
- have a right to receive the personal information you provided to us in a structured, commonly used and
- machine-readable format, or ask us to send it to another person.
-
- Right to freedom from automated decision-making
-
- As explained above, we do not use automated decision-making, but where any automated decision-making takes place,
- you have the right in this case to express your point of view and to contest the decision, as well as request that
- decisions based on automated processing concerning you or significantly affecting you and based on your personal
- data are made by natural persons, not only by computers.
-
- Right to object to direct marketing (‘opting out’)
-
- You have a choice about whether or not you wish to receive information from us. We will not contact you for
- marketing purposes unless:
-
-
-
- you have a business relationship with us, and we rely on our legitimate interests as the lawful basis for
- processing (as described above)
-
- you have otherwise given your prior consent (such as when you download one of our guides)
-
-
- You can change your marketing preferences at any time by contacting us on the above details. On each and every
- marketing communication, we will always provide the option for you to exercise your right to object to the
- processing of your personal data for marketing purposes (known as ‘opting-out’) by clicking on the
- ‘unsubscribe’ button on our marketing emails or choosing a similar opt-out option on any forms we use
- to collect your data. You may also opt-out at any time by contacting us on the below details.
-
-
- Please note that any administrative or service-related communications (to offer our services, or notify you of an
- update to this Privacy Policy or applicable terms of business, etc.) will solely be directed at our clients or
- business partners, and such communications generally do not offer an option to unsubscribe as they are necessary
- to provide the services requested. Therefore, please be aware that your ability to opt-out from receiving
- marketing and promotional materials does not change our right to contact you regarding your use of our website or
- as part of a contractual relationship we may have with you.
-
- Right to request access
-
- You also have a right to access information we hold about you. We are happy to provide you with details of your
- Personal Information that we hold or process. To protect your personal information, we follow set storage and
- disclosure procedures, which mean that we will require proof of identity from you prior to disclosing such
- information. You can exercise this right at any time by contacting us on the above details.
-
- Right to withdraw consent
-
- Where the legal basis for processing your personal information is your consent, you have the right to withdraw
- that consent at any time by contacting us on the above details.
-
- Raising a complaint about how we have handled your personal data
-
- If you wish to raise a complaint on how we have handled your personal data, you can contact us as set out above
- and we will then investigate the matter.
-
- Right to lodge a complaint with a relevant supervisory authority
-
- We encourage you to contact us at privacy@cc0de.dev if you have any privacy related concerns. Should you
- disapprove of the response we have provided you, you have the right to lodge a complaint with our supervisory
- authority, or with the data protection authority of the European member state you live or work in. The details of
- the supervisory authority responsible for Berlin, Germany, are:
-
- Berliner Beauftragte für Datenschutz und Informationsfreiheit
-
- Alt-Moabit 59-61
-
- 10555 Berlin
-
- Germany
-
- Phone: 030/138 89-0
-
-
-
- https://www.datenschutz-berlin.de
-
-
-
-
- You also have the right to lodge a complaint with the supervisory authority in the country of your habitual
- residence, place of work, or the place where you allege an infringement of one or more of our rights has taken
- place, if that is based in the EEA.
-
- 11. Storing Personal Data
-
- We retain your information only for as long as is necessary for the purposes for which we process the information
- as set out in this policy.
-
-
- However, we may retain your Personal Data for a longer period of time where such retention is necessary for
- compliance with a legal obligation to which we are subject, or in order to protect your vital interests or the
- vital interests of another natural person.
-
- 12. Childrenโs data
-
- Our products and services are neither designed nor intended for use by children and persons under the age of 18.
- If you suspect or discover that our products and services are being used by a child, please contact us immediately
- at privacy@cc0x.dev
-
- 13. Changes to this Privacy Policy
-
- We may modify this privacy policy at any time to comply with legal requirements as well as developments within our
- organization. When we do, we will revise the date at the top of this page. Each visit or interaction with our
- services will be subject to the new privacy policy. We encourage you to regularly review our privacy policy to
- stay informed about our data protection policy. Unless, we implement profound changes that we proactively notify
- you about, you acknowledge that it is your responsibility to review our privacy policy to be aware of
- modifications. If you do not agree to the revised policy, you should discontinue your use of this website.
-
- 14. Contact Us
- Contact us by post or email at:
-
- Core Contributors GmbH
-
- Gontardstraรe 11
-
- 10178 Berlin
-
- Germany
-
- privacy@cc0x.dev
-
- Contact our Data Protection Officer by post or email at:
-
- TechGDPR DPC GmbH
-
- Heinrich-Roller Str. 15
-
- 10405 Berlin
-
- Germany
-
-
- corecontributors.dpo@techgdpr.com
-
-
-)
-
-const PrivacyPolicy: NextPage = () => {
- return (
- <>
-
- {'Rootstock Safe โ Privacy policy'}
-
-
- {IS_OFFICIAL_HOST && }
- >
- )
-}
-
-export default PrivacyPolicy
diff --git a/src/pages/stake.tsx b/src/pages/stake.tsx
new file mode 100644
index 000000000..2b629a267
--- /dev/null
+++ b/src/pages/stake.tsx
@@ -0,0 +1,32 @@
+import type { NextPage } from 'next'
+import Head from 'next/head'
+import dynamic from 'next/dynamic'
+import { Typography } from '@mui/material'
+import { useHasFeature } from '@/hooks/useChains'
+import { FEATURES } from '@/utils/chains'
+
+const LazyStakePage = dynamic(() => import('@/features/stake/components/StakePage'), { ssr: false })
+
+const StakePage: NextPage = () => {
+ const isFeatureEnabled = useHasFeature(FEATURES.STAKING)
+
+ return (
+ <>
+
+ {'Safe{Wallet} โ Stake'}
+
+
+ {isFeatureEnabled === true ? (
+
+ ) : isFeatureEnabled === false ? (
+
+
+ Staking is not available on this network.
+
+
+ ) : null}
+ >
+ )
+}
+
+export default StakePage
diff --git a/src/pages/swap.tsx b/src/pages/swap.tsx
index 297affc64..0a18c061b 100644
--- a/src/pages/swap.tsx
+++ b/src/pages/swap.tsx
@@ -1,26 +1,32 @@
import type { NextPage } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
-import { GeoblockingContext } from '@/components/common/GeoblockingProvider'
-import { useContext } from 'react'
-import { AppRoutes } from '@/config/routes'
import dynamic from 'next/dynamic'
+import { Typography } from '@mui/material'
+import { useHasFeature } from '@/hooks/useChains'
+import { FEATURES } from '@/utils/chains'
+
+// Cow Swap expects native token addresses to be in the format '0xeeee...eeee'
+const adjustEthAddress = (address: string) => {
+ if (address && Number(address) === 0) {
+ const ETH_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
+ return ETH_ADDRESS
+ }
+ return address
+}
const SwapWidgetNoSSR = dynamic(() => import('@/features/swap'), { ssr: false })
-const Swap: NextPage = () => {
+
+const SwapPage: NextPage = () => {
const router = useRouter()
- const isBlockedCountry = useContext(GeoblockingContext)
const { token, amount } = router.query
-
- if (isBlockedCountry) {
- router.replace(AppRoutes['403'])
- }
+ const isFeatureEnabled = useHasFeature(FEATURES.NATIVE_SWAPS)
let sell = undefined
if (token && amount) {
sell = {
- asset: String(token),
- amount: String(amount),
+ asset: adjustEthAddress(String(token ?? '')),
+ amount: adjustEthAddress(String(amount ?? '')),
}
}
@@ -30,11 +36,17 @@ const Swap: NextPage = () => {
{'Safe{Wallet} โ Swap'}
-
-
+
+ {isFeatureEnabled === true ? (
+
+ ) : isFeatureEnabled === false ? (
+
+ Swaps are not supported on this network.
+
+ ) : null}
>
)
}
-export default Swap
+export default SwapPage
diff --git a/src/pages/terms.tsx b/src/pages/terms.tsx
index 34ad89672..4dc639ca6 100644
--- a/src/pages/terms.tsx
+++ b/src/pages/terms.tsx
@@ -1,151 +1,130 @@
import type { NextPage } from 'next'
import Head from 'next/head'
-import { Typography } from '@mui/material'
-import Link from 'next/link'
-import MUILink from '@mui/material/Link'
-import { AppRoutes } from '@/config/routes'
-import { DISCORD_URL, HELP_CENTER_URL, TWITTER_URL } from '@/config/constants'
-import { IS_OFFICIAL_HOST } from '@/config/constants'
const SafeTerms = () => (
-
- Terms and Conditions
-
-
Last updated: July 2024.
+
Terms and Conditions
+
Last updated: October 14, 2024.
-
1. What is the scope of the Terms?
-
-
- These Terms and Conditions (“Terms”) become part of any contract (“Agreement”) between
- you (“you”, “yours” or “User”) and Core Contributors GmbH (“CC”,
- “we”, “our” or “us”) provided we made these Terms accessible to you prior to
- entering into the Agreement and you consent to these Terms. We are a limited liability company registered with
- the commercial register of Berlin Charlottenburg under company number HRB 240421 B, with its registered
- office at Gontardstraรe 11, 10178 Berlin, Germany. You can contact us by writing to info@cc0x.dev.
-
-
- The Agreement is concluded by using the Mobile App, Web App and/or Browser Extension subject to these
- Terms. The use of our Services is only permitted to legal entities, partnerships and natural persons with
- unlimited legal capacity. In particular, minors are prohibited from using our Services.
-
-
- The application of your general terms and conditions is excluded. Your deviating, conflicting or supplementary
- general terms and conditions shall only become part of the Agreement if and to the extent that CC has expressly
- agreed to their application in writing. This consent requirement shall apply in any case, even if for example
- CC, being aware of your general terms and conditions, accepts payments by the contractual partner without
- reservations.
-
-
- We reserve the right to change these Terms at any time and without giving reasons, while considering and
- weighing your interests. The new Terms will be communicated to you in advance. They are considered as agreed
- upon if you do not object to their validity within 14 days after receipt of the notification. We will separately
- inform you about the essential changes, the possibility to object, the deadline and the consequences of
- inactivity. If you object, the current version of the Terms remains applicable. Our right to terminate the
- contract according to Clause 8 remains unaffected.
-
-
+
1. What is the scope of the Terms?
+
+ These Terms and Conditions (โTermsโ) become part of any contract (โAgreementโ) between you (โyouโ, โyoursโ or
+ โUserโ) and Core Contributors (โCCโ, โweโ, โourโ or โusโ) provided we made these Terms accessible to you prior to
+ entering into the Agreement and you consent to these Terms. We are Altoros LLC, a limited liability company
+ registered with the commercial register of Puerto Rico under company number 416164, with its registered office at
+ 1607 Ponce de Leon Avenue, GM-06, San Juan, PR, 00909-1803. You can contact us by writing to{' '}
+ safe@protofire.io .
+
+
+ The Agreement is concluded by using the Web App subject to these Terms. The use of our Services is only permitted
+ to legal entities, partnerships and natural persons with unlimited legal capacity. In particular, minors are
+ prohibited from using our Services.
+
+
+ The application of your general terms and conditions is excluded. Your deviating, conflicting or supplementary
+ general terms and conditions shall only become part of the Agreement if and to the extent that CC has expressly
+ agreed to their application in writing. This consent requirement shall apply in any case, even if for example CC,
+ being aware of your general terms and conditions, accepts payments by the contractual partner without
+ reservations.
+
+
+ We reserve the right to change these Terms at any time and without giving reasons, while considering and weighing
+ your interests. The new Terms will be communicated to you in advance. They are considered as agreed upon if you do
+ not object to their validity within 14 days after receipt of the notification. We will separately inform you about
+ the essential changes, the possibility to object, the deadline and the consequences of inactivity. If you object,
+ the current version of the Terms remains applicable. Our right to terminate the contract according to Clause 13
+ remains unaffected.
+
-
2. What do some of the capitalized terms mean in the Agreement?
-
+ 2. What do some of the capitalized terms mean in the Agreement?
+
- “Blockchain” means a mathematically secured consensus ledger such as the Ethereum Virtual Machine,
- an Ethereum Virtual Machine compatible validation mechanism, or other decentralized validation mechanisms.
+ โBlockchainโ means a mathematically secured consensus ledger such as the Ethereum Virtual
+ Machine, an Ethereum Virtual Machine compatible validation mechanism, or other decentralized validation
+ mechanisms.
- “Transaction” means a change to the data set through a new entry in the continuous Blockchain.
+ โTransactionโ means a change to the data set through a new entry in the continuous Blockchain.
- “Smart Contract” means a piece of source code deployed as an application on the Blockchain which can
- be executed, including self-execution of Transactions as well as execution triggered by 3rd parties.
+ โSmart Contractโ means a piece of source code deployed as an application on the Blockchain
+ which can be executed, including self-execution of Transactions as well as execution triggered by 3rd parties.
- “Token” means a digital asset transferred in a Transaction, including ETH, ERC20, ERC721 and ERC1155
- tokens.
+ โTokenโ means a digital asset transferred in a Transaction.
- “Wallet” means a cryptographic storage solution permitting you to store cryptographic assets by
+ โWalletโ means a cryptographic storage solution permitting you to store cryptographic assets by
correlation of a (i) Public Key and (ii) a Private Key, or a Smart Contract to receive, manage and send Tokens.
- “Recovery Phrase” means a series of secret words used to generate one or more Private Keys and
+ โRecovery Phraseโ means a series of secret words used to generate one or more Private Keys and
derived Public Keys.
- “Public Key” means a unique sequence of numbers and letters within the Blockchain to distinguish the
- network participants from each other.
+ โPublic Keyโ means a unique sequence of numbers and letters within the Blockchain to
+ distinguish the network participants from each other.
- “Private Key” means a unique sequence of numbers and/or letters required to initiate a Blockchain
- Transaction and should only be known by the legal owner of the Wallet.
+ โPrivate Keyโ means a unique sequence of numbers and/or letters required to initiate a
+ Blockchain Transaction and should only be known by the legal owner of the Wallet.
-
+
-
3. What are the Services offered?
+
3. What are the Services offered?
- Our services (“Services”) primarily consist of enabling users to create their Safe Accounts and
- ongoing interaction with it on the Blockchain.
+ Our services (โServicesโ) primarily consist of enabling users to create their Safe Accounts and ongoing
+ interaction with it on the Blockchain.
-
- “Safe Account”
-
+
โSafe Accountโ
A Safe Account is a modular, self-custodial (i.e. not supervised by us) smart contract-based wallet not provided
- by CC. Safe Accounts are{' '}
-
-
- open-source
-
-
- released under LGPL-3.0.
+ by CC. Safe Accounts are open-source released under LGPL-3.0.
Smart contract wallet means, unlike a standard private key Wallet, that access control for authorizing any
Transaction is defined in code. An example are multi-signature wallets which require that any Transaction must be
signed by a minimum number of signing wallets whereby the specifics of the requirements to authorize a Transaction
- can be configured in code.{' '}
+ can be configured in code.
Owners need to connect a signing wallet with a Safe Account. Safe Accounts are compatible inter alia with standard
private key Wallets such as hardware wallets, browser extension wallets and mobile wallets that support
WalletConnect.
-
- “Safe App”
-
-
- You may access Safe Accounts using the {'Rootstock Safe'} web app, mobile app for iOS and android, or the browser
- extension (each a “Safe App”). The Safe App may be used to manage your personal digital assets on
- Ethereum and other common EVM chains when you connect a Safe Account with third-party services (as defined
- below). The Safe App provides certain features that may be amended from time to time.{' '}
-
-
- “Third-Party Safe Apps”
-
-
- The Safe App allows you to connect Safe Accounts to third-party decentralized applications
- (“Third-Party Safe Apps”) and use third-party services such as from the decentralized
- finance sector, DAO Tools or services related to NFTs (“Third-Party Services"). The
- Third-Party Safe Apps are integrated in the user interface of the Safe App via inline framing. The provider
- of the Third-Party Safe App and related Third-Party Service is responsible for the operation of the service
- and the correctness, completeness and actuality of any information provided therein. We make a pre-selection of
- Third-Party Safe Apps that we show in the Safe App. However, we only perform a rough triage in advance for
- obvious problems and functionality in terms of loading time and resolution capability of the transactions.
- Accordingly, in the event of any (technical) issues concerning the Third-Party Services, the user must only
- contact the respective service provider directly. The terms of service, if any, shall be governed by the
- applicable contractual provisions between the User and the respective provider of the Third-Party Service.
+
+
โSafe Appโ
+
+ You may access Safe Accounts using the {'Safe{Wallet}'} web app, mobile app for iOS and android, or the browser
+ extension (each a โSafe Appโ). The Safe App may be used to manage your personal digital assets on Ethereum and
+ other common EVM chains when you connect a Safe Account with third-party services (as defined below). The Safe App
+ provides certain features that may be amended from time to time.
+
+
+
โThird-Party Safe Appsโ
+
+ The Safe App allows you to connect Safe Accounts to third-party decentralized applications (โThird-Party Safe
+ Appsโ) and use third-party services such as from the decentralized finance sector, DAO Tools or services related
+ to NFTs (โThird-Party Servicesโ). The Third-Party Safe Apps are integrated in the user interface of the Safe App
+ via inline framing. The provider of the Third-Party Safe App and related Third-Party Service is responsible for
+ the operation of the service and the correctness, completeness and actuality of any information provided therein.
+ We make a pre-selection of Third-Party Safe Apps that we show in the Safe App. However, we only perform a rough
+ triage in advance for obvious problems and functionality in terms of loading time and resolution capability of the
+ transactions. Accordingly, in the event of any (technical) issues concerning the Third-Party Services, the user
+ must only contact the respective service provider directly. The terms of service, if any, shall be governed by the
+ applicable contractual provisions between the User and the respective provider of the Third-Party Service.
Accordingly, we are not liable in the event of a breach of contract, damage or loss related to the use of such
Third-Party Service.
-
4. What do the Services not consist of?
-
Our Services do not consist of:
-
+ 4. What do the Services not consist of?
+ Our Services do not consist of:
+
activity regulated by the Federal Financial Supervisory Authority (BaFin) or any other regulatory agency in any
jurisdiction;
- coverage underwritten by any regulatory agency’s compensation scheme;
+ coverage underwritten by any regulatory agencyโs compensation scheme;
custody of your Recovery Phrase, Private Keys, Tokens or the ability to remove or freeze your Tokens, i.e. a
Safe Account is a self-custodial wallet;
@@ -157,7 +136,7 @@ const SafeTerms = () => (
any form of legal, financial, investment, accounting, tax or other professional advice regarding Transactions
- and their suitability to you;{' '}
+ and their suitability to you;
the responsibility to monitor authorized Transactions or to check the correctness or completeness of
@@ -167,33 +146,31 @@ const SafeTerms = () => (
recovery of your Safe Account;
flagging malicious transactions;
issuance of the Safe Token and any related functionalities or reward programs.
-
+
-
5. What do you need to know about Third-Party Services?
-
-
- We provide you the possibility to interact with your Safe Account through Third-Party Services. Any
- activities you engage in with, or services you receive from a third party is between you and that third party
- directly. The conditions of service provisions, if any, shall be governed by the applicable contractual
- provisions between you and the respective provider of the Third-Party Service.{' '}
-
-
- The Services rely in part on third-party and open-source software, including the Blockchain, and the continued
- development and support by third parties. There is no assurance or guarantee that those third parties will
- maintain their support of their software or that open-source software will continue to be maintained. This may
- have a material adverse effect on the Services.
-
- This means specifically:
-
+
5. What do you need to know about Third-Party Services?
+
+ We provide you the possibility to interact with your Safe Account through Third-Party Services. Any activities you
+ engage in with, or services you receive from a third party is between you and that third party directly. The
+ conditions of service provisions, if any, shall be governed by the applicable contractual provisions between you
+ and the respective provider of the Third-Party Service.
+
+
+ The Services rely in part on third-party and open-source software, including the Blockchain, and the continued
+ development and support by third parties. There is no assurance or guarantee that those third parties will
+ maintain their support of their software or that open-source software will continue to be maintained. This may
+ have a material adverse effect on the Services.
+
+
This means specifically:
- We do not have any oversight over your activities with Third-Party Services especially by using
- Third-Party Safe Apps, and therefore we do not and cannot make any representation regarding their
- appropriateness and suitability for you.
+ We do not have any oversight over your activities with Third-Party Services especially by using Third-Party Safe
+ Apps, and therefore we do not and cannot make any representation regarding their appropriateness and suitability
+ for you.
- Third-Party Services are not hosted, owned, controlled or maintained by us. We also do not participate in
- the Transaction and will not and cannot monitor, verify, censor or edit the functioning or content of any
+ Third-Party Services are not hosted, owned, controlled or maintained by us. We also do not participate in the
+ Transaction and will not and cannot monitor, verify, censor or edit the functioning or content of any
Third-Party Service.
@@ -202,14 +179,14 @@ const SafeTerms = () => (
We have no control over, do not recommend, endorse, or otherwise take a position on the integrity, functioning
- of, content and your use of Third-Party Services, whose sole responsibility lies with the person from whom
- such services or content originated.
+ of, content and your use of Third-Party Services, whose sole responsibility lies with the person from whom such
+ services or content originated.
- When you access or use Third-Party Services you accept that there are risks in doing so and that you alone
+ When you access or use Third-Party Services you accept that there are risks in doing so and that you alone
assume any such risks when choosing to interact with them. We are not liable for any errors or omissions or for
- any damages or loss you might suffer through interacting with those Third-Party Services, such as
- Third-Party Safe Apps.
+ any damages or loss you might suffer through interacting with those Third-Party Services, such as Third-Party
+ Safe Apps.
You know of the inherent risks of cryptographic and Blockchain-based systems and the high volatility of Token
@@ -217,223 +194,176 @@ const SafeTerms = () => (
to refund Token that have been deployed.
- You should read the license requirements, terms and conditions as well as privacy policy of each
- Third-Party Service that you access or use. Certain Third-Party Services may involve complex
- Transactions that entail a high degree of risk.
+ You should read the license requirements, terms and conditions as well as privacy policy of each Third-Party
+ Service that you access or use. Certain Third-Party Services may involve complex Transactions that entail a high
+ degree of risk.
- If you contribute integrations to Third-Party Services, you are responsible for all content you contribute,
- in any manner, and you must have all rights necessary to do so, in the manner in which you contribute it. You
- are responsible for all your activity in connection with any such Third-Party Service.{' '}
+ If you contribute integrations to Third-Party Services, you are responsible for all content you contribute, in
+ any manner, and you must have all rights necessary to do so, in the manner in which you contribute it. You are
+ responsible for all your activity in connection with any such Third-Party Service.
- Your interactions with persons found on or through the Third-Party Service, including payment and delivery
- of goods and services, financial transactions, and any other terms associated with such dealings, are solely
+ Your interactions with persons found on or through the Third-Party Service, including payment and delivery of
+ goods and services, financial transactions, and any other terms associated with such dealings, are solely
between you and such persons. You agree that we shall not be responsible or liable for any loss or damage of any
sort incurred as the result of any such dealings.
- If there is a dispute between you and the Third-Party Service provider or/and other users of the
- Third-Party Service, you agree that we are under no obligation to become involved. In the event that you
- have a dispute with one or more other users, you release us, our officers, employees, agents, contractors and
- successors from claims, demands, and damages of every kind or nature, known or unknown, suspected or
- unsuspected, disclosed or undisclosed, arising out of or in any way related to such disputes and/or our
- Services.
+ If there is a dispute between you and the Third-Party Service provider or/and other users of the Third-Party
+ Service, you agree that we are under no obligation to become involved. In the event that you have a dispute with
+ one or more other users, you release us, our officers, employees, agents, contractors and successors from
+ claims, demands, and damages of every kind or nature, known or unknown, suspected or unsuspected, disclosed or
+ undisclosed, arising out of or in any way related to such disputes and/or our Services.
-
6. What are the fees for the Services?
-
-
- The use of the Safe App or Third-Party Safe Apps may cause fees, including network fees, as indicated in
- the respective app. CC has no control over the fees charged by the Third-Party Services. CC may change its own
- fees at any time. Price changes will be communicated to the User in due time before taking effect.
-
-
- The User is only entitled to offset and/or assert rights of retention if his counterclaims are legally
- established, undisputed or recognized by CC.
-
-
-
-
7. Are we responsible for the security of your Private Keys, Recovery Phrase or other credentials?
-
-
- We shall not be responsible to secure your Private Keys, Recovery Phrase, credentials or other means of
- authorization of your wallet(s).
-
-
- You must own and control any wallet you use in connection with our Services. You are responsible for
- implementing all appropriate measures for securing any wallet you use, including any Private Key(s), Recovery
- Phrase, credentials or other means of authorization necessary to access such storage mechanism(s).
-
-
- We exclude any and all liability for any security breaches or other acts or omissions, which result in your loss
- of access or custody of any cryptographic assets stored thereon.
-
-
+
6. What are the fees for the Services?
+
+ The use of the Safe App or Third-Party Safe Apps may cause fees, including network fees, as indicated in the
+ respective app. CC has no control over the fees charged by the Third-Party Services. CC may change its own fees at
+ any time. Price changes will be communicated to the User in due time before taking effect.
+
+
+ The User is only entitled to offset and/or assert rights of retention if his counterclaims are legally
+ established, undisputed or recognized by CC.
+
-
8. Are we responsible for recovering your Safe Account?
-
- We shall not be responsible for recovering your Safe Account.
- You are solely responsible for securing a back-up of your Safe Account access as you see fit.
-
- Any recovery feature we provide access to within the Safe App is a mechanism controlled by your Safe Account on
- the Blockchain, both of which we don't have any influence over once you have set it up. We will never act
- as a recoverer ourselves and don't offer recovery services. The Self Custodial Recovery feature allows you
- to determine your own recovery setup and nominate anyone including yourself as your recoverer. The recoverer can
- start the recovery process at any time. Please note that we are not responsible for notifying you of this
- process (see Section 7 above). Furthermore we reserve the right to cease the access to the Self Custodial
- Recovery feature via our Safe App taking the user's reasonable interests into account and providing due
- notification.
-
- The recovery feature is provided free of charge and liability is limited pursuant to Section 17.4 below.
-
+
7. Are we responsible for the security of your Private Keys, Recovery Phrase or other credentials?
+
+ We shall not be responsible to secure your Private Keys, Recovery Phrase, credentials or other means of
+ authorization of your wallet(s).
+
+
+ You must own and control any wallet you use in connection with our Services. You are responsible for implementing
+ all appropriate measures for securing any wallet you use, including any Private Key(s), Recovery Phrase,
+ credentials or other means of authorization necessary to access such storage mechanism(s).
+
+
+ We exclude any and all liability for any security breaches or other acts or omissions, which result in your loss
+ of access or custody of any cryptographic assets stored thereon.
+
-
9. Are we responsible for notifying you about events occuring in your Safe Account?
-
-
- We shall not be responsible for notifying you of any interactions or events occurring in your Safe Account, be
- it on the Blockchain, third-party interfaces, within any other infrastructure, or our Services.
-
- You are responsible for monitoring Safe Account as you see fit.
-
- Any notification service we provide or offer for subscription within the Safe App via e-mail or push
- notifications or any other means of communication is provided free of charge and liability is limited pursuant
- to Section 17.4 below. Furthermore we reserve the right to change the notification feature from time to time or
- cease to provide them without notice.
-
-
+
8. Are we responsible for recovering your Safe Account?
+
We shall not be responsible for recovering your Safe Account.
+
You are solely responsible for securing a back-up of your Safe Account access as you see fit.
+
+ Any recovery feature we provide access to within the Safe App is a mechanism controlled by your Safe Account on
+ the Blockchain, both of which we don't have any influence over once you have set it up. We will never act as
+ a recoverer ourselves and don't offer recovery services. The Self Custodial Recovery feature allows you to
+ determine your own recovery setup and nominate anyone including yourself as your recoverer. The recoverer can
+ start the recovery process at any time. Please note that we are not responsible for notifying you of this process
+ (see Section 7 above). Furthermore we reserve the right to cease the access to the Self Custodial Recovery feature
+ via our Safe App taking the user's reasonable interests into account and providing due notification.
+
+
The recovery feature is provided free of charge and liability is limited pursuant to Section 18 below.
-
10. Are we responsible for flagging malicious transactions?
-
- We shall not be responsible for flagging malicious transactions in our Safe App.
-
- You are solely responsible for checking any transaction, address, Token or other item you interact with via your
- Smart Account in our Safe App.{' '}
-
-
- Any security flagging or warning service we provide or offer for subscription within the Safe App is provided
- free of charge and liability is limited pursuant to Section 17.4 below. Furthermore we reserve the right to
- change the feature from time to time or cease to provide them without notice.
-
-
+
9. Are we responsible for notifying you about events occurring in your Safe Account?
+
+ We shall not be responsible for notifying you of any interactions or events occurring in your Safe Account, be it
+ on the Blockchain, third-party interfaces, within any other infrastructure, or our Services.
+
+
You are responsible for monitoring Safe Account as you see fit.
+
+ Any notification service we provide or offer for subscription within the Safe App via e-mail or push notifications
+ or any other means of communication is provided free of charge and liability is limited pursuant to Section 18
+ below. Furthermore we reserve the right to change the notification feature from time to time or cease to provide
+ them without notice.
+
-
- 11. Are we responsible for the issuance of the Safe Token and any related functionalities or reward programs?
-
-
-
- The Safe Token is issued by the Safe Ecosystem Foundation. We are not the issuer or in any way responsible for
- the Safe Token. Furthermore, we do not provide any functionalities to the Safe Token or Safe Token reward
- programs.
-
-
- You are solely responsible for managing your Safe Tokens just like any other Token in your Safe Account and
- solely responsible for your eligibility for any reward programs.
-
-
- Any interface we provide that allows you to claim or delegate your Safe Tokens or to participate in any third
- party program related to Safe Tokens is provided free of charge and we exclude any and all liability for the
- correctness, completeness, speed or timeliness of these services. Furthermore we reserve the right to change the
- feature from time to time or cease to provide them without notice.
-
-
+
10. Are we responsible for flagging malicious transactions?
+
We shall not be responsible for flagging malicious transactions in our Safe App.
+
+ You are solely responsible for checking any transaction, address, Token or other item you interact with via your
+ Smart Account in our Safe App.
+
+
+ Any security flagging or warning service we provide or offer for subscription within the Safe App is provided free
+ of charge and liability is limited pursuant to Section 18 below. Furthermore we reserve the right to change the
+ feature from time to time or cease to provide them without notice.
+
-
12. Are we responsible for third-party content and services?
-
-
- You may view, have access to, and use third-party content and services, for example widget integrations, within
- the Safe App (โThird-Party Featuresโ). You view, access, or use Third-Party Features at your own election. Your
- reliance on Third-Party Features is subject to separate terms and conditions set forth by the applicable third
- party content and/or service provider (โThird-Party Termsโ). Third-Party Terms may, amongst other things,
-
- involve separate fees and charges,
- include disclaimers or risk warnings,
- apply a different terms and privacy policy.
-
-
-
- Third Party Features are provided for your convenience only. We do not verify, curate, or control Third Party
- Features.{' '}
-
-
- If we offer access to Third-Party Features in the Safe App free of charge by us (Third-Parties may charge
- separate fees), the liability for providing access to such Third-Party Feature is limited pursuant to Section
- 17.1 below. Furthermore we reserve the right to cease to provide access to those Third-Party Features through
- the Safe App without notice.
-
-
+
12. Are we responsible for third-party content and services?
+
+ You may view, have access to, and use third-party content and services, for example widget integrations, within
+ the Safe App (โThird-Party Featuresโ). You view, access, or use Third-Party Features at your own election. Your
+ reliance on Third-Party Features is subject to separate terms and conditions set forth by the applicable third
+ party content and/or service provider (โThird-Party Termsโ). Third-Party Terms may, amongst other things, involve
+ separate fees and charges, include disclaimers or risk warnings, apply a different terms and privacy policy.
+
+
+ Third Party Features are provided for your convenience only. We do not verify, curate, or control Third Party
+ Features.
+
+
+ If we offer access to Third-Party Features in the Safe App free of charge by us (Third-Parties may charge separate
+ fees), the liability for providing access to such Third-Party Feature is limited pursuant to Section 18 below.
+ Furthermore we reserve the right to cease to provide access to those Third-Party Features through the Safe App
+ without notice.
+
-
13. Can we terminate or limit your right to use our Services?
-
-
- We may cease offering our Services and/or terminate the Agreement and refuse access to the Safe Apps at any
- time. The right of the parties to terminate the Agreement at any time for cause remains unaffected. In case of
- our termination of the Agreement, you may no longer access your Safe Account via our Services. However, you may
- continue to access your Safe Account and any Tokens via a third-party wallet provider using your Recovery Phrase
- and Private Keys.
-
-
- We reserve the right to limit the use of the Safe Apps to a specified number of Users if necessary to protect or
- ensure the stability and integrity of the Services. We will only be able to limit access to the Services. At no
- time will we be able to limit or block access to or transfer your funds without your consent.
-
-
+
13. Can we terminate or limit your right to use our Services?
+
+ We may cease offering our Services and/or terminate the Agreement and refuse access to the Safe Apps at any time.
+ The right of the parties to terminate the Agreement at any time for cause remains unaffected. In case of our
+ termination of the Agreement, you may no longer access your Safe Account via our Services. However, you may
+ continue to access your Safe Account and any Tokens via a third-party wallet provider using your Recovery Phrase
+ and Private Keys.
+
+
+ We reserve the right to limit the use of the Safe Apps to a specified number of Users if necessary to protect or
+ ensure the stability and integrity of the Services. We will only be able to limit access to the Services. At no
+ time will we be able to limit or block access to or transfer your funds without your consent.
+
-
14. Can you terminate your Agreement with us?
+
14. Can you terminate your Agreement with us?
You may terminate the Agreement at any time without notice.
-
15. What licenses and access do we grant to you?
-
-
- All intellectual property rights in Safe Accounts and the Services throughout the world belong to us as owner or
- our licensors. Nothing in these Terms gives you any rights in respect of any intellectual property owned by us
- or our licensors and you acknowledge that you do not acquire any ownership rights by downloading the Safe App or
- any content from the Safe App.
-
-
- If you are a consumer we grant you a simple, limited license, but do not sell, to you the Services you download
- solely for your own personal, non-commercial use.{' '}
-
-
+
15. What licenses and access do we grant to you?
+
+ All intellectual property rights in Safe Accounts and the Services throughout the world belong to Safe Global(Core
+ Contributors GmbH) as owner or their licensors. Nothing in these Terms gives you any rights in respect of any
+ intellectual property owned by us or our licensors and you acknowledge that you do not acquire any ownership
+ rights by downloading the Safe App or any content from the Safe App.
+
+
+ If you are a consumer we grant you a simple, limited license, but do not sell to you the Services you download
+ solely for your own personal, non-commercial use.
+
-
16. What can you expect from the Services and can we make changes to them?
-
-
- Without limiting your mandatory warranties, we provide the Services to you “as is” and “as
- available” in relation to merchantability, fitness for a particular purpose, availability, security, title
- or non-infringement.{' '}
-
-
- If you use the Safe App via web browser, the strict liability of CC for damages (sec. 536a German Civil Code)
- for defects existing at the time of conclusion of the contract is precluded.{' '}
-
- The foregoing provisions will not limit CC’s liability as defined in Clause 13.
-
- We reserve the right to change the format and features of the Services by making any updates to Services
- available for you to download or, where your device settings permit it, by automatic delivery of updates.
-
-
- You are not obliged to download the updated Services, but we may cease to provide and/or update prior versions
- of the Services and, depending on the nature of the update, in some circumstances you may not be able to
- continue using the Services until you have downloaded the updated version.
-
-
- We may cease to provide and/or update content to the Services, with or without notice to you, if it improves the
- Services we provide to you, or we need to do so for security, legal or any other reasons.
-
-
+
16. What can you expect from the Services and can we make changes to them?
+
+ Without limiting your mandatory warranties, we provide the Services to you โas isโ and โas availableโ in relation
+ to merchantability, fitness for a particular purpose, availability, security, title or non-infringement.
+
+
+ If you use the Safe App via web browser, the strict liability of CC for damages for defects existing at the time
+ of conclusion of the contract is precluded.
+
+
+ We reserve the right to change the format and features of the Services by making any updates to Services available
+ for you to download or, where your device settings permit it, by automatic delivery of updates.
+
+
+ You are not obliged to download the updated Services, but we may cease to provide and/or update prior versions of
+ the Services and, depending on the nature of the update, in some circumstances you may not be able to continue
+ using the Services until you have downloaded the updated version.
+
+
+ We may cease to provide and/or update content to the Services, with or without notice to you, if it improves the
+ Services we provide to you, or we need to do so for security, legal or any other reasons.
+
-
17. What do you agree, warrant and represent?
+
17. What do you agree, warrant and represent?
By using our Services you hereby agree, represent and warrant that:
-
+
You are not a citizen, resident, or member of any jurisdiction or group that is subject to economic sanctions by
the European Union or the United States or any other relevant jurisdiction.
- You do not appear on HMT Sanctions List, the U.S. Treasury Department’s Office of Foreign Asset
- Control’s sanctions lists, the U.S. commerce department's consolidated screening list, the EU
+ You do not appear on HMT Sanctions List, the U.S. Treasury Department's Office of Foreign Asset
+ Control's sanctions lists, the U.S. commerce department's consolidated screening list, the EU
consolidated list of persons, groups or entities subject to EU Financial Sanctions, nor do you act on behalf of
a person sanctioned thereunder.
@@ -443,7 +373,7 @@ const SafeTerms = () => (
jurisdiction to which you may be subject.
- You won’t use the Services or interact with the Services in a manner that violates any law or regulation,
+ You wonโt use the Services or interact with the Services in a manner that violates any law or regulation,
including, without limitation, any applicable export control laws.
@@ -465,16 +395,16 @@ const SafeTerms = () => (
authorities and we will cooperate with those authorities as required.
- You won’t access without authority, interfere with, damage or disrupt any part of our Services, any
- equipment or network on which our Services is stored, any software used in the provision of our Services or any
- equipment or network or software owned or used by any third party.
+ You wonโt access without authority, interfere with, damage or disrupt any part of our Services, any equipment or
+ network on which our Services is stored, any software used in the provision of our Services or any equipment or
+ network or software owned or used by any third party.
- You won’t use our Services for activities that are unlawful or fraudulent or have such purpose or effect
- or otherwise support any activities that breach applicable local, national or international law or regulations.
+ You wonโt use our Services for activities that are unlawful or fraudulent or have such purpose or effect or
+ otherwise support any activities that breach applicable local, national or international law or regulations.
- You won’t use our Services to store, trade or transmit Tokens that are proceeds of criminal or fraudulent
+ You wonโt use our Services to store, trade or transmit Tokens that are proceeds of criminal or fraudulent
activity.
@@ -482,207 +412,65 @@ const SafeTerms = () => (
accordingly do not guarantee an error-free process and give no price or liquidity guarantee.
You are using the Services at your own risk.
-
-
-
18. What about our liability to you?
-
-
- If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
- service, network, and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
- by us), CC shall be liable only in cases of intent, gross negligence, or if CC has fraudulently concealed a
- possible material or legal defect of the Safe App or Services. If the Safe App or Services are not provided to
- the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 17.1 as well as (ii) in cases
- of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the
- performance of which enables the proper execution of this Agreement in the first place and on the compliance of
- which the User regularly relies and may rely, whereby CC's liability shall be limited to the compensation
- of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the
- sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in
- which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages
- resulting from the breach of a non-essential contractual duty are excluded. The limitations of liability
- according to Clause 17.1 and Clause 17.2 do not apply (i) to damages resulting from injury to life, body or
- health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product
- Liability Act and (iv) to claims of the User according to the applicable data protection law. The limitation of
- liability also applies to the personal liability of the organs, legal representatives, employees and vicarious
- agents of CC. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the
- damage would have been avoided by a regular and complete backup of all relevant data by the User. In the event
- of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not
- responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from
- performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be
- economically expected of CC.
-
-
- If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
- service, network and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
- by us), CC shall be liable only in cases of intent, gross negligence or if CC has fraudulently concealed a
- possible material or legal defect of the Safe App or Services. If the Safe App or Services are not provided to
- the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 17.1 as well as (ii) in cases
- of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the
- performance of which enables the proper execution of this Agreement in the first place and on the compliance of
- which the User regularly relies and may rely, whereby CC's liability shall be limited to the compensation
- of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the
- sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in
- which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages
- resulting from the breach of a non-essential contractual duty are excluded. The limitations of liability
- according to Clause 17.1 and Clause 17.2 do not apply (i) to damages resulting from injury to life, body or
- health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product
- Liability Act and (iv) to claims of the User according to the applicable data protection law. The limitation of
- liability also applies to the personal liability of the organs, legal representatives, employees and vicarious
- agents of CC. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the
- damage would have been avoided by a regular and complete backup of all relevant data by the User. In the event
- of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not
- responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from
- performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be
- economically expected of CC.
-
-
- If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
- service, network and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
- by us), CC shall be liable only in cases of intent, gross negligence or if CC has fraudulently concealed a
- possible material or legal defect of the Safe App or Services. If the Safe App or Services are not provided to
- the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 17.1 as well as (ii) in cases
- of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the
- performance of which enables the proper execution of this Agreement in the first place and on the compliance of
- which the User regularly relies and may rely, whereby CC's liability shall be limited to the compensation
- of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the
- sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in
- which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages
- resulting from the breach of a non-essential contractual duty are excluded. The limitations of liability
- according to Clause 17.1 and Clause 17.2 do not apply (i) to damages resulting from injury to life, body or
- health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product
- Liability Act and (iv) to claims of the User according to the applicable data protection law. The limitation of
- liability also applies to the personal liability of the organs, legal representatives, employees and vicarious
- agents of CC. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the
- damage would have been avoided by a regular and complete backup of all relevant data by the User. In the event
- of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not
- responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from
- performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be
- economically expected of CC.
-
-
- If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
- service, network and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
- by us), CC shall be liable only in cases of intent, gross negligence or if CC has fraudulently concealed a
- possible material or legal defect of the Safe App or Services. If the Safe App or Services are not provided to
- the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 17.1 as well as (ii) in cases
- of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the
- performance of which enables the proper execution of this Agreement in the first place and on the compliance of
- which the User regularly relies and may rely, whereby CC's liability shall be limited to the compensation
- of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the
- sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in
- which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages
- resulting from the breach of a non-essential contractual duty are excluded. The limitations of liability
- according to Clause 17.1 and Clause 17.2 do not apply (i) to damages resulting from injury to life, body or
- health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product
- Liability Act and (iv) to claims of the User according to the applicable data protection law. The limitation of
- liability also applies to the personal liability of the organs, legal representatives, employees and vicarious
- agents of CC. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the
- damage would have been avoided by a regular and complete backup of all relevant data by the User. In the event
- of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not
- responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from
- performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be
- economically expected of CC.
-
-
- If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
- service, network and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
- by us), CC shall be liable only in cases of intent, gross negligence or if CC has fraudulently concealed a
- possible material or legal defect of the Safe App or Services. If the Safe App or Services are not provided to
- the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 17.1 as well as (ii) in cases
- of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the
- performance of which enables the proper execution of this Agreement in the first place and on the compliance of
- which the User regularly relies and may rely, whereby CC's liability shall be limited to the compensation
- of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the
- sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in
- which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages
- resulting from the breach of a non-essential contractual duty are excluded. The limitations of liability
- according to Clause 17.1 and Clause 17.2 do not apply (i) to damages resulting from injury to life, body or
- health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product
- Liability Act and (iv) to claims of the User according to the applicable data protection law. The limitation of
- liability also applies to the personal liability of the organs, legal representatives, employees and vicarious
- agents of CC. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the
- damage would have been avoided by a regular and complete backup of all relevant data by the User. In the event
- of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not
- responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from
- performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be
- economically expected of CC.
-
-
- If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
- service, network and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
- by us), CC shall be liable only in cases of intent, gross negligence or if CC has fraudulently concealed a
- possible material or legal defect of the Safe App or Services. If the Safe App or Services are not provided to
- the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 17.1 as well as (ii) in cases
- of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the
- performance of which enables the proper execution of this Agreement in the first place and on the compliance of
- which the User regularly relies and may rely, whereby CC's liability shall be limited to the compensation
- of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the
- sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in
- which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages
- resulting from the breach of a non-essential contractual duty are excluded. The limitations of liability
- according to Clause 17.1 and Clause 17.2 do not apply (i) to damages resulting from injury to life, body or
- health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product
- Liability Act and (iv) to claims of the User according to the applicable data protection law. The limitation of
- liability also applies to the personal liability of the organs, legal representatives, employees and vicarious
- agents of CC. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the
- damage would have been avoided by a regular and complete backup of all relevant data by the User. In the event
- of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not
- responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from
- performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be
- economically expected of CC.
-
-
- If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
- service, network and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
- by us), CC shall be liable only in cases of intent, gross negligence or if CC has fraudulently concealed a
- possible material or legal defect of the Safe App or Services. If the Safe App or Services are not provided to
- the User free of charge, CC shall be liable only (i) in cases pursuant to Clause 17.1 as well as (ii) in cases
- of simple negligence for damages resulting from the breach of an essential contractual duty, a duty, the
- performance of which enables the proper execution of this Agreement in the first place and on the compliance of
- which the User regularly relies and may rely, whereby CC's liability shall be limited to the compensation
- of the foreseeable, typically occurring damage. The Parties agree that the typical foreseeable damage equals the
- sum of the annual Fees paid or agreed to be paid by the User to CC during the course of the calendar year in
- which the event giving rise to the damage claim occurred. Liability in cases of simple negligence for damages
- resulting from the breach of a non-essential contractual duty are excluded. The limitations of liability
- according to Clause 17.1 and Clause 17.2 do not apply (i) to damages resulting from injury to life, body or
- health, (ii) insofar as CC has assumed a guarantee, (iii) to claims of the User according to the Product
- Liability Act and (iv) to claims of the User according to the applicable data protection law. The limitation of
- liability also applies to the personal liability of the organs, legal representatives, employees and vicarious
- agents of CC. If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the
- damage would have been avoided by a regular and complete backup of all relevant data by the User. In the event
- of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that we are not
- responsible for, we shall be exempt from our obligation to perform. This also applies if we are prevented from
- performing due to force majeure or other circumstances, the elimination of which is not possible or cannot be
- economically expected of CC.
-
-
-
-
19. What about viruses, bugs and security vulnerabilities?
-
- We endeavor to provide our Service free from material bugs, security vulnerabilities or viruses.
-
- You are responsible for configuring your information technology and computer programmes to access our Services
- and to use your own virus protection software.
-
- If you become aware of any exploits, bugs or vulnerabilities, please inform bounty@safe.global.
-
- You must not misuse our Services by knowingly introducing material that is malicious or technologically harmful.
- If you do, your right to use our Services will cease immediately.
-
-
-
-
20. What if an event outside our control happens that affects our Services?
-
-
- We may update and change our Services from time to time. We may suspend or withdraw or restrict the availability
- of all or any part of our Services for business, operational or regulatory reasons or because of a Force Majeure
- Event at no notice.
-
-
- A “Force Majeure Event” shall mean any event, circumstance or cause beyond our reasonable control,
- which prevents, hinders or delays the provision of our Services or makes their provision impossible or onerous,
- including, without limitation:
-
-
+
+
+
18. What about our liability to you?
+
+ If the Safe App or Services are provided to the User free of charge (please note, in this context, that any
+ service, network, and/or transaction fees may be charged by third parties via the Blockchain and not necessarily
+ by us), CC shall be liable only in cases of intent, gross negligence, or if CC has fraudulently concealed a
+ possible material or legal defect of the Safe App or Services.
+
+
+ If the Safe App or Services are not provided to the User free of charge, CC shall be liable only (i) in cases
+ pursuant to Clause 18.1 as well as (ii) in cases of simple negligence for damages resulting from the breach of an
+ essential contractual duty, a duty, the performance of which enables the proper execution of this Agreement in the
+ first place and on the compliance of which the User regularly relies and may rely, whereby CC's liability
+ shall be limited to the compensation of the foreseeable, typically occurring damage. The Parties agree that the
+ typical foreseeable damage equals 100$ (one hundred USD). Liability in cases of simple negligence for damages
+ resulting from the breach of a non-essential contractual duty are excluded.
+
+
+ The limitation of liability also applies to the personal liability of the organs, legal representatives, employees
+ and vicarious agents of CC.
+
+
+ If the User suffers damages due to the loss of data, CC is not liable for this, insofar as the damage would have
+ been avoided by a regular and complete backup of all relevant data by the User.
+
+
+ In the event of disruptions to the technical infrastructure, the internet connection or a relevant Blockchain that
+ we are not responsible for, we shall be exempt from our obligation to perform. This also applies if we are
+ prevented from performing due to force majeure or other circumstances, the elimination of which is not possible or
+ cannot be economically expected of CC.
+
+
+
19. What about viruses, bugs and security vulnerabilities?
+
We endeavor to provide our Service free from material bugs, security vulnerabilities or viruses.
+
+ You are responsible for configuring your information technology and computer programmes to access our Services and
+ to use your own virus protection software.
+
+
+ If you become aware of any exploits, bugs or vulnerabilities, please inform{' '}
+ safe-support@protofire.io .
+
+
+ You must not misuse our Services by knowingly introducing material that is malicious or technologically harmful.
+ If you do, your right to use our Services will cease immediately.
+
+
+
20. What if an event outside our control happens that affects our Services?
+
+ We may update and change our Services from time to time. We may suspend or withdraw or restrict the availability
+ of all or any part of our Services for business, operational or regulatory reasons or because of a Force Majeure
+ Event at no notice.
+
+
+ A โForce Majeure Eventโ shall mean any event, circumstance or cause beyond our reasonable control, which prevents,
+ hinders or delays the provision of our Services or makes their provision impossible or onerous, including, without
+ limitation:
+
acts of God, flood, storm, drought, earthquake or other natural disaster;
epidemic or pandemic (for the avoidance of doubt, including the 2020 Coronavirus Pandemic);
@@ -703,16 +491,14 @@ const SafeTerms = () => (
collapse of buildings, breakdown of plant or machinery, fire, explosion or accident; and
strike, industrial action or lockout.
-
-
- We shall not be liable or responsible to you, or be deemed to have defaulted under or breached this Agreement,
- for any failure or delay in the provision of the Services or the performance of this Agreement, if and to the
- extent such failure or delay is caused by or results from or is connected to acts beyond our reasonable control,
- including the occurrence of a Force Majeure Event.
-
-
+
+ We shall not be liable or responsible to you, or be deemed to have defaulted under or breached this Agreement, for
+ any failure or delay in the provision of the Services or the performance of this Agreement, if and to the extent
+ such failure or delay is caused by or results from or is connected to acts beyond our reasonable control,
+ including the occurrence of a Force Majeure Event.
+
-
21. Who is responsible for your tax liabilities?
+
21. Who is responsible for your tax liabilities?
You are solely responsible to determine if your use of the Services have tax implications, in particular income
tax and capital gains tax relating to the purchase or sale of Tokens, for you. By using the Services you agree not
@@ -720,7 +506,7 @@ const SafeTerms = () => (
action or transaction related thereto.
-
22. What if a court disagrees with part of this Agreement?
+
22. What if a court disagrees with part of this Agreement?
Should individual provisions of these Terms be or become invalid or unenforceable in whole or in part, this shall
not affect the validity of the remaining provisions. The invalid or unenforceable provision shall be replaced by
@@ -729,100 +515,74 @@ const SafeTerms = () => (
valid provision that comes as close as possible to the economic purpose of the invalid or unenforceable provision.
-
23. What if we do not enforce certain rights under this Agreement?
+
23. What if we do not enforce certain rights under this Agreement?
Our failure to exercise or enforce any right or remedy provided under this Agreement or by law shall not
constitute a waiver of that or any other right or remedy, nor shall it prevent or restrict any further exercise of
that or any other right or remedy.
-
24. Do third parties have rights?
+
24. Do third parties have rights?
Unless it expressly states otherwise, this Agreement does not give rise to any third-party rights, which may be
enforced against us.
-
25. Can this Agreement be assigned?
-
-
- We are entitled to transfer our rights and obligations under the Agreement in whole or in part to third parties
- with a notice period of four weeks. In this case, you have the right to terminate the Agreement without notice.
-
-
- You shall not be entitled to assign this Agreement to any third party without our express prior written consent.
-
-
+
25. Can this Agreement be assigned?
+
+ We are entitled to transfer our rights and obligations under the Agreement in whole or in part to third parties
+ with a notice period of four weeks. In this case, you have the right to terminate the Agreement without notice.
+
+
+ You shall not be entitled to assign this Agreement to any third party without our express prior written consent.
+
-
26. Which Clauses of this Agreement survive termination?
+
26. Which Clauses of this Agreement survive termination?
All covenants, agreements, representations and warranties made in this Agreement shall survive your acceptance of
this Agreement and its termination.
-
27. Data Protection
+
27. Data Protection
We inform you about our processing of personal data, including the disclosure to third parties and your rights as
- an affected party, in the{' '}
-
- Privacy Policy
-
- .
+ an affected party, in the Privacy Policy.
-
28. Which laws apply to the Agreement?
+
28. Which laws apply to the Agreement?
- The Agreement including these Terms shall be governed by German law. The application of the UN Convention on
- Contracts for the International Sale of Goods is excluded. For consumers domiciled in another European country but
- Germany, the mandatory provisions of the consumer protection laws of the member state in which the consumer is
- domiciled shall also apply, provided that these are more advantageous for the consumer than the provisions of the
- German law.
+ The Agreement including these Terms shall in all respects be governed by, and construed and interpreted in
+ accordance with, the law of England and Wales without giving effect to any choice or conflict of law provision or
+ rule.
-
29. How can you get support for Safe Accounts and tell us about any problems?
+
29. How can you get support for Safe Accounts and tell us about any problems?
If you want to learn more about Safe Accounts or the Service or have any problems using them or have any
complaints please get in touch via any of the following channels:
-
-
- Intercom:{' '}
-
-
- {HELP_CENTER_URL}
-
-
-
-
- Discord:{' '}
-
-
- {DISCORD_URL}
-
-
-
-
- Twitter:{' '}
-
-
- {TWITTER_URL}
-
-
-
-
+
+ Email: safe-support@protofire.io
+
+
+ Report form: https://safe-support.protofire.io/
+
-
30. Where is the place of legal proceedings?
+
30. Where is the place of legal proceedings?
- For users who are merchants within the meaning of the German Commercial Code (Handelsgesetzbuch), a special fund
- (Sondervermögen) under public law or a legal person under public law, Berlin shall be the exclusive place of
- jurisdiction for all disputes arising from the contractual relationship.
+ Any dispute arising out of or in connection with this Agreement, including any question regarding its existence,
+ validity or termination, shall be referred to and finally resolved by arbitration under the London Court of
+ International Arbitration (LCIA) Rules, which Rules are deemed to be incorporated by reference to this Clause. The
+ number of arbitrators shall be one. The seat, or legal place, of arbitration shall be London, England. The
+ language to be used in the arbitral proceedings shall be English.
-
31. Is this all?
+
31. Is this all?
- These Terms constitute the entire agreement between you and us in relation to the Agreement’s subject
- matter. It replaces and extinguishes any and all prior agreements, draft agreements, arrangements, warranties,
- statements, assurances, representations and undertakings of any nature made by, or on behalf of either of us,
- whether oral or written, public or private, in relation to that subject matter.
+ These Terms constitute the entire agreement between you and us in relation to the Agreementโs subject matter. It
+ replaces and extinguishes any and all prior agreements, draft agreements, arrangements, warranties, statements,
+ assurances, representations and undertakings of any nature made by, or on behalf of either of us, whether oral or
+ written, public or private, in relation to that subject matter.
)
@@ -831,10 +591,10 @@ const Terms: NextPage = () => {
return (
<>
- {'Rootstock Safe โ Terms'}
+ {'Rootstock Wallet โ Terms'}
- {IS_OFFICIAL_HOST && }
+ { }
>
)
}
diff --git a/src/services/analytics/__tests__/tx-tracking.test.ts b/src/services/analytics/__tests__/tx-tracking.test.ts
index d8c58b321..90e5d6492 100644
--- a/src/services/analytics/__tests__/tx-tracking.test.ts
+++ b/src/services/analytics/__tests__/tx-tracking.test.ts
@@ -1,176 +1,177 @@
-import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
-import { SettingsInfoType, TransactionInfoType, TransactionTokenType } from '@safe-global/safe-gateway-typescript-sdk'
+import {
+ SettingsInfoType,
+ type TransactionDetails,
+ TransactionInfoType,
+ TransactionTokenType,
+} from '@safe-global/safe-gateway-typescript-sdk'
import { getTransactionTrackingType } from '../tx-tracking'
-import * as txDetailsModule from '@/services/transactions'
import { TX_TYPES } from '../events/transactions'
-const getMockTxType = (txDetails: unknown) => {
- jest.spyOn(txDetailsModule, 'getTxDetails').mockImplementation(() => Promise.resolve(txDetails as TransactionDetails))
- return getTransactionTrackingType('1', '0x123')
-}
-
describe('getTransactionTrackingType', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
it('should return transfer_token for native token transfers', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.TRANSFER,
transferInfo: {
type: TransactionTokenType.NATIVE_COIN,
},
},
- } as unknown)
-
+ } as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.transfer_token)
})
it('should return transfer_token for ERC20 token transfers', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.TRANSFER,
transferInfo: {
type: TransactionTokenType.ERC20,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.transfer_token)
})
it('should return transfer_nft for ERC721 token transfers', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.TRANSFER,
transferInfo: {
type: TransactionTokenType.ERC721,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.transfer_nft)
})
it('should return owner_add for add owner settings changes', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.SETTINGS_CHANGE,
settingsInfo: {
type: SettingsInfoType.ADD_OWNER,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.owner_add)
})
it('should return owner_remove for remove owner settings changes', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.SETTINGS_CHANGE,
settingsInfo: {
type: SettingsInfoType.REMOVE_OWNER,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.owner_remove)
})
it('should return owner_swap for swap owner settings changes', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.SETTINGS_CHANGE,
settingsInfo: {
type: SettingsInfoType.SWAP_OWNER,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.owner_swap)
})
it('should return owner_threshold_change for change threshold settings changes', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.SETTINGS_CHANGE,
settingsInfo: {
type: SettingsInfoType.CHANGE_THRESHOLD,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.owner_threshold_change)
})
it('should return module_remove for disable module settings changes', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.SETTINGS_CHANGE,
settingsInfo: {
type: SettingsInfoType.DISABLE_MODULE,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.module_remove)
})
it('should return guard_remove for delete guard settings changes', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.SETTINGS_CHANGE,
settingsInfo: {
type: SettingsInfoType.DELETE_GUARD,
},
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.guard_remove)
})
it('should return rejection for rejection transactions', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.CUSTOM,
isCancellation: true,
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.rejection)
})
it('should return walletconnect for transactions w/o safeAppInfo', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.CUSTOM,
},
safeAppInfo: null,
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.walletconnect)
})
it('should return safeapps for safeapps transactions', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.CUSTOM,
},
safeAppInfo: {
url: 'https://gnosis-safe.io/app',
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual('https://gnosis-safe.io/app')
})
it('should return batch for multisend transactions', async () => {
- const txType = await getMockTxType({
+ const details = {
txInfo: {
type: TransactionInfoType.CUSTOM,
methodName: 'multiSend',
actionCount: 2,
},
- } as unknown)
-
+ } as unknown as TransactionDetails
+ const txType = getTransactionTrackingType(details)
expect(txType).toEqual(TX_TYPES.batch)
})
})
diff --git a/src/services/analytics/events/modals.ts b/src/services/analytics/events/modals.ts
index e90fb9d33..cba11054e 100644
--- a/src/services/analytics/events/modals.ts
+++ b/src/services/analytics/events/modals.ts
@@ -53,6 +53,11 @@ export const MODALS_EVENTS = {
category: MODALS_CATEGORY,
event: EventType.META,
},
+ BLOCKAID_RESULT: {
+ action: 'Blockaid scan result',
+ category: MODALS_CATEGORY,
+ event: EventType.META,
+ },
OPEN_SPEED_UP_MODAL: {
action: 'Open speed-up modal',
category: MODALS_CATEGORY,
diff --git a/src/services/analytics/events/overview.ts b/src/services/analytics/events/overview.ts
index b24f4d733..dd5e8647d 100644
--- a/src/services/analytics/events/overview.ts
+++ b/src/services/analytics/events/overview.ts
@@ -53,6 +53,10 @@ export const OVERVIEW_EVENTS = {
action: "Open What's New",
category: OVERVIEW_CATEGORY,
},
+ SUGGESTIONS: {
+ action: 'Suggest any new Feature',
+ category: OVERVIEW_CATEGORY,
+ },
HELP_CENTER: {
action: 'Open Help Center',
category: OVERVIEW_CATEGORY,
@@ -141,10 +145,17 @@ export const OVERVIEW_EVENTS = {
category: OVERVIEW_CATEGORY,
},
OPEN_ACTIVITY_APP: {
- event: EventType.CLICK,
action: 'Open activity app from widget',
category: OVERVIEW_CATEGORY,
},
+ HIDE_ACTIVITY_APP_WIDGET: {
+ action: 'Hide activity app widget',
+ category: OVERVIEW_CATEGORY,
+ },
+ OPEN_LEARN_MORE_ACTIVITY_APP: {
+ action: 'Activity app learn more',
+ category: OVERVIEW_CATEGORY,
+ },
}
export enum OPEN_SAFE_LABELS {
diff --git a/src/services/analytics/events/recovery.ts b/src/services/analytics/events/recovery.ts
index b6acc7b69..478aa9313 100644
--- a/src/services/analytics/events/recovery.ts
+++ b/src/services/analytics/events/recovery.ts
@@ -12,22 +12,22 @@ export const RECOVERY_EVENTS = {
SETUP_RECOVERY: {
action: 'Start recovery setup',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
SELECT_RECOVERY_METHOD: {
action: 'Select recovery method',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
CONTINUE_WITH_RECOVERY: {
action: 'Continue with recovery method',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
CONTINUE_TO_WAITLIST: {
action: 'Continue to waitlist',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
+ },
+ SYGNUM_APP: {
+ action: 'Go to Sygnum app',
+ category: RECOVERY_CATEGORY,
},
RECOVERY_SETTINGS: {
action: 'Recovery settings',
@@ -37,42 +37,34 @@ export const RECOVERY_EVENTS = {
EDIT_RECOVERY: {
action: 'Start edit recovery',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
REMOVE_RECOVERY: {
action: 'Start recovery removal',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
START_RECOVERY: {
action: 'Start recovery proposal',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
CANCEL_RECOVERY: {
action: 'Start recovery cancellation',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
SHOW_ADVANCED: {
action: 'Show advanced recovery settings',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
DISMISS_PROPOSAL_CARD: {
action: 'Dismiss recovery proposal card',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
LEARN_MORE: {
action: 'Recovery info click',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
GO_BACK: {
action: 'Recovery cancellation back',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
GIVE_US_FEEDBACK: {
action: 'Recovery feedback click',
@@ -82,7 +74,6 @@ export const RECOVERY_EVENTS = {
CHECK_RECOVERY_PROPOSAL: {
action: 'Check recovery proposal',
category: RECOVERY_CATEGORY,
- event: EventType.CLICK,
},
SUBMIT_RECOVERY_CREATE: {
action: 'Submit recovery setup',
diff --git a/src/services/analytics/events/settings.ts b/src/services/analytics/events/settings.ts
index 1ed6294c0..0b1518ef1 100644
--- a/src/services/analytics/events/settings.ts
+++ b/src/services/analytics/events/settings.ts
@@ -87,6 +87,10 @@ export const SETTINGS_EVENTS = {
action: 'Imported Safe apps via Import all',
category: SETTINGS_CATEGORY,
},
+ IMPORT_UNDEPLOYED_SAFES: {
+ action: 'Imported counterfactual safes via Import all',
+ category: SETTINGS_CATEGORY,
+ },
},
ENV_VARIABLES: {
SAVE: {
diff --git a/src/services/analytics/events/stake.ts b/src/services/analytics/events/stake.ts
new file mode 100644
index 000000000..675493948
--- /dev/null
+++ b/src/services/analytics/events/stake.ts
@@ -0,0 +1,14 @@
+const STAKE_CATEGORY = 'stake'
+
+export const STAKE_EVENTS = {
+ OPEN_STAKE: {
+ action: 'Open stake',
+ category: STAKE_CATEGORY,
+ },
+}
+
+export enum STAKE_LABELS {
+ dashboard = 'dashboard',
+ sidebar = 'sidebar',
+ asset = 'asset',
+}
diff --git a/src/services/analytics/events/transactions.ts b/src/services/analytics/events/transactions.ts
index 74f83b9c7..1a4a49ce3 100644
--- a/src/services/analytics/events/transactions.ts
+++ b/src/services/analytics/events/transactions.ts
@@ -38,6 +38,21 @@ export const TX_EVENTS = {
category: TX_CATEGORY,
// label: TX_TYPES,
},
+ CREATE_VIA_ROLE: {
+ event: EventType.TX_CREATED,
+ action: 'Create via role',
+ category: TX_CATEGORY,
+ },
+ CREATE_VIA_SPENDING_LIMTI: {
+ event: EventType.TX_CREATED,
+ action: 'Create via spending limit',
+ category: TX_CATEGORY,
+ },
+ CREATE_VIA_DELEGATE: {
+ event: EventType.TX_CREATED,
+ action: 'Create via delegate',
+ category: TX_CATEGORY,
+ },
CONFIRM: {
event: EventType.TX_CONFIRMED,
action: 'Confirm transaction',
@@ -53,9 +68,14 @@ export const TX_EVENTS = {
action: 'Speed up transaction',
category: TX_CATEGORY,
},
- EXECUTE_THROUGH_ROLE: {
- event: EventType.TX_EXECUTED_THROUGH_ROLE,
- action: 'Execute transaction through role',
+ EXECUTE_VIA_SPENDING_LIMIT: {
+ event: EventType.TX_EXECUTED,
+ action: 'Execute via spending limit',
+ category: TX_CATEGORY,
+ },
+ EXECUTE_VIA_ROLE: {
+ event: EventType.TX_EXECUTED,
+ action: 'Execute via role',
category: TX_CATEGORY,
},
}
diff --git a/src/services/analytics/events/txList.ts b/src/services/analytics/events/txList.ts
index 12b0ea586..159b33df1 100644
--- a/src/services/analytics/events/txList.ts
+++ b/src/services/analytics/events/txList.ts
@@ -54,6 +54,11 @@ export const TX_LIST_EVENTS = {
category: TX_LIST_CATEGORY,
event: EventType.CLICK,
},
+ COPY_WARNING_CLOSE: {
+ action: 'Do not copy address',
+ category: TX_LIST_CATEGORY,
+ event: EventType.CLICK,
+ },
TOGGLE_UNTRUSTED: {
action: 'Toggle untrusted transactions',
category: TX_LIST_CATEGORY,
diff --git a/src/services/analytics/tx-tracking.ts b/src/services/analytics/tx-tracking.ts
index 0f9af3359..b1397a664 100644
--- a/src/services/analytics/tx-tracking.ts
+++ b/src/services/analytics/tx-tracking.ts
@@ -1,5 +1,4 @@
import { TX_TYPES } from '@/services/analytics/events/transactions'
-import { getTxDetails } from '@/services/transactions'
import { SettingsInfoType, type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import {
isERC721Transfer,
@@ -9,14 +8,11 @@ import {
isCustomTxInfo,
isCancellationTxInfo,
isSwapOrderTxInfo,
+ isAnyStakingTxInfo,
} from '@/utils/transaction-guards'
-export const getTransactionTrackingType = async (chainId: string, txId: string): Promise => {
- let details: TransactionDetails
-
- try {
- details = await getTxDetails(chainId, txId)
- } catch {
+export const getTransactionTrackingType = (details: TransactionDetails | undefined): string => {
+ if (!details) {
return TX_TYPES.custom
}
@@ -33,6 +29,10 @@ export const getTransactionTrackingType = async (chainId: string, txId: string):
return TX_TYPES.native_swap
}
+ if (isAnyStakingTxInfo(txInfo)) {
+ return txInfo.type
+ }
+
if (isSettingsChangeTxInfo(txInfo)) {
switch (txInfo.settingsInfo?.type) {
case SettingsInfoType.ADD_OWNER: {
diff --git a/src/services/analytics/types.ts b/src/services/analytics/types.ts
index 781615ffb..855b79140 100644
--- a/src/services/analytics/types.ts
+++ b/src/services/analytics/types.ts
@@ -13,7 +13,6 @@ export enum EventType {
TX_CREATED = 'tx_created',
TX_CONFIRMED = 'tx_confirmed',
TX_EXECUTED = 'tx_executed',
- TX_EXECUTED_THROUGH_ROLE = 'tx_executed_through_role',
}
export type EventLabel = string | number | boolean | null
diff --git a/src/services/analytics/useGtm.ts b/src/services/analytics/useGtm.ts
index 95f546f6d..91fcffda1 100644
--- a/src/services/analytics/useGtm.ts
+++ b/src/services/analytics/useGtm.ts
@@ -16,7 +16,7 @@ import {
} from '@/services/analytics/gtm'
import { spindlInit, spindlAttribute } from './spindl'
import { useAppSelector } from '@/store'
-import { CookieAndTermType, selectCookies } from '@/store/cookiesAndTermsSlice'
+import { CookieAndTermType, hasConsentFor } from '@/store/cookiesAndTermsSlice'
import useChainId from '@/hooks/useChainId'
import { useRouter } from 'next/router'
import { AppRoutes } from '@/config/routes'
@@ -29,8 +29,7 @@ import { OVERVIEW_EVENTS } from './events'
const useGtm = () => {
const chainId = useChainId()
- const cookies = useAppSelector(selectCookies)
- const isAnalyticsEnabled = cookies[CookieAndTermType.ANALYTICS] || false
+ const isAnalyticsEnabled = useAppSelector((state) => hasConsentFor(state, CookieAndTermType.ANALYTICS))
const [, setPrevAnalytics] = useState(isAnalyticsEnabled)
const router = useRouter()
const theme = useTheme()
diff --git a/src/services/contracts/__tests__/deployments.test.ts b/src/services/contracts/__tests__/deployments.test.ts
index dc194418f..209250ee4 100644
--- a/src/services/contracts/__tests__/deployments.test.ts
+++ b/src/services/contracts/__tests__/deployments.test.ts
@@ -1,8 +1,24 @@
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import * as safeDeployments from '@safe-global/safe-deployments'
-import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import * as deployments from '../deployments'
+import { FEATURES, getLatestSafeVersion } from '@/utils/chains'
+import { chainBuilder } from '@/tests/builders/chains'
+
+const mainnetInfo = chainBuilder()
+ .with({ chainId: '1', features: [FEATURES.SAFE_141 as any], l2: false })
+ .build()
+const l2ChainInfo = chainBuilder()
+ .with({ chainId: '137', features: [FEATURES.SAFE_141 as any], l2: true })
+ .build()
+const unsupportedChainInfo = chainBuilder()
+ .with({ chainId: '69420', features: [FEATURES.SAFE_141 as any], l2: false })
+ .build()
+
+const unsupportedL2ChainInfo = chainBuilder()
+ .with({ chainId: '69420', features: [FEATURES.SAFE_141 as any], l2: true })
+ .build()
+const latestSafeVersion = getLatestSafeVersion(mainnetInfo)
describe('deployments', () => {
beforeEach(() => {
@@ -15,7 +31,7 @@ describe('deployments', () => {
it('should call the deployment getter with a supported version/network', () => {
deployments._tryDeploymentVersions(
getSafeSpy as unknown as typeof safeDeployments.getSafeSingletonDeployment,
- '1',
+ mainnetInfo,
'1.1.1',
)
@@ -30,7 +46,7 @@ describe('deployments', () => {
it('should call the deployment getter with a supported version/unsupported network', () => {
deployments._tryDeploymentVersions(
getSafeSpy as unknown as typeof safeDeployments.getSafeSingletonDeployment,
- '69420',
+ unsupportedChainInfo,
'1.1.1',
)
@@ -45,7 +61,7 @@ describe('deployments', () => {
it('should call the deployment getter with an unsupported version/unsupported', () => {
deployments._tryDeploymentVersions(
getSafeSpy as unknown as typeof safeDeployments.getSafeSingletonDeployment,
- '69420',
+ unsupportedChainInfo,
'1.2.3',
)
@@ -60,14 +76,14 @@ describe('deployments', () => {
it('should call the deployment getter with the latest version/supported network if no version is provider', () => {
deployments._tryDeploymentVersions(
getSafeSpy as unknown as typeof safeDeployments.getSafeSingletonDeployment,
- '1',
+ mainnetInfo,
null,
)
- expect(getSafeSpy).toHaveBeenCalledTimes(1)
+ expect(getSafeSpy).toHaveBeenCalledTimes(2)
- expect(getSafeSpy).toHaveBeenNthCalledWith(1, {
- version: '1.3.0',
+ expect(getSafeSpy).toHaveBeenNthCalledWith(2, {
+ version: '1.4.1',
network: '1',
})
})
@@ -75,15 +91,15 @@ describe('deployments', () => {
it('should call the deployment getter with the latest version/unsupported network if no version is provider', () => {
deployments._tryDeploymentVersions(
getSafeSpy as unknown as typeof safeDeployments.getSafeSingletonDeployment,
- '69420',
+ mainnetInfo,
null,
)
- expect(getSafeSpy).toHaveBeenCalledTimes(1)
+ expect(getSafeSpy).toHaveBeenCalledTimes(2)
- expect(getSafeSpy).toHaveBeenNthCalledWith(1, {
- network: '69420',
- version: '1.3.0',
+ expect(getSafeSpy).toHaveBeenNthCalledWith(2, {
+ network: '1',
+ version: '1.4.1',
})
})
})
@@ -98,7 +114,7 @@ describe('deployments', () => {
expect(deployments._isLegacy('1.1.1')).toBe(false)
expect(deployments._isLegacy('1.2.0')).toBe(false)
expect(deployments._isLegacy('1.3.0')).toBe(false)
- expect(deployments._isLegacy(LATEST_SAFE_VERSION)).toBe(false)
+ expect(deployments._isLegacy('1.4.1')).toBe(false)
})
it('should return false for unsupported versions', () => {
@@ -110,8 +126,7 @@ describe('deployments', () => {
it('should return true for L2 versions', () => {
expect(deployments._isL2({ l2: true } as ChainInfo, '1.3.0')).toBe(true)
expect(deployments._isL2({ l2: true } as ChainInfo, '1.3.0+L2')).toBe(true)
- expect(deployments._isL2({ l2: true } as ChainInfo, LATEST_SAFE_VERSION)).toBe(true)
- expect(deployments._isL2({ l2: true } as ChainInfo, `${LATEST_SAFE_VERSION}+L2`)).toBe(true)
+ expect(deployments._isL2({ l2: true } as ChainInfo, '1.4.1+L2')).toBe(true)
})
it('should return true for unsupported L2 versions', () => {
@@ -123,7 +138,7 @@ describe('deployments', () => {
expect(deployments._isL2({ l2: false } as ChainInfo, '1.1.1')).toBe(false)
expect(deployments._isL2({ l2: false } as ChainInfo, '1.2.0')).toBe(false)
expect(deployments._isL2({ l2: false } as ChainInfo, '1.3.0')).toBe(false)
- expect(deployments._isL2({ l2: false } as ChainInfo, LATEST_SAFE_VERSION)).toBe(false)
+ expect(deployments._isL2({ l2: false } as ChainInfo, '1.4.1+L2')).toBe(false)
})
})
@@ -136,12 +151,12 @@ describe('deployments', () => {
})
expect(expected).toBeDefined()
- const deployment = deployments.getSafeContractDeployment({ chainId: '1' } as ChainInfo, '1.1.1')
+ const deployment = deployments.getSafeContractDeployment(mainnetInfo, '1.1.1')
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for supported version/unsupported chain', () => {
- const deployment = deployments.getSafeContractDeployment({ chainId: '69420' } as ChainInfo, '1.1.1')
+ const deployment = deployments.getSafeContractDeployment(unsupportedChainInfo, '1.1.1')
expect(deployment).toBe(undefined)
})
@@ -151,7 +166,7 @@ describe('deployments', () => {
})
expect(expected).toBeDefined()
- const deployment = deployments.getSafeContractDeployment({ chainId: '1' } as ChainInfo, '0.0.1')
+ const deployment = deployments.getSafeContractDeployment(mainnetInfo, '0.0.1')
expect(deployment).toStrictEqual(expected)
})
@@ -161,28 +176,28 @@ describe('deployments', () => {
})
expect(expected).toBeDefined()
- const deployment = deployments.getSafeContractDeployment({ chainId: '69420' } as ChainInfo, '0.0.1')
+ const deployment = deployments.getSafeContractDeployment(unsupportedChainInfo, '0.0.1')
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for unsupported version/chain', () => {
- const deployment = deployments.getSafeContractDeployment({ chainId: '69420' } as ChainInfo, '1.2.3')
+ const deployment = deployments.getSafeContractDeployment(unsupportedChainInfo, '1.2.3')
expect(deployment).toStrictEqual(undefined)
})
it('should return the latest deployment for no version/supported chain', () => {
const expected = safeDeployments.getSafeSingletonDeployment({
- version: LATEST_SAFE_VERSION,
+ version: latestSafeVersion,
network: '1',
})
expect(expected).toBeDefined()
- const deployment = deployments.getSafeContractDeployment({ chainId: '1' } as ChainInfo, null)
+ const deployment = deployments.getSafeContractDeployment(mainnetInfo, null)
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for no version/unsupported chain', () => {
- const deployment = deployments.getSafeContractDeployment({ chainId: '69420' } as ChainInfo, null)
+ const deployment = deployments.getSafeContractDeployment(unsupportedChainInfo, null)
expect(deployment).toBe(undefined)
})
})
@@ -191,42 +206,37 @@ describe('deployments', () => {
it('should return the versioned deployment for supported version/chain', () => {
const expected = safeDeployments.getSafeL2SingletonDeployment({
version: '1.3.0', // First available version
- network: '1',
+ network: '137',
})
expect(expected).toBeDefined()
- const deployment = deployments.getSafeContractDeployment({ chainId: '1', l2: true } as ChainInfo, '1.3.0')
+ const deployment = deployments.getSafeContractDeployment(l2ChainInfo, '1.3.0')
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for supported version/unsupported chain', () => {
- const deployment = deployments.getSafeContractDeployment({ chainId: '69420', l2: true } as ChainInfo, '1.3.0')
+ const deployment = deployments.getSafeContractDeployment(unsupportedL2ChainInfo, '1.3.0')
expect(deployment).toBe(undefined)
})
it('should return undefined for unsupported version/chain', () => {
- const expected = safeDeployments.getSafeSingletonDeployment({
- version: LATEST_SAFE_VERSION,
- })
-
- expect(expected).toBeDefined()
- const deployment = deployments.getSafeContractDeployment({ chainId: '69420' } as ChainInfo, '1.2.3')
+ const deployment = deployments.getSafeContractDeployment(unsupportedL2ChainInfo, '1.2.3')
expect(deployment).toBe(undefined)
})
it('should return the latest deployment for no version/supported chain', () => {
const expected = safeDeployments.getSafeL2SingletonDeployment({
- version: LATEST_SAFE_VERSION,
- network: '1',
+ version: latestSafeVersion,
+ network: '137',
})
expect(expected).toBeDefined()
- const deployment = deployments.getSafeContractDeployment({ chainId: '1', l2: true } as ChainInfo, null)
+ const deployment = deployments.getSafeContractDeployment(l2ChainInfo, null)
expect(deployment).toStrictEqual(expected)
})
it('should return undefined no version/unsupported chain', () => {
- const deployment = deployments.getSafeContractDeployment({ chainId: '69420', l2: true } as ChainInfo, null)
+ const deployment = deployments.getSafeContractDeployment(unsupportedL2ChainInfo, null)
expect(deployment).toStrictEqual(undefined)
})
})
@@ -240,33 +250,33 @@ describe('deployments', () => {
})
expect(expected).toBeDefined()
- const deployment = deployments.getMultiSendCallOnlyContractDeployment('1', '1.3.0')
+ const deployment = deployments.getMultiSendCallOnlyContractDeployment(mainnetInfo, '1.3.0')
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for supported version/unsupported chain', () => {
- const deployment = deployments.getMultiSendCallOnlyContractDeployment('69420', '1.3.0')
+ const deployment = deployments.getMultiSendCallOnlyContractDeployment(unsupportedChainInfo, '1.3.0')
expect(deployment).toBe(undefined)
})
it('should return undefined for unsupported version/chain', () => {
- const deployment = deployments.getMultiSendCallOnlyContractDeployment('69420', '1.2.3')
+ const deployment = deployments.getMultiSendCallOnlyContractDeployment(unsupportedChainInfo, '1.2.3')
expect(deployment).toBe(undefined)
})
it('should return the latest deployment for no version/supported chain', () => {
const expected = safeDeployments.getMultiSendCallOnlyDeployment({
- version: LATEST_SAFE_VERSION,
+ version: latestSafeVersion,
network: '1',
})
expect(expected).toBeDefined()
- const deployment = deployments.getMultiSendCallOnlyContractDeployment('1', null)
+ const deployment = deployments.getMultiSendCallOnlyContractDeployment(mainnetInfo, null)
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for no version/unsupported chain', () => {
- const deployment = deployments.getMultiSendCallOnlyContractDeployment('69420', null)
+ const deployment = deployments.getMultiSendCallOnlyContractDeployment(unsupportedChainInfo, null)
expect(deployment).toBe(undefined)
})
})
@@ -279,33 +289,33 @@ describe('deployments', () => {
})
expect(expected).toBeDefined()
- const deployment = deployments.getFallbackHandlerContractDeployment('1', '1.3.0')
+ const deployment = deployments.getFallbackHandlerContractDeployment(mainnetInfo, '1.3.0')
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for supported version/unsupported chain', () => {
- const deployment = deployments.getFallbackHandlerContractDeployment('69420', '1.3.0')
+ const deployment = deployments.getFallbackHandlerContractDeployment(unsupportedChainInfo, '1.3.0')
expect(deployment).toBe(undefined)
})
it('should return undefined for unsupported version/chain', () => {
- const deployment = deployments.getFallbackHandlerContractDeployment('69420', '1.2.3')
+ const deployment = deployments.getFallbackHandlerContractDeployment(unsupportedChainInfo, '1.2.3')
expect(deployment).toBe(undefined)
})
it('should return the latest deployment for no version/supported chain', () => {
const expected = safeDeployments.getFallbackHandlerDeployment({
- version: LATEST_SAFE_VERSION,
+ version: latestSafeVersion,
network: '1',
})
expect(expected).toBeDefined()
- const deployment = deployments.getFallbackHandlerContractDeployment('1', null)
+ const deployment = deployments.getFallbackHandlerContractDeployment(mainnetInfo, null)
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for no version/unsupported chain', () => {
- const deployment = deployments.getFallbackHandlerContractDeployment('69420', null)
+ const deployment = deployments.getFallbackHandlerContractDeployment(unsupportedChainInfo, null)
expect(deployment).toBe(undefined)
})
})
@@ -318,33 +328,33 @@ describe('deployments', () => {
})
expect(expected).toBeDefined()
- const deployment = deployments.getProxyFactoryContractDeployment('1', '1.1.1')
+ const deployment = deployments.getProxyFactoryContractDeployment(mainnetInfo, '1.1.1')
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for supported version/unsupported chain', () => {
- const deployment = deployments.getProxyFactoryContractDeployment('69420', '1.1.1')
+ const deployment = deployments.getProxyFactoryContractDeployment(unsupportedChainInfo, '1.1.1')
expect(deployment).toBe(undefined)
})
it('should return undefined for unsupported version/chain', () => {
- const deployment = deployments.getProxyFactoryContractDeployment('69420', '1.2.3')
+ const deployment = deployments.getProxyFactoryContractDeployment(unsupportedChainInfo, '1.2.3')
expect(deployment).toBe(undefined)
})
it('should return the latest deployment for no version/supported chain', () => {
const expected = safeDeployments.getProxyFactoryDeployment({
- version: LATEST_SAFE_VERSION,
+ version: latestSafeVersion,
network: '1',
})
expect(expected).toBeDefined()
- const deployment = deployments.getProxyFactoryContractDeployment('1', null)
+ const deployment = deployments.getProxyFactoryContractDeployment(mainnetInfo, null)
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for no version/unsupported chain', () => {
- const deployment = deployments.getProxyFactoryContractDeployment('69420', null)
+ const deployment = deployments.getProxyFactoryContractDeployment(unsupportedChainInfo, null)
expect(deployment).toBe(undefined)
})
})
@@ -357,33 +367,33 @@ describe('deployments', () => {
})
expect(expected).toBeDefined()
- const deployment = deployments.getSignMessageLibContractDeployment('1', '1.3.0')
+ const deployment = deployments.getSignMessageLibContractDeployment(mainnetInfo, '1.3.0')
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for supported version/unsupported chain', () => {
- const deployment = deployments.getSignMessageLibContractDeployment('69420', '1.3.0')
+ const deployment = deployments.getSignMessageLibContractDeployment(unsupportedChainInfo, '1.3.0')
expect(deployment).toBe(undefined)
})
it('should return undefined for unsupported version/chain', () => {
- const deployment = deployments.getSignMessageLibContractDeployment('69420', '1.2.3')
+ const deployment = deployments.getSignMessageLibContractDeployment(unsupportedChainInfo, '1.2.3')
expect(deployment).toBe(undefined)
})
it('should return the latest deployment for no version/supported chain', () => {
const expected = safeDeployments.getSignMessageLibDeployment({
- version: LATEST_SAFE_VERSION,
+ version: latestSafeVersion,
network: '1',
})
expect(expected).toBeDefined()
- const deployment = deployments.getSignMessageLibContractDeployment('1', null)
+ const deployment = deployments.getSignMessageLibContractDeployment(mainnetInfo, null)
expect(deployment).toStrictEqual(expected)
})
it('should return undefined for no version/unsupported chain', () => {
- const deployment = deployments.getSignMessageLibContractDeployment('69420', null)
+ const deployment = deployments.getSignMessageLibContractDeployment(unsupportedChainInfo, null)
expect(deployment).toBe(undefined)
})
})
diff --git a/src/services/contracts/deployments.ts b/src/services/contracts/deployments.ts
index 181b8ff5b..cfec7b48b 100644
--- a/src/services/contracts/deployments.ts
+++ b/src/services/contracts/deployments.ts
@@ -10,27 +10,26 @@ import {
} from '@safe-global/safe-deployments'
import type { SingletonDeployment, DeploymentFilter } from '@safe-global/safe-deployments'
import type { ChainInfo, SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
-
-import { LATEST_SAFE_VERSION } from '@/config/constants'
+import { getLatestSafeVersion } from '@/utils/chains'
export const _tryDeploymentVersions = (
getDeployment: (filter?: DeploymentFilter) => SingletonDeployment | undefined,
- network: string,
+ network: ChainInfo,
version: SafeInfo['version'],
): SingletonDeployment | undefined => {
// Unsupported Safe version
if (version === null) {
// Assume latest version as fallback
return getDeployment({
- version: LATEST_SAFE_VERSION,
- network,
+ version: getLatestSafeVersion(network),
+ network: network.chainId,
})
}
// Supported Safe version
return getDeployment({
version,
- network,
+ network: network.chainId,
})
}
@@ -62,25 +61,25 @@ export const getSafeContractDeployment = (
const getDeployment = _isL2(chain, safeVersion) ? getSafeL2SingletonDeployment : getSafeSingletonDeployment
- return _tryDeploymentVersions(getDeployment, chain.chainId, safeVersion)
+ return _tryDeploymentVersions(getDeployment, chain, safeVersion)
}
-export const getMultiSendCallOnlyContractDeployment = (chainId: string, safeVersion: SafeInfo['version']) => {
- return _tryDeploymentVersions(getMultiSendCallOnlyDeployment, chainId, safeVersion)
+export const getMultiSendCallOnlyContractDeployment = (chain: ChainInfo, safeVersion: SafeInfo['version']) => {
+ return _tryDeploymentVersions(getMultiSendCallOnlyDeployment, chain, safeVersion)
}
-export const getFallbackHandlerContractDeployment = (chainId: string, safeVersion: SafeInfo['version']) => {
- return _tryDeploymentVersions(getFallbackHandlerDeployment, chainId, safeVersion)
+export const getFallbackHandlerContractDeployment = (chain: ChainInfo, safeVersion: SafeInfo['version']) => {
+ return _tryDeploymentVersions(getFallbackHandlerDeployment, chain, safeVersion)
}
-export const getProxyFactoryContractDeployment = (chainId: string, safeVersion: SafeInfo['version']) => {
- return _tryDeploymentVersions(getProxyFactoryDeployment, chainId, safeVersion)
+export const getProxyFactoryContractDeployment = (chain: ChainInfo, safeVersion: SafeInfo['version']) => {
+ return _tryDeploymentVersions(getProxyFactoryDeployment, chain, safeVersion)
}
-export const getSignMessageLibContractDeployment = (chainId: string, safeVersion: SafeInfo['version']) => {
- return _tryDeploymentVersions(getSignMessageLibDeployment, chainId, safeVersion)
+export const getSignMessageLibContractDeployment = (chain: ChainInfo, safeVersion: SafeInfo['version']) => {
+ return _tryDeploymentVersions(getSignMessageLibDeployment, chain, safeVersion)
}
-export const getCreateCallContractDeployment = (chainId: string, safeVersion: SafeInfo['version']) => {
- return _tryDeploymentVersions(getCreateCallDeployment, chainId, safeVersion)
+export const getCreateCallContractDeployment = (chain: ChainInfo, safeVersion: SafeInfo['version']) => {
+ return _tryDeploymentVersions(getCreateCallDeployment, chain, safeVersion)
}
diff --git a/src/services/contracts/safeContracts.ts b/src/services/contracts/safeContracts.ts
index 2a99c4ba2..035240948 100644
--- a/src/services/contracts/safeContracts.ts
+++ b/src/services/contracts/safeContracts.ts
@@ -1,20 +1,20 @@
+import { _isL2 } from '@/services/contracts/deployments'
+import { getSafeProvider } from '@/services/tx/tx-sender/sdk'
+import { type GetContractProps, SafeProvider } from '@safe-global/protocol-kit'
import {
- getFallbackHandlerContractDeployment,
- getMultiSendCallOnlyContractDeployment,
- getProxyFactoryContractDeployment,
- getSafeContractDeployment,
- getSignMessageLibContractDeployment,
-} from './deployments'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
-import { ImplementationVersionState } from '@safe-global/safe-gateway-typescript-sdk'
-import type { ChainInfo, SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import type { GetContractProps, SafeVersion } from '@safe-global/safe-core-sdk-types'
-import { assertValidSafeVersion, createEthersAdapter, createReadOnlyEthersAdapter } from '@/hooks/coreSDK/safeCoreSDK'
-import type { BrowserProvider } from 'ethers'
-import type { EthersAdapter, SafeContractEthers, SignMessageLibEthersContract } from '@safe-global/protocol-kit'
+ getCompatibilityFallbackHandlerContractInstance,
+ getMultiSendCallOnlyContractInstance,
+ getSafeContractInstance,
+ getSafeProxyFactoryContractInstance,
+ getSignMessageLibContractInstance,
+} from '@safe-global/protocol-kit/dist/src/contracts/contractInstances'
+import type SafeBaseContract from '@safe-global/protocol-kit/dist/src/contracts/Safe/SafeBaseContract'
+import { type ChainInfo, ImplementationVersionState } from '@safe-global/safe-gateway-typescript-sdk'
+import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import type { SafeVersion } from '@safe-global/safe-core-sdk-types'
+import { assertValidSafeVersion, getSafeSDK } from '@/hooks/coreSDK/safeCoreSDK'
import semver from 'semver'
-
-import type CompatibilityFallbackHandlerEthersContract from '@safe-global/protocol-kit/dist/src/adapters/ethers/contracts/CompatibilityFallbackHandler/CompatibilityFallbackHandlerEthersContract'
+import { getLatestSafeVersion } from '@/utils/chains'
// `UNKNOWN` is returned if the mastercopy does not match supported ones
// @see https://github.com/safe-global/safe-client-gateway/blob/main/src/routes/safes/handlers/safes.rs#L28-L31
@@ -39,33 +39,45 @@ export const _getValidatedGetContractProps = (
// GnosisSafe
-const getGnosisSafeContractEthers = async (safe: SafeInfo, ethAdapter: EthersAdapter): Promise => {
- return ethAdapter.getSafeContract({
- customContractAddress: safe.address.value,
- ..._getValidatedGetContractProps(safe.version),
- })
+const getGnosisSafeContract = async (safe: SafeInfo, safeProvider: SafeProvider) => {
+ return getSafeContractInstance(
+ _getValidatedGetContractProps(safe.version).safeVersion,
+ safeProvider,
+ safe.address.value,
+ )
}
-export const getReadOnlyCurrentGnosisSafeContract = async (safe: SafeInfo): Promise => {
- const ethAdapter = createReadOnlyEthersAdapter()
- return getGnosisSafeContractEthers(safe, ethAdapter)
+export const getReadOnlyCurrentGnosisSafeContract = async (safe: SafeInfo): Promise> => {
+ const safeSDK = getSafeSDK()
+ if (!safeSDK) {
+ throw new Error('Safe SDK not found.')
+ }
+
+ const safeProvider = safeSDK.getSafeProvider()
+
+ return getGnosisSafeContract(safe, safeProvider)
}
-export const getCurrentGnosisSafeContract = async (
- safe: SafeInfo,
- provider: BrowserProvider,
-): Promise => {
- const ethAdapter = await createEthersAdapter(provider)
- return getGnosisSafeContractEthers(safe, ethAdapter)
+export const getCurrentGnosisSafeContract = async (safe: SafeInfo, provider: string) => {
+ const safeProvider = new SafeProvider({ provider })
+
+ return getGnosisSafeContract(safe, safeProvider)
}
-export const getReadOnlyGnosisSafeContract = async (chain: ChainInfo, safeVersion: string = LATEST_SAFE_VERSION) => {
- const ethAdapter = createReadOnlyEthersAdapter()
+export const getReadOnlyGnosisSafeContract = async (chain: ChainInfo, safeVersion: SafeInfo['version']) => {
+ const version = safeVersion ?? getLatestSafeVersion(chain)
+
+ const safeProvider = getSafeProvider()
+
+ const isL1SafeSingleton = !_isL2(chain, _getValidatedGetContractProps(version).safeVersion)
- return ethAdapter.getSafeContract({
- singletonDeployment: getSafeContractDeployment(chain, safeVersion),
- ..._getValidatedGetContractProps(safeVersion),
- })
+ return getSafeContractInstance(
+ _getValidatedGetContractProps(version).safeVersion,
+ safeProvider,
+ undefined,
+ undefined,
+ isL1SafeSingleton,
+ )
}
// MultiSend
@@ -80,65 +92,49 @@ export const _getMinimumMultiSendCallOnlyVersion = (safeVersion: SafeInfo['versi
return semver.gte(safeVersion, INITIAL_CALL_ONLY_VERSION) ? safeVersion : INITIAL_CALL_ONLY_VERSION
}
-export const getMultiSendCallOnlyContract = async (
- chainId: string,
- safeVersion: SafeInfo['version'],
- provider: BrowserProvider,
-) => {
- const ethAdapter = await createEthersAdapter(provider)
- const multiSendVersion = _getMinimumMultiSendCallOnlyVersion(safeVersion)
-
- return ethAdapter.getMultiSendCallOnlyContract({
- singletonDeployment: getMultiSendCallOnlyContractDeployment(chainId, multiSendVersion),
- ..._getValidatedGetContractProps(safeVersion),
- })
-}
+export const getReadOnlyMultiSendCallOnlyContract = async (safeVersion: SafeInfo['version']) => {
+ const safeSDK = getSafeSDK()
+ if (!safeSDK) {
+ throw new Error('Safe SDK not found.')
+ }
-export const getReadOnlyMultiSendCallOnlyContract = async (chainId: string, safeVersion: SafeInfo['version']) => {
- const ethAdapter = createReadOnlyEthersAdapter()
- const multiSendVersion = _getMinimumMultiSendCallOnlyVersion(safeVersion)
+ const safeProvider = safeSDK.getSafeProvider()
- return ethAdapter.getMultiSendCallOnlyContract({
- singletonDeployment: getMultiSendCallOnlyContractDeployment(chainId, multiSendVersion),
- ..._getValidatedGetContractProps(safeVersion),
- })
+ return getMultiSendCallOnlyContractInstance(_getValidatedGetContractProps(safeVersion).safeVersion, safeProvider)
}
// GnosisSafeProxyFactory
-export const getReadOnlyProxyFactoryContract = (chainId: string, safeVersion: SafeInfo['version']) => {
- const ethAdapter = createReadOnlyEthersAdapter()
+export const getReadOnlyProxyFactoryContract = async (safeVersion: SafeInfo['version']) => {
+ const safeProvider = getSafeProvider()
- return ethAdapter.getSafeProxyFactoryContract({
- singletonDeployment: getProxyFactoryContractDeployment(chainId, safeVersion),
- ..._getValidatedGetContractProps(safeVersion),
- })
+ return getSafeProxyFactoryContractInstance(
+ _getValidatedGetContractProps(safeVersion).safeVersion,
+ safeProvider,
+ safeProvider.getExternalProvider(),
+ )
}
// Fallback handler
-export const getReadOnlyFallbackHandlerContract = async (
- chainId: string,
- safeVersion: SafeInfo['version'],
-): Promise => {
- const ethAdapter = createReadOnlyEthersAdapter()
+export const getReadOnlyFallbackHandlerContract = async (safeVersion: SafeInfo['version']) => {
+ const safeProvider = getSafeProvider()
- return ethAdapter.getCompatibilityFallbackHandlerContract({
- singletonDeployment: getFallbackHandlerContractDeployment(chainId, safeVersion),
- ..._getValidatedGetContractProps(safeVersion),
- })
+ return getCompatibilityFallbackHandlerContractInstance(
+ _getValidatedGetContractProps(safeVersion).safeVersion,
+ safeProvider,
+ )
}
// Sign messages deployment
-export const getReadOnlySignMessageLibContract = async (
- chainId: string,
- safeVersion: SafeInfo['version'],
-): Promise => {
- const ethAdapter = createReadOnlyEthersAdapter()
+export const getReadOnlySignMessageLibContract = async (safeVersion: SafeInfo['version']) => {
+ const safeSDK = getSafeSDK()
+ if (!safeSDK) {
+ throw new Error('Safe SDK not found.')
+ }
+
+ const safeProvider = safeSDK.getSafeProvider()
- return ethAdapter.getSignMessageLibContract({
- singletonDeployment: getSignMessageLibContractDeployment(chainId, safeVersion),
- ..._getValidatedGetContractProps(safeVersion),
- })
+ return getSignMessageLibContractInstance(_getValidatedGetContractProps(safeVersion).safeVersion, safeProvider)
}
diff --git a/src/services/ens/index.ts b/src/services/ens/index.ts
index 295f1368f..60990a26c 100644
--- a/src/services/ens/index.ts
+++ b/src/services/ens/index.ts
@@ -14,11 +14,6 @@ export function isDomain(domain: string): boolean {
}
export const resolveName = async (rpcProvider: Provider, name: string): Promise => {
- let chainId = ''
- try {
- chainId = (await rpcProvider.getNetwork()).chainId.toString()
- } catch {}
-
try {
return (await rpcProvider.resolveName(name)) || undefined
} catch (e) {
diff --git a/src/services/exceptions/ErrorCodes.ts b/src/services/exceptions/ErrorCodes.ts
index be96e6098..b2b380d7d 100644
--- a/src/services/exceptions/ErrorCodes.ts
+++ b/src/services/exceptions/ErrorCodes.ts
@@ -42,6 +42,8 @@ enum ErrorCodes {
_631 = '631: Transaction failed to be relayed',
_632 = '632: Error fetching relay task status',
_633 = '633: Notification (un-)registration failed',
+ _640 = '640: User account not found',
+ _641 = '641: Error creating user account',
_700 = '700: Failed to read from local/session storage',
_701 = '701: Failed to write to local/session storage',
diff --git a/src/services/ofac/blockedAddressList.json b/src/services/ofac/blockedAddressList.json
deleted file mode 100644
index 2a75c8a50..000000000
--- a/src/services/ofac/blockedAddressList.json
+++ /dev/null
@@ -1,158 +0,0 @@
-[
- "0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c",
- "0x901bb9583b24d97e995513c6778dc6888ab6870e",
- "0xa7e5d5a720f06526557c513402f2e6b5fa20b008",
- "0xd882cfc20f52f2599d84b8e8d58c7fb62cfe344b",
- "0x7f367cc41522ce07553e823bf3be79a889debe1b",
- "0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a",
- "0x7db418b5d567a4e0e8c59ad71be1fce48f3e6107",
- "0x72a5843cc08275c8171e582972aa4fda8c397b2a",
- "0x7f19720a857f834887fc9a7bc0a0fbe7fc7f8102",
- "0x9f4cda013e354b8fc285bf4b9a60460cee7f7ea9",
- "0x2f389ce8bd8ff92de3402ffce4691d17fc4f6535",
- "0x19aa5fe80d33a56d56c78e82ea5e50e5d80b4dff",
- "0xe7aa314c77f4233c18c6cc84384a9247c0cf367b",
- "0x308ed4b7b49797e1a98d3818bff6fe5385410370",
- "0xfec8a60023265364d066a1212fde3930f6ae8da7",
- "0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45",
- "0x6f1ca141a28907f78ebaa64fb83a9088b02a8352",
- "0x6acdfba02d390b97ac2b2d42a63e85293bcc160e",
- "0x48549a34ae37b12f6a30566245176994e17c6b4a",
- "0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0",
- "0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a",
- "0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d",
- "0x7ff9cfad3877f21d41da833e2f775db0569ee3d9",
- "0x098b716b8aaf21512996dc57eb0615e2383e2f96",
- "0xa0e1c89ef1a489c9c7de96311ed5ce5d32c20e4b",
- "0x3cffd56b47b7b41c56258d9c7731abadc360e073",
- "0x53b6936513e738f44fb50d2b9476730c0ab3bfc1",
- "0x35fb6f6db4fb05e6a4ce86f2c93691425626d4b1",
- "0xf7b31119c2682c88d88d455dbb9d5932c65cf1be",
- "0x3e37627deaa754090fbfbb8bd226c1ce66d255e9",
- "0x08723392ed15743cc38513c4925f5e6be5c17243",
- "0x8589427373d6d84e98730d7795d8f6f8731fda16",
- "0x722122df12d4e14e13ac3b6895a86e84145b6967",
- "0xdd4c48c0b24039969fc16d1cdf626eab821d3384",
- "0xd90e2f925da726b50c4ed8d0fb90ad053324f31b",
- "0xd96f2b1c14db8458374d9aca76e26c3d18364307",
- "0x4736dcf1b7a3d580672cce6e7c65cd5cc9cfba9d",
- "0xd4b88df4d29f5cedd6857912842cff3b20c8cfa3",
- "0x910cbd523d972eb0a6f4cae4618ad62622b39dbf",
- "0xa160cdab225685da1d56aa342ad8841c3b53f291",
- "0xfd8610d20aa15b7b2e3be39b396a1bc3516c7144",
- "0xf60dd140cff0706bae9cd734ac3ae76ad9ebc32a",
- "0x22aaa7720ddd5388a3c0a3333430953c68f1849b",
- "0xba214c1c1928a32bffe790263e38b4af9bfcd659",
- "0xb1c8094b234dce6e03f10a5b673c1d8c69739a00",
- "0x527653ea119f3e6a1f5bd18fbf4714081d7b31ce",
- "0x58e8dcc13be9780fc42e8723d8ead4cf46943df2",
- "0xd691f27f38b395864ea86cfc7253969b409c362d",
- "0xaeaac358560e11f52454d997aaff2c5731b6f8a6",
- "0x1356c899d8c9467c7f71c195612f8a395abf2f0a",
- "0xa60c772958a3ed56c1f15dd055ba37ac8e523a0d",
- "0x169ad27a470d064dede56a2d3ff727986b15d52b",
- "0x0836222f2b2b24a3f36f98668ed8f0b38d1a872f",
- "0xf67721a2d8f736e75a49fdd7fad2e31d8676542a",
- "0x9ad122c22b14202b4490edaf288fdb3c7cb3ff5e",
- "0x905b63fff465b9ffbf41dea908ceb12478ec7601",
- "0x07687e702b410fa43f4cb4af7fa097918ffd2730",
- "0x94a1b5cdb22c43faab4abeb5c74999895464ddaf",
- "0xb541fc07bc7619fd4062a54d96268525cbc6ffef",
- "0x12d66f87a04a9e220743712ce6d9bb1b5616b8fc",
- "0x47ce0c6ed5b0ce3d3a51fdb1c52dc66a7c3c2936",
- "0x23773e65ed146a459791799d01336db287f25334",
- "0xd21be7248e0197ee08e0c20d4a96debdac3d20af",
- "0x610b717796ad172b316836ac95a2ffad065ceab4",
- "0x178169b423a011fff22b9e3f3abea13414ddd0f1",
- "0xbb93e510bbcd0b7beb5a853875f9ec60275cf498",
- "0x2717c5e28cf931547b621a5dddb772ab6a35b701",
- "0x03893a7c7463ae47d46bc7f091665f1893656003",
- "0xca0840578f57fe71599d29375e16783424023357",
- "0xc2a3829f459b3edd87791c74cd45402ba0a20be3",
- "0x3ad9db589d201a710ed237c829c7860ba86510fc",
- "0x3aac1cc67c2ec5db4ea850957b967ba153ad6279",
- "0x76d85b4c0fc497eecc38902397ac608000a06607",
- "0x0e3a09dda6b20afbb34ac7cd4a6881493f3e7bf7",
- "0x723b78e67497e85279cb204544566f4dc5d2aca0",
- "0xcc84179ffd19a1627e79f8648d09e095252bc418",
- "0x6bf694a291df3fec1f7e69701e3ab6c592435ae7",
- "0x330bdfade01ee9bf63c209ee33102dd334618e0a",
- "0xa5c2254e4253490c54cef0a4347fddb8f75a4998",
- "0xaf4c0b70b2ea9fb7487c7cbb37ada259579fe040",
- "0xdf231d99ff8b6c6cbf4e9b9a945cbacef9339178",
- "0x1e34a77868e19a6647b1f2f47b51ed72dede95dd",
- "0xd47438c816c9e7f2e2888e060936a499af9582b3",
- "0x84443cfd09a48af6ef360c6976c5392ac5023a1f",
- "0xd5d6f8d9e784d0e26222ad3834500801a68d027d",
- "0xaf8d1839c3c67cf571aa74b5c12398d4901147b3",
- "0x407cceeaa7c95d2fe2250bf9f2c105aa7aafb512",
- "0x05e0b5b40b7b66098c2161a5ee11c5740a3a7c45",
- "0xd8d7de3349ccaa0fde6298fe6d7b7d0d34586193",
- "0x3efa30704d2b8bbac821307230376556cf8cc39e",
- "0x746aebc06d2ae31b71ac51429a19d54e797878e9",
- "0x5f6c97c6ad7bdd0ae7e0dd4ca33a4ed3fdabd4d7",
- "0xf4b067dd14e95bab89be928c07cb22e3c94e0daa",
- "0x01e2919679362dfbc9ee1644ba9c6da6d6245bb1",
- "0x2fc93484614a34f26f7970cbb94615ba109bb4bf",
- "0x26903a5a198d571422b2b4ea08b56a37cbd68c89",
- "0xb20c66c4de72433f3ce747b58b86830c459ca911",
- "0x2573bac39ebe2901b4389cd468f2872cf7767faf",
- "0x653477c392c16b0765603074f157314cc4f40c32",
- "0x88fd245fedec4a936e700f9173454d1931b4c307",
- "0x09193888b3f38c82dedfda55259a82c0e7de875e",
- "0x5cab7692d4e94096462119ab7bf57319726eed2a",
- "0x756c4628e57f7e7f8a459ec2752968360cf4d1aa",
- "0xd82ed8786d7c69dc7e052f7a542ab047971e73d2",
- "0x77777feddddffc19ff86db637967013e6c6a116c",
- "0x833481186f16cece3f1eeea1a694c42034c3a0db",
- "0xb04e030140b30c27bcdfaafffa98c57d80eda7b4",
- "0xcee71753c9820f063b38fdbe4cfdaf1d3d928a80",
- "0x8281aa6795ade17c8973e1aedca380258bc124f9",
- "0x57b2b8c82f065de8ef5573f9730fc1449b403c9f",
- "0x23173fe8b96a4ad8d2e17fb83ea5dcccdca1ae52",
- "0x538ab61e8a9fc1b2f93b3dd9011d662d89be6fe6",
- "0x94be88213a387e992dd87de56950a9aef34b9448",
- "0x242654336ca2205714071898f67e254eb49acdce",
- "0x776198ccf446dfa168347089d7338879273172cf",
- "0xedc5d01286f99a066559f60a585406f3878a033e",
- "0xd692fd2d0b2fbd2e52cfa5b5b9424bc981c30696",
- "0xdf3a408c53e5078af6e8fb2a85088d46ee09a61b",
- "0x743494b60097a2230018079c02fe21a7b687eaa5",
- "0x94c92f096437ab9958fc0a37f09348f30389ae79",
- "0x5efda50f22d34f262c29268506c5fa42cb56a1ce",
- "0x2f50508a8a3d323b91336fa3ea6ae50e55f32185",
- "0x179f48c78f57a3a78f0608cc9197b8972921d1d2",
- "0xffbac21a641dcfe4552920138d90f3638b3c9fba",
- "0xd0975b32cea532eadddfc9c60481976e39db3472",
- "0x1967d8af5bd86a497fb3dd7899a020e47560daaf",
- "0x83e5bc4ffa856bb84bb88581f5dd62a433a25e0d",
- "0x08b2eFdcdB8822EfE5ad0Eae55517cf5DC544251",
- "0x04DBA1194ee10112fE6C3207C0687DEf0e78baCf",
- "0x0Ee5067b06776A89CcC7dC8Ee369984AD7Db5e06",
- "0x502371699497d08D5339c870851898D6D72521Dd",
- "0x5A14E72060c11313E38738009254a90968F58f51",
- "0xEFE301d259F525cA1ba74A7977b80D5b060B3ccA",
- "0x39d908dac893cbcb53cc86e0ecc369aa4def1a29",
- "0x4f47bc496083c727c5fbe3ce9cdf2b0f6496270c",
- "0x38735f03b30FbC022DdD06ABED01F0Ca823C6a94",
- "0x97b1043abd9e6fc31681635166d430a458d14f9c",
- "0xb6f5ec1a0a9cd1526536d3f0426c429529471f40",
- "0xdcbEfFBECcE100cCE9E4b153C4e15cB885643193",
- "0x5f48c2a71b2cc96e3f0ccae4e39318ff0dc375b2",
- "0x5a7a51bfb49f190e5a6060a5bc6052ac14a3b59f",
- "0xed6e0a7e4ac94d976eebfb82ccf777a3c6bad921",
- "0x797d7ae72ebddcdea2a346c1834e04d1f8df102b",
- "0x931546D9e66836AbF687d2bc64B30407bAc8C568",
- "0x43fa21d92141BA9db43052492E0DeEE5aa5f0A93",
- "0x6be0ae71e6c41f2f9d0d1a3b8d0f75e6f6a0b46e",
- "0x9c2bc757b66f24d60f016b6237f8cdd414a879fa",
- "0x530a64c0ce595026a4a556b703644228179e2d57",
- "0xfac583c0cf07ea434052c49115a4682172ab6b4f",
- "0x961c5be54a2ffc17cf4cb021d863c42dacd47fc1",
- "0x983a81ca6fb1e441266d2fbcb7d8e530ac2e05a2",
- "0xf3701f445b6bdafedbca97d1e477357839e4120d",
- "0xe950dc316b836e4eefb8308bf32bf7c72a1358ff",
- "0x21b8d56bda776bbe68655a16895afd96f5534fed",
- "0x175d44451403edf28469df03a9280c1197adb92c",
- "0x19f8f2b0915daa12a3f5c9cf01df9e24d53794f7"
-]
diff --git a/src/services/ofac/index.ts b/src/services/ofac/index.ts
deleted file mode 100644
index 3d2c35c16..000000000
--- a/src/services/ofac/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { sameAddress } from '@/utils/addresses'
-import blockedAddresses from './blockedAddressList.json'
-
-export const isBlockedAddress = (address: string) => {
- return blockedAddresses.some((blockedAddress) => sameAddress(blockedAddress, address))
-}
diff --git a/src/services/private-key-module/index.ts b/src/services/private-key-module/index.ts
index 355bd2ded..f8c6dd78d 100644
--- a/src/services/private-key-module/index.ts
+++ b/src/services/private-key-module/index.ts
@@ -87,8 +87,9 @@ const PrivateKeyModule = (chainId: ChainInfo['chainId'], rpcUri: ChainInfo['rpcU
// @ts-ignore
eth_getCode: async ({ params }) => provider.getCode(params[0], params[1]),
-
+ // @ts-ignore
eth_accounts: async () => [wallet.address],
+ // @ts-ignore
eth_requestAccounts: async () => [wallet.address],
eth_call: async ({ params }: { params: any }) => wallet.call(params[0]),
@@ -104,8 +105,7 @@ const PrivateKeyModule = (chainId: ChainInfo['chainId'], rpcUri: ChainInfo['rpcU
},
eth_signTypedData: async ({ params }) => {
- const [, json] = params
- const typedData = JSON.parse(json)
+ const [, typedData] = params
return await wallet.signTypedData(
typedData.domain,
{ [typedData.primaryType]: typedData.types[typedData.primaryType] },
diff --git a/src/services/safe-wallet-provider/useSafeWalletProvider.test.tsx b/src/services/safe-wallet-provider/useSafeWalletProvider.test.tsx
index f1cf2c374..d1721cb01 100644
--- a/src/services/safe-wallet-provider/useSafeWalletProvider.test.tsx
+++ b/src/services/safe-wallet-provider/useSafeWalletProvider.test.tsx
@@ -9,11 +9,15 @@ import { act, renderHook } from '@/tests/test-utils'
import { TxModalContext } from '@/components/tx-flow'
import useSafeWalletProvider, { _useTxFlowApi } from './useSafeWalletProvider'
import { SafeWalletProvider } from '.'
+import type { RootState } from '@/store'
import { makeStore } from '@/store'
import * as messages from '@/utils/safe-messages'
import { faker } from '@faker-js/faker'
import { Interface } from 'ethers'
import { getCreateCallDeployment } from '@safe-global/safe-deployments'
+import { useCurrentChain } from '@/hooks/useChains'
+import { chainBuilder } from '@/tests/builders/chains'
+import { FEATURES } from '@/utils/chains'
const appInfo = {
id: 1,
@@ -30,9 +34,23 @@ jest.mock('./notifications', () => {
}
})
+jest.mock('@/hooks/useChains', () => ({
+ __esModule: true,
+ ...jest.requireActual('@/hooks/useChains'),
+ useCurrentChain: jest.fn(),
+}))
+
describe('useSafeWalletProvider', () => {
+ const mockUseCurrentChain = useCurrentChain as jest.MockedFunction
+
beforeEach(() => {
jest.clearAllMocks()
+
+ mockUseCurrentChain.mockReturnValue(
+ chainBuilder()
+ .with({ chainId: '1', features: [FEATURES.SAFE_141 as any] })
+ .build(),
+ )
})
describe('useSafeWalletProvider', () => {
@@ -111,10 +129,19 @@ describe('useSafeWalletProvider', () => {
const mockSetTxFlow = jest.fn()
+ const testStore = makeStore({
+ settings: {
+ signing: {
+ onChainSigning: false,
+ blindSigning: false,
+ },
+ },
+ } as Partial)
+
const { result } = renderHook(() => _useTxFlowApi('1', '0x1234567890000000000000000000000000000000'), {
// TODO: Improve render/renderHook to allow custom wrappers within the "defaults"
wrapper: ({ children }) => (
-
+
{children}
),
diff --git a/src/services/safe-wallet-provider/useSafeWalletProvider.tsx b/src/services/safe-wallet-provider/useSafeWalletProvider.tsx
index 6bb82c093..060647035 100644
--- a/src/services/safe-wallet-provider/useSafeWalletProvider.tsx
+++ b/src/services/safe-wallet-provider/useSafeWalletProvider.tsx
@@ -208,7 +208,9 @@ export const _useTxFlowApi = (chainId: string, safeAddress: string): WalletSDK |
},
getCreateCallTransaction(data) {
- const createCallDeployment = getCreateCallContractDeployment(safe.chainId, safe.version)
+ const createCallDeployment = currentChain
+ ? getCreateCallContractDeployment(currentChain, safe.version)
+ : undefined
if (!createCallDeployment) {
throw new Error('No CreateCall deployment found for chain and safe version')
}
diff --git a/src/services/security/modules/BlockaidModule/index.ts b/src/services/security/modules/BlockaidModule/index.ts
new file mode 100644
index 000000000..a58e4b998
--- /dev/null
+++ b/src/services/security/modules/BlockaidModule/index.ts
@@ -0,0 +1,165 @@
+import { isEIP712TypedData } from '@/utils/safe-messages'
+import { normalizeTypedData } from '@/utils/web3'
+import { type SafeTransaction } from '@safe-global/safe-core-sdk-types'
+import { generateTypedData } from '@safe-global/protocol-kit/dist/src/utils/eip-712'
+import type { EIP712TypedData } from '@safe-global/safe-gateway-typescript-sdk'
+import { type SecurityResponse, type SecurityModule, SecuritySeverity } from '../types'
+import type { AssetDiff, TransactionScanResponse } from './types'
+import { BLOCKAID_API, BLOCKAID_CLIENT_ID } from '@/config/constants'
+
+/** @see https://docs.blockaid.io/docs/supported-chains */
+const API_CHAINS: Record = {
+ 1: 'ethereum',
+ 10: 'optimism',
+ 56: 'bsc',
+ 100: 'gnosis',
+ 137: 'polygon',
+ 238: 'blast',
+ 324: 'zksync',
+ 8453: 'base',
+ 42161: 'arbitrum',
+ 43114: 'avalanche',
+ 59144: 'linea',
+ 534352: 'scroll',
+ 7777777: 'zora',
+}
+const blockaidSeverityMap: Record = {
+ Malicious: SecuritySeverity.HIGH,
+ Warning: SecuritySeverity.MEDIUM,
+ Benign: SecuritySeverity.NONE,
+ Info: SecuritySeverity.NONE,
+}
+
+export type BlockaidModuleRequest = {
+ chainId: number
+ safeAddress: string
+ walletAddress: string
+ data: SafeTransaction | EIP712TypedData
+ threshold: number
+}
+
+export type BlockaidModuleResponse = {
+ description?: string
+ classification?: string
+ reason?: string
+ issues: {
+ severity: SecuritySeverity
+ description: string
+ }[]
+ balanceChange: AssetDiff[]
+ error: Error | undefined
+}
+
+type BlockaidPayload = {
+ chain: string
+ account_address: string
+ metadata: {
+ domain: string
+ }
+ data: {
+ method: 'eth_signTypedData_v4'
+ params: [string, string]
+ }
+ options: ['simulation', 'validation']
+}
+
+export class BlockaidModule implements SecurityModule {
+ static prepareMessage(request: BlockaidModuleRequest): string {
+ const { data, safeAddress, chainId } = request
+ if (isEIP712TypedData(data)) {
+ const normalizedMsg = normalizeTypedData(data)
+ return JSON.stringify(normalizedMsg)
+ } else {
+ return JSON.stringify(
+ generateTypedData({
+ safeAddress,
+ safeVersion: '1.3.0', // TODO: pass to module, taking into account that lower Safe versions don't have chainId in payload
+ chainId: BigInt(chainId),
+ data: {
+ ...data.data,
+ safeTxGas: data.data.safeTxGas,
+ baseGas: data.data.baseGas,
+ gasPrice: data.data.gasPrice,
+ },
+ }),
+ )
+ }
+ }
+ async scanTransaction(request: BlockaidModuleRequest): Promise> {
+ if (!BLOCKAID_CLIENT_ID) {
+ throw new Error('Security check CLIENT_ID not configured')
+ }
+
+ const { chainId, safeAddress } = request
+
+ if (!API_CHAINS[chainId]) {
+ throw new Error('Security checks are not available on the current chain.')
+ }
+
+ const message = BlockaidModule.prepareMessage(request)
+
+ const payload: BlockaidPayload = {
+ chain: API_CHAINS[chainId],
+ account_address: safeAddress,
+ data: {
+ method: 'eth_signTypedData_v4',
+ params: [safeAddress, message],
+ },
+ options: ['simulation', 'validation'],
+ metadata: {
+ // TODO: Pass domain from safe app or wallet connect connection if the tx originates from there
+ domain: window.location.host,
+ },
+ }
+ const res = await fetch(`${BLOCKAID_API}/v0/evm/json-rpc/scan`, {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ accept: 'application/json',
+ 'X-CLIENT-ID': BLOCKAID_CLIENT_ID,
+ },
+ body: JSON.stringify(payload),
+ })
+
+ if (!res.ok) {
+ throw new Error('Blockaid scan failed', await res.json())
+ }
+
+ const result = (await res.json()) as TransactionScanResponse
+
+ const issues = (result.validation?.features ?? [])
+ .filter((feature) => feature.type === 'Malicious' || feature.type === 'Warning')
+ .map((feature) => ({
+ severity: blockaidSeverityMap[feature.type],
+ description: feature.description,
+ }))
+
+ const simulation = result.simulation
+ let balanceChange: AssetDiff[] = []
+ let error: Error | undefined = undefined
+ if (simulation?.status === 'Success') {
+ balanceChange = simulation.assets_diffs[safeAddress]
+ } else if (simulation?.status === 'Error') {
+ error = new Error('Simulation failed')
+ }
+
+ // Sometimes the validation is missing
+ if (result.validation === undefined) {
+ error = new Error('Validation result missing')
+ }
+
+ return {
+ severity: result.validation?.result_type
+ ? blockaidSeverityMap[result.validation.result_type]
+ : SecuritySeverity.NONE ?? SecuritySeverity.NONE,
+ payload: {
+ description: result.validation?.description,
+ classification: result.validation?.classification,
+ reason: result.validation?.reason,
+ issues,
+ balanceChange,
+ error,
+ },
+ }
+ }
+}
diff --git a/src/services/security/modules/BlockaidModule/types.ts b/src/services/security/modules/BlockaidModule/types.ts
new file mode 100644
index 000000000..e956e32f8
--- /dev/null
+++ b/src/services/security/modules/BlockaidModule/types.ts
@@ -0,0 +1,619 @@
+export interface AddressAssetExposure {
+ /**
+ * description of the asset for the current diff
+ */
+ asset: Erc20TokenDetails | Erc1155TokenDetails | Erc721TokenDetails | NonercTokenDetails
+
+ /**
+ * dictionary of spender addresses where the exposure has changed during this
+ * transaction for the current address and asset
+ */
+ spenders: Record
+}
+
+export interface AssetDiff {
+ /**
+ * description of the asset for the current diff
+ */
+ asset: Erc20TokenDetails | Erc1155TokenDetails | Erc721TokenDetails | NonercTokenDetails | NativeAssetDetails
+
+ /**
+ * amount of the asset that was transferred to the address in this transaction
+ */
+ in: Array
+
+ /**
+ * amount of the asset that was transferred from the address in this transaction
+ */
+ out: Array
+}
+
+export type GeneralAssetDiff = Erc1155Diff | Erc721Diff | Erc20Diff | NativeDiff
+
+export interface Erc1155Diff {
+ /**
+ * id of the token
+ */
+ token_id: number
+
+ /**
+ * value before divided by decimal, that was transferred from this address
+ */
+ value: string
+
+ /**
+ * url of the token logo
+ */
+ logo_url?: string
+
+ /**
+ * user friendly description of the asset transfer
+ */
+ summary?: string
+
+ /**
+ * usd equal of the asset that was transferred from this address
+ */
+ usd_price?: string
+}
+
+export interface Erc1155Exposure {
+ exposure: Array
+
+ /**
+ * boolean indicates whether an is_approved_for_all function was used (missing in
+ * case of ERC20 / ERC1155)
+ */
+ is_approved_for_all: boolean
+
+ /**
+ * user friendly description of the approval
+ */
+ summary?: string
+}
+
+export interface Erc1155TokenDetails {
+ /**
+ * address of the token
+ */
+ address: string
+
+ /**
+ * asset type.
+ */
+ type: 'ERC1155'
+
+ /**
+ * url of the token logo
+ */
+ logo_url?: string
+
+ /**
+ * string represents the name of the asset
+ */
+ name?: string
+
+ /**
+ * asset's symbol name
+ */
+ symbol?: string
+}
+
+export interface Erc20Diff {
+ /**
+ * value before divided by decimal, that was transferred from this address
+ */
+ raw_value: string
+
+ /**
+ * user friendly description of the asset transfer
+ */
+ summary?: string
+
+ /**
+ * usd equal of the asset that was transferred from this address
+ */
+ usd_price?: string
+
+ /**
+ * value after divided by decimals, that was transferred from this address
+ */
+ value?: string
+}
+
+export interface Erc20Exposure {
+ /**
+ * the amount that was asked in the approval request for this spender from the
+ * current address and asset
+ */
+ approval: number
+
+ exposure: Array
+
+ /**
+ * the expiration time of the permit2 protocol
+ */
+ expiration?: string
+
+ /**
+ * user friendly description of the approval
+ */
+ summary?: string
+}
+
+export interface Erc20TokenDetails {
+ /**
+ * address of the token
+ */
+ address: string
+
+ /**
+ * asset's decimals
+ */
+ decimals: number
+
+ /**
+ * asset type.
+ */
+ type: 'ERC20'
+
+ /**
+ * url of the token logo
+ */
+ logo_url?: string
+
+ /**
+ * string represents the name of the asset
+ */
+ name?: string
+
+ /**
+ * asset's symbol name
+ */
+ symbol?: string
+}
+
+export interface Erc721Diff {
+ /**
+ * id of the token
+ */
+ token_id: number
+
+ /**
+ * url of the token logo
+ */
+ logo_url?: string
+
+ /**
+ * user friendly description of the asset transfer
+ */
+ summary?: string
+
+ /**
+ * usd equal of the asset that was transferred from this address
+ */
+ usd_price?: string
+}
+
+export interface Erc721Exposure {
+ exposure: Array
+
+ /**
+ * boolean indicates whether an is_approved_for_all function was used (missing in
+ * case of ERC20 / ERC1155)
+ */
+ is_approved_for_all: boolean
+
+ /**
+ * user friendly description of the approval
+ */
+ summary?: string
+}
+
+export interface Erc721TokenDetails {
+ /**
+ * address of the token
+ */
+ address: string
+
+ /**
+ * asset type.
+ */
+ type: 'ERC721'
+
+ /**
+ * url of the token logo
+ */
+ logo_url?: string
+
+ /**
+ * string represents the name of the asset
+ */
+ name?: string
+
+ /**
+ * asset's symbol name
+ */
+ symbol?: string
+}
+
+export interface Metadata {
+ /**
+ * cross reference transaction against the domain.
+ */
+ domain: string
+}
+
+export interface NativeAssetDetails {
+ chain_id: number
+
+ chain_name: string
+
+ decimals: number
+
+ logo_url: string
+
+ /**
+ * asset type.
+ */
+ type: 'NATIVE'
+
+ /**
+ * string represents the name of the asset
+ */
+ name?: string
+
+ /**
+ * asset's symbol name
+ */
+ symbol?: string
+}
+
+export interface NativeDiff {
+ /**
+ * value before divided by decimal, that was transferred from this address
+ */
+ raw_value: string
+
+ /**
+ * user friendly description of the asset transfer
+ */
+ summary?: string
+
+ /**
+ * usd equal of the asset that was transferred from this address
+ */
+ usd_price?: string
+
+ /**
+ * value after divided by decimals, that was transferred from this address
+ */
+ value?: string
+}
+
+export interface NonercTokenDetails {
+ /**
+ * address of the token
+ */
+ address: string
+
+ /**
+ * asset type.
+ */
+ type: 'NONERC'
+
+ /**
+ * url of the token logo
+ */
+ logo_url?: string
+
+ /**
+ * string represents the name of the asset
+ */
+ name?: string
+
+ /**
+ * asset's symbol name
+ */
+ symbol?: string
+}
+
+export type TransactionSimulationResponse = TransactionSimulation | TransactionSimulationError
+
+export type TransactionValidationResponse = TransactionValidation | TransactionValidationError
+
+/**
+ * The chain name
+ */
+export type TokenScanSupportedChain =
+ | 'arbitrum'
+ | 'avalanche'
+ | 'base'
+ | 'bsc'
+ | 'ethereum'
+ | 'optimism'
+ | 'polygon'
+ | 'zora'
+ | 'solana'
+ | 'unknown'
+
+export interface TransactionScanFeature {
+ /**
+ * Textual description
+ */
+ description: string
+
+ /**
+ * Feature name
+ */
+ feature_id: string
+
+ /**
+ * An enumeration.
+ */
+ type: 'Malicious' | 'Warning' | 'Benign' | 'Info'
+
+ /**
+ * Address the feature refers to
+ */
+ address?: string
+}
+
+export interface TransactionScanResponse {
+ block: string
+
+ chain: string
+
+ events?: Array
+
+ features?: unknown
+
+ gas_estimation?:
+ | TransactionScanResponse.TransactionScanGasEstimation
+ | TransactionScanResponse.TransactionScanGasEstimationError
+
+ simulation?: TransactionSimulationResponse
+
+ validation?: TransactionValidationResponse
+}
+
+export namespace TransactionScanResponse {
+ export interface Event {
+ data: string
+
+ emitter_address: string
+
+ topics: Array
+
+ emitter_name?: string
+
+ name?: string
+
+ params?: Array
+ }
+
+ export namespace Event {
+ export interface Param {
+ type: string
+
+ value: string | unknown | Array
+
+ internalType?: string
+
+ name?: string
+ }
+ }
+
+ export interface TransactionScanGasEstimation {
+ estimate: number
+
+ status: 'Success'
+
+ used: number
+ }
+
+ export interface TransactionScanGasEstimationError {
+ error: string
+
+ status: 'Error'
+ }
+}
+
+/**
+ * The chain name
+ */
+export type TransactionScanSupportedChain =
+ | 'arbitrum'
+ | 'avalanche'
+ | 'base'
+ | 'base-sepolia'
+ | 'bsc'
+ | 'ethereum'
+ | 'optimism'
+ | 'polygon'
+ | 'zksync'
+ | 'zora'
+ | 'linea'
+ | 'blast'
+ | 'unknown'
+
+export interface TransactionSimulation {
+ /**
+ * Account summary for the account address. account address is determined implicit
+ * by the `from` field in the transaction request, or explicit by the
+ * account_address field in the request.
+ */
+ account_summary: TransactionSimulation.AccountSummary
+
+ /**
+ * a dictionary including additional information about each relevant address in the
+ * transaction.
+ */
+ address_details: Record
+
+ /**
+ * dictionary describes the assets differences as a result of this transaction for
+ * every involved address
+ */
+ assets_diffs: Record>
+
+ /**
+ * dictionary describes the exposure differences as a result of this transaction
+ * for every involved address (as a result of any approval / setApproval / permit
+ * function)
+ */
+ exposures: Record>
+
+ /**
+ * A string indicating if the simulation was successful or not.
+ */
+ status: 'Success'
+
+ /**
+ * dictionary represents the usd value each address gained / lost during this
+ * transaction
+ */
+ total_usd_diff: Record
+
+ /**
+ * a dictionary representing the usd value each address is exposed to, split by
+ * spenders
+ */
+ total_usd_exposure: Record>
+}
+
+export namespace TransactionSimulation {
+ /**
+ * Account summary for the account address. account address is determined implicit
+ * by the `from` field in the transaction request, or explicit by the
+ * account_address field in the request.
+ */
+ export interface AccountSummary {
+ /**
+ * All assets diffs related to the account address
+ */
+ assets_diffs: Array
+
+ /**
+ * All assets exposures related to the account address
+ */
+ exposures: Array
+
+ /**
+ * Total usd diff related to the account address
+ */
+ total_usd_diff: UsdDiff
+
+ /**
+ * Total usd exposure related to the account address
+ */
+ total_usd_exposure: Record
+ }
+
+ export interface AddressDetails {
+ /**
+ * contains the contract's name if the address is a verified contract
+ */
+ contract_name?: string
+
+ /**
+ * known name tag for the address
+ */
+ name_tag?: string
+ }
+}
+
+export interface TransactionSimulationError {
+ /**
+ * An error message if the simulation failed.
+ */
+ error: string
+
+ /**
+ * A string indicating if the simulation was successful or not.
+ */
+ status: 'Error'
+}
+
+export interface TransactionValidation {
+ /**
+ * A list of features about this transaction explaining the validation.
+ */
+ features: Array
+
+ /**
+ * An enumeration.
+ */
+ result_type: 'Benign' | 'Warning' | 'Malicious'
+
+ /**
+ * A string indicating if the simulation was successful or not.
+ */
+ status: 'Success'
+
+ /**
+ * A textual classification that can be presented to the user explaining the
+ * reason.
+ */
+ classification?: string
+
+ /**
+ * A textual description that can be presented to the user about what this
+ * transaction is doing.
+ */
+ description?: string
+
+ /**
+ * A textual description about the reasons the transaction was flagged with
+ * result_type.
+ */
+ reason?: string
+}
+
+export interface TransactionValidationError {
+ /**
+ * A textual classification that can be presented to the user explaining the
+ * reason.
+ */
+ classification: ''
+
+ /**
+ * A textual description that can be presented to the user about what this
+ * transaction is doing.
+ */
+ description: ''
+
+ /**
+ * An error message if the validation failed.
+ */
+ error: string
+
+ /**
+ * A list of features about this transaction explaining the validation.
+ */
+ features: Array
+
+ /**
+ * A textual description about the reasons the transaction was flagged with
+ * result_type.
+ */
+ reason: ''
+
+ /**
+ * A string indicating if the transaction is safe to sign or not.
+ */
+ result_type: 'Error'
+
+ /**
+ * A string indicating if the simulation was successful or not.
+ */
+ status: 'Success'
+}
+
+export interface UsdDiff {
+ in: string
+
+ out: string
+
+ total: string
+}
diff --git a/src/services/security/modules/DelegateCallModule/index.test.ts b/src/services/security/modules/DelegateCallModule/index.test.ts
index a7278a739..c8d3506f8 100644
--- a/src/services/security/modules/DelegateCallModule/index.test.ts
+++ b/src/services/security/modules/DelegateCallModule/index.test.ts
@@ -4,10 +4,13 @@ import { toBeHex } from 'ethers'
import { DelegateCallModule } from '.'
import { createMockSafeTransaction, getMockMultiSendCalldata } from '@/tests/transactions'
+import { chainBuilder } from '@/tests/builders/chains'
describe('DelegateCallModule', () => {
const DelegateCallModuleInstance = new DelegateCallModule()
+ const chainInfo = chainBuilder().with({ chainId: '1' }).build()
+
it('should not warn about Call operation transactions', async () => {
const recipient = toBeHex('0x1', 20)
@@ -19,7 +22,7 @@ describe('DelegateCallModule', () => {
const result = await DelegateCallModuleInstance.scanTransaction({
safeTransaction,
- chainId: '5',
+ chain: chainInfo,
safeVersion: '1.3.0',
})
@@ -29,7 +32,7 @@ describe('DelegateCallModule', () => {
})
it('should not warn about MultiSendCallOnly DelegateCall operation transactions', async () => {
- const CHAIN_ID = '5'
+ const CHAIN_ID = '1'
const SAFE_VERSION = '1.3.0'
const multiSend = getMultiSendCallOnlyDeployment({
@@ -50,7 +53,7 @@ describe('DelegateCallModule', () => {
const result = await DelegateCallModuleInstance.scanTransaction({
safeTransaction,
- chainId: CHAIN_ID,
+ chain: chainInfo,
safeVersion: SAFE_VERSION,
})
@@ -70,7 +73,7 @@ describe('DelegateCallModule', () => {
const result = await DelegateCallModuleInstance.scanTransaction({
safeTransaction,
- chainId: '5',
+ chain: chainInfo,
safeVersion: '1.3.0',
})
diff --git a/src/services/security/modules/DelegateCallModule/index.ts b/src/services/security/modules/DelegateCallModule/index.ts
index 85555bafc..4fe516f74 100644
--- a/src/services/security/modules/DelegateCallModule/index.ts
+++ b/src/services/security/modules/DelegateCallModule/index.ts
@@ -1,13 +1,13 @@
import { OperationType } from '@safe-global/safe-core-sdk-types'
import { getMultiSendCallOnlyContractDeployment } from '@/services/contracts/deployments'
import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
-import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import type { ChainInfo, SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { SecuritySeverity } from '../types'
import type { SecurityModule, SecurityResponse } from '../types'
type DelegateCallModuleRequest = {
- chainId: string
+ chain: ChainInfo
safeVersion: SafeInfo['version']
safeTransaction: SafeTransaction
}
@@ -21,16 +21,16 @@ export type DelegateCallModuleResponse = {
export class DelegateCallModule implements SecurityModule {
private isUnexpectedDelegateCall(request: DelegateCallModuleRequest): boolean {
- const { chainId, safeTransaction, safeVersion } = request
+ const { chain, safeTransaction, safeVersion } = request
if (safeTransaction.data.operation !== OperationType.DelegateCall) {
return false
}
// We need not check for nested delegate calls as we only use MultiSendCallOnly in the UI
- const multiSendDeployment = getMultiSendCallOnlyContractDeployment(chainId, safeVersion)
+ const multiSendDeployment = getMultiSendCallOnlyContractDeployment(chain, safeVersion)
- return multiSendDeployment?.networkAddresses[chainId] !== safeTransaction.data.to
+ return multiSendDeployment?.networkAddresses[chain.chainId] !== safeTransaction.data.to
}
async scanTransaction(request: DelegateCallModuleRequest): Promise> {
diff --git a/src/services/security/modules/RecipientAddressModule/index.test.ts b/src/services/security/modules/RecipientAddressModule/index.test.ts
index c94208b52..b8aef115b 100644
--- a/src/services/security/modules/RecipientAddressModule/index.test.ts
+++ b/src/services/security/modules/RecipientAddressModule/index.test.ts
@@ -4,7 +4,7 @@ import { toBeHex } from 'ethers'
import type { JsonRpcProvider } from 'ethers'
import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import * as web3 from '@/hooks/wallets/web3'
+import * as walletUtils from '@/utils/wallets'
import { RecipientAddressModule } from '.'
import {
createMockSafeTransaction,
@@ -16,7 +16,7 @@ import {
} from '@/tests/transactions'
describe('RecipientAddressModule', () => {
- const isSmartContractSpy = jest.spyOn(web3, 'isSmartContract')
+ const isSmartContractSpy = jest.spyOn(walletUtils, 'isSmartContract')
const mockGetBalance = jest.fn()
const mockProvider = {
diff --git a/src/services/security/modules/RecipientAddressModule/index.ts b/src/services/security/modules/RecipientAddressModule/index.ts
index d0223a892..77d4f898a 100644
--- a/src/services/security/modules/RecipientAddressModule/index.ts
+++ b/src/services/security/modules/RecipientAddressModule/index.ts
@@ -2,7 +2,7 @@ import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
import type { JsonRpcProvider } from 'ethers'
import { getSafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import { isSmartContract } from '@/hooks/wallets/web3'
+import { isSmartContract } from '@/utils/wallets'
import { sameAddress } from '@/utils/addresses'
import { getTransactionRecipients } from '@/utils/transaction-calldata'
import { SecuritySeverity } from '../types'
@@ -68,7 +68,7 @@ export class RecipientAddressModule
return warnings
}
- if (await isSmartContract(provider, address)) {
+ if (await isSmartContract(address)) {
return warnings
}
diff --git a/src/services/security/modules/RedefineModule/index.ts b/src/services/security/modules/RedefineModule/index.ts
deleted file mode 100644
index b0d7ac93e..000000000
--- a/src/services/security/modules/RedefineModule/index.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import { REDEFINE_API } from '@/config/constants'
-import { isEIP712TypedData } from '@/utils/safe-messages'
-import { normalizeTypedData } from '@/utils/web3'
-import { type SafeTransaction } from '@safe-global/safe-core-sdk-types'
-import { generateTypedData } from '@safe-global/protocol-kit/dist/src/utils/eip-712'
-import type { EIP712TypedData } from '@safe-global/safe-gateway-typescript-sdk'
-import { type SecurityResponse, type SecurityModule, SecuritySeverity } from '../types'
-
-export const enum REDEFINE_ERROR_CODES {
- ANALYSIS_IN_PROGRESS = 1000,
- SIMULATION_FAILED = 1001,
- INPUT_VALIDATION = 2000,
- BAD_REQUEST = 3000,
-}
-
-const redefineSeverityMap: Record = {
- CRITICAL: SecuritySeverity.CRITICAL,
- HIGH: SecuritySeverity.HIGH,
- MEDIUM: SecuritySeverity.MEDIUM,
- LOW: SecuritySeverity.LOW,
- NO_ISSUES: SecuritySeverity.NONE,
-}
-
-export type RedefineModuleRequest = {
- chainId: number
- safeAddress: string
- walletAddress: string
- data: SafeTransaction | EIP712TypedData
- threshold: number
-}
-
-export type RedefineModuleResponse = {
- issues?: Array<
- Omit['insights']['issues'][number], 'severity'> & {
- severity: SecuritySeverity
- }
- >
- balanceChange?: NonNullable['balanceChange']
- simulation?: NonNullable['simulation']
- errors: RedefineResponse['errors']
-}
-
-type RedefinePayload = {
- chainId: number
- domain?: string
- payload: {
- method: 'eth_signTypedData_v4'
- params: [string, string]
- }
-}
-
-type RedefineSeverity = {
- code: number
- label: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'NO_ISSUES'
-}
-
-type RedefineBalanceChange =
- | {
- address: string
- amount: {
- value: string
- normalizedValue: string
- }
- type: 'ERC20'
- symbol: string
- decimals: number
- name: string
- }
- | {
- amount: {
- value: string
- normalizedValue: string
- }
- type: 'NATIVE'
- symbol: string
- decimals: number
- name: string
- }
- | { type: 'ERC721'; address: string; tokenId: string; name?: string; symbol?: string }
-
-export type RedefineResponse = {
- data?: {
- balanceChange?: {
- in: RedefineBalanceChange[]
- out: RedefineBalanceChange[]
- }
- insights: {
- issues: {
- description: {
- short: string
- long: string
- }
- severity: RedefineSeverity
- category: string
- }[]
- verdict: RedefineSeverity
- }
- simulation: {
- uuid: string
- time: string
- block: string
- }
- }
- errors: {
- code: number
- message: string
- extendedInfo?: Record
- }[]
-}
-
-export class RedefineModule implements SecurityModule {
- static prepareMessage(request: RedefineModuleRequest): string {
- const { data, safeAddress, chainId } = request
- if (isEIP712TypedData(data)) {
- const normalizedMsg = normalizeTypedData(data)
- return JSON.stringify(normalizedMsg)
- } else {
- return JSON.stringify(
- generateTypedData({
- safeAddress,
- safeVersion: '1.3.0', // TODO: pass to module, taking into account that lower Safe versions don't have chainId in payload
- chainId: BigInt(chainId),
- // TODO: find out why these types are incompaitble
- data: {
- ...data.data,
- safeTxGas: data.data.safeTxGas,
- baseGas: data.data.baseGas,
- gasPrice: data.data.gasPrice,
- },
- }),
- )
- }
- }
- async scanTransaction(request: RedefineModuleRequest): Promise> {
- if (!REDEFINE_API) {
- throw new Error('Redefine API URL is not set')
- }
-
- const { chainId, safeAddress, data } = request
-
- const message = RedefineModule.prepareMessage(request)
-
- const payload: RedefinePayload = {
- chainId,
- payload: {
- method: 'eth_signTypedData_v4',
- params: [safeAddress, message],
- },
- }
-
- const res = await fetch(REDEFINE_API, {
- method: 'POST',
- headers: {
- 'content-type': 'application/JSON',
- },
- body: JSON.stringify(payload),
- })
-
- if (!res.ok) {
- throw new Error('Redefine scan failed', await res.json())
- }
-
- const result = (await res.json()) as RedefineResponse
-
- return {
- severity: result.data ? redefineSeverityMap[result.data.insights.verdict.label] : SecuritySeverity.NONE,
- payload: {
- issues: result.data?.insights.issues.map((issue) => ({
- ...issue,
- severity: redefineSeverityMap[issue.severity.label],
- })),
- balanceChange: result.data?.balanceChange,
- simulation: isEIP712TypedData(data) ? undefined : result.data?.simulation,
- errors: result.errors,
- },
- }
- }
-}
diff --git a/src/services/siwe/index.ts b/src/services/siwe/index.ts
index 682e79c1b..cc051b660 100644
--- a/src/services/siwe/index.ts
+++ b/src/services/siwe/index.ts
@@ -5,7 +5,7 @@ import type { BrowserProvider } from 'ethers'
* Prompt the user to sign in with their wallet and set an access_token cookie
* @param provider
*/
-async function signInWithEthereum(provider: BrowserProvider) {
+export const signInWithEthereum = async (provider: BrowserProvider) => {
const { nonce } = await getAuthNonce()
const [network, signer] = await Promise.all([provider.getNetwork(), provider.getSigner()])
@@ -14,7 +14,8 @@ async function signInWithEthereum(provider: BrowserProvider) {
domain: window.location.host,
address: signer.address as `0x${string}`,
// Results in special signing window in MetaMask
- statement: 'Sign in with Ethereum to the app.',
+ statement:
+ 'By signing, you are agreeing to store this data on the Safe Cloud. This does not initiate a transaction or cost any fees.',
uri: window.location.origin,
version: '1',
chainId: Number(network.chainId),
@@ -37,5 +38,3 @@ Issued At: ${message.issuedAt.toISOString()}`
return verifyAuth({ message: signableMessage, signature })
}
-
-export default signInWithEthereum
diff --git a/src/services/transactions/index.tests.ts b/src/services/transactions/index.tests.ts
index 7b2634e07..d0310a8db 100644
--- a/src/services/transactions/index.tests.ts
+++ b/src/services/transactions/index.tests.ts
@@ -1,8 +1,9 @@
-import { getTimezoneOffset } from '.'
+import { getTimezone } from '.'
-describe('getTimezoneOffset', () => {
- it('should return timezone offset in milliseconds', () => {
- const CET = 60 * 60 * 1000 // tests are run in CET
- expect(getTimezoneOffset()).toBe(-CET)
+describe('getTimezone', () => {
+ it('should return timezone', () => {
+ const result = getTimezone()
+
+ expect(result).toBeDefined()
})
})
diff --git a/src/services/transactions/index.ts b/src/services/transactions/index.ts
index 01e8fce25..9712dc9d8 100644
--- a/src/services/transactions/index.ts
+++ b/src/services/transactions/index.ts
@@ -1,59 +1,32 @@
-import memoize from 'lodash/memoize'
-import { getTransactionDetails, getTransactionHistory } from '@safe-global/safe-gateway-typescript-sdk'
-import { trimTrailingSlash } from '@/utils/url'
+import { getModuleTransactions, getTransactionHistory } from '@safe-global/safe-gateway-typescript-sdk'
-export const getTimezoneOffset = () => new Date().getTimezoneOffset() * 60 * -1000
+export const getTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone
-/**
- * Fetch and memoize transaction details from Safe Gateway
- *
- * @param chainId Chain id
- * @param id Transaction id or hash
- * @returns Transaction details
- */
-export const getTxDetails = memoize(
- (chainId: string, id: string) => {
- return getTransactionDetails(chainId, id)
- },
- (id: string, chainId: string) => `${chainId}-${id}`,
-)
-
-export const getTxHistory = (chainId: string, safeAddress: string, trusted = false, pageUrl?: string) => {
+export const getTxHistory = (
+ chainId: string,
+ safeAddress: string,
+ hideUntrustedTxs: boolean,
+ hideImitationTxs: boolean,
+ pageUrl?: string,
+) => {
return getTransactionHistory(
chainId,
safeAddress,
{
- timezone_offset: getTimezoneOffset(), // used for grouping txs by date
- trusted, // if false, load all transactions, mark untrusted in the UI
+ timezone: getTimezone(), // used for grouping txs by date
+ // Untrusted and imitation txs are filtered together in the UI
+ trusted: hideUntrustedTxs, // if false, include transactions marked untrusted in the UI
+ imitation: !hideImitationTxs, // If true, include transactions marked imitation in the UI
},
pageUrl,
)
}
/**
- * Fetch the module transaction id from the transaction service providing the transaction hash
+ * Fetch the ID of a module transaction for the given transaction hash
*/
-export const getModuleTransactionId = async ({
- transactionService,
- safeAddress,
- txHash,
-}: {
- transactionService: string
- safeAddress: string
- txHash: string
-}) => {
- const url = `${trimTrailingSlash(
- transactionService,
- )}/api/v1/safes/${safeAddress}/module-transactions/?transaction_hash=${txHash}`
- const { results } = await fetch(url).then((res) => {
- if (res.ok && res.status === 200) {
- return res.json() as Promise
- } else {
- throw new Error('Error fetching Safe module transactions')
- }
- })
-
+export const getModuleTransactionId = async (chainId: string, safeAddress: string, txHash: string) => {
+ const { results } = await getModuleTransactions(chainId, safeAddress, { transaction_hash: txHash })
if (results.length === 0) throw new Error('module transaction not found')
-
- return results[0].moduleTransactionId as string
+ return results[0].transaction.id
}
diff --git a/src/services/tx/__tests__/safeUpdateParams.test.ts b/src/services/tx/__tests__/safeUpdateParams.test.ts
index 0d8d32f30..1adc17e7a 100644
--- a/src/services/tx/__tests__/safeUpdateParams.test.ts
+++ b/src/services/tx/__tests__/safeUpdateParams.test.ts
@@ -1,36 +1,34 @@
+import * as sdkHelpers from '@/services/tx/tx-sender/sdk'
import { sameAddress } from '@/utils/addresses'
+import type { SafeProvider } from '@safe-global/protocol-kit'
import {
getFallbackHandlerDeployment,
getSafeL2SingletonDeployment,
getSafeSingletonDeployment,
} from '@safe-global/safe-deployments'
-import type { ChainInfo, SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import { Interface, BrowserProvider, type JsonRpcProvider } from 'ethers'
+import { type SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { Interface, JsonRpcProvider } from 'ethers'
import { createUpdateSafeTxs } from '../safeUpdateParams'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import * as web3 from '@/hooks/wallets/web3'
-import { MockEip1193Provider } from '@/tests/mocks/providers'
+import { FEATURES, getLatestSafeVersion } from '@/utils/chains'
+import { chainBuilder } from '@/tests/builders/chains'
const MOCK_SAFE_ADDRESS = '0x0000000000000000000000000000000000005AFE'
-jest.mock('@safe-global/protocol-kit', () => {
- const originalModule = jest.requireActual('@safe-global/protocol-kit')
-
- // Mock class
- class MockEthersAdapter extends originalModule.EthersAdapter {
- getChainId = jest.fn().mockImplementation(() => Promise.resolve(BigInt(4)))
- }
-
+const getMockSafeProviderForChain = (chainId: number) => {
return {
- ...originalModule,
- EthersAdapter: MockEthersAdapter,
- }
-})
+ getExternalProvider: jest.fn(),
+ getExternalSigner: jest.fn(),
+ getChainId: jest.fn().mockReturnValue(BigInt(chainId)),
+ } as unknown as SafeProvider
+}
describe('safeUpgradeParams', () => {
jest
.spyOn(web3, 'getWeb3ReadOnly')
- .mockImplementation(() => new BrowserProvider(MockEip1193Provider) as unknown as JsonRpcProvider)
+ .mockImplementation(() => new JsonRpcProvider(undefined, { name: 'ethereum', chainId: 1 }))
+
+ jest.spyOn(sdkHelpers, 'getSafeProvider').mockImplementation(() => getMockSafeProviderForChain(1))
it('Should add empty setFallbackHandler transaction data for older Safes', async () => {
const mockSafe = {
@@ -39,7 +37,11 @@ describe('safeUpgradeParams', () => {
},
version: '1.0.0',
} as SafeInfo
- const txs = await createUpdateSafeTxs(mockSafe, { chainId: '4', l2: false } as ChainInfo)
+
+ const mockChainInfo = chainBuilder()
+ .with({ chainId: '1', l2: false, features: [FEATURES.SAFE_141 as any] })
+ .build()
+ const txs = await createUpdateSafeTxs(mockSafe, mockChainInfo)
const [masterCopyTx, fallbackHandlerTx] = txs
// Safe upgrades mastercopy and fallbackhandler
expect(txs).toHaveLength(2)
@@ -49,7 +51,7 @@ describe('safeUpgradeParams', () => {
expect(
sameAddress(
decodeChangeMasterCopyAddress(masterCopyTx.data),
- getSafeSingletonDeployment({ version: '1.3.0', network: '4' })?.defaultAddress,
+ getSafeSingletonDeployment({ version: '1.4.1', network: '1' })?.defaultAddress,
),
).toBeTruthy()
@@ -59,14 +61,17 @@ describe('safeUpgradeParams', () => {
expect(fallbackHandlerTx.data).toEqual('0x')
})
- it('Should upgrade L1 safe to L1 1.3.0', async () => {
+ it('Should upgrade L1 safe to L1 1.4.1', async () => {
const mockSafe = {
address: {
value: MOCK_SAFE_ADDRESS,
},
version: '1.1.1',
} as SafeInfo
- const txs = await createUpdateSafeTxs(mockSafe, { chainId: '4', l2: false } as ChainInfo)
+ const mockChainInfo = chainBuilder()
+ .with({ chainId: '1', l2: false, features: [FEATURES.SAFE_141 as any] })
+ .build()
+ const txs = await createUpdateSafeTxs(mockSafe, mockChainInfo)
const [masterCopyTx, fallbackHandlerTx] = txs
// Safe upgrades mastercopy and fallbackhandler
expect(txs).toHaveLength(2)
@@ -76,7 +81,7 @@ describe('safeUpgradeParams', () => {
expect(
sameAddress(
decodeChangeMasterCopyAddress(masterCopyTx.data),
- getSafeSingletonDeployment({ version: '1.3.0', network: '4' })?.defaultAddress,
+ getSafeSingletonDeployment({ version: '1.4.1', network: '1' })?.defaultAddress,
),
).toBeTruthy()
@@ -86,19 +91,25 @@ describe('safeUpgradeParams', () => {
expect(
sameAddress(
decodeSetFallbackHandlerAddress(fallbackHandlerTx.data),
- getFallbackHandlerDeployment({ version: LATEST_SAFE_VERSION, network: '4' })?.defaultAddress,
+ getFallbackHandlerDeployment({ version: getLatestSafeVersion(mockChainInfo), network: '1' })?.defaultAddress,
),
).toBeTruthy()
})
- it('Should upgrade L2 safe to L2 1.3.0', async () => {
+ it('Should upgrade L2 safe to L2 1.4.1', async () => {
+ jest.spyOn(sdkHelpers, 'getSafeProvider').mockImplementation(() => getMockSafeProviderForChain(100))
+
const mockSafe = {
address: {
value: MOCK_SAFE_ADDRESS,
},
version: '1.1.1',
} as SafeInfo
- const txs = await createUpdateSafeTxs(mockSafe, { chainId: '100', l2: true } as ChainInfo)
+ const mockChainInfo = chainBuilder()
+ .with({ chainId: '100', l2: true, features: [FEATURES.SAFE_141 as any] })
+ .build()
+
+ const txs = await createUpdateSafeTxs(mockSafe, mockChainInfo)
const [masterCopyTx, fallbackHandlerTx] = txs
// Safe upgrades mastercopy and fallbackhandler
expect(txs).toHaveLength(2)
@@ -108,7 +119,7 @@ describe('safeUpgradeParams', () => {
expect(
sameAddress(
decodeChangeMasterCopyAddress(masterCopyTx.data),
- getSafeL2SingletonDeployment({ version: '1.3.0', network: '100' })?.defaultAddress,
+ getSafeL2SingletonDeployment({ version: '1.4.1', network: '100' })?.defaultAddress,
),
).toBeTruthy()
@@ -118,7 +129,7 @@ describe('safeUpgradeParams', () => {
expect(
sameAddress(
decodeSetFallbackHandlerAddress(fallbackHandlerTx.data),
- getFallbackHandlerDeployment({ version: '1.3.0', network: '100' })?.defaultAddress,
+ getFallbackHandlerDeployment({ version: '1.4.1', network: '100' })?.defaultAddress,
),
).toBeTruthy()
})
diff --git a/src/services/tx/__tests__/txEvents.test.ts b/src/services/tx/__tests__/txEvents.test.ts
index d6a11e623..be2f36b42 100644
--- a/src/services/tx/__tests__/txEvents.test.ts
+++ b/src/services/tx/__tests__/txEvents.test.ts
@@ -11,6 +11,7 @@ describe('txEvents', () => {
const event = TxEvent.PROCESSING
const detail = {
+ nonce: 1,
txId: '123',
txHash: '0x123',
signerAddress: faker.finance.ethereumAddress(),
@@ -28,6 +29,7 @@ describe('txEvents', () => {
expect(callback).toHaveBeenCalledWith(detail)
const detail2 = {
+ nonce: 1,
txId: '123',
txHash: '0x456',
signerAddress: faker.finance.ethereumAddress(),
@@ -52,6 +54,7 @@ describe('txEvents', () => {
const event = TxEvent.FAILED
const detail = {
+ nonce: 1,
txId: '0x123',
tx,
error: new Error('Tx failed'),
diff --git a/src/services/tx/__tests__/txMonitor.test.ts b/src/services/tx/__tests__/txMonitor.test.ts
index cc46409c6..749e30b32 100644
--- a/src/services/tx/__tests__/txMonitor.test.ts
+++ b/src/services/tx/__tests__/txMonitor.test.ts
@@ -1,8 +1,9 @@
+import { waitFor } from '@testing-library/react'
+import { act } from 'react'
import { _getRemainingTimeout } from '@/services/tx/txMonitor'
import * as txEvents from '@/services/tx/txEvents'
import * as txMonitor from '@/services/tx/txMonitor'
-import { act } from '@testing-library/react'
import { toBeHex } from 'ethers'
import { MockEip1193Provider } from '@/tests/mocks/providers'
import { BrowserProvider, type JsonRpcProvider, type TransactionReceipt } from 'ethers'
@@ -13,7 +14,7 @@ const { waitForTx, waitForRelayedTx } = txMonitor
const provider = new BrowserProvider(MockEip1193Provider) as unknown as JsonRpcProvider
-const setupFetchStub = (data: any) => (_url: string) => {
+const setupFetchStub = (data: any) => () => {
return Promise.resolve({
json: () => Promise.resolve(data),
status: 200,
@@ -25,7 +26,6 @@ describe('txMonitor', () => {
const simpleTxWatcherInstance = SimpleTxWatcher.getInstance()
let txDispatchSpy = jest.spyOn(txEvents, 'txDispatch')
- let waitForTxSpy = jest.spyOn(provider, 'waitForTransaction')
// let simpleWatcherSpy = jest.spyOn(SimpleTxWatcher, 'getInstance')
const safeAddress = toBeHex('0x123', 20)
@@ -36,7 +36,7 @@ describe('txMonitor', () => {
jest.resetAllMocks()
txDispatchSpy = jest.spyOn(txEvents, 'txDispatch')
- waitForTxSpy = jest.spyOn(provider, 'waitForTransaction')
+ jest.spyOn(provider, 'waitForTransaction')
watchTxHashSpy = jest.spyOn(simpleTxWatcherInstance, 'watchTxHash')
})
@@ -47,9 +47,9 @@ describe('txMonitor', () => {
// https://docs.ethers.io/v5/single-page/#/v5/api/providers/provider/-%23-Provider-waitForTransaction
const receipt = null as unknown as TransactionReceipt
watchTxHashSpy.mockImplementation(() => Promise.resolve(receipt))
- await waitForTx(provider, ['0x0'], '0x0', safeAddress, faker.finance.ethereumAddress(), 1)
+ await waitForTx(provider, ['0x0'], '0x0', safeAddress, faker.finance.ethereumAddress(), 1, 1, '11155111')
- expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', { txId: '0x0', error: expect.any(Error) })
+ expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', { txId: '0x0', error: expect.any(Error), nonce: 1 })
})
it('emits a REVERTED event if the tx reverted', async () => {
@@ -58,9 +58,10 @@ describe('txMonitor', () => {
} as TransactionReceipt
watchTxHashSpy.mockImplementation(() => Promise.resolve(receipt))
- await waitForTx(provider, ['0x0'], '0x0', safeAddress, faker.finance.ethereumAddress(), 1)
+ await waitForTx(provider, ['0x0'], '0x0', safeAddress, faker.finance.ethereumAddress(), 1, 1, '11155111')
expect(txDispatchSpy).toHaveBeenCalledWith('REVERTED', {
+ nonce: 1,
txId: '0x0',
error: new Error('Transaction reverted by EVM.'),
})
@@ -68,9 +69,9 @@ describe('txMonitor', () => {
it('emits a FAILED event if waitForTransaction throws', async () => {
watchTxHashSpy.mockImplementation(() => Promise.reject(new Error('Test error.')))
- await waitForTx(provider, ['0x0'], '0x0', safeAddress, faker.finance.ethereumAddress(), 1)
+ await waitForTx(provider, ['0x0'], '0x0', safeAddress, faker.finance.ethereumAddress(), 1, 1, '11155111')
- expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', { txId: '0x0', error: new Error('Test error.') })
+ expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', { txId: '0x0', error: new Error('Test error.'), nonce: 1 })
})
})
@@ -85,18 +86,20 @@ describe('txMonitor', () => {
const mockFetch = jest.spyOn(global, 'fetch')
- waitForRelayedTx('0x1', ['0x2'], safeAddress)
+ waitForRelayedTx('0x1', ['0x2'], safeAddress, 1)
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(15_000 + 1)
})
- expect(mockFetch).toHaveBeenCalledTimes(1)
- expect(txDispatchSpy).toHaveBeenCalledWith('PROCESSED', { txId: '0x2', safeAddress })
+ await waitFor(() => {
+ expect(mockFetch).toHaveBeenCalledTimes(1)
+ expect(txDispatchSpy).toHaveBeenCalledWith('PROCESSED', { txId: '0x2', safeAddress, nonce: 1 })
+ })
// The relay timeout should have been cancelled
txDispatchSpy.mockClear()
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(3 * 60_000 + 1)
})
expect(txDispatchSpy).not.toHaveBeenCalled()
@@ -112,21 +115,24 @@ describe('txMonitor', () => {
const mockFetch = jest.spyOn(global, 'fetch')
- waitForRelayedTx('0x1', ['0x2'], safeAddress)
+ waitForRelayedTx('0x1', ['0x2'], safeAddress, 1)
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(15_000 + 1)
})
- expect(mockFetch).toHaveBeenCalledTimes(1)
- expect(txDispatchSpy).toHaveBeenCalledWith('REVERTED', {
- txId: '0x2',
- error: new Error(`Relayed transaction reverted by EVM.`),
+ await waitFor(() => {
+ expect(mockFetch).toHaveBeenCalledTimes(1)
+ expect(txDispatchSpy).toHaveBeenCalledWith('REVERTED', {
+ nonce: 1,
+ txId: '0x2',
+ error: new Error(`Relayed transaction reverted by EVM.`),
+ })
})
// The relay timeout should have been cancelled
txDispatchSpy.mockClear()
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(3 * 60_000 + 1)
})
expect(txDispatchSpy).not.toHaveBeenCalled()
@@ -142,21 +148,24 @@ describe('txMonitor', () => {
const mockFetch = jest.spyOn(global, 'fetch')
- waitForRelayedTx('0x1', ['0x2'], safeAddress)
+ waitForRelayedTx('0x1', ['0x2'], safeAddress, 1)
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(15_000 + 1)
})
- expect(mockFetch).toHaveBeenCalledTimes(1)
- expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', {
- txId: '0x2',
- error: new Error(`Relayed transaction was blacklisted by relay provider.`),
+ await waitFor(() => {
+ expect(mockFetch).toHaveBeenCalledTimes(1)
+ expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', {
+ nonce: 1,
+ txId: '0x2',
+ error: new Error(`Relayed transaction was blacklisted by relay provider.`),
+ })
})
// The relay timeout should have been cancelled
txDispatchSpy.mockClear()
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(3 * 60_000 + 1)
})
expect(txDispatchSpy).not.toHaveBeenCalled()
@@ -172,21 +181,24 @@ describe('txMonitor', () => {
const mockFetch = jest.spyOn(global, 'fetch')
- waitForRelayedTx('0x1', ['0x2'], safeAddress)
+ waitForRelayedTx('0x1', ['0x2'], safeAddress, 1)
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(15_000 + 1)
})
- expect(mockFetch).toHaveBeenCalledTimes(1)
- expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', {
- txId: '0x2',
- error: new Error(`Relayed transaction was cancelled by relay provider.`),
+ await waitFor(() => {
+ expect(mockFetch).toHaveBeenCalledTimes(1)
+ expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', {
+ nonce: 1,
+ txId: '0x2',
+ error: new Error(`Relayed transaction was cancelled by relay provider.`),
+ })
})
// The relay timeout should have been cancelled
txDispatchSpy.mockClear()
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(3 * 60_000 + 1)
})
expect(txDispatchSpy).not.toHaveBeenCalled()
@@ -202,21 +214,24 @@ describe('txMonitor', () => {
const mockFetch = jest.spyOn(global, 'fetch')
- waitForRelayedTx('0x1', ['0x2'], safeAddress)
+ waitForRelayedTx('0x1', ['0x2'], safeAddress, 1)
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(15_000 + 1)
})
- expect(mockFetch).toHaveBeenCalledTimes(1)
- expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', {
- txId: '0x2',
- error: new Error(`Relayed transaction was not found.`),
+ await waitFor(() => {
+ expect(mockFetch).toHaveBeenCalledTimes(1)
+ expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', {
+ nonce: 1,
+ txId: '0x2',
+ error: new Error(`Relayed transaction was not found.`),
+ })
})
// The relay timeout should have been cancelled
txDispatchSpy.mockClear()
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(3 * 60_000 + 1)
})
expect(txDispatchSpy).not.toHaveBeenCalled()
@@ -230,13 +245,14 @@ describe('txMonitor', () => {
}
global.fetch = jest.fn().mockImplementation(setupFetchStub(mockData))
- waitForRelayedTx('0x1', ['0x2'], safeAddress)
+ waitForRelayedTx('0x1', ['0x2'], safeAddress, 1)
- await act(() => {
+ act(() => {
jest.advanceTimersByTime(3 * 60_000 + 1)
})
expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', {
+ nonce: 1,
txId: '0x2',
error: new Error('Transaction not relayed in 3 minutes. Be aware that it might still be relayed.'),
})
diff --git a/src/services/tx/extractTxInfo.ts b/src/services/tx/extractTxInfo.ts
index 6fc690beb..c40e6862d 100644
--- a/src/services/tx/extractTxInfo.ts
+++ b/src/services/tx/extractTxInfo.ts
@@ -61,6 +61,10 @@ const extractTxInfo = (
return txDetails.txData?.value ?? '0'
case 'SwapOrder':
return txDetails.txData?.value ?? '0'
+ case 'NativeStakingDeposit':
+ case 'NativeStakingValidatorsExit':
+ case 'NativeStakingWithdraw':
+ return txDetails.txData?.value ?? '0'
case 'Custom':
return txDetails.txInfo.value
case 'Creation':
@@ -87,6 +91,14 @@ const extractTxInfo = (
throw new Error('Order tx data does not have a `to` field')
}
return orderTo
+ case 'NativeStakingDeposit':
+ case 'NativeStakingValidatorsExit':
+ case 'NativeStakingWithdraw':
+ const stakingTo = txDetails.txData?.to.value
+ if (!stakingTo) {
+ throw new Error('Staking tx data does not have a `to` field')
+ }
+ return stakingTo
case 'Custom':
return txDetails.txInfo.to.value
case 'Creation':
diff --git a/src/services/tx/safeUpdateParams.ts b/src/services/tx/safeUpdateParams.ts
index af9013e99..3d9fff40b 100644
--- a/src/services/tx/safeUpdateParams.ts
+++ b/src/services/tx/safeUpdateParams.ts
@@ -1,25 +1,26 @@
+import type { SafeContractImplementationType } from '@safe-global/protocol-kit/dist/src/types/contracts'
import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types'
import { OperationType } from '@safe-global/safe-core-sdk-types'
import type { ChainInfo, SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { getReadOnlyFallbackHandlerContract, getReadOnlyGnosisSafeContract } from '@/services/contracts/safeContracts'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import { assertValidSafeVersion } from '@/hooks/coreSDK/safeCoreSDK'
import { SAFE_FEATURES } from '@safe-global/protocol-kit/dist/src/utils/safeVersions'
import { hasSafeFeature } from '@/utils/safe-versions'
-import type { SafeContractEthers } from '@safe-global/protocol-kit'
+import { getLatestSafeVersion } from '@/utils/chains'
const getChangeFallbackHandlerCallData = async (
safe: SafeInfo,
+ safeContractInstance: SafeContractImplementationType,
chain: ChainInfo,
- safeContractInstance: SafeContractEthers,
): Promise => {
if (!hasSafeFeature(SAFE_FEATURES.SAFE_FALLBACK_HANDLER, safe.version)) {
return '0x'
}
const fallbackHandlerAddress = await (
- await getReadOnlyFallbackHandlerContract(chain.chainId, LATEST_SAFE_VERSION)
+ await getReadOnlyFallbackHandlerContract(getLatestSafeVersion(chain))
).getAddress()
+ // @ts-ignore
return safeContractInstance.encode('setFallbackHandler', [fallbackHandlerAddress])
}
@@ -32,12 +33,14 @@ const getChangeFallbackHandlerCallData = async (
export const createUpdateSafeTxs = async (safe: SafeInfo, chain: ChainInfo): Promise => {
assertValidSafeVersion(safe.version)
- const latestMasterCopyAddress = await (await getReadOnlyGnosisSafeContract(chain, LATEST_SAFE_VERSION)).getAddress()
+ const latestMasterCopyAddress = await (
+ await getReadOnlyGnosisSafeContract(chain, getLatestSafeVersion(chain))
+ ).getAddress()
const readOnlySafeContract = await getReadOnlyGnosisSafeContract(chain, safe.version)
// @ts-expect-error this was removed in 1.3.0 but we need to support it for older safe versions
const changeMasterCopyCallData = readOnlySafeContract.encode('changeMasterCopy', [latestMasterCopyAddress])
- const changeFallbackHandlerCallData = await getChangeFallbackHandlerCallData(safe, chain, readOnlySafeContract)
+ const changeFallbackHandlerCallData = await getChangeFallbackHandlerCallData(safe, readOnlySafeContract, chain)
const txs: MetaTransactionData[] = [
{
diff --git a/src/services/tx/spendingLimitParams.ts b/src/services/tx/spendingLimitParams.ts
index 8246f32ab..e1ea0f9e9 100644
--- a/src/services/tx/spendingLimitParams.ts
+++ b/src/services/tx/spendingLimitParams.ts
@@ -11,6 +11,7 @@ export const createEnableModuleTx = async (
): Promise => {
const contract = await getReadOnlyGnosisSafeContract(chain, safeVersion)
+ // @ts-ignore
const data = contract.encode('enableModule', [spendingLimitAddress])
return {
diff --git a/src/services/tx/tokenTransferParams.ts b/src/services/tx/tokenTransferParams.ts
index 233c5664b..31d17999f 100644
--- a/src/services/tx/tokenTransferParams.ts
+++ b/src/services/tx/tokenTransferParams.ts
@@ -1,5 +1,5 @@
import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types'
-import type { DecodedDataResponse } from '@safe-global/safe-gateway-typescript-sdk'
+import { ConfirmationViewTypes, type BaselineConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
import { safeParseUnits } from '@/utils/formatters'
import { Interface } from 'ethers'
import { checksumAddress, sameAddress } from '@/utils/addresses'
@@ -66,19 +66,23 @@ export const createNftTransferParams = (
}
}
-export const getNativeTransferData = (data: MetaTransactionData): DecodedDataResponse => {
+export const getNativeTransferData = ({
+ to,
+ value,
+}: Pick): BaselineConfirmationView => {
return {
- method: 'Native token transfer',
+ type: ConfirmationViewTypes.GENERIC,
+ method: '',
parameters: [
{
name: 'to',
type: 'address',
- value: data.to,
+ value: to,
},
{
name: 'value',
type: 'uint256',
- value: data.value,
+ value,
},
],
}
diff --git a/src/services/tx/tx-sender/__tests__/ts-sender.test.ts b/src/services/tx/tx-sender/__tests__/ts-sender.test.ts
index f961694bd..40ce5714e 100644
--- a/src/services/tx/tx-sender/__tests__/ts-sender.test.ts
+++ b/src/services/tx/tx-sender/__tests__/ts-sender.test.ts
@@ -1,5 +1,6 @@
import { setSafeSDK } from '@/hooks/coreSDK/safeCoreSDK'
import type Safe from '@safe-global/protocol-kit'
+import type { MultiSendCallOnlyContractImplementationType } from '@safe-global/protocol-kit'
import { type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { getTransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import extractTxInfo from '../../extractTxInfo'
@@ -23,10 +24,9 @@ import {
} from 'ethers'
import * as safeContracts from '@/services/contracts/safeContracts'
-import type { MultiSendCallOnlyEthersContract } from '@safe-global/protocol-kit'
import * as web3 from '@/hooks/wallets/web3'
-const setupFetchStub = (data: any) => (_url: string) => {
+const setupFetchStub = (data: any) => () => {
return Promise.resolve({
json: () => Promise.resolve(data),
status: 200,
@@ -72,7 +72,7 @@ const mockSafeSDK = {
signatures: new Map(),
addSignature: jest.fn(),
data: {
- nonce: '1',
+ nonce: 1,
},
})),
createRejectionTransaction: jest.fn(() => ({
@@ -102,7 +102,7 @@ describe('txSender', () => {
const mockBrowserProvider = new BrowserProvider(MockEip1193Provider)
jest.spyOn(mockBrowserProvider, 'getSigner').mockImplementation(
- async (address?: string | number | undefined) =>
+ async () =>
Promise.resolve({
getAddress: jest.fn(() => Promise.resolve('0x0000000000000000000000000000000000000123')),
provider: MockEip1193Provider,
@@ -210,7 +210,7 @@ describe('txSender', () => {
expect(proposeTx).toHaveBeenCalledWith('4', '0x123', '0x456', tx, '0x1234567890', undefined)
expect(proposedTx).toEqual({ txId: '123' })
- expect(txEvents.txDispatch).toHaveBeenCalledWith('PROPOSED', { txId: '123' })
+ expect(txEvents.txDispatch).toHaveBeenCalledWith('PROPOSED', { txId: '123', nonce: 0 })
})
it('should dispatch a SIGNATURE_PROPOSED event if tx has signatures and an id', async () => {
@@ -231,7 +231,11 @@ describe('txSender', () => {
expect(proposeTx).toHaveBeenCalledWith('4', '0x123', '0x456', tx, '0x1234567890', undefined)
expect(proposedTx).toEqual({ txId: '123' })
- expect(txEvents.txDispatch).toHaveBeenCalledWith('SIGNATURE_PROPOSED', { txId: '123', signerAddress: '0x456' })
+ expect(txEvents.txDispatch).toHaveBeenCalledWith('SIGNATURE_PROPOSED', {
+ txId: '123',
+ signerAddress: '0x456',
+ nonce: 0,
+ })
})
it('should fail to propose a signature', async () => {
@@ -442,11 +446,12 @@ describe('txSender', () => {
nonce: 1,
})
- await dispatchTxExecution(safeTx, { nonce: 1 }, txId, MockEip1193Provider, SIGNER_ADDRESS, safeAddress)
+ await dispatchTxExecution(safeTx, { nonce: 1 }, txId, MockEip1193Provider, SIGNER_ADDRESS, safeAddress, false)
expect(mockSafeSDK.executeTransaction).toHaveBeenCalled()
- expect(txEvents.txDispatch).toHaveBeenCalledWith('EXECUTING', { txId })
+ expect(txEvents.txDispatch).toHaveBeenCalledWith('EXECUTING', { txId, nonce: 1 })
expect(txEvents.txDispatch).toHaveBeenCalledWith('PROCESSING', {
+ nonce: 1,
txId,
signerAddress: SIGNER_ADDRESS,
signerNonce: 1,
@@ -454,7 +459,6 @@ describe('txSender', () => {
gasLimit: undefined,
txType: 'SafeTx',
})
- expect(txEvents.txDispatch).toHaveBeenCalledWith('PROCESSED', { txId, safeAddress, txHash: TX_HASH })
})
it('should fail executing a tx', async () => {
@@ -470,12 +474,12 @@ describe('txSender', () => {
nonce: 1,
})
- await expect(dispatchTxExecution(safeTx, {}, txId, MockEip1193Provider, '5', safeAddress)).rejects.toThrow(
+ await expect(dispatchTxExecution(safeTx, {}, txId, MockEip1193Provider, '5', safeAddress, false)).rejects.toThrow(
'error',
)
expect(mockSafeSDK.executeTransaction).toHaveBeenCalled()
- expect(txEvents.txDispatch).toHaveBeenCalledWith('FAILED', { txId, error: new Error('error') })
+ expect(txEvents.txDispatch).toHaveBeenCalledWith('FAILED', { txId, error: new Error('error'), nonce: 1 })
})
it('should revert a tx', async () => {
@@ -491,11 +495,12 @@ describe('txSender', () => {
nonce: 1,
})
- await dispatchTxExecution(safeTx, { nonce: 1 }, txId, MockEip1193Provider, SIGNER_ADDRESS, '0x123')
+ await dispatchTxExecution(safeTx, { nonce: 1 }, txId, MockEip1193Provider, SIGNER_ADDRESS, '0x123', false)
expect(mockSafeSDK.executeTransaction).toHaveBeenCalled()
- expect(txEvents.txDispatch).toHaveBeenCalledWith('EXECUTING', { txId })
+ expect(txEvents.txDispatch).toHaveBeenCalledWith('EXECUTING', { txId, nonce: 1 })
expect(txEvents.txDispatch).toHaveBeenCalledWith('PROCESSING', {
+ nonce: 1,
txId,
signerAddress: SIGNER_ADDRESS,
signerNonce: 1,
@@ -503,10 +508,6 @@ describe('txSender', () => {
txType: 'SafeTx',
gasLimit: undefined,
})
- expect(txEvents.txDispatch).toHaveBeenCalledWith('REVERTED', {
- txId,
- error: new Error('Transaction reverted by EVM.'),
- })
})
})
@@ -517,10 +518,16 @@ describe('txSender', () => {
const txDetails1 = {
txId: 'multisig_0x01',
+ detailedExecutionInfo: {
+ type: 'MULTISIG',
+ },
} as TransactionDetails
const txDetails2 = {
txId: 'multisig_0x02',
+ detailedExecutionInfo: {
+ type: 'MULTISIG',
+ },
} as TransactionDetails
const txs = [txDetails1, txDetails2]
@@ -534,7 +541,7 @@ describe('txSender', () => {
},
} as any,
getAddress: async () => mockMultisendAddress,
- } as MultiSendCallOnlyEthersContract
+ } as MultiSendCallOnlyContractImplementationType
jest
.spyOn(safeContracts, 'getReadOnlyMultiSendCallOnlyContract')
diff --git a/src/services/tx/tx-sender/create.ts b/src/services/tx/tx-sender/create.ts
index 150cbb3a5..4d348a035 100644
--- a/src/services/tx/tx-sender/create.ts
+++ b/src/services/tx/tx-sender/create.ts
@@ -41,7 +41,7 @@ export const createAddOwnerTx = async (
const safeVersion = await safeSDK.getContractVersion()
const contract = await getReadOnlyGnosisSafeContract(chain, safeVersion)
- // @ts-ignore TODO: Fix overload issue
+ // @ts-ignore
const data = contract.encode('addOwnerWithThreshold', [txParams.ownerAddress, txParams.threshold])
const tx = {
@@ -66,6 +66,7 @@ export const createSwapOwnerTx = async (
const safeVersion = await safeSDK.getContractVersion()
const contract = await getReadOnlyGnosisSafeContract(chain, safeVersion)
+ // @ts-ignore SwapOwnerTxParams is a union type and the method expects a specific one
const data = contract.encode('swapOwner', [SENTINEL_ADDRESS, txParams.oldOwnerAddress, txParams.newOwnerAddress])
const tx = {
diff --git a/src/services/tx/tx-sender/dispatch.ts b/src/services/tx/tx-sender/dispatch.ts
index 5af9b9f57..6da7ee20d 100644
--- a/src/services/tx/tx-sender/dispatch.ts
+++ b/src/services/tx/tx-sender/dispatch.ts
@@ -1,32 +1,42 @@
-import { relayTransaction, type SafeInfo, type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
+import type { ConnectedWallet } from '@/hooks/wallets/useOnboard'
+import { isMultisigExecutionInfo } from '@/utils/transaction-guards'
+import { isHardwareWallet, isSmartContractWallet } from '@/utils/wallets'
+import type { MultiSendCallOnlyContractImplementationType } from '@safe-global/protocol-kit'
+import {
+ type ChainInfo,
+ relayTransaction,
+ type SafeInfo,
+ type TransactionDetails,
+} from '@safe-global/safe-gateway-typescript-sdk'
import type {
+ SafeSignature,
SafeTransaction,
Transaction,
TransactionOptions,
TransactionResult,
} from '@safe-global/safe-core-sdk-types'
import { didRevert } from '@/utils/ethers-utils'
-import type { MultiSendCallOnlyEthersContract } from '@safe-global/protocol-kit'
import { type SpendingLimitTxParams } from '@/components/tx-flow/flows/TokenTransfer/ReviewSpendingLimitTx'
import { getSpendingLimitContract } from '@/services/contracts/spendingLimitContracts'
import type { ContractTransactionResponse, Eip1193Provider, Overrides, TransactionResponse } from 'ethers'
import type { RequestId } from '@safe-global/safe-apps-sdk'
import proposeTx from '../proposeTransaction'
import { txDispatch, TxEvent } from '../txEvents'
-import { waitForRelayedTx, waitForTx } from '@/services/tx/txMonitor'
+import { waitForRelayedTx } from '@/services/tx/txMonitor'
import { getReadOnlyCurrentGnosisSafeContract } from '@/services/contracts/safeContracts'
import {
getAndValidateSafeSDK,
getSafeSDKWithSigner,
- getUncheckedSafeSDK,
tryOffChainTxSigning,
getUncheckedSigner,
+ prepareTxExecution,
+ prepareApproveTxHash,
} from './sdk'
-import { createWeb3, getUserNonce, getWeb3ReadOnly } from '@/hooks/wallets/web3'
+import { createWeb3, getUserNonce } from '@/hooks/wallets/web3'
import { asError } from '@/services/exceptions/utils'
import chains from '@/config/chains'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import { createExistingTx } from './create'
+import { getLatestSafeVersion } from '@/utils/chains'
/**
* Propose a transaction
@@ -68,6 +78,7 @@ export const dispatchTxProposal = async ({
txDispatch(txId ? TxEvent.SIGNATURE_PROPOSED : TxEvent.PROPOSED, {
txId: proposedTx.txId,
signerAddress: txId ? sender : undefined,
+ nonce: safeTx.data.nonce,
})
}
@@ -101,6 +112,23 @@ export const dispatchTxSigning = async (
return signedTx
}
+// We have to manually sign because sdk.signTransaction doesn't support delegates
+export const dispatchDelegateTxSigning = async (safeTx: SafeTransaction, wallet: ConnectedWallet) => {
+ const sdk = await getSafeSDKWithSigner(wallet.provider)
+
+ let signature: SafeSignature
+ if (isHardwareWallet(wallet)) {
+ const txHash = await sdk.getTransactionHash(safeTx)
+ signature = await sdk.signHash(txHash)
+ } else {
+ signature = await sdk.signTypedData(safeTx)
+ }
+
+ safeTx.addSignature(signature)
+
+ return safeTx
+}
+
const ZK_SYNC_ON_CHAIN_SIGNATURE_GAS_LIMIT = 4_500_000
/**
@@ -111,17 +139,23 @@ export const dispatchOnChainSigning = async (
txId: string,
provider: Eip1193Provider,
chainId: SafeInfo['chainId'],
+ signerAddress: string,
+ safeAddress: string,
) => {
- const sdkUnchecked = await getUncheckedSafeSDK(provider)
- const safeTxHash = await sdkUnchecked.getTransactionHash(safeTx)
- const eventParams = { txId }
+ const sdk = await getSafeSDKWithSigner(provider)
+ const safeTxHash = await sdk.getTransactionHash(safeTx)
+ const eventParams = { txId, nonce: safeTx.data.nonce }
const options = chainId === chains.zksync ? { gasLimit: ZK_SYNC_ON_CHAIN_SIGNATURE_GAS_LIMIT } : undefined
try {
- // With the unchecked signer, the contract call resolves once the tx
- // has been submitted in the wallet not when it has been executed
- await sdkUnchecked.approveTransactionHash(safeTxHash, options)
+ // TODO: This is a workaround until there is a fix for unchecked transactions in the protocol-kit
+ const encodedApproveHashTx = await prepareApproveTxHash(safeTxHash, provider)
+
+ await provider.request({
+ method: 'eth_sendTransaction',
+ params: [{ from: signerAddress, to: safeAddress, data: encodedApproveHashTx, gas: options?.gasLimit }],
+ })
txDispatch(TxEvent.ONCHAIN_SIGNATURE_REQUESTED, eventParams)
} catch (err) {
@@ -142,16 +176,33 @@ export const dispatchSafeTxSpeedUp = async (
chainId: SafeInfo['chainId'],
signerAddress: string,
safeAddress: string,
+ nonce: number,
) => {
- const sdkUnchecked = await getUncheckedSafeSDK(provider)
- const eventParams = { txId }
+ const sdk = await getSafeSDKWithSigner(provider)
+ const eventParams = { txId, nonce }
const signerNonce = txOptions.nonce
+ const isSmartAccount = await isSmartContractWallet(chainId, signerAddress)
// Execute the tx
let result: TransactionResult | undefined
try {
const safeTx = await createExistingTx(chainId, safeAddress, txId)
- result = await sdkUnchecked.executeTransaction(safeTx, txOptions)
+
+ // TODO: This is a workaround until there is a fix for unchecked transactions in the protocol-kit
+ if (isSmartAccount) {
+ const encodedTx = await prepareTxExecution(safeTx, provider)
+ const txHash = await provider.request({
+ method: 'eth_sendTransaction',
+ params: [{ from: signerAddress, to: safeAddress, data: encodedTx }],
+ })
+
+ result = {
+ hash: txHash,
+ transactionResponse: null,
+ }
+ } else {
+ result = await sdk.executeTransaction(safeTx, txOptions)
+ }
txDispatch(TxEvent.EXECUTING, eventParams)
} catch (error) {
txDispatch(TxEvent.SPEEDUP_FAILED, { ...eventParams, error: asError(error) })
@@ -167,13 +218,6 @@ export const dispatchSafeTxSpeedUp = async (
txType: 'SafeTx',
})
- const readOnlyProvider = getWeb3ReadOnly()
-
- if (readOnlyProvider) {
- // don't await as we don't want to block
- waitForTx(readOnlyProvider, [txId], result.hash, safeAddress, signerAddress, signerNonce)
- }
-
return result.hash
}
@@ -185,8 +229,9 @@ export const dispatchCustomTxSpeedUp = async (
provider: Eip1193Provider,
signerAddress: string,
safeAddress: string,
+ nonce: number,
) => {
- const eventParams = { txId }
+ const eventParams = { txId, nonce }
const signerNonce = txOptions.nonce
// Execute the tx
@@ -208,15 +253,9 @@ export const dispatchCustomTxSpeedUp = async (
to,
groupKey: result?.hash,
txType: 'Custom',
+ nonce,
})
- const readOnlyProvider = getWeb3ReadOnly()
-
- if (readOnlyProvider) {
- // don't await as we don't want to block
- waitForTx(readOnlyProvider, [txId], result.hash, safeAddress, signerAddress, signerNonce)
- }
-
return result.hash
}
@@ -230,17 +269,32 @@ export const dispatchTxExecution = async (
provider: Eip1193Provider,
signerAddress: string,
safeAddress: string,
+ isSmartAccount: boolean,
): Promise => {
- const sdkUnchecked = await getUncheckedSafeSDK(provider)
- const eventParams = { txId }
+ const sdk = await getSafeSDKWithSigner(provider)
+ const eventParams = { txId, nonce: safeTx.data.nonce }
const signerNonce = txOptions.nonce ?? (await getUserNonce(signerAddress))
// Execute the tx
let result: TransactionResult | undefined
try {
- result = await sdkUnchecked.executeTransaction(safeTx, txOptions)
- txDispatch(TxEvent.EXECUTING, eventParams)
+ // TODO: This is a workaround until there is a fix for unchecked transactions in the protocol-kit
+ if (isSmartAccount) {
+ const encodedTx = await prepareTxExecution(safeTx, provider)
+ const txHash = await provider.request({
+ method: 'eth_sendTransaction',
+ params: [{ from: signerAddress, to: safeAddress, data: encodedTx }],
+ })
+
+ result = {
+ hash: txHash,
+ transactionResponse: null,
+ }
+ } else {
+ result = await sdk.executeTransaction(safeTx, txOptions)
+ }
+ txDispatch(TxEvent.EXECUTING, { ...eventParams })
} catch (error) {
txDispatch(TxEvent.FAILED, { ...eventParams, error: asError(error) })
throw error
@@ -248,6 +302,7 @@ export const dispatchTxExecution = async (
txDispatch(TxEvent.PROCESSING, {
...eventParams,
+ nonce: safeTx.data.nonce,
txHash: result.hash,
signerAddress,
signerNonce,
@@ -255,29 +310,22 @@ export const dispatchTxExecution = async (
txType: 'SafeTx',
})
- const readOnlyProvider = getWeb3ReadOnly()
-
- // Asynchronously watch the tx to be mined/validated
- if (readOnlyProvider) {
- // don't await as we don't want to block
- waitForTx(readOnlyProvider, [txId], result.hash, safeAddress, signerAddress, signerNonce)
- }
-
return result.hash
}
export const dispatchBatchExecution = async (
txs: TransactionDetails[],
- multiSendContract: MultiSendCallOnlyEthersContract,
+ multiSendContract: MultiSendCallOnlyContractImplementationType,
multiSendTxData: string,
provider: Eip1193Provider,
signerAddress: string,
safeAddress: string,
overrides: Omit & { nonce: number },
+ nonce: number,
) => {
const groupKey = multiSendTxData
- let result: ContractTransactionResponse | undefined
+ let result: ContractTransactionResponse
const txIds = txs.map((tx) => tx.txId)
let signerNonce = overrides.nonce
let txData = multiSendContract.encode('multiSend', [multiSendTxData])
@@ -287,14 +335,15 @@ export const dispatchBatchExecution = async (
signerNonce = await getUserNonce(signerAddress)
}
const signer = await getUncheckedSigner(provider)
+ // @ts-ignore
result = await multiSendContract.contract.connect(signer).multiSend(multiSendTxData, overrides)
txIds.forEach((txId) => {
- txDispatch(TxEvent.EXECUTING, { txId, groupKey })
+ txDispatch(TxEvent.EXECUTING, { txId, groupKey, nonce })
})
} catch (err) {
txIds.forEach((txId) => {
- txDispatch(TxEvent.FAILED, { txId, error: asError(err), groupKey })
+ txDispatch(TxEvent.FAILED, { txId, error: asError(err), groupKey, nonce })
})
throw err
}
@@ -303,23 +352,17 @@ export const dispatchBatchExecution = async (
txIds.forEach((txId) => {
txDispatch(TxEvent.PROCESSING, {
txId,
- txHash: result!.hash,
+ txHash: result.hash,
groupKey,
signerNonce,
signerAddress,
txType: 'Custom',
data: txData,
to: txTo,
+ nonce,
})
})
- const readOnlyProvider = getWeb3ReadOnly()
-
- if (readOnlyProvider) {
- // don't await as we don't want to block
- waitForTx(readOnlyProvider, txIds, result.hash, safeAddress, signerAddress, signerNonce)
- }
-
return result!.hash
}
@@ -444,6 +487,7 @@ export const dispatchTxRelay = async (
safeTx: SafeTransaction,
safe: SafeInfo,
txId: string,
+ chain: ChainInfo,
gasLimit?: string | number,
) => {
const readOnlySafeContract = await getReadOnlyCurrentGnosisSafeContract(safe)
@@ -467,7 +511,7 @@ export const dispatchTxRelay = async (
to: safe.address.value,
data,
gasLimit: gasLimit?.toString(),
- version: safe.version ?? LATEST_SAFE_VERSION,
+ version: safe.version ?? getLatestSafeVersion(chain),
})
const taskId = relayResponse.taskId
@@ -475,19 +519,19 @@ export const dispatchTxRelay = async (
throw new Error('Transaction could not be relayed')
}
- txDispatch(TxEvent.RELAYING, { taskId, txId })
+ txDispatch(TxEvent.RELAYING, { taskId, txId, nonce: safeTx.data.nonce })
// Monitor relay tx
- waitForRelayedTx(taskId, [txId], safe.address.value)
+ waitForRelayedTx(taskId, [txId], safe.address.value, safeTx.data.nonce)
} catch (error) {
- txDispatch(TxEvent.FAILED, { txId, error: asError(error) })
+ txDispatch(TxEvent.FAILED, { txId, error: asError(error), nonce: safeTx.data.nonce })
throw error
}
}
export const dispatchBatchExecutionRelay = async (
txs: TransactionDetails[],
- multiSendContract: MultiSendCallOnlyEthersContract,
+ multiSendContract: MultiSendCallOnlyContractImplementationType,
multiSendTxData: string,
chainId: string,
safeAddress: string,
@@ -516,8 +560,10 @@ export const dispatchBatchExecutionRelay = async (
}
const taskId = relayResponse.taskId
- txs.forEach(({ txId }) => {
- txDispatch(TxEvent.RELAYING, { taskId, txId, groupKey })
+ txs.forEach(({ txId, detailedExecutionInfo }) => {
+ if (isMultisigExecutionInfo(detailedExecutionInfo)) {
+ txDispatch(TxEvent.RELAYING, { taskId, txId, groupKey, nonce: detailedExecutionInfo.nonce })
+ }
})
// Monitor relay tx
@@ -525,6 +571,7 @@ export const dispatchBatchExecutionRelay = async (
taskId,
txs.map((tx) => tx.txId),
safeAddress,
+ isMultisigExecutionInfo(txs[0].detailedExecutionInfo) ? txs[0].detailedExecutionInfo.nonce : 0,
groupKey,
)
}
diff --git a/src/services/tx/tx-sender/sdk.ts b/src/services/tx/tx-sender/sdk.ts
index 3071bd19a..ae00cebeb 100644
--- a/src/services/tx/tx-sender/sdk.ts
+++ b/src/services/tx/tx-sender/sdk.ts
@@ -1,14 +1,18 @@
import { getSafeSDK } from '@/hooks/coreSDK/safeCoreSDK'
import type Safe from '@safe-global/protocol-kit'
-import { EthersAdapter, SigningMethod } from '@safe-global/protocol-kit'
+import { SafeProvider, SigningMethod } from '@safe-global/protocol-kit'
+import {
+ generatePreValidatedSignature,
+ isSafeMultisigTransactionResponse,
+ sameString,
+} from '@safe-global/protocol-kit/dist/src/utils'
import type { Eip1193Provider, JsonRpcSigner } from 'ethers'
-import { ethers } from 'ethers'
import { isWalletRejection, isHardwareWallet, isWalletConnect } from '@/utils/wallets'
import { OperationType, type SafeTransaction } from '@safe-global/safe-core-sdk-types'
import { getChainConfig, type SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { SAFE_FEATURES } from '@safe-global/protocol-kit/dist/src/utils/safeVersions'
import { hasSafeFeature } from '@/utils/safe-versions'
-import { createWeb3 } from '@/hooks/wallets/web3'
+import { createWeb3, getWeb3ReadOnly } from '@/hooks/wallets/web3'
import { toQuantity } from 'ethers'
import { connectWallet, getConnectedWallet } from '@/hooks/wallets/useOnboard'
import { type OnboardAPI } from '@web3-onboard/core'
@@ -27,6 +31,15 @@ export const getAndValidateSafeSDK = (): Safe => {
return safeSDK
}
+export const getSafeProvider = () => {
+ const provider = getWeb3ReadOnly()
+ if (!provider) {
+ throw new Error('Provider not found.')
+ }
+
+ return new SafeProvider({ provider: provider._getConnection().url })
+}
+
async function switchOrAddChain(walletProvider: ConnectedWallet['provider'], chainId: string): Promise {
const UNKNOWN_CHAIN_ERROR_CODE = 4902
const hexChainId = toQuantity(parseInt(chainId))
@@ -127,37 +140,10 @@ export const getUncheckedSigner = async (provider: Eip1193Provider) => {
return new UncheckedJsonRpcSigner(browserProvider, (await browserProvider.getSigner()).address)
}
-/**
- * https://docs.ethers.io/v5/api/providers/jsonrpc-provider/#UncheckedJsonRpcSigner
- * This resolves the promise sooner when executing a tx and mocks
- * most of the values of transactionResponse which is needed when
- * dealing with smart-contract wallet owners
- */
-export const getUncheckedSafeSDK = async (provider: Eip1193Provider): Promise => {
- const browserProvider = createWeb3(provider)
- const signer = await browserProvider.getSigner()
- const uncheckedJsonRpcSigner = new UncheckedJsonRpcSigner(signer.provider, await signer.getAddress())
- const sdk = getAndValidateSafeSDK()
-
- const ethAdapter = new EthersAdapter({
- ethers,
- signerOrProvider: uncheckedJsonRpcSigner,
- })
-
- return sdk.connect({ ethAdapter })
-}
-
export const getSafeSDKWithSigner = async (provider: Eip1193Provider): Promise => {
- const browserProvider = createWeb3(provider)
- const signer = await browserProvider.getSigner()
const sdk = getAndValidateSafeSDK()
- const ethAdapter = new EthersAdapter({
- ethers,
- signerOrProvider: signer,
- })
-
- return sdk.connect({ ethAdapter })
+ return sdk.connect({ provider })
}
export const getSupportedSigningMethods = (safeVersion: SafeInfo['version']): SigningMethod[] => {
@@ -194,3 +180,73 @@ export const tryOffChainTxSigning = async (
export const isDelegateCall = (safeTx: SafeTransaction): boolean => {
return safeTx.data.operation === OperationType.DelegateCall
}
+
+// TODO: This is a workaround and a duplication of sdk.executeTransaction but it returns the encoded tx instead of executing it.
+export const prepareTxExecution = async (safeTransaction: SafeTransaction, provider: Eip1193Provider) => {
+ const sdk = await getSafeSDKWithSigner(provider)
+
+ if (!sdk.getContractManager().safeContract) {
+ throw new Error('Safe is not deployed')
+ }
+
+ const transaction = isSafeMultisigTransactionResponse(safeTransaction)
+ ? await sdk.toSafeTransactionType(safeTransaction)
+ : safeTransaction
+
+ const signedSafeTransaction = await sdk.copyTransaction(transaction)
+
+ const txHash = await sdk.getTransactionHash(signedSafeTransaction)
+ const ownersWhoApprovedTx = await sdk.getOwnersWhoApprovedTx(txHash)
+ for (const owner of ownersWhoApprovedTx) {
+ signedSafeTransaction.addSignature(generatePreValidatedSignature(owner))
+ }
+ const owners = await sdk.getOwners()
+ const threshold = await sdk.getThreshold()
+ const signerAddress = await sdk.getSafeProvider().getSignerAddress()
+ if (threshold > signedSafeTransaction.signatures.size && signerAddress && owners.includes(signerAddress)) {
+ signedSafeTransaction.addSignature(generatePreValidatedSignature(signerAddress))
+ }
+
+ if (threshold > signedSafeTransaction.signatures.size) {
+ const signaturesMissing = threshold - signedSafeTransaction.signatures.size
+ throw new Error(
+ `There ${signaturesMissing > 1 ? 'are' : 'is'} ${signaturesMissing} signature${
+ signaturesMissing > 1 ? 's' : ''
+ } missing`,
+ )
+ }
+
+ const value = BigInt(signedSafeTransaction.data.value)
+ if (value !== 0n) {
+ const balance = await sdk.getBalance()
+ if (value > balance) {
+ throw new Error('Not enough Ether funds')
+ }
+ }
+
+ return sdk.getEncodedTransaction(signedSafeTransaction)
+}
+
+// TODO: This is a duplication of sdk.approveTransactionHash but it returns the encoded tx instead of executing it.
+export const prepareApproveTxHash = async (hash: string, provider: Eip1193Provider) => {
+ const sdk = await getSafeSDKWithSigner(provider)
+
+ const safeContract = sdk.getContractManager().safeContract
+
+ if (!safeContract) {
+ throw new Error('Safe is not deployed')
+ }
+
+ const owners = await sdk.getOwners()
+ const signerAddress = await sdk.getSafeProvider().getSignerAddress()
+ if (!signerAddress) {
+ throw new Error('SafeProvider must be initialized with a signer to use this method')
+ }
+ const addressIsOwner = owners.some((owner: string) => signerAddress && sameString(owner, signerAddress))
+ if (!addressIsOwner) {
+ throw new Error('Transaction hashes can only be approved by Safe owners')
+ }
+
+ // @ts-ignore
+ return safeContract.encode('approveHash', [hash])
+}
diff --git a/src/services/tx/txEvents.ts b/src/services/tx/txEvents.ts
index 165ffd7ce..562e52135 100644
--- a/src/services/tx/txEvents.ts
+++ b/src/services/tx/txEvents.ts
@@ -25,16 +25,16 @@ export enum TxEvent {
SPEEDUP_FAILED = 'SPEEDUP_FAILED',
}
-type Id = { txId: string; groupKey?: string } | { txId?: string; groupKey: string }
+type Id = { txId: string; nonce: number; groupKey?: string } | { txId?: string; nonce?: number; groupKey: string }
interface TxEvents {
[TxEvent.SIGNED]: { txId?: string }
[TxEvent.SIGN_FAILED]: { txId?: string; error: Error }
[TxEvent.PROPOSE_FAILED]: { error: Error }
- [TxEvent.PROPOSED]: { txId: string }
+ [TxEvent.PROPOSED]: { txId: string; nonce: number }
[TxEvent.DELETED]: { safeTxHash: string }
[TxEvent.SIGNATURE_PROPOSE_FAILED]: { txId: string; error: Error }
- [TxEvent.SIGNATURE_PROPOSED]: { txId: string; signerAddress: string }
+ [TxEvent.SIGNATURE_PROPOSED]: { txId: string; nonce: number; signerAddress: string }
[TxEvent.SIGNATURE_INDEXED]: { txId: string }
[TxEvent.ONCHAIN_SIGNATURE_REQUESTED]: Id
[TxEvent.ONCHAIN_SIGNATURE_SUCCESS]: Id
diff --git a/src/services/tx/txMonitor.ts b/src/services/tx/txMonitor.ts
index 293180812..a6a82317d 100644
--- a/src/services/tx/txMonitor.ts
+++ b/src/services/tx/txMonitor.ts
@@ -4,6 +4,7 @@ import { txDispatch, TxEvent } from '@/services/tx/txEvents'
import { POLLING_INTERVAL } from '@/config/constants'
import { Errors, logError } from '@/services/exceptions'
+import { getSafeTransaction } from '@/utils/transactions'
import { asError } from '../exceptions/utils'
import { type JsonRpcProvider, type TransactionReceipt } from 'ethers'
import { SimpleTxWatcher } from '@/utils/SimpleTxWatcher'
@@ -23,11 +24,14 @@ export const waitForTx = async (
safeAddress: string,
walletAddress: string,
walletNonce: number,
+ nonce: number,
+ chainId: string,
) => {
const processReceipt = (receipt: TransactionReceipt | null, txIds: string[]) => {
if (receipt === null) {
txIds.forEach((txId) => {
txDispatch(TxEvent.FAILED, {
+ nonce,
txId,
error: new Error(`Transaction not found. It might have been replaced or cancelled in the connected wallet.`),
})
@@ -35,6 +39,7 @@ export const waitForTx = async (
} else if (didRevert(receipt)) {
txIds.forEach((txId) => {
txDispatch(TxEvent.REVERTED, {
+ nonce,
txId,
error: new Error('Transaction reverted by EVM.'),
})
@@ -42,6 +47,7 @@ export const waitForTx = async (
} else {
txIds.forEach((txId) => {
txDispatch(TxEvent.PROCESSED, {
+ nonce,
txId,
safeAddress,
txHash,
@@ -55,6 +61,7 @@ export const waitForTx = async (
txIds.forEach((txId) => {
txDispatch(TxEvent.FAILED, {
+ nonce,
txId,
error: asError(error),
})
@@ -62,8 +69,27 @@ export const waitForTx = async (
}
try {
- const receipt = await SimpleTxWatcher.getInstance().watchTxHash(txHash, walletAddress, walletNonce, provider)
- processReceipt(receipt, txIds)
+ const isSafeTx = !!(await getSafeTransaction(txHash, chainId, safeAddress))
+ if (isSafeTx) {
+ // Poll for the transaction until it has a transactionHash and start the watcher
+ const interval = setInterval(async () => {
+ const safeTx = await getSafeTransaction(txHash, chainId, safeAddress)
+ if (!safeTx?.txHash) return
+
+ clearInterval(interval)
+
+ const receipt = await SimpleTxWatcher.getInstance().watchTxHash(
+ safeTx.txHash,
+ walletAddress,
+ walletNonce,
+ provider,
+ )
+ processReceipt(receipt, txIds)
+ }, POLLING_INTERVAL)
+ } else {
+ const receipt = await SimpleTxWatcher.getInstance().watchTxHash(txHash, walletAddress, walletNonce, provider)
+ processReceipt(receipt, txIds)
+ }
} catch (error) {
processError(error, txIds)
}
@@ -121,7 +147,13 @@ export const getRelayTxStatus = async (taskId: string): Promise<{ task: Transact
const WAIT_FOR_RELAY_TIMEOUT = 3 * 60_000 // 3 minutes
-export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: string, groupKey?: string): void => {
+export const waitForRelayedTx = (
+ taskId: string,
+ txIds: string[],
+ safeAddress: string,
+ nonce: number,
+ groupKey?: string,
+): void => {
let intervalId: NodeJS.Timeout
let failAfterTimeoutId: NodeJS.Timeout
@@ -137,6 +169,7 @@ export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: s
case TaskState.ExecSuccess:
txIds.forEach((txId) =>
txDispatch(TxEvent.PROCESSED, {
+ nonce,
txId,
groupKey,
safeAddress,
@@ -146,6 +179,7 @@ export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: s
case TaskState.ExecReverted:
txIds.forEach((txId) =>
txDispatch(TxEvent.REVERTED, {
+ nonce,
txId,
error: new Error(`Relayed transaction reverted by EVM.`),
groupKey,
@@ -155,6 +189,7 @@ export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: s
case TaskState.Blacklisted:
txIds.forEach((txId) =>
txDispatch(TxEvent.FAILED, {
+ nonce,
txId,
error: new Error(`Relayed transaction was blacklisted by relay provider.`),
groupKey,
@@ -164,6 +199,7 @@ export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: s
case TaskState.Cancelled:
txIds.forEach((txId) =>
txDispatch(TxEvent.FAILED, {
+ nonce,
txId,
error: new Error(`Relayed transaction was cancelled by relay provider.`),
groupKey,
@@ -173,6 +209,7 @@ export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: s
case TaskState.NotFound:
txIds.forEach((txId) =>
txDispatch(TxEvent.FAILED, {
+ nonce,
txId,
error: new Error(`Relayed transaction was not found.`),
groupKey,
@@ -191,6 +228,7 @@ export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: s
failAfterTimeoutId = setTimeout(() => {
txIds.forEach((txId) =>
txDispatch(TxEvent.FAILED, {
+ nonce,
txId,
error: new Error(
`Transaction not relayed in ${
diff --git a/src/store/__tests__/pendingTxsSlice.test.ts b/src/store/__tests__/pendingTxsSlice.test.ts
index 96837b07e..d2b62529f 100644
--- a/src/store/__tests__/pendingTxsSlice.test.ts
+++ b/src/store/__tests__/pendingTxsSlice.test.ts
@@ -4,11 +4,13 @@ import { selectPendingTxIdsBySafe } from '../pendingTxsSlice'
const pendingTxs: PendingTxsState = {
'123': {
+ nonce: 1,
chainId: '5',
safeAddress: '0x123',
status: PendingStatus.INDEXING,
},
'456': {
+ nonce: 1,
chainId: '5',
safeAddress: '0x456',
status: PendingStatus.INDEXING,
@@ -30,6 +32,7 @@ describe('pendingTxsSlice', () => {
it('should select a pending tx by id', () => {
expect(selectPendingTxById({ pendingTxs } as unknown as RootState, '456')).toEqual({
+ nonce: 1,
chainId: '5',
safeAddress: '0x456',
status: PendingStatus.INDEXING,
diff --git a/src/store/__tests__/swapOrderSlice.test.ts b/src/store/__tests__/swapOrderSlice.test.ts
index e88d0ead5..73f46273c 100644
--- a/src/store/__tests__/swapOrderSlice.test.ts
+++ b/src/store/__tests__/swapOrderSlice.test.ts
@@ -8,11 +8,22 @@ import {
} from '@safe-global/safe-gateway-typescript-sdk'
import * as notificationsSlice from '@/store/notificationsSlice'
+import { type TypedStartListening } from '@reduxjs/toolkit'
+import { type RootState, type AppDispatch } from '@/store' // adjust the import path as needed
+
+type StartListeningType = TypedStartListening & {
+ withTypes: () => TypedStartListening
+} & jest.Mock
+const createStartListeningMock = () => {
+ const mock = jest.fn() as unknown as StartListeningType
+ mock.withTypes = jest.fn().mockReturnValue(mock)
+ return mock
+}
describe('swapOrderSlice', () => {
describe('swapOrderListener', () => {
const listenerMiddleware = listenerMiddlewareInstance
const mockDispatch = jest.fn()
- const startListeningMock = jest.fn()
+ const startListeningMock = createStartListeningMock()
beforeEach(() => {
jest.clearAllMocks()
@@ -158,7 +169,7 @@ describe('swapOrderSlice', () => {
const listenerMiddleware = listenerMiddlewareInstance
const mockDispatch = jest.fn()
const showNotificationSpy = jest.spyOn(notificationsSlice, 'showNotification')
- const startListeningMock = jest.fn()
+ const startListeningMock = createStartListeningMock()
beforeEach(() => {
jest.clearAllMocks()
diff --git a/src/store/__tests__/txHistorySlice.test.ts b/src/store/__tests__/txHistorySlice.test.ts
index fc96f898d..f307c2f66 100644
--- a/src/store/__tests__/txHistorySlice.test.ts
+++ b/src/store/__tests__/txHistorySlice.test.ts
@@ -1,12 +1,12 @@
+import * as txEvents from '@/services/tx/txEvents'
+import { pendingTxBuilder } from '@/tests/builders/pendingTx'
import { createListenerMiddleware } from '@reduxjs/toolkit'
+import type { ConflictHeader, DateLabel, Label, TransactionListItem } from '@safe-global/safe-gateway-typescript-sdk'
import { LabelValue, TransactionListItemType } from '@safe-global/safe-gateway-typescript-sdk'
-import type { TransactionListItem, Label, ConflictHeader, DateLabel } from '@safe-global/safe-gateway-typescript-sdk'
-
-import * as txEvents from '@/services/tx/txEvents'
-import { txHistoryListener, txHistorySlice } from '../txHistorySlice'
+import type { RootState } from '..'
import type { PendingTxsState } from '../pendingTxsSlice'
import { PendingStatus } from '../pendingTxsSlice'
-import type { RootState } from '..'
+import { txHistoryListener, txHistorySlice } from '../txHistorySlice'
describe('txHistorySlice', () => {
describe('txHistoryListener', () => {
@@ -24,12 +24,7 @@ describe('txHistorySlice', () => {
it('should dispatch SUCCESS event if tx is pending', () => {
const state = {
pendingTxs: {
- '0x123': {
- chainId: '5',
- safeAddress: '0x0000000000000000000000000000000000000000',
- status: PendingStatus.INDEXING,
- groupKey: 'groupKey',
- },
+ '0x123': pendingTxBuilder().with({ nonce: 1, status: PendingStatus.INDEXING }).build(),
} as PendingTxsState,
} as RootState
@@ -42,6 +37,10 @@ describe('txHistorySlice', () => {
type: TransactionListItemType.TRANSACTION,
transaction: {
id: '0x123',
+ executionInfo: {
+ type: 'MULTISIG',
+ nonce: 1,
+ },
},
} as TransactionListItem
@@ -55,20 +54,16 @@ describe('txHistorySlice', () => {
listenerMiddlewareInstance.middleware(listenerApi)(jest.fn())(action)
expect(txDispatchSpy).toHaveBeenCalledWith(txEvents.TxEvent.SUCCESS, {
+ nonce: 1,
txId: '0x123',
- groupKey: 'groupKey',
+ groupKey: expect.anything(),
})
})
it('should not dispatch an event if the history slice is cleared', () => {
const state = {
pendingTxs: {
- '0x123': {
- chainId: '5',
- safeAddress: '0x0000000000000000000000000000000000000000',
- status: PendingStatus.INDEXING,
- groupKey: 'groupKey',
- },
+ '0x123': pendingTxBuilder().build(),
} as PendingTxsState,
} as RootState
@@ -77,13 +72,6 @@ describe('txHistorySlice', () => {
dispatch: jest.fn(),
}
- const transaction = {
- type: TransactionListItemType.TRANSACTION,
- transaction: {
- id: '0x123',
- },
- } as TransactionListItem
-
const action = txHistorySlice.actions.set({
loading: false,
data: undefined, // Cleared
@@ -97,12 +85,7 @@ describe('txHistorySlice', () => {
it('should not dispatch an event for date labels, labels or conflict headers', () => {
const state = {
pendingTxs: {
- '0x123': {
- chainId: '5',
- safeAddress: '0x0000000000000000000000000000000000000000',
- status: PendingStatus.INDEXING,
- groupKey: '',
- },
+ '0x123': pendingTxBuilder().build(),
} as PendingTxsState,
} as RootState
@@ -141,12 +124,7 @@ describe('txHistorySlice', () => {
it('should not dispatch an event if tx is not pending', () => {
const state = {
pendingTxs: {
- '0x123': {
- chainId: '5',
- safeAddress: '0x0000000000000000000000000000000000000000',
- status: PendingStatus.INDEXING,
- groupKey: '',
- },
+ '0x123': pendingTxBuilder().build(),
} as PendingTxsState,
} as RootState
@@ -173,5 +151,43 @@ describe('txHistorySlice', () => {
expect(txDispatchSpy).not.toHaveBeenCalled()
})
+
+ it('should clear a replaced pending transaction', () => {
+ const state = {
+ pendingTxs: {
+ '0x123': pendingTxBuilder().with({ nonce: 1, status: PendingStatus.INDEXING }).build(),
+ } as PendingTxsState,
+ } as RootState
+
+ const listenerApi = {
+ getState: jest.fn(() => state),
+ dispatch: jest.fn(),
+ }
+
+ const transaction = {
+ type: TransactionListItemType.TRANSACTION,
+ transaction: {
+ id: '0x456',
+ executionInfo: {
+ nonce: 1,
+ type: 'MULTISIG',
+ },
+ },
+ } as TransactionListItem
+
+ const action = txHistorySlice.actions.set({
+ loading: false,
+ data: {
+ results: [transaction],
+ },
+ })
+
+ listenerMiddlewareInstance.middleware(listenerApi)(jest.fn())(action)
+
+ expect(listenerApi.dispatch).toHaveBeenCalledWith({
+ payload: expect.anything(),
+ type: 'pendingTxs/clearPendingTx',
+ })
+ })
})
})
diff --git a/src/store/__tests__/txQueueSlice.test.ts b/src/store/__tests__/txQueueSlice.test.ts
index 6b5ee5032..939c720c1 100644
--- a/src/store/__tests__/txQueueSlice.test.ts
+++ b/src/store/__tests__/txQueueSlice.test.ts
@@ -34,6 +34,7 @@ describe('txQueueSlice', () => {
const state = {
pendingTxs: {
'0x123': {
+ nonce: 1,
chainId: '5',
safeAddress: '0x0000000000000000000000000000000000000000',
status: PendingStatus.SIGNING,
@@ -74,6 +75,7 @@ describe('txQueueSlice', () => {
const state = {
pendingTxs: {
'0x123': {
+ nonce: 1,
chainId: '5',
safeAddress: '0x0000000000000000000000000000000000000000',
status: PendingStatus.SIGNING,
@@ -101,6 +103,7 @@ describe('txQueueSlice', () => {
const state = {
pendingTxs: {
'0x123': {
+ nonce: 1,
chainId: '5',
safeAddress: '0x0000000000000000000000000000000000000000',
status: PendingStatus.SIGNING,
@@ -145,6 +148,7 @@ describe('txQueueSlice', () => {
const state = {
pendingTxs: {
'0x123': {
+ nonce: 1,
chainId: '5',
safeAddress: '0x0000000000000000000000000000000000000000',
status: PendingStatus.SIGNING,
diff --git a/src/store/batchSlice.ts b/src/store/batchSlice.ts
index 4591aaad2..b272d90f8 100644
--- a/src/store/batchSlice.ts
+++ b/src/store/batchSlice.ts
@@ -1,6 +1,7 @@
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit'
import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import type { RootState } from '.'
+import { selectChainIdAndSafeAddress } from '@/store/common'
export type DraftBatchItem = {
id: string
@@ -65,7 +66,7 @@ const selectAllBatches = (state: RootState): BatchTxsState => {
}
export const selectBatchBySafe = createSelector(
- [selectAllBatches, (_, chainId: string, safeAddress: string) => [chainId, safeAddress]],
+ [selectAllBatches, selectChainIdAndSafeAddress],
(allBatches, [chainId, safeAddress]): DraftBatchItem[] => {
return allBatches[chainId]?.[safeAddress] || []
},
diff --git a/src/store/broadcast.ts b/src/store/broadcast.ts
index 5108f8851..8253f4273 100644
--- a/src/store/broadcast.ts
+++ b/src/store/broadcast.ts
@@ -1,22 +1,23 @@
import type { Store } from 'redux'
-import type { Middleware, PreloadedState } from '@reduxjs/toolkit'
+import type { Middleware } from '@reduxjs/toolkit'
import type { RootState } from '@/store'
-type PreloadedRootState = PreloadedState
-
const BC_NAME = 'SAFE__store-updates'
const tabId = Math.random().toString(32).slice(2)
let broadcast: BroadcastChannel | undefined
-export const broadcastState = (sliceNames: K[]): Middleware<{}, RootState> => {
- return (_) => (next) => (action) => {
+export const broadcastState = (sliceNames: K[]): Middleware<{}, RootState> => {
+ return () => (next) => (action: unknown) => {
const result = next(action)
// Broadcast actions that aren't being already broadcasted
- if (!action._isBroadcasted) {
- const sliceType = action.type.split('/')[0]
- if (sliceNames.includes(sliceType)) {
- broadcast?.postMessage({ action, tabId })
+ if (typeof action === 'object' && action !== null) {
+ const actionObj = action as { _isBroadcasted?: boolean; type?: string }
+ if (!actionObj._isBroadcasted && actionObj.type) {
+ const sliceType = actionObj.type.split('/')[0]
+ if (sliceNames.includes(sliceType as K)) {
+ broadcast?.postMessage({ action, tabId })
+ }
}
}
diff --git a/src/store/common.ts b/src/store/common.ts
index 78465ddf5..e2b977627 100644
--- a/src/store/common.ts
+++ b/src/store/common.ts
@@ -1,4 +1,5 @@
-import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
+import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit'
+import type { RootState } from '@/store/index'
export type Loadable = {
data: T
@@ -32,3 +33,9 @@ export const makeLoadableSlice = (name: N, data: T) => {
selector,
}
}
+
+// Memoized selector for chainId and safeAddress
+export const selectChainIdAndSafeAddress = createSelector(
+ [(_: RootState, chainId: string) => chainId, (_: RootState, _chainId: string, safeAddress: string) => safeAddress],
+ (chainId, safeAddress) => [chainId, safeAddress] as const,
+)
diff --git a/src/store/cookiesAndTermsSlice.ts b/src/store/cookiesAndTermsSlice.ts
index 2c87b0f1f..7f9c2a641 100644
--- a/src/store/cookiesAndTermsSlice.ts
+++ b/src/store/cookiesAndTermsSlice.ts
@@ -1,6 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
import type { RootState } from '.'
+import { metadata } from '@/markdown/terms/terms.md'
export enum CookieAndTermType {
TERMS = 'terms',
@@ -9,23 +10,40 @@ export enum CookieAndTermType {
ANALYTICS = 'analytics',
}
-export type CookiesAndTermsState = Record
+export type CookiesAndTermsState = {
+ [CookieAndTermType.TERMS]: boolean | undefined
+ [CookieAndTermType.NECESSARY]: boolean | undefined
+ [CookieAndTermType.UPDATES]: boolean | undefined
+ [CookieAndTermType.ANALYTICS]: boolean | undefined
+ termsVersion: string | undefined
+}
-const initialState: CookiesAndTermsState = {
+export const cookiesAndTermsInitialState: CookiesAndTermsState = {
[CookieAndTermType.TERMS]: undefined,
[CookieAndTermType.NECESSARY]: undefined,
[CookieAndTermType.UPDATES]: undefined,
[CookieAndTermType.ANALYTICS]: undefined,
+ termsVersion: undefined,
}
export const cookiesAndTermsSlice = createSlice({
- name: 'cookies_terms_v1',
- initialState,
+ name: `cookies_terms`,
+ initialState: cookiesAndTermsInitialState,
reducers: {
saveCookieAndTermConsent: (_, { payload }: PayloadAction) => payload,
},
})
-export const { saveCookieAndTermConsent } = cookiesAndTermsSlice.actions
-
export const selectCookies = (state: RootState) => state[cookiesAndTermsSlice.name]
+
+export const hasAcceptedTerms = (state: RootState): boolean => {
+ const cookies = selectCookies(state)
+ return cookies[CookieAndTermType.TERMS] === true && cookies.termsVersion === metadata.version
+}
+
+export const hasConsentFor = (state: RootState, type: CookieAndTermType): boolean => {
+ const cookies = selectCookies(state)
+ return cookies[type] === true && cookies.termsVersion === metadata.version
+}
+
+export const { saveCookieAndTermConsent } = cookiesAndTermsSlice.actions
diff --git a/src/store/gateway.ts b/src/store/gateway.ts
new file mode 100644
index 000000000..3b027245b
--- /dev/null
+++ b/src/store/gateway.ts
@@ -0,0 +1,59 @@
+import { createApi } from '@reduxjs/toolkit/query/react'
+
+import { getTransactionDetails, type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
+import type { BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes'
+import type { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query/react'
+import { getDelegates } from '@safe-global/safe-gateway-typescript-sdk'
+import type { DelegateResponse } from '@safe-global/safe-gateway-typescript-sdk/dist/types/delegates'
+
+const noopBaseQuery: BaseQueryFn<
+ unknown, // QueryArg type
+ unknown, // ResultType
+ FetchBaseQueryError, // ErrorType
+ {}, // DefinitionExtraOptions
+ {} // Meta
+> = async () => ({ data: null })
+
+export const gatewayApi = createApi({
+ reducerPath: 'gatewayApi',
+ baseQuery: noopBaseQuery,
+ endpoints: (builder) => ({
+ getTransactionDetails: builder.query({
+ async queryFn({ chainId, txId }) {
+ try {
+ const txDetails = await getTransactionDetails(chainId, txId)
+ return { data: txDetails }
+ } catch (error) {
+ return { error: error as FetchBaseQueryError }
+ }
+ },
+ }),
+ getMultipleTransactionDetails: builder.query({
+ async queryFn({ chainId, txIds }) {
+ try {
+ const txDetails = await Promise.all(txIds.map((txId) => getTransactionDetails(chainId, txId)))
+ return { data: txDetails }
+ } catch (error) {
+ return { error: error as FetchBaseQueryError }
+ }
+ },
+ }),
+ getDelegates: builder.query({
+ async queryFn({ chainId, safeAddress }) {
+ try {
+ const delegates = await getDelegates(chainId, { safe: safeAddress })
+ return { data: delegates }
+ } catch (error) {
+ return { error: error as FetchBaseQueryError }
+ }
+ },
+ }),
+ }),
+})
+
+export const {
+ useGetTransactionDetailsQuery,
+ useGetMultipleTransactionDetailsQuery,
+ useLazyGetTransactionDetailsQuery,
+ useGetDelegatesQuery,
+} = gatewayApi
diff --git a/src/store/index.ts b/src/store/index.ts
index e6b2f8f81..70097eec6 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -3,8 +3,10 @@ import {
combineReducers,
createListenerMiddleware,
type ThunkAction,
- type PreloadedState,
- type AnyAction,
+ type Action,
+ type Middleware,
+ type EnhancedStore,
+ type ThunkDispatch,
} from '@reduxjs/toolkit'
import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux'
import merge from 'lodash/merge'
@@ -12,6 +14,8 @@ import { IS_PRODUCTION } from '@/config/constants'
import { getPreloadedState, persistState } from './persistStore'
import { broadcastState, listenToBroadcast } from './broadcast'
import {
+ cookiesAndTermsSlice,
+ cookiesAndTermsInitialState,
safeMessagesListener,
swapOrderListener,
swapOrderStatusListener,
@@ -20,6 +24,9 @@ import {
} from './slices'
import * as slices from './slices'
import * as hydrate from './useHydrateStore'
+import { ofacApi } from '@/store/ofac'
+import { safePassApi } from './safePass'
+import { metadata } from '@/markdown/terms/terms.md'
const rootReducer = combineReducers({
[slices.chainsSlice.name]: slices.chainsSlice.reducer,
@@ -43,9 +50,12 @@ const rootReducer = combineReducers({
[slices.batchSlice.name]: slices.batchSlice.reducer,
[slices.undeployedSafesSlice.name]: slices.undeployedSafesSlice.reducer,
[slices.swapParamsSlice.name]: slices.swapParamsSlice.reducer,
+ [ofacApi.reducerPath]: ofacApi.reducer,
+ [safePassApi.reducerPath]: safePassApi.reducer,
+ [slices.gatewayApi.reducerPath]: slices.gatewayApi.reducer,
})
-const persistedSlices: (keyof PreloadedState)[] = [
+const persistedSlices: (keyof Partial)[] = [
slices.sessionSlice.name,
slices.addressBookSlice.name,
slices.pendingTxsSlice.name,
@@ -66,10 +76,13 @@ export const getPersistedState = () => {
export const listenerMiddlewareInstance = createListenerMiddleware()
-const middleware = [
+const middleware: Middleware<{}, RootState>[] = [
persistState(persistedSlices),
broadcastState(persistedSlices),
listenerMiddlewareInstance.middleware,
+ ofacApi.middleware,
+ safePassApi.middleware,
+ slices.gatewayApi.middleware,
]
const listeners = [safeMessagesListener, txHistoryListener, txQueueListener, swapOrderListener, swapOrderStatusListener]
@@ -83,13 +96,25 @@ export const _hydrationReducer: typeof rootReducer = (state, action) => {
*
* @see https://lodash.com/docs/4.17.15#merge
*/
+ const nextState = merge({}, state, action.payload) as RootState
- return merge({}, state, action.payload)
+ // Check if termsVersion matches
+ if (
+ nextState[cookiesAndTermsSlice.name] &&
+ nextState[cookiesAndTermsSlice.name].termsVersion !== metadata.version
+ ) {
+ // Reset consent
+ nextState[cookiesAndTermsSlice.name] = {
+ ...cookiesAndTermsInitialState,
+ }
+ }
+
+ return nextState
}
- return rootReducer(state, action)
+ return rootReducer(state, action) as RootState
}
-export const makeStore = (initialState?: Record) => {
+export const makeStore = (initialState?: Partial): EnhancedStore => {
const store = configureStore({
reducer: _hydrationReducer,
middleware: (getDefaultMiddleware) => {
@@ -105,10 +130,9 @@ export const makeStore = (initialState?: Record) => {
return store
}
-export type AppDispatch = ReturnType['dispatch']
-export type RootState = ReturnType
-
-export type AppThunk = ThunkAction
+export type RootState = ReturnType
+export type AppDispatch = ThunkDispatch & EnhancedStore['dispatch']
+export type AppThunk = ThunkAction
export const useAppDispatch = () => useDispatch()
export const useAppSelector: TypedUseSelectorHook = useSelector
diff --git a/src/store/ofac.ts b/src/store/ofac.ts
new file mode 100644
index 000000000..0982ce5c9
--- /dev/null
+++ b/src/store/ofac.ts
@@ -0,0 +1,72 @@
+import { createApi } from '@reduxjs/toolkit/query/react'
+import { selectChainById } from '@/store/chainsSlice'
+import { Contract } from 'ethers'
+import { createWeb3ReadOnly } from '@/hooks/wallets/web3'
+import type { RootState } from '.'
+import { CHAINALYSIS_OFAC_CONTRACT } from '@/config/constants'
+import chains from '@/config/chains'
+
+// Chainalysis contract ABI and address
+const contractAbi = [
+ {
+ inputs: [],
+ stateMutability: 'nonpayable',
+ type: 'constructor',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'address',
+ name: 'addr',
+ type: 'address',
+ },
+ ],
+ name: 'isSanctioned',
+ outputs: [
+ {
+ internalType: 'bool',
+ name: '',
+ type: 'bool',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+]
+
+const noopBaseQuery = async () => ({ data: null })
+
+const createBadRequestError = (message: string) => ({
+ error: { status: 400, statusText: 'Bad Request', data: message },
+})
+
+export const ofacApi = createApi({
+ reducerPath: 'ofacApi',
+ baseQuery: noopBaseQuery,
+ endpoints: (builder) => ({
+ getIsSanctioned: builder.query({
+ async queryFn(address, { getState }) {
+ const state = getState()
+ const chain = selectChainById(state as RootState, chains.eth)
+
+ if (!chain) return createBadRequestError('Chain info not found')
+ if (!address) return createBadRequestError('No address provided')
+
+ const provider = createWeb3ReadOnly(chain)
+ const contract = new Contract(CHAINALYSIS_OFAC_CONTRACT, contractAbi, provider)
+
+ try {
+ const isAddressBlocked: boolean = await contract['isSanctioned'](address)
+ return { data: isAddressBlocked }
+ } catch (error) {
+ return { error: { status: 'CUSTOM_ERROR', data: (error as Error).message } }
+ }
+ },
+ keepUnusedDataFor: 24 * 60 * 60, // 24 hours
+ }),
+ }),
+})
+
+// Export hooks for usage in functional components, which are
+// auto-generated based on the defined endpoints
+export const { useGetIsSanctionedQuery } = ofacApi
diff --git a/src/store/pendingTxsSlice.ts b/src/store/pendingTxsSlice.ts
index 4c263ede8..671c550a0 100644
--- a/src/store/pendingTxsSlice.ts
+++ b/src/store/pendingTxsSlice.ts
@@ -2,6 +2,7 @@ import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolki
import type { RootState } from '@/store'
import { sameAddress } from '@/utils/addresses'
+import { selectChainIdAndSafeAddress } from '@/store/common'
export enum PendingStatus {
SIGNING = 'SIGNING',
@@ -16,11 +17,10 @@ export enum PendingTxType {
SAFE_TX = 'SAFE_TX',
}
-const ActivePendingStates = [PendingStatus.RELAYING, PendingStatus.INDEXING, PendingStatus.PROCESSING]
-
export type PendingTxCommonProps = {
chainId: string
safeAddress: string
+ nonce: number
groupKey?: string
}
@@ -106,9 +106,10 @@ export const selectPendingTxById = createSelector(
)
export const selectPendingTxIdsBySafe = createSelector(
- [selectPendingTxs, (_: RootState, chainId: string, safeAddress: string) => [chainId, safeAddress]],
- (pendingTxs, [chainId, safeAddress]) =>
- Object.keys(pendingTxs).filter(
+ [selectPendingTxs, selectChainIdAndSafeAddress],
+ (pendingTxs, [chainId, safeAddress]) => {
+ return Object.keys(pendingTxs).filter(
(id) => pendingTxs[id].chainId === chainId && sameAddress(pendingTxs[id].safeAddress, safeAddress),
- ),
+ )
+ },
)
diff --git a/src/store/persistStore.ts b/src/store/persistStore.ts
index 45d1433e3..58fc9378a 100644
--- a/src/store/persistStore.ts
+++ b/src/store/persistStore.ts
@@ -1,13 +1,11 @@
-import type { Middleware, PreloadedState } from '@reduxjs/toolkit'
+import type { Middleware } from '@reduxjs/toolkit'
import local from '@/services/local-storage/local'
import type { RootState } from '@/store'
-type PreloadedRootState = PreloadedState
-
-export const getPreloadedState = (sliceNames: K[]): PreloadedRootState => {
- return sliceNames.reduce((preloadedState, sliceName) => {
- const sliceState = local.getItem(sliceName)
+export const getPreloadedState = (sliceNames: K[]): Partial => {
+ return sliceNames.reduce>((preloadedState, sliceName) => {
+ const sliceState = local.getItem(sliceName as string)
if (sliceState) {
preloadedState[sliceName] = sliceState
@@ -17,24 +15,26 @@ export const getPreloadedState = (sliceNames
}, {})
}
-export const persistState = (sliceNames: K[]): Middleware<{}, RootState> => {
+export const persistState = (sliceNames: K[]): Middleware<{}, RootState> => {
return (store) => (next) => (action) => {
const result = next(action)
- // No need to persist broadcasted actions because they are persisted in another tab
- if (action._isBroadcasted) return result
+ if (typeof action === 'object' && action !== null && 'type' in action) {
+ // No need to persist broadcasted actions because they are persisted in another tab
+ if ('_isBroadcasted' in action && action._isBroadcasted) return result
- const sliceType = action.type.split('/')[0]
- const name = sliceNames.find((slice) => slice === sliceType)
+ const sliceType = (action as { type: string }).type.split('/')[0]
+ const name = sliceNames.find((slice) => slice === sliceType)
- if (name) {
- const state = store.getState()
- const sliceState = state[name]
+ if (name) {
+ const state = store.getState()
+ const sliceState = state[name]
- if (sliceState) {
- local.setItem(name, sliceState)
- } else {
- local.removeItem(name)
+ if (sliceState) {
+ local.setItem(name as string, sliceState)
+ } else {
+ local.removeItem(name as string)
+ }
}
}
diff --git a/src/store/safePass.ts b/src/store/safePass.ts
new file mode 100644
index 000000000..875c44f9f
--- /dev/null
+++ b/src/store/safePass.ts
@@ -0,0 +1,37 @@
+import { cgwDebugStorage } from '@/components/sidebar/DebugToggle'
+import { IS_PRODUCTION, GATEWAY_URL_PRODUCTION, GATEWAY_URL_STAGING } from '@/config/constants'
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
+
+// TODO: Replace this with the auto-generated SDK once available.
+const GATEWAY_URL = IS_PRODUCTION || cgwDebugStorage.get() ? GATEWAY_URL_PRODUCTION : GATEWAY_URL_STAGING
+const GLOBAL_CAMPAIGN_IDS: Record<'1' | '11155111', string> = {
+ '11155111': 'fa9f462b-8e8c-4122-aa41-2464e919b721',
+ '1': '9ed78b8b-178d-4e25-9ef2-1517865991ee',
+}
+
+export type CampaignLeaderboardEntry = {
+ holder: string
+ position: number
+ boost: string
+ totalPoints: number
+ totalBoostedPoints: number
+}
+
+export const safePassApi = createApi({
+ reducerPath: 'safePassApi',
+ baseQuery: fetchBaseQuery({ baseUrl: GATEWAY_URL }),
+ endpoints: (builder) => ({
+ getOwnGlobalCampaignRank: builder.query<
+ CampaignLeaderboardEntry,
+ { chainId: '1' | '11155111'; safeAddress: string }
+ >({
+ query: (request) => ({
+ url: `v1/community/campaigns/${GLOBAL_CAMPAIGN_IDS[request.chainId]}/leaderboard/${request.safeAddress}`,
+ }),
+ }),
+ }),
+})
+
+// Export hooks for usage in functional components, which are
+// auto-generated based on the defined endpoints
+export const { useGetOwnGlobalCampaignRankQuery } = safePassApi
diff --git a/src/store/settingsSlice.ts b/src/store/settingsSlice.ts
index f92495868..8f0422d0c 100644
--- a/src/store/settingsSlice.ts
+++ b/src/store/settingsSlice.ts
@@ -29,7 +29,7 @@ export type SettingsState = {
tokenList: TOKEN_LISTS
- showOnlyTrustedTransactions?: boolean
+ hideSuspiciousTransactions?: boolean
shortName: {
copy: boolean
@@ -53,7 +53,7 @@ export const initialState: SettingsState = {
hiddenTokens: {},
- showOnlyTrustedTransactions: false,
+ hideSuspiciousTransactions: false,
shortName: {
copy: true,
@@ -100,8 +100,8 @@ export const settingsSlice = createSlice({
setTokenList: (state, { payload }: PayloadAction) => {
state.tokenList = payload
},
- setshowOnlyTrustedTransactions: (state, { payload }: PayloadAction) => {
- state.showOnlyTrustedTransactions = payload
+ hideSuspiciousTransactions: (state, { payload }: PayloadAction) => {
+ state.hideSuspiciousTransactions = payload
},
setRpc: (state, { payload }: PayloadAction<{ chainId: string; rpc: string }>) => {
const { chainId, rpc } = payload
@@ -135,7 +135,7 @@ export const {
setDarkMode,
setHiddenTokensForChain,
setTokenList,
- setshowOnlyTrustedTransactions,
+ hideSuspiciousTransactions,
setRpc,
setTenderly,
setOnChainSigning,
diff --git a/src/store/slices.ts b/src/store/slices.ts
index 210bd7d7c..c0dce32a5 100644
--- a/src/store/slices.ts
+++ b/src/store/slices.ts
@@ -19,3 +19,4 @@ export * from './batchSlice'
export * from '@/features/counterfactual/store/undeployedSafesSlice'
export * from '@/features/swap/store/swapParamsSlice'
export * from './swapOrderSlice'
+export * from './gateway'
diff --git a/src/store/txHistorySlice.ts b/src/store/txHistorySlice.ts
index 10cd8908a..80b1e35ae 100644
--- a/src/store/txHistorySlice.ts
+++ b/src/store/txHistorySlice.ts
@@ -1,9 +1,14 @@
import type { listenerMiddlewareInstance } from '@/store'
import { createSelector } from '@reduxjs/toolkit'
import type { TransactionListPage } from '@safe-global/safe-gateway-typescript-sdk'
-import { isCreationTxInfo, isIncomingTransfer, isTransactionListItem } from '@/utils/transaction-guards'
+import {
+ isCreationTxInfo,
+ isIncomingTransfer,
+ isMultisigExecutionInfo,
+ isTransactionListItem,
+} from '@/utils/transaction-guards'
import { txDispatch, TxEvent } from '@/services/tx/txEvents'
-import { selectPendingTxs } from './pendingTxsSlice'
+import { clearPendingTx, selectPendingTxs } from './pendingTxsSlice'
import { makeLoadableSlice } from './common'
const { slice, selector } = makeLoadableSlice('txHistory', undefined as TransactionListPage | undefined)
@@ -32,17 +37,29 @@ export const txHistoryListener = (listenerMiddleware: typeof listenerMiddlewareI
continue
}
+ const pendingTxByNonce = Object.entries(pendingTxs).find(([, pendingTx]) =>
+ isMultisigExecutionInfo(result.transaction.executionInfo)
+ ? pendingTx.nonce === result.transaction.executionInfo.nonce
+ : false,
+ )
+
+ if (!pendingTxByNonce) continue
+
const txId = result.transaction.id
- const pendingTx = pendingTxs[txId]
+ const [pendingTxId, pendingTx] = pendingTxByNonce
- if (pendingTx) {
+ if (pendingTxId === txId) {
const txHash = 'txHash' in pendingTx ? pendingTx.txHash : undefined
txDispatch(TxEvent.SUCCESS, {
+ nonce: pendingTx.nonce,
txId,
groupKey: pendingTxs[txId].groupKey,
txHash,
})
+ } else {
+ // There is a pending tx with the same nonce as a history tx but their txIds don't match
+ listenerApi.dispatch(clearPendingTx({ txId: pendingTxId }))
}
}
},
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 3b324c269..d9574f20c 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -14,10 +14,6 @@ main {
width: 100%;
}
-main.swapWrapper {
- height: calc(100vh - 52px);
-}
-
a {
color: inherit;
text-decoration: none;
diff --git a/src/tests/builders/pendingTx.ts b/src/tests/builders/pendingTx.ts
new file mode 100644
index 000000000..a655859ec
--- /dev/null
+++ b/src/tests/builders/pendingTx.ts
@@ -0,0 +1,14 @@
+import { PendingStatus } from '@/store/pendingTxsSlice'
+import type { PendingTx } from '@/store/pendingTxsSlice'
+import { Builder, type IBuilder } from '@/tests/Builder'
+import { faker } from '@faker-js/faker'
+
+export function pendingTxBuilder(): IBuilder {
+ return Builder.new().with({
+ chainId: faker.string.numeric(),
+ safeAddress: faker.finance.ethereumAddress(),
+ nonce: faker.number.int(),
+ groupKey: faker.string.hexadecimal(),
+ status: faker.helpers.enumValue(PendingStatus),
+ })
+}
diff --git a/src/tests/builders/safe.ts b/src/tests/builders/safe.ts
index 1ecacd8cd..e7f9972b6 100644
--- a/src/tests/builders/safe.ts
+++ b/src/tests/builders/safe.ts
@@ -5,7 +5,6 @@ import type { SafeInfo, AddressEx } from '@safe-global/safe-gateway-typescript-s
import { Builder } from '../Builder'
import { generateRandomArray } from './utils'
-import { LATEST_SAFE_VERSION } from '@/config/constants'
import { checksumAddress } from '@/utils/addresses'
import type { IBuilder } from '../Builder'
@@ -20,9 +19,10 @@ export function addressExBuilder(): IBuilder {
}
export function safeInfoBuilder(): IBuilder {
+ const chainId = faker.helpers.arrayElement(['1', '11155111', '100', '10', '137'])
return Builder.new().with({
address: addressExBuilder().build(),
- chainId: faker.string.numeric(),
+ chainId,
nonce: faker.number.int(),
threshold: faker.number.int(),
owners: generateRandomArray(() => addressExBuilder().build(), { min: 1, max: MAX_OWNERS_LENGTH }),
@@ -31,7 +31,7 @@ export function safeInfoBuilder(): IBuilder {
modules: [],
guard: null,
fallbackHandler: addressExBuilder().build(),
- version: LATEST_SAFE_VERSION,
+ version: '1.4.1',
collectiblesTag: faker.string.numeric(),
txQueuedTag: faker.string.numeric(),
txHistoryTag: faker.string.numeric(),
diff --git a/src/tests/e2e-wallet.ts b/src/tests/e2e-wallet.ts
deleted file mode 100644
index 17ce32c32..000000000
--- a/src/tests/e2e-wallet.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { type HDNodeWallet, JsonRpcProvider, Wallet } from 'ethers'
-import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
-import { type WalletInit, createEIP1193Provider } from '@web3-onboard/common'
-import { getRpcServiceUrl } from '@/hooks/wallets/web3'
-import { numberToHex } from '@/utils/hex'
-import { CYPRESS_MNEMONIC } from '@/config/constants'
-
-export const E2E_WALLET_NAME = 'E2E Wallet'
-
-let currentChainId = ''
-let currentRpcUri = ''
-
-const E2EWalletMoule = (chainId: ChainInfo['chainId'], rpcUri: ChainInfo['rpcUri']): WalletInit => {
- currentChainId = chainId
- currentRpcUri = getRpcServiceUrl(rpcUri)
-
- return () => {
- return {
- label: E2E_WALLET_NAME,
- getIcon: async () => ' ',
- getInterface: async () => {
- let provider: JsonRpcProvider
- let wallet: HDNodeWallet
- const chainChangedListeners = new Set<(chainId: string) => void>()
-
- const updateProvider = () => {
- provider?.destroy()
- provider = new JsonRpcProvider(currentRpcUri, Number(currentChainId), { staticNetwork: true })
- wallet = Wallet.fromPhrase(CYPRESS_MNEMONIC, provider)
-
- setTimeout(() => {
- chainChangedListeners.forEach((listener) => listener(numberToHex(Number(currentChainId))))
- }, 100)
- }
-
- updateProvider()
-
- return {
- provider: createEIP1193Provider(
- {
- on: (event: string, listener: (...args: any[]) => void) => {
- if (event === 'accountsChanged') {
- return
- } else if (event === 'chainChanged') {
- chainChangedListeners.add(listener)
- } else {
- provider.on(event, listener)
- }
- },
-
- request: async (request: { method: string; params: any[] }) => {
- return provider.send(request.method, request.params)
- },
- },
- {
- eth_chainId: async () => currentChainId,
-
- // @ts-ignore
- eth_getCode: async ({ params }) => provider.getCode(params[0], params[1]),
-
- eth_accounts: async () => [wallet.address],
- eth_requestAccounts: async () => [wallet.address],
-
- eth_call: async ({ params }: { params: any }) => wallet.call(params[0]),
-
- eth_sendTransaction: async ({ params }) => {
- const tx = await wallet.sendTransaction(params[0] as any)
- return tx.hash // return transaction hash
- },
-
- personal_sign: async ({ params }) => {
- const signedMessage = wallet.signingKey.sign(params[0])
- return signedMessage.serialized
- },
-
- eth_signTypedData: async ({ params }) => {
- const [, json] = params
- const typedData = JSON.parse(json)
- return await wallet.signTypedData(
- typedData.domain,
- { [typedData.primaryType]: typedData.types[typedData.primaryType] },
- typedData.message,
- )
- },
-
- // @ts-ignore
- wallet_switchEthereumChain: async ({ params }) => {
- updateProvider()
- },
- },
- ),
- }
- },
- platforms: ['desktop'],
- }
- }
-}
-
-export default E2EWalletMoule
diff --git a/src/tests/mocks/chains.ts b/src/tests/mocks/chains.ts
index f21904c27..8d9c6c346 100644
--- a/src/tests/mocks/chains.ts
+++ b/src/tests/mocks/chains.ts
@@ -1,9 +1,22 @@
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { FEATURES, GAS_PRICE_TYPE, RPC_AUTHENTICATION } from '@safe-global/safe-gateway-typescript-sdk'
+const contractAddresses = {
+ createCallAddress: null,
+ fallbackHandlerAddress: null,
+ multiSendAddress: null,
+ multiSendCallOnlyAddress: null,
+ safeProxyFactoryAddress: null,
+ safeSingletonAddress: null,
+ safeWebAuthnSignerFactoryAddress: null,
+ signMessageLibAddress: null,
+ simulateTxAccessorAddress: null,
+}
+
const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
{
transactionService: 'https://safe-transaction.mainnet.gnosis.io',
+ contractAddresses,
chainId: '1',
chainName: 'Ethereum',
chainLogoUri: '',
@@ -58,6 +71,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.xdai.gnosis.io',
+ contractAddresses,
chainId: '100',
chainName: 'Gnosis Chain',
chainLogoUri: '',
@@ -111,6 +125,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.polygon.gnosis.io',
+ contractAddresses,
chainId: '137',
chainName: 'Polygon',
chainLogoUri: '',
@@ -170,6 +185,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.bsc.gnosis.io',
+ contractAddresses,
chainId: '56',
chainName: 'BNB Smart Chain',
chainLogoUri: '',
@@ -225,6 +241,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.ewc.gnosis.io',
+ contractAddresses,
chainId: '246',
chainName: 'Energy Web Chain',
chainLogoUri: '',
@@ -278,6 +295,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.arbitrum.gnosis.io',
+ contractAddresses,
chainId: '42161',
chainName: 'Arbitrum',
chainLogoUri: '',
@@ -324,6 +342,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.aurora.gnosis.io',
+ contractAddresses,
chainId: '1313161554',
chainName: 'Aurora',
chainLogoUri: '',
@@ -371,6 +390,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.avalanche.gnosis.io',
+ contractAddresses,
chainId: '43114',
chainName: 'Avalanche',
chainLogoUri: '',
@@ -429,6 +449,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.optimism.gnosis.io',
+ contractAddresses,
chainId: '10',
chainName: 'Optimism',
chainLogoUri: '',
@@ -475,6 +496,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.goerli.gnosis.io/',
+ contractAddresses,
chainId: '5',
chainName: 'Goerli',
chainLogoUri: '',
@@ -529,6 +551,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.rinkeby.gnosis.io',
+ contractAddresses,
chainId: '4',
chainName: 'Rinkeby',
chainLogoUri: '',
@@ -573,6 +596,7 @@ const CONFIG_SERVICE_CHAINS: ChainInfo[] = [
},
{
transactionService: 'https://safe-transaction.volta.gnosis.io',
+ contractAddresses,
chainId: '73799',
chainName: 'Volta',
chainLogoUri: '',
diff --git a/src/tests/mocks/contractManager.ts b/src/tests/mocks/contractManager.ts
index 3e7e33dfe..86eb6c8d1 100644
--- a/src/tests/mocks/contractManager.ts
+++ b/src/tests/mocks/contractManager.ts
@@ -4,7 +4,6 @@ import {
} from '@/types/contracts/factories/@safe-global/safe-deployments/dist/assets/v1.3.0'
import { faker } from '@faker-js/faker'
import type { ContractManager } from '@safe-global/protocol-kit'
-import { type SafeContract } from '@safe-global/safe-core-sdk-types'
const safeContractInterface = Gnosis_safe__factory.createInterface()
const multiSendInterface = Multi_send__factory.createInterface()
@@ -32,7 +31,7 @@ export const mockContractManager = (owners: string[] = [fakeDefaultOwner], thres
isOwner: (address: string) => Promise.resolve(owners.includes(address)),
isModuleEnabled: () => Promise.resolve(false),
getVersion: () => Promise.resolve('1.3.0'),
- } as unknown as SafeContract,
+ },
contractNetworks: {},
isL1SafeSingleton: true,
multiSendCallOnlyContract: {
diff --git a/src/tests/pages/apps.test.tsx b/src/tests/pages/apps.test.tsx
index b2bbdd72f..fce69c255 100644
--- a/src/tests/pages/apps.test.tsx
+++ b/src/tests/pages/apps.test.tsx
@@ -1,4 +1,5 @@
-import React from 'react'
+import { userEvent } from '@testing-library/user-event'
+import React, { act } from 'react'
import * as safeAppsGatewaySDK from '@safe-global/safe-gateway-typescript-sdk'
import { SafeAppFeatures } from '@safe-global/safe-gateway-typescript-sdk'
import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk'
@@ -8,7 +9,6 @@ import {
screen,
waitFor,
fireEvent,
- act,
getByRole,
getByText,
waitForElementToBeRemoved,
@@ -22,7 +22,7 @@ import * as chainHooks from '@/hooks/useChains'
jest.mock('@safe-global/safe-gateway-typescript-sdk', () => ({
...jest.requireActual('@safe-global/safe-gateway-typescript-sdk'),
- getSafeApps: (chainId: string) => Promise.resolve(mockedSafeApps),
+ getSafeApps: () => Promise.resolve(mockedSafeApps),
}))
jest.mock('next/navigation', () => ({
@@ -201,7 +201,7 @@ describe('AppsPage', () => {
// Add custom app modal is not present
expect(screen.queryByRole('presentation')).not.toBeInTheDocument()
- await act(() => {
+ act(() => {
fireEvent.click(screen.getByRole('button', { name: 'Add custom Safe App' }))
})
@@ -258,7 +258,7 @@ describe('AppsPage', () => {
}),
).toBeInTheDocument(),
)
- await act(() => {
+ act(() => {
fireEvent.click(screen.getByText('Add'))
})
@@ -298,7 +298,7 @@ describe('AppsPage', () => {
})
await waitFor(() => expect(screen.getByText('Add custom Safe App')).toBeInTheDocument())
const addCustomAppButton = screen.getByText('Add custom Safe App')
- await act(() => {
+ act(() => {
fireEvent.click(addCustomAppButton)
})
await waitFor(() => expect(screen.getByLabelText(/Safe App URL/)).toBeInTheDocument(), { timeout: 3000 })
@@ -336,23 +336,22 @@ describe('AppsPage', () => {
},
})
await waitFor(() => expect(screen.getByText('Add custom Safe App')).toBeInTheDocument())
+
const addCustomAppButton = screen.getByText('Add custom Safe App')
- await act(() => {
- fireEvent.click(addCustomAppButton)
- })
+ await userEvent.click(addCustomAppButton)
+
await waitFor(() => expect(screen.getByLabelText(/Safe App URL/)).toBeInTheDocument(), { timeout: 3000 })
+
const appURLInput = screen.getByLabelText(/Safe App URL/)
- fireEvent.change(appURLInput, { target: { value: APP_URL } })
+ await userEvent.type(appURLInput, APP_URL)
+
const riskCheckbox = await screen.findByText(
/This Safe App is not part of Rootstock Safe and I agree to use it at my own risk\./,
)
- await act(() => {
- fireEvent.click(riskCheckbox)
- })
- await act(() => {
- fireEvent.click(riskCheckbox)
- })
- fireEvent.click(screen.getByText('Add'))
+
+ await userEvent.click(riskCheckbox)
+ await userEvent.click(riskCheckbox)
+
await waitFor(() => expect(screen.getByText('Accepting the disclaimer is mandatory')).toBeInTheDocument())
})
@@ -399,7 +398,7 @@ describe('AppsPage', () => {
}),
).toBeInTheDocument(),
)
- await act(() => {
+ act(() => {
fireEvent.click(screen.getByText('Add'))
})
@@ -408,14 +407,14 @@ describe('AppsPage', () => {
const removeCustomSafeAppButton = screen.getByLabelText('Delete Custom test Safe app')
- await act(() => {
+ act(() => {
fireEvent.click(removeCustomSafeAppButton)
})
await waitFor(() => expect(screen.getByText('Confirm Safe App removal')).toBeInTheDocument())
const confirmRemovalButton = screen.getByRole('button', { name: 'Remove' })
- await act(() => {
+ act(() => {
fireEvent.click(confirmRemovalButton)
})
@@ -511,7 +510,7 @@ describe('AppsPage', () => {
const searchInput = screen.getByPlaceholderText('Search by name or category')
- await act(() => fireEvent.change(searchInput, { target: { value: query } }))
+ act(() => fireEvent.change(searchInput, { target: { value: query } }))
await waitFor(() => {
expect(screen.queryByText('Compound', { selector: 'h5' })).not.toBeInTheDocument()
@@ -521,7 +520,6 @@ describe('AppsPage', () => {
// zero results component
expect(screen.getByText('No Safe Apps found', { exact: false })).toBeInTheDocument()
- expect(screen.queryByText('Use WalletConnect')).toBeInTheDocument()
})
})
})
@@ -546,7 +544,7 @@ describe('AppsPage', () => {
const categorySelector = screen.getByText('Select category')
- await act(() => fireEvent.mouseDown(categorySelector))
+ act(() => fireEvent.mouseDown(categorySelector))
const categoriesDropdown = within(screen.getByRole('listbox'))
@@ -557,22 +555,23 @@ describe('AppsPage', () => {
await waitFor(() => expect(categoriesDropdown.queryByText('transaction-builder')).not.toBeInTheDocument())
// filter by Infrastructure category
- await act(() => fireEvent.click(categoriesDropdown.getByText('Infrastructure')))
+ act(() => {
+ fireEvent.click(categoriesDropdown.getByText('Infrastructure'))
+ })
// close the dropdown
- await act(() =>
+ act(() => {
fireEvent.keyDown(screen.getByRole('listbox'), {
key: 'Escape',
code: 'Escape',
keyCode: 27,
charCode: 27,
- }),
- )
-
- // 1 categories selected label
- expect(screen.queryByText('1 categories selected')).toBeInTheDocument()
+ })
+ })
await waitFor(() => {
+ // 1 categories selected label
+ expect(screen.queryByText('1 categories selected')).toBeInTheDocument()
expect(screen.queryByText('Compound', { selector: 'h5' })).not.toBeInTheDocument()
expect(screen.queryByText('ENS App', { selector: 'h5' })).not.toBeInTheDocument()
expect(screen.queryByText('Transaction Builder', { selector: 'h5' })).toBeInTheDocument()
@@ -599,12 +598,12 @@ describe('AppsPage', () => {
const categorySelector = screen.getByText('Select category')
- await act(() => fireEvent.mouseDown(categorySelector))
+ act(() => fireEvent.mouseDown(categorySelector))
const categoriesDropdown = within(screen.getByRole('listbox'))
// filter by Infrastructure category
- await act(() => fireEvent.click(categoriesDropdown.getByText('Infrastructure')))
+ act(() => fireEvent.click(categoriesDropdown.getByText('Infrastructure')))
await waitFor(() => {
expect(screen.queryByText('Compound', { selector: 'h5' })).not.toBeInTheDocument()
@@ -614,10 +613,10 @@ describe('AppsPage', () => {
})
// clear active Infrastructure filter
- await act(() => fireEvent.click(categoriesDropdown.getByText('Infrastructure')))
+ act(() => fireEvent.click(categoriesDropdown.getByText('Infrastructure')))
// close the dropdown
- await act(() =>
+ act(() =>
fireEvent.keyDown(screen.getByRole('listbox'), {
key: 'Escape',
code: 'Escape',
@@ -654,12 +653,12 @@ describe('AppsPage', () => {
const categorySelector = screen.getByText('Select category')
- await act(() => fireEvent.mouseDown(categorySelector))
+ act(() => fireEvent.mouseDown(categorySelector))
const categoriesDropdown = within(screen.getByRole('listbox'))
// filter by Infrastructure category
- await act(() => fireEvent.click(categoriesDropdown.getByText('Infrastructure')))
+ act(() => fireEvent.click(categoriesDropdown.getByText('Infrastructure')))
await waitFor(() => {
expect(screen.queryByText('Compound', { selector: 'h5' })).not.toBeInTheDocument()
@@ -669,7 +668,7 @@ describe('AppsPage', () => {
})
// close the dropdown
- await act(() =>
+ act(() =>
fireEvent.keyDown(screen.getByRole('listbox'), {
key: 'Escape',
code: 'Escape',
@@ -679,7 +678,7 @@ describe('AppsPage', () => {
)
// clear all selected filters
- await act(() => fireEvent.click(screen.getByLabelText('clear selected categories')))
+ act(() => fireEvent.click(screen.getByLabelText('clear selected categories')))
// show all safe apps again
await waitFor(() => {
@@ -710,7 +709,7 @@ describe('AppsPage', () => {
})
// filter by optimized for batch transactions
- await act(() => fireEvent.click(screen.getByRole('checkbox', { checked: false })))
+ act(() => fireEvent.click(screen.getByRole('checkbox', { checked: false })))
// show only transaction builder safe app
await waitFor(() => {
@@ -739,10 +738,10 @@ describe('AppsPage', () => {
})
// filter by optimized for batch transactions
- await act(() => fireEvent.click(screen.getByRole('checkbox', { checked: false })))
+ act(() => fireEvent.click(screen.getByRole('checkbox', { checked: false })))
// clears the optimized for batch transactions filter
- await act(() => fireEvent.click(screen.getByRole('checkbox', { checked: true })))
+ act(() => fireEvent.click(screen.getByRole('checkbox', { checked: true })))
// show all safe apps
await waitFor(() => {
diff --git a/src/utils/__tests__/SimpleTxWatcher.test.ts b/src/utils/__tests__/SimpleTxWatcher.test.ts
index 32e845771..5e27b09ae 100644
--- a/src/utils/__tests__/SimpleTxWatcher.test.ts
+++ b/src/utils/__tests__/SimpleTxWatcher.test.ts
@@ -167,7 +167,7 @@ describe('SimpleTxWatcher', () => {
off: jest.fn(),
} as unknown as JsonRpcProvider
const txHash = `0x${faker.number.hex()}`
- const result = watcher.watchTxHash(txHash, faker.finance.ethereumAddress(), 0, mockProvider).catch((error) => error)
+ watcher.watchTxHash(txHash, faker.finance.ethereumAddress(), 0, mockProvider).catch((error) => error)
expect(mockProvider.on).toHaveBeenCalledTimes(1)
expect(mockProvider.getTransactionReceipt).not.toHaveBeenCalled()
diff --git a/src/utils/__tests__/addresses.test.ts b/src/utils/__tests__/addresses.test.ts
index fcfd3d10c..e76d8adf3 100644
--- a/src/utils/__tests__/addresses.test.ts
+++ b/src/utils/__tests__/addresses.test.ts
@@ -1,4 +1,4 @@
-import { checksumAddress, isChecksummedAddress, parsePrefixedAddress, sameAddress } from '../addresses'
+import { checksumAddress, cleanInputValue, isChecksummedAddress, parsePrefixedAddress, sameAddress } from '../addresses'
describe('Addresses', () => {
describe('checksumAddress', () => {
@@ -99,4 +99,97 @@ describe('Addresses', () => {
expect(address).toBe('sdfgsdfg')
})
})
+
+ describe('cleanInputValue', () => {
+ it('should return the address when input is a valid address without prefix', () => {
+ const input = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the address with prefix when input has a valid prefix', () => {
+ const input = 'prefix:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('prefix:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the matched address when input contains text before the match', () => {
+ const input = 'some text prefix:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('prefix:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the matched address when input contains text after the match', () => {
+ const input = 'prefix:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd some text'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('prefix:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the original value when input does not match the regex', () => {
+ const input = 'invalid input'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('invalid input')
+ })
+
+ it('should handle prefixes with hyphens', () => {
+ const input = 'uh-huh:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('uh-huh:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the address when input has uppercase letters', () => {
+ const input = '0xABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('0xABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD')
+ })
+
+ it('should return the original value when Ethereum address is invalid (too short)', () => {
+ const input = '0x123'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('0x123')
+ })
+
+ it('should trim spaces and return the address when input has leading and trailing spaces', () => {
+ const input = ' 0xabcdefabcdefabcdefabcdefabcdefabcdefabcd '
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the first matched address when input contains multiple addresses', () => {
+ const input = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd 0x1234567890abcdef1234567890abcdef12345678'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the address with numeric prefix', () => {
+ const input = '12345:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('12345:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the address when prefix is missing colon', () => {
+ const input = 'prefix0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+ })
+
+ it('should return the original value when prefix contains invalid characters', () => {
+ const input = 'invalid!prefix:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
+ const output = cleanInputValue(input)
+
+ expect(output).toBe(input)
+ })
+ })
})
diff --git a/src/utils/__tests__/chains.test.ts b/src/utils/__tests__/chains.test.ts
index ed8a0cf15..baebc5009 100644
--- a/src/utils/__tests__/chains.test.ts
+++ b/src/utils/__tests__/chains.test.ts
@@ -1,5 +1,6 @@
-import { hasFeature, getBlockExplorerLink, FEATURES } from '@/utils/chains'
+import { hasFeature, getBlockExplorerLink, FEATURES, getLatestSafeVersion } from '@/utils/chains'
import { CONFIG_SERVICE_CHAINS } from '@/tests/mocks/chains'
+import { chainBuilder } from '@/tests/builders/chains'
describe('chains', () => {
describe('hasFeature', () => {
@@ -37,4 +38,50 @@ describe('chains', () => {
})
})
})
+
+ describe('chains', () => {
+ describe('getLatestSafeVersion', () => {
+ it('should return 1.4.1 on supported networks', () => {
+ expect(
+ getLatestSafeVersion(
+ chainBuilder()
+ .with({ chainId: '1', features: [FEATURES.SAFE_141 as any] })
+ .build(),
+ ),
+ ).toEqual('1.4.1')
+ expect(
+ getLatestSafeVersion(
+ chainBuilder()
+ .with({ chainId: '137', features: [FEATURES.SAFE_141 as any] })
+ .build(),
+ ),
+ ).toEqual('1.4.1')
+ expect(
+ getLatestSafeVersion(
+ chainBuilder()
+ .with({ chainId: '11155111', features: [FEATURES.SAFE_141 as any] })
+ .build(),
+ ),
+ ).toEqual('1.4.1')
+ })
+
+ it('should return 1.3.0 on networks where 1.4.1 is not released', () => {
+ expect(
+ getLatestSafeVersion(
+ chainBuilder()
+ .with({ chainId: '324', features: [FEATURES.SAFE_141 as any] })
+ .build(),
+ ),
+ ).toEqual('1.3.0')
+ })
+
+ it('should return 1.3.0 if the feature is off', () => {
+ expect(getLatestSafeVersion(chainBuilder().with({ chainId: '1', features: [] }).build())).toEqual('1.3.0')
+ expect(getLatestSafeVersion(chainBuilder().with({ chainId: '137', features: [] }).build())).toEqual('1.3.0')
+ expect(getLatestSafeVersion(chainBuilder().with({ chainId: '11155111', features: [] }).build())).toEqual(
+ '1.3.0',
+ )
+ })
+ })
+ })
})
diff --git a/src/utils/__tests__/date.test.ts b/src/utils/__tests__/date.test.ts
index 71213cfee..26a4b8745 100644
--- a/src/utils/__tests__/date.test.ts
+++ b/src/utils/__tests__/date.test.ts
@@ -1,4 +1,4 @@
-import { getCountdown } from '../date'
+import { getCountdown, getPeriod } from '../date'
describe('getCountdown', () => {
it('should convert 0 seconds to 0 days, 0 hours, and 0 minutes', () => {
@@ -21,3 +21,30 @@ describe('getCountdown', () => {
expect(result).toEqual({ days: 1, hours: 10, minutes: 17 })
})
})
+
+describe('getPeriod', () => {
+ it('returns correct period for days', () => {
+ expect(getPeriod(86400)).toBe('1 day')
+ expect(getPeriod(172800)).toBe('2 days')
+ })
+
+ it('returns correct period for hours', () => {
+ expect(getPeriod(3600)).toBe('1 hour')
+ expect(getPeriod(7200)).toBe('2 hours')
+ })
+
+ it('returns correct period for minutes', () => {
+ expect(getPeriod(60)).toBe('1 minute')
+ expect(getPeriod(120)).toBe('2 minutes')
+ })
+
+ it('returns undefined for seconds less than 60', () => {
+ expect(getPeriod(59)).toBeUndefined()
+ })
+
+ it('returns correct period when there are multiple units', () => {
+ expect(getPeriod(90000)).toBe('1 day')
+ expect(getPeriod(86400 + 3600)).toBe('1 day')
+ expect(getPeriod(86400 + 3600 + 60)).toBe('1 day')
+ })
+})
diff --git a/src/utils/__tests__/formatNumber.test.ts b/src/utils/__tests__/formatNumber.test.ts
index b3890ffc3..d222a0e5b 100644
--- a/src/utils/__tests__/formatNumber.test.ts
+++ b/src/utils/__tests__/formatNumber.test.ts
@@ -1,4 +1,4 @@
-import { formatAmountPrecise, formatAmount, formatCurrency } from '@/utils/formatNumber'
+import { formatAmountPrecise, formatAmount, formatCurrency, formatCurrencyPrecise } from '@/utils/formatNumber'
describe('formatNumber', () => {
describe('formatAmountPrecise', () => {
@@ -31,31 +31,63 @@ describe('formatNumber', () => {
describe('formatCurrency', () => {
it('should format a 0', () => {
- expect(formatCurrency(0, 'USD')).toBe('$โฏ0')
+ expect(formatCurrency(0, 'USD')).toBe('$โ0')
})
it('should format a number below 1', () => {
- expect(formatCurrency(0.5678, 'USD')).toBe('$โฏ0.57')
+ expect(formatCurrency(0.5678, 'USD')).toBe('$โ0.57')
})
it('should format a number above 1', () => {
- expect(formatCurrency(285.1257657, 'EUR')).toBe('โฌโฏ285')
+ expect(formatCurrency(285.1257657, 'EUR')).toBe('โฌโ285')
})
it('should abbreviate billions', () => {
- expect(formatCurrency(12_345_678_901, 'USD')).toBe('$โฏ12.35B')
+ expect(formatCurrency(12_345_678_901, 'USD')).toBe('$โ12.35B')
})
it('should abbreviate millions', () => {
- expect(formatCurrency(9_589_009.543645, 'EUR')).toBe('โฌโฏ9.59M')
+ expect(formatCurrency(9_589_009.543645, 'EUR')).toBe('โฌโ9.59M')
})
it('should abbreviate thousands', () => {
- expect(formatCurrency(119_589.543645, 'EUR')).toBe('โฌโฏ119.59K')
+ expect(formatCurrency(119_589.543645, 'EUR')).toBe('โฌโ119.59K')
})
it('should abbreviate a number with more than a given amount of digits', () => {
- expect(formatCurrency(1234.12, 'USD', 4)).toBe('$โฏ1.23K')
+ expect(formatCurrency(1234.12, 'USD', 4)).toBe('$โ1.23K')
+ })
+ })
+
+ describe('formatCurrencyPrecise', () => {
+ it('should format the number correctly for USD', () => {
+ const result = formatCurrencyPrecise(1234.56, 'USD')
+ expect(result).toBe('$โ1,234.56')
+ })
+
+ it('should format the number correctly for EUR', () => {
+ const result = formatCurrencyPrecise(1234.56, 'EUR')
+ expect(result).toBe('โฌโ1,234.56')
+ })
+
+ it('should handle string input as number', () => {
+ const result = formatCurrencyPrecise('1234.56', 'USD')
+ expect(result).toBe('$โ1,234.56')
+ })
+
+ it('should add the narrow non-breaking space after the currency symbol', () => {
+ const result = formatCurrencyPrecise(1234.56, 'USD')
+ expect(result).toBe('$โ1,234.56')
+ })
+
+ it('should format the number correctly with 5 decimal places for USD', () => {
+ const result = formatCurrencyPrecise(1234.56789, 'USD')
+ expect(result).toBe('$โ1,234.57')
+ })
+
+ it('should return "NaN" for invalid number input', () => {
+ const result = formatCurrencyPrecise('invalid-number', 'USD')
+ expect(result).toBe('$NaNโ')
})
})
})
diff --git a/src/utils/__tests__/formatters.test.ts b/src/utils/__tests__/formatters.test.ts
index 72bb37350..4fc94b8a0 100644
--- a/src/utils/__tests__/formatters.test.ts
+++ b/src/utils/__tests__/formatters.test.ts
@@ -1,4 +1,5 @@
import * as formatters from '@/utils/formatters'
+import { parseEther } from 'ethers'
describe('formatters', () => {
describe('removeTrailingZeros', () => {
@@ -67,4 +68,37 @@ describe('formatters', () => {
expect(formatters.shortenAddress(null, 5)).toEqual('')
})
})
+
+ describe('formatVisualAmount', () => {
+ it('should format with different decimals', () => {
+ expect(formatters.formatVisualAmount('123456789', 0, 10)).toEqual('123,456,789')
+ expect(formatters.formatVisualAmount('123456789', 1, 10)).toEqual('12,345,678.9')
+ expect(formatters.formatVisualAmount('123456789', 2, 10)).toEqual('1,234,567.89')
+ expect(formatters.formatVisualAmount('123456789', 3, 10)).toEqual('123,456.789')
+ expect(formatters.formatVisualAmount('123456789', 4, 10)).toEqual('12,345.6789')
+ expect(formatters.formatVisualAmount('123456789', 5, 10)).toEqual('1,234.56789')
+ expect(formatters.formatVisualAmount('123456789', 6, 10)).toEqual('123.456789')
+ expect(formatters.formatVisualAmount('123456789', 7, 10)).toEqual('12.3456789')
+ expect(formatters.formatVisualAmount('123456789', 8, 10)).toEqual('1.23456789')
+ expect(formatters.formatVisualAmount('123456789', 9, 10)).toEqual('0.123456789')
+ })
+
+ it('should format with different precisions', () => {
+ expect(formatters.formatVisualAmount('123456789', 6, 0)).toEqual('123')
+ expect(formatters.formatVisualAmount('123456789', 6, 1)).toEqual('123.5')
+ expect(formatters.formatVisualAmount('123456789', 6, 2)).toEqual('123.46')
+ expect(formatters.formatVisualAmount('123456789', 6, 3)).toEqual('123.457')
+ expect(formatters.formatVisualAmount('123456789', 6, 4)).toEqual('123.4568')
+ expect(formatters.formatVisualAmount('123456789', 6, 5)).toEqual('123.45679')
+ expect(formatters.formatVisualAmount('123456789', 6, 6)).toEqual('123.456789')
+ })
+
+ it('should format wei correctly', () => {
+ expect(formatters.formatVisualAmount(parseEther('1'), 18, 18)).toEqual('1')
+ expect(formatters.formatVisualAmount(parseEther('10'), 18, 18)).toEqual('10')
+ expect(formatters.formatVisualAmount(parseEther('1000'), 18, 18)).toEqual('1,000')
+ expect(formatters.formatVisualAmount(parseEther('0.00001'), 18, 18)).toEqual('0.00001')
+ expect(formatters.formatVisualAmount('1', 18, 18)).toEqual('0.000000000000000001')
+ })
+ })
})
diff --git a/src/utils/__tests__/helpers.test.ts b/src/utils/__tests__/helpers.test.ts
new file mode 100644
index 000000000..bc0bfc57f
--- /dev/null
+++ b/src/utils/__tests__/helpers.test.ts
@@ -0,0 +1,40 @@
+import { getKeyWithTrueValue } from '../helpers'
+import { faker } from '@faker-js/faker'
+
+describe('helpers', () => {
+ describe('getKeyWithTrueValue', () => {
+ const address1 = faker.finance.ethereumAddress()
+ const address2 = faker.finance.ethereumAddress()
+ const address3 = faker.finance.ethereumAddress()
+
+ it('should return the address with value of true', async () => {
+ const obj = {
+ [address1]: false,
+ [address2]: false,
+ [address3]: true,
+ }
+ const result = getKeyWithTrueValue(obj)
+ expect(result).toEqual(address3)
+ })
+
+ it('should return undefined when none of the objects properties are true', async () => {
+ const obj = {
+ [address1]: false,
+ [address2]: false,
+ [address3]: false,
+ }
+ const result = getKeyWithTrueValue(obj)
+ expect(result).toEqual(undefined)
+ })
+
+ it('should return the first true value if there are more than one', async () => {
+ const obj = {
+ [address1]: true,
+ [address2]: true,
+ [address3]: true,
+ }
+ const result = getKeyWithTrueValue(obj)
+ expect(result).toEqual(address1)
+ })
+ })
+})
diff --git a/src/utils/__tests__/transactions.test.ts b/src/utils/__tests__/transactions.test.ts
index 52c47123a..2fa9ae755 100644
--- a/src/utils/__tests__/transactions.test.ts
+++ b/src/utils/__tests__/transactions.test.ts
@@ -82,6 +82,15 @@ describe('transactions', () => {
expect(getTxOrigin(app)).toBe('{"url":"https://test.com","name":"Test name"}')
})
+ it('should return a stringified object with the app name and url with a query param', () => {
+ const app = {
+ url: 'https://test.com/hello?world=1',
+ name: 'Test name',
+ } as SafeAppData
+
+ expect(getTxOrigin(app)).toBe('{"url":"https://test.com/hello","name":"Test name"}')
+ })
+
it('should limit the origin to 200 characters with preference of the URL', () => {
const app = {
url: 'https://test.com/' + 'a'.repeat(160),
diff --git a/src/utils/__tests__/tx-history-filter.test.ts b/src/utils/__tests__/tx-history-filter.test.ts
index 956715118..a2efaafd7 100644
--- a/src/utils/__tests__/tx-history-filter.test.ts
+++ b/src/utils/__tests__/tx-history-filter.test.ts
@@ -19,6 +19,7 @@ import {
import { renderHook } from '@/tests/test-utils'
import type { NextRouter } from 'next/router'
import { type TxFilterFormState } from '@/components/transactions/TxFilterForm'
+import { getTimezone } from '@/services/transactions'
MockDate.set('2021-01-01T00:00:00.000Z')
@@ -388,13 +389,14 @@ describe('tx-history-filter', () => {
'0x123',
{ type: 'Incoming' as TxFilterType, filter: { value: '123' } },
false,
+ false,
'pageUrl1',
)
expect(getIncomingTransfers).toHaveBeenCalledWith(
'4',
'0x123',
- { value: '123', executed: undefined, timezone_offset: 3600000, trusted: false, imitation: false },
+ { value: '123', executed: undefined, timezone: getTimezone(), trusted: false, imitation: true },
'pageUrl1',
)
@@ -411,6 +413,7 @@ describe('tx-history-filter', () => {
filter: { execution_date__gte: '1970-01-01T00:00:00.000Z', executed: 'true' },
},
false,
+ false,
'pageUrl2',
)
@@ -420,9 +423,9 @@ describe('tx-history-filter', () => {
{
execution_date__gte: '1970-01-01T00:00:00.000Z',
executed: 'true',
- timezone_offset: 3600000,
+ timezone: getTimezone(),
trusted: false,
- imitation: false,
+ imitation: true,
},
'pageUrl2',
)
@@ -437,13 +440,14 @@ describe('tx-history-filter', () => {
'0x789',
{ type: 'Module-based' as TxFilterType, filter: { to: '0x123' } },
false,
+ false,
'pageUrl3',
)
expect(getModuleTransactions).toHaveBeenCalledWith(
'1',
'0x789',
- { to: '0x123', executed: undefined, timezone_offset: 3600000, trusted: false, imitation: false },
+ { to: '0x123', executed: undefined, timezone: getTimezone(), trusted: false, imitation: true },
'pageUrl3',
)
@@ -460,6 +464,7 @@ describe('tx-history-filter', () => {
filter: { token_address: '0x123' },
},
false,
+ false,
'pageUrl3',
)
diff --git a/src/utils/addresses.ts b/src/utils/addresses.ts
index 7e5f64cc0..8c56c7506 100644
--- a/src/utils/addresses.ts
+++ b/src/utils/addresses.ts
@@ -60,7 +60,7 @@ export const formatPrefixedAddress = (address: string, prefix?: string): string
}
export const cleanInputValue = (value: string): string => {
- const regex = /(?:([a-z0-9]+):)?(0x[a-f0-9]{40})\b/i
+ const regex = /(?:([^\s:]+):)?(0x[a-f0-9]{40})\b/i
const match = value.match(regex)
// if match, return the address with optional prefix
if (match) return match[0]
diff --git a/src/utils/chains.ts b/src/utils/chains.ts
index ae181213c..0e5179df6 100644
--- a/src/utils/chains.ts
+++ b/src/utils/chains.ts
@@ -1,6 +1,13 @@
import { AppRoutes } from '@/config/routes'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { getExplorerLink } from './gateway'
+import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
+import { getSafeSingletonDeployment } from '@safe-global/safe-deployments'
+import semverSatisfies from 'semver/functions/satisfies'
+import { LATEST_SAFE_VERSION } from '@/config/constants'
+
+/** This version is used if a network does not have the LATEST_SAFE_VERSION deployed yet */
+const FALLBACK_SAFE_VERSION = '1.3.0' as const
export enum FEATURES {
ERC721 = 'ERC721',
@@ -26,11 +33,14 @@ export enum FEATURES {
NATIVE_SWAPS_FEE_ENABLED = 'NATIVE_SWAPS_FEE_ENABLED',
RELAY_NATIVE_SWAPS = 'RELAY_NATIVE_SWAPS',
ZODIAC_ROLES = 'ZODIAC_ROLES',
+ SAFE_141 = 'SAFE_141',
+ STAKING = 'STAKING',
}
export const FeatureRoutes = {
[AppRoutes.apps.index]: FEATURES.SAFE_APPS,
[AppRoutes.swap]: FEATURES.NATIVE_SWAPS,
+ [AppRoutes.stake]: FEATURES.STAKING,
[AppRoutes.balances.nfts]: FEATURES.ERC721,
[AppRoutes.settings.notifications]: FEATURES.PUSH_NOTIFICATIONS,
}
@@ -53,3 +63,17 @@ export const isRouteEnabled = (route: string, chain?: ChainInfo) => {
const featureRoute = FeatureRoutes[route]
return !featureRoute || hasFeature(chain, featureRoute)
}
+
+export const getLatestSafeVersion = (chain: ChainInfo | undefined): SafeVersion => {
+ const latestSafeVersion = chain && hasFeature(chain, FEATURES.SAFE_141) ? LATEST_SAFE_VERSION : FALLBACK_SAFE_VERSION
+ // Without version filter it will always return the LATEST_SAFE_VERSION constant to avoid automatically updating to the newest version if the deployments change
+ const latestDeploymentVersion = (getSafeSingletonDeployment({ network: chain?.chainId, released: true })?.version ??
+ FALLBACK_SAFE_VERSION) as SafeVersion
+
+ // The version needs to be smaller or equal to the
+ if (semverSatisfies(latestDeploymentVersion, `<=${latestSafeVersion}`)) {
+ return latestDeploymentVersion
+ } else {
+ return latestSafeVersion as SafeVersion
+ }
+}
diff --git a/src/utils/formatNumber.ts b/src/utils/formatNumber.ts
index f0d0a1b20..b05ca4b34 100644
--- a/src/utils/formatNumber.ts
+++ b/src/utils/formatNumber.ts
@@ -1,5 +1,37 @@
+import memoize from 'lodash/memoize'
+
const locale = typeof navigator !== 'undefined' ? navigator.language : undefined
+const _getNumberFormatter = (maximumFractionDigits?: number, compact?: boolean) => {
+ return new Intl.NumberFormat(locale, {
+ style: 'decimal',
+ maximumFractionDigits,
+ notation: compact ? 'compact' : 'standard',
+ })
+}
+const getNumberFormatter = memoize(_getNumberFormatter, (...args: Parameters) =>
+ args.join(''),
+)
+
+const _getCurrencyFormatter = (
+ currency: string,
+ compact?: boolean,
+ maximumFractionDigits?: number,
+ minimumFractionDigits?: number,
+) => {
+ return new Intl.NumberFormat(locale, {
+ style: 'currency',
+ currency,
+ currencyDisplay: 'narrowSymbol',
+ maximumFractionDigits,
+ minimumFractionDigits,
+ notation: compact ? 'compact' : 'standard',
+ })
+}
+const getCurrencyFormatter = memoize(_getCurrencyFormatter, (...args: Parameters) =>
+ args.join(''),
+)
+
/**
* Intl.NumberFormat number formatter that adheres to our style guide
* @param number Number to format
@@ -10,19 +42,12 @@ export const formatAmount = (number: string | number, precision = 5, maxLength =
if (float === Math.round(float)) precision = 0
if (Math.abs(float) < 0.00001) return '< 0.00001'
- const fullNum = new Intl.NumberFormat(locale, {
- style: 'decimal',
- maximumFractionDigits: precision,
- }).format(float)
+ const fullNum = getNumberFormatter(precision).format(float)
// +3 for the decimal point and the two decimal places
if (fullNum.length <= maxLength + 3) return fullNum
- return new Intl.NumberFormat(locale, {
- style: 'decimal',
- notation: 'compact',
- maximumFractionDigits: 2,
- }).format(float)
+ return getNumberFormatter(2, true).format(float)
}
/**
@@ -31,10 +56,7 @@ export const formatAmount = (number: string | number, precision = 5, maxLength =
* @param precision Fraction digits to show
*/
export const formatAmountPrecise = (number: string | number, precision?: number): string => {
- return new Intl.NumberFormat(locale, {
- style: 'decimal',
- maximumFractionDigits: precision,
- }).format(Number(number))
+ return getNumberFormatter(precision).format(Number(number))
}
/**
@@ -43,25 +65,19 @@ export const formatAmountPrecise = (number: string | number, precision?: number)
* @param currency ISO 4217 currency code
*/
export const formatCurrency = (number: string | number, currency: string, maxLength = 6): string => {
- let float = Number(number)
+ const float = Number(number)
- let result = new Intl.NumberFormat(locale, {
- style: 'currency',
- currency,
- currencyDisplay: 'narrowSymbol',
- maximumFractionDigits: Math.abs(float) >= 1 || float === 0 ? 0 : 2,
- }).format(float)
+ let result = getCurrencyFormatter(currency, false, Math.abs(float) >= 1 || float === 0 ? 0 : 2).format(float)
// +1 for the currency symbol
if (result.length > maxLength + 1) {
- result = new Intl.NumberFormat(locale, {
- style: 'currency',
- currency,
- currencyDisplay: 'narrowSymbol',
- notation: 'compact',
- maximumFractionDigits: 2,
- }).format(float)
+ result = getCurrencyFormatter(currency, true, 2).format(float)
}
- return result.replace(/^(\D+)/, '$1โฏ')
+ return result.replace(/^(\D+)/, '$1โ')
+}
+
+export const formatCurrencyPrecise = (number: string | number, currency: string): string => {
+ const result = getCurrencyFormatter(currency, false, 2, 2).format(Number(number))
+ return result.replace(/^(\D+)/, '$1โ')
}
diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts
index bbff73c66..af5c88079 100644
--- a/src/utils/formatters.ts
+++ b/src/utils/formatters.ts
@@ -1,6 +1,7 @@
import type { BigNumberish } from 'ethers'
import { formatUnits, parseUnits } from 'ethers'
import { formatAmount, formatAmountPrecise } from './formatNumber'
+import { formatDuration, intervalToDuration } from 'date-fns'
const GWEI = 'gwei'
@@ -39,7 +40,7 @@ export const formatVisualAmount = (
precision?: number,
): string => {
const amount = safeFormatUnits(value, decimals)
- return precision ? formatAmountPrecise(amount, precision) : formatAmount(amount)
+ return precision !== undefined ? formatAmountPrecise(amount, precision) : formatAmount(amount)
}
export const safeParseUnits = (value: string, decimals: number | string = GWEI): bigint | undefined => {
@@ -94,3 +95,11 @@ export const formatError = (error: Error & { reason?: string }): string => {
if (!reason.endsWith('.')) reason += '.'
return ` ${capitalize(reason)}`
}
+
+export const formatDurationFromMilliseconds = (
+ seconds: number,
+ format: Array<'years' | 'months' | 'days' | 'hours' | 'minutes' | 'seconds'> = ['hours', 'minutes'],
+) => {
+ const duration = intervalToDuration({ start: 0, end: seconds })
+ return formatDuration(duration, { format })
+}
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index a66781819..fd9f04b9c 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -1,6 +1,7 @@
// `assert` does not work with arrow functions
import type { ConnectedWallet } from '@/hooks/wallets/useOnboard'
import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
+import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import type { OnboardAPI } from '@web3-onboard/core'
import type { Eip1193Provider } from 'ethers'
@@ -24,6 +25,14 @@ export function assertOnboard(onboard: OnboardAPI | undefined): asserts onboard
return invariant(onboard, 'Onboard not connected')
}
+export function assertChainInfo(chainInfo: ChainInfo | undefined): asserts chainInfo {
+ return invariant(chainInfo, 'No chain config available')
+}
+
export function assertProvider(provider: Eip1193Provider | undefined): asserts provider {
return invariant(provider, 'Provider not found')
}
+
+export const getKeyWithTrueValue = (obj: Record) => {
+ return Object.entries(obj).find(([, value]) => !!value)?.[0]
+}
diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts
index 966a0dfc8..483ee136e 100644
--- a/src/utils/tokens.ts
+++ b/src/utils/tokens.ts
@@ -1,10 +1,13 @@
import { getWeb3ReadOnly } from '@/hooks/wallets/web3'
-import { ERC20__factory } from '@/types/contracts'
+import { ERC20__factory, ERC721__factory } from '@/types/contracts'
import { type TokenInfo, TokenType } from '@safe-global/safe-gateway-typescript-sdk'
export const UNLIMITED_APPROVAL_AMOUNT = 2n ** 256n - 1n
export const UNLIMITED_PERMIT2_AMOUNT = 2n ** 160n - 1n
+// As per https://eips.ethereum.org/EIPS/eip-721#specification
+export const ERC721_IDENTIFIER = '0x80ac58cd'
+
/**
* Fetches ERC20 token symbol and decimals from on-chain.
* @param address address of erc20 token
@@ -24,3 +27,29 @@ export const getERC20TokenInfoOnChain = async (
type: TokenType.ERC20,
}
}
+
+export const getErc721Symbol = async (address: string) => {
+ const web3 = getWeb3ReadOnly()
+ if (!web3) return ''
+
+ const erc721 = ERC721__factory.connect(address, web3)
+
+ try {
+ return await erc721.symbol()
+ } catch (e) {
+ return ''
+ }
+}
+
+export const isErc721Token = async (address: string) => {
+ const web3 = getWeb3ReadOnly()
+ if (!web3) return false
+
+ const erc721 = ERC721__factory.connect(address, web3)
+
+ try {
+ return await erc721.supportsInterface(ERC721_IDENTIFIER)
+ } catch (e) {
+ return false
+ }
+}
diff --git a/src/utils/transaction-guards.ts b/src/utils/transaction-guards.ts
index a92fe19a1..38e94421b 100644
--- a/src/utils/transaction-guards.ts
+++ b/src/utils/transaction-guards.ts
@@ -6,7 +6,6 @@ import type {
Creation,
Custom,
DateLabel,
- DecodedDataResponse,
DetailedExecutionInfo,
Erc20Transfer,
Erc721Transfer,
@@ -18,8 +17,10 @@ import type {
MultisigExecutionDetails,
MultisigExecutionInfo,
NativeCoinTransfer,
+ NativeStakingDepositConfirmationView,
Order,
- OrderConfirmationView,
+ AnyConfirmationView,
+ AnySwapOrderConfirmationView,
SafeInfo,
SettingsChange,
SwapOrder,
@@ -32,6 +33,13 @@ import type {
TransferInfo,
TwapOrder,
TwapOrderConfirmationView,
+ AnyStakingConfirmationView,
+ StakingTxExitInfo,
+ StakingTxDepositInfo,
+ StakingTxWithdrawInfo,
+ NativeStakingWithdrawConfirmationView,
+ NativeStakingValidatorsExitConfirmationView,
+ StakingTxInfo,
} from '@safe-global/safe-gateway-typescript-sdk'
import {
ConfirmationViewTypes,
@@ -119,14 +127,24 @@ export const isTwapOrderTxInfo = (value: TransactionInfo): value is TwapOrder =>
return value.type === TransactionInfoType.TWAP_ORDER
}
-export const isConfirmationViewOrder = (
- decodedData: DecodedDataResponse | BaselineConfirmationView | OrderConfirmationView | undefined,
-): decodedData is OrderConfirmationView => {
- return isSwapConfirmationViewOrder(decodedData) || isTwapConfirmationViewOrder(decodedData)
+export const isStakingTxDepositInfo = (value: TransactionInfo): value is StakingTxDepositInfo => {
+ return value.type === TransactionInfoType.NATIVE_STAKING_DEPOSIT
+}
+
+export const isStakingTxExitInfo = (value: TransactionInfo): value is StakingTxExitInfo => {
+ return value.type === TransactionInfoType.NATIVE_STAKING_VALIDATORS_EXIT
+}
+
+export const isStakingTxWithdrawInfo = (value: TransactionInfo): value is StakingTxWithdrawInfo => {
+ return value.type === TransactionInfoType.NATIVE_STAKING_WITHDRAW
+}
+
+export const isAnyStakingTxInfo = (value: TransactionInfo): value is StakingTxInfo => {
+ return isStakingTxDepositInfo(value) || isStakingTxExitInfo(value) || isStakingTxWithdrawInfo(value)
}
export const isTwapConfirmationViewOrder = (
- decodedData: DecodedDataResponse | BaselineConfirmationView | OrderConfirmationView | undefined,
+ decodedData: AnyConfirmationView | undefined,
): decodedData is TwapOrderConfirmationView => {
if (decodedData && 'type' in decodedData) {
return decodedData.type === ConfirmationViewTypes.COW_SWAP_TWAP_ORDER
@@ -136,7 +154,7 @@ export const isTwapConfirmationViewOrder = (
}
export const isSwapConfirmationViewOrder = (
- decodedData: DecodedDataResponse | BaselineConfirmationView | OrderConfirmationView | undefined,
+ decodedData: AnyConfirmationView | undefined,
): decodedData is SwapOrderConfirmationView => {
if (decodedData && 'type' in decodedData) {
return decodedData.type === ConfirmationViewTypes.COW_SWAP_ORDER
@@ -145,6 +163,58 @@ export const isSwapConfirmationViewOrder = (
return false
}
+export const isAnySwapConfirmationViewOrder = (
+ decodedData: AnyConfirmationView | undefined,
+): decodedData is AnySwapOrderConfirmationView => {
+ return isSwapConfirmationViewOrder(decodedData) || isTwapConfirmationViewOrder(decodedData)
+}
+
+export const isStakingDepositConfirmationView = (
+ decodedData: AnyConfirmationView | undefined,
+): decodedData is NativeStakingDepositConfirmationView => {
+ if (decodedData && 'type' in decodedData) {
+ return decodedData?.type === ConfirmationViewTypes.KILN_NATIVE_STAKING_DEPOSIT
+ }
+ return false
+}
+
+export const isStakingExitConfirmationView = (
+ decodedData: AnyConfirmationView | undefined,
+): decodedData is NativeStakingValidatorsExitConfirmationView => {
+ if (decodedData && 'type' in decodedData) {
+ return decodedData?.type === ConfirmationViewTypes.KILN_NATIVE_STAKING_VALIDATORS_EXIT
+ }
+ return false
+}
+
+export const isStakingWithdrawConfirmationView = (
+ decodedData: AnyConfirmationView | undefined,
+): decodedData is NativeStakingWithdrawConfirmationView => {
+ if (decodedData && 'type' in decodedData) {
+ return decodedData?.type === ConfirmationViewTypes.KILN_NATIVE_STAKING_WITHDRAW
+ }
+ return false
+}
+
+export const isAnyStakingConfirmationView = (
+ decodedData: AnyConfirmationView | undefined,
+): decodedData is AnyStakingConfirmationView => {
+ return (
+ isStakingDepositConfirmationView(decodedData) ||
+ isStakingExitConfirmationView(decodedData) ||
+ isStakingWithdrawConfirmationView(decodedData)
+ )
+}
+
+export const isGenericConfirmation = (
+ decodedData: AnyConfirmationView | undefined,
+): decodedData is BaselineConfirmationView => {
+ if (decodedData && 'type' in decodedData) {
+ return decodedData.type === ConfirmationViewTypes.GENERIC
+ }
+ return false
+}
+
export const isCancelledSwapOrder = (value: TransactionInfo) => {
return isSwapOrderTxInfo(value) && value.status === 'cancelled'
}
@@ -192,11 +262,15 @@ export function isRecoveryQueueItem(value: TransactionListItem | RecoveryQueueIt
}
// Narrows `Transaction`
-export const isMultisigExecutionInfo = (value?: ExecutionInfo): value is MultisigExecutionInfo =>
- value?.type === DetailedExecutionInfoType.MULTISIG
+// TODO: Consolidate these types with the new sdk
+export const isMultisigExecutionInfo = (
+ value?: ExecutionInfo | DetailedExecutionInfo,
+): value is MultisigExecutionInfo => {
+ return value?.type === 'MULTISIG'
+}
-export const isModuleExecutionInfo = (value?: ExecutionInfo): value is ModuleExecutionInfo =>
- value?.type === DetailedExecutionInfoType.MODULE
+export const isModuleExecutionInfo = (value?: ExecutionInfo | DetailedExecutionInfo): value is ModuleExecutionInfo =>
+ value?.type === 'MODULE'
export const isSignableBy = (txSummary: TransactionSummary, walletAddress: string): boolean => {
const executionInfo = isMultisigExecutionInfo(txSummary.executionInfo) ? txSummary.executionInfo : undefined
diff --git a/src/utils/transactions.ts b/src/utils/transactions.ts
index 97c2b028b..cf2ff95c8 100644
--- a/src/utils/transactions.ts
+++ b/src/utils/transactions.ts
@@ -4,7 +4,6 @@ import type {
MultisigExecutionDetails,
MultisigExecutionInfo,
SafeAppData,
- SafeInfo,
Transaction,
TransactionDetails,
TransactionListPage,
@@ -34,6 +33,7 @@ import { toBeHex, AbiCoder } from 'ethers'
import { type BaseTransaction } from '@safe-global/safe-apps-sdk'
import { id } from 'ethers'
import { isEmptyHexData } from '@/utils/hex'
+import { getOriginPath } from './url'
export const makeTxFromDetails = (txDetails: TransactionDetails): Transaction => {
const getMissingSigners = ({
@@ -94,9 +94,9 @@ export const makeTxFromDetails = (txDetails: TransactionDetails): Transaction =>
const getSignatures = (confirmations: Record) => {
return Object.entries(confirmations)
- .filter(([_, signature]) => Boolean(signature))
+ .filter(([, signature]) => Boolean(signature))
.sort(([signerA], [signerB]) => signerA.toLowerCase().localeCompare(signerB.toLowerCase()))
- .reduce((prev, [_, signature]) => {
+ .reduce((prev, [, signature]) => {
return prev + signature.slice(2)
}, '0x')
}
@@ -116,6 +116,7 @@ export const getMultiSendTxs = async (
const args = extractTxInfo(tx, safeAddress)
const sigs = getSignatures(args.signatures)
+ // @ts-ignore
const data = readOnlySafeContract.encode('execTransaction', [
args.txParams.to,
args.txParams.value,
@@ -139,14 +140,6 @@ export const getMultiSendTxs = async (
.filter(Boolean) as MetaTransactionData[]
}
-export const getTxsWithDetails = (txs: Transaction[], chainId: string) => {
- return Promise.all(
- txs.map(async (tx) => {
- return await getTransactionDetails(chainId, tx.transaction.id)
- }),
- )
-}
-
export const getTxOptions = (params: AdvancedParameters, currentChain: ChainInfo | undefined): TransactionOptions => {
const txOptions: TransactionOptions = {
gasLimit: params.gasLimit?.toString(),
@@ -193,7 +186,7 @@ export const getTxOrigin = (app?: Partial): string | undefined => {
try {
// Must include empty string to avoid including the length of `undefined`
const maxUrlLength = MAX_ORIGIN_LENGTH - JSON.stringify({ url: '', name: '' }).length
- const trimmedUrl = url.slice(0, maxUrlLength)
+ const trimmedUrl = getOriginPath(url).slice(0, maxUrlLength)
const maxNameLength = Math.max(0, maxUrlLength - trimmedUrl.length)
const trimmedName = name.slice(0, maxNameLength)
@@ -206,8 +199,6 @@ export const getTxOrigin = (app?: Partial): string | undefined => {
return origin
}
-export const hasEnoughSignatures = (tx: SafeTransaction, safe: SafeInfo) => tx.signatures.size >= safe.threshold
-
const multiSendInterface = Multi_send__factory.createInterface()
const multiSendFragment = multiSendInterface.getFunction('multiSend')
@@ -302,3 +293,13 @@ export const isTrustedTx = (tx: TransactionSummary) => {
export const isImitation = ({ txInfo }: TransactionSummary): boolean => {
return isTransferTxInfo(txInfo) && isERC20Transfer(txInfo.transferInfo) && Boolean(txInfo.transferInfo.imitation)
}
+
+export const getSafeTransaction = async (safeTxHash: string, chainId: string, safeAddress: string) => {
+ const txId = `multisig_${safeAddress}_${safeTxHash}`
+
+ try {
+ return await getTransactionDetails(chainId, txId)
+ } catch (e) {
+ return undefined
+ }
+}
diff --git a/src/utils/tx-history-filter.ts b/src/utils/tx-history-filter.ts
index bcc4c5ba4..62788697d 100644
--- a/src/utils/tx-history-filter.ts
+++ b/src/utils/tx-history-filter.ts
@@ -12,7 +12,7 @@ import { startOfDay, endOfDay } from 'date-fns'
import type { TxFilterFormState } from '@/components/transactions/TxFilterForm'
import { safeFormatUnits, safeParseUnits } from '@/utils/formatters'
-import { getTimezoneOffset } from '@/services/transactions'
+import { getTimezone } from '@/services/transactions'
type IncomingTxFilter = NonNullable
type MultisigTxFilter = NonNullable
@@ -31,7 +31,7 @@ export type TxFilter = {
export const _omitNullish = (data: { [key: string]: any }) => {
return Object.fromEntries(
- Object.entries(data).filter(([_, value]) => {
+ Object.entries(data).filter(([, value]) => {
return value !== '' && value != null
}),
)
@@ -119,15 +119,16 @@ export const fetchFilteredTxHistory = async (
chainId: string,
safeAddress: string,
filterData: TxFilter,
- onlyTrusted: boolean,
+ hideUntrustedTxs: boolean,
+ hideImitationTxs: boolean,
pageUrl?: string,
): Promise => {
const fetchPage = () => {
const query = {
...filterData.filter,
- timezone_offset: getTimezoneOffset(),
- trusted: onlyTrusted ?? false,
- imitation: onlyTrusted ?? false,
+ timezone: getTimezone(),
+ trusted: hideUntrustedTxs,
+ imitation: !hideImitationTxs,
executed: filterData.type === TxFilterType.MULTISIG ? 'true' : undefined,
}
diff --git a/src/utils/url.ts b/src/utils/url.ts
index d0322e474..a9c5eb19f 100644
--- a/src/utils/url.ts
+++ b/src/utils/url.ts
@@ -1,8 +1,8 @@
-const trimTrailingSlash = (url: string): string => {
+export const trimTrailingSlash = (url: string): string => {
return url.replace(/\/$/, '')
}
-const isSameUrl = (url1: string, url2: string): boolean => {
+export const isSameUrl = (url1: string, url2: string): boolean => {
return trimTrailingSlash(url1) === trimTrailingSlash(url2)
}
export const prefixedAddressRe = /[a-z0-9-]+\:0x[a-f0-9]{40}/i
@@ -10,11 +10,11 @@ const invalidProtocolRegex = /^(\W*)(javascript|data|vbscript)/im
const ctrlCharactersRegex = /[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim
const urlSchemeRegex = /^([^:]+):/gm
const relativeFirstCharacters = ['.', '/']
-const isRelativeUrl = (url: string): boolean => {
+export const isRelativeUrl = (url: string): boolean => {
return relativeFirstCharacters.indexOf(url[0]) > -1
}
-const sanitizeUrl = (url: string): string => {
+export const sanitizeUrl = (url: string): string => {
const sanitizedUrl = url.replace(ctrlCharactersRegex, '').trim()
if (isRelativeUrl(sanitizedUrl)) {
@@ -34,4 +34,12 @@ const sanitizeUrl = (url: string): string => {
return sanitizedUrl
}
-export { trimTrailingSlash, isSameUrl, sanitizeUrl, isRelativeUrl }
+export const getOriginPath = (url: string): string => {
+ try {
+ const { origin, pathname } = new URL(url)
+ return origin + (pathname === '/' ? '' : pathname)
+ } catch (e) {
+ console.error('Error parsing URL', url, e)
+ return url
+ }
+}
diff --git a/src/utils/wallets.ts b/src/utils/wallets.ts
index 9a2469e4d..ccfd007ee 100644
--- a/src/utils/wallets.ts
+++ b/src/utils/wallets.ts
@@ -1,7 +1,8 @@
import type { EthersError } from '@/utils/ethers-utils'
import { type ConnectedWallet } from '@/hooks/wallets/useOnboard'
-import { getWeb3ReadOnly, isSmartContract } from '@/hooks/wallets/web3'
+import { getWeb3ReadOnly } from '@/hooks/wallets/web3'
import { WALLET_KEYS } from '@/hooks/wallets/consts'
+import { EMPTY_DATA } from '@safe-global/protocol-kit/dist/src/utils/constants'
import memoize from 'lodash/memoize'
import { PRIVATE_KEY_MODULE_LABEL } from '@/services/private-key-module'
@@ -33,15 +34,21 @@ export const isHardwareWallet = (wallet: ConnectedWallet): boolean => {
)
}
-export const isSmartContractWallet = memoize(
- async (_chainId: string, address: string) => {
- const provider = getWeb3ReadOnly()
+export const isSmartContract = async (address: string): Promise => {
+ const provider = getWeb3ReadOnly()
- if (!provider) {
- throw new Error('Provider not found')
- }
+ if (!provider) {
+ throw new Error('Provider not found')
+ }
+
+ const code = await provider.getCode(address)
- return isSmartContract(provider, address)
+ return code !== EMPTY_DATA
+}
+
+export const isSmartContractWallet = memoize(
+ async (_chainId: string, address: string): Promise => {
+ return isSmartContract(address)
},
(chainId, address) => chainId + address,
)
diff --git a/yarn.lock b/yarn.lock
index 99d41efec..a99f55d45 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -39,13 +39,6 @@
resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.9.4.tgz#a483c54c1253656bb33babd464e3154a173e1577"
integrity sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==
-"@aw-web-design/x-default-browser@1.4.126":
- version "1.4.126"
- resolved "https://registry.yarnpkg.com/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz#43e4bd8f0314ed907a8718d7e862a203af79bc16"
- integrity sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==
- dependencies:
- default-browser-id "3.0.0"
-
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae"
@@ -62,6 +55,14 @@
"@babel/highlight" "^7.24.7"
picocolors "^1.0.0"
+"@babel/code-frame@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7"
+ integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==
+ dependencies:
+ "@babel/highlight" "^7.25.7"
+ picocolors "^1.0.0"
+
"@babel/code-frame@~7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
@@ -74,21 +75,47 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a"
integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==
-"@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.18.9", "@babel/core@^7.19.6", "@babel/core@^7.23.0", "@babel/core@^7.23.9", "@babel/core@^7.24.4":
- version "7.24.5"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a"
- integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==
+"@babel/compat-data@^7.25.7":
+ version "7.25.8"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402"
+ integrity sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==
+
+"@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.18.9", "@babel/core@^7.19.6":
+ version "7.24.4"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717"
+ integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.24.2"
- "@babel/generator" "^7.24.5"
+ "@babel/generator" "^7.24.4"
"@babel/helper-compilation-targets" "^7.23.6"
- "@babel/helper-module-transforms" "^7.24.5"
- "@babel/helpers" "^7.24.5"
- "@babel/parser" "^7.24.5"
+ "@babel/helper-module-transforms" "^7.23.3"
+ "@babel/helpers" "^7.24.4"
+ "@babel/parser" "^7.24.4"
"@babel/template" "^7.24.0"
- "@babel/traverse" "^7.24.5"
- "@babel/types" "^7.24.5"
+ "@babel/traverse" "^7.24.1"
+ "@babel/types" "^7.24.0"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/core@^7.23.9", "@babel/core@^7.24.4":
+ version "7.25.8"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.8.tgz#a57137d2a51bbcffcfaeba43cb4dd33ae3e0e1c6"
+ integrity sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.25.7"
+ "@babel/generator" "^7.25.7"
+ "@babel/helper-compilation-targets" "^7.25.7"
+ "@babel/helper-module-transforms" "^7.25.7"
+ "@babel/helpers" "^7.25.7"
+ "@babel/parser" "^7.25.8"
+ "@babel/template" "^7.25.7"
+ "@babel/traverse" "^7.25.7"
+ "@babel/types" "^7.25.8"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@@ -115,6 +142,16 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
+"@babel/generator@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56"
+ integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==
+ dependencies:
+ "@babel/types" "^7.25.7"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^3.0.2"
+
"@babel/helper-annotate-as-pure@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
@@ -122,6 +159,13 @@
dependencies:
"@babel/types" "^7.22.5"
+"@babel/helper-annotate-as-pure@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz#63f02dbfa1f7cb75a9bdb832f300582f30bb8972"
+ integrity sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==
+ dependencies:
+ "@babel/types" "^7.25.7"
+
"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956"
@@ -140,6 +184,17 @@
lru-cache "^5.1.1"
semver "^6.3.1"
+"@babel/helper-compilation-targets@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz#11260ac3322dda0ef53edfae6e97b961449f5fa4"
+ integrity sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==
+ dependencies:
+ "@babel/compat-data" "^7.25.7"
+ "@babel/helper-validator-option" "^7.25.7"
+ browserslist "^4.24.0"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
"@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4", "@babel/helper-create-class-features-plugin@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz#7d19da92c7e0cd8d11c09af2ce1b8e7512a6e723"
@@ -155,6 +210,19 @@
"@babel/helper-split-export-declaration" "^7.24.5"
semver "^6.3.1"
+"@babel/helper-create-class-features-plugin@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz#5d65074c76cae75607421c00d6bd517fe1892d6b"
+ integrity sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.7"
+ "@babel/helper-member-expression-to-functions" "^7.25.7"
+ "@babel/helper-optimise-call-expression" "^7.25.7"
+ "@babel/helper-replace-supers" "^7.25.7"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7"
+ "@babel/traverse" "^7.25.7"
+ semver "^6.3.1"
+
"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1"
@@ -202,6 +270,14 @@
dependencies:
"@babel/types" "^7.24.5"
+"@babel/helper-member-expression-to-functions@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz#541a33b071f0355a63a0fa4bdf9ac360116b8574"
+ integrity sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==
+ dependencies:
+ "@babel/traverse" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3":
version "7.24.3"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128"
@@ -217,6 +293,14 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
+"@babel/helper-module-imports@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472"
+ integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==
+ dependencies:
+ "@babel/traverse" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
"@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545"
@@ -228,6 +312,16 @@
"@babel/helper-split-export-declaration" "^7.24.5"
"@babel/helper-validator-identifier" "^7.24.5"
+"@babel/helper-module-transforms@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz#2ac9372c5e001b19bc62f1fe7d96a18cb0901d1a"
+ integrity sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.25.7"
+ "@babel/helper-simple-access" "^7.25.7"
+ "@babel/helper-validator-identifier" "^7.25.7"
+ "@babel/traverse" "^7.25.7"
+
"@babel/helper-optimise-call-expression@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e"
@@ -235,6 +329,13 @@
dependencies:
"@babel/types" "^7.22.5"
+"@babel/helper-optimise-call-expression@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz#1de1b99688e987af723eed44fa7fc0ee7b97d77a"
+ integrity sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==
+ dependencies:
+ "@babel/types" "^7.25.7"
+
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a"
@@ -245,6 +346,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
+"@babel/helper-plugin-utils@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c"
+ integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==
+
"@babel/helper-remap-async-to-generator@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0"
@@ -263,6 +369,15 @@
"@babel/helper-member-expression-to-functions" "^7.23.0"
"@babel/helper-optimise-call-expression" "^7.22.5"
+"@babel/helper-replace-supers@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz#38cfda3b6e990879c71d08d0fef9236b62bd75f5"
+ integrity sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.25.7"
+ "@babel/helper-optimise-call-expression" "^7.25.7"
+ "@babel/traverse" "^7.25.7"
+
"@babel/helper-simple-access@^7.22.5", "@babel/helper-simple-access@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba"
@@ -270,6 +385,14 @@
dependencies:
"@babel/types" "^7.24.5"
+"@babel/helper-simple-access@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz#5eb9f6a60c5d6b2e0f76057004f8dacbddfae1c0"
+ integrity sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==
+ dependencies:
+ "@babel/traverse" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
"@babel/helper-skip-transparent-expression-wrappers@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847"
@@ -277,6 +400,14 @@
dependencies:
"@babel/types" "^7.22.5"
+"@babel/helper-skip-transparent-expression-wrappers@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz#382831c91038b1a6d32643f5f49505b8442cb87c"
+ integrity sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==
+ dependencies:
+ "@babel/traverse" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
"@babel/helper-split-export-declaration@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6"
@@ -294,6 +425,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d"
integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
+"@babel/helper-string-parser@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54"
+ integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==
+
"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62"
@@ -304,11 +440,21 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
+"@babel/helper-validator-identifier@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5"
+ integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==
+
"@babel/helper-validator-option@^7.23.5":
version "7.23.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307"
integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==
+"@babel/helper-validator-option@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729"
+ integrity sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==
+
"@babel/helper-wrap-function@^7.22.20":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz#335f934c0962e2c1ed1fb9d79e06a56115067c09"
@@ -327,6 +473,14 @@
"@babel/traverse" "^7.24.5"
"@babel/types" "^7.24.5"
+"@babel/helpers@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.7.tgz#091b52cb697a171fe0136ab62e54e407211f09c2"
+ integrity sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==
+ dependencies:
+ "@babel/template" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
"@babel/highlight@^7.10.4", "@babel/highlight@^7.24.2":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e"
@@ -347,7 +501,17 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5":
+"@babel/highlight@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5"
+ integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.7"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790"
integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==
@@ -359,6 +523,13 @@
dependencies:
"@babel/types" "^7.25.2"
+"@babel/parser@^7.25.7", "@babel/parser@^7.25.8":
+ version "7.25.8"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2"
+ integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==
+ dependencies:
+ "@babel/types" "^7.25.8"
+
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz#4c3685eb9cd790bcad2843900fe0250c91ccf895"
@@ -438,13 +609,6 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-flow@^7.24.1":
- version "7.24.1"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz#875c25e3428d7896c87589765fc8b9d32f24bd8d"
- integrity sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.24.0"
-
"@babel/plugin-syntax-import-assertions@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971"
@@ -480,6 +644,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.0"
+"@babel/plugin-syntax-jsx@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz#5352d398d11ea5e7ef330c854dea1dae0bf18165"
+ integrity sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.7"
+
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
@@ -543,6 +714,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.0"
+"@babel/plugin-syntax-typescript@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.7.tgz#bfc05b0cc31ebd8af09964650cee723bb228108b"
+ integrity sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.7"
+
"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
@@ -591,7 +769,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.5"
-"@babel/plugin-transform-class-properties@^7.22.5", "@babel/plugin-transform-class-properties@^7.24.1":
+"@babel/plugin-transform-class-properties@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29"
integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==
@@ -676,14 +854,6 @@
"@babel/helper-plugin-utils" "^7.24.0"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"
-"@babel/plugin-transform-flow-strip-types@^7.24.1":
- version "7.24.1"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz#fa8d0a146506ea195da1671d38eed459242b2dcc"
- integrity sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==
- dependencies:
- "@babel/helper-plugin-utils" "^7.24.0"
- "@babel/plugin-syntax-flow" "^7.24.1"
-
"@babel/plugin-transform-for-of@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd"
@@ -739,7 +909,7 @@
"@babel/helper-module-transforms" "^7.23.3"
"@babel/helper-plugin-utils" "^7.24.0"
-"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.24.1":
+"@babel/plugin-transform-modules-commonjs@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9"
integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==
@@ -748,6 +918,15 @@
"@babel/helper-plugin-utils" "^7.24.0"
"@babel/helper-simple-access" "^7.22.5"
+"@babel/plugin-transform-modules-commonjs@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz#173f0c791bb7407c092ce6d77ee90eb3f2d1d2fd"
+ integrity sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.25.7"
+ "@babel/helper-plugin-utils" "^7.25.7"
+ "@babel/helper-simple-access" "^7.25.7"
+
"@babel/plugin-transform-modules-systemjs@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e"
@@ -781,7 +960,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.0"
-"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.1":
+"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988"
integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==
@@ -823,15 +1002,23 @@
"@babel/helper-plugin-utils" "^7.24.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
-"@babel/plugin-transform-optional-chaining@^7.23.0", "@babel/plugin-transform-optional-chaining@^7.24.1", "@babel/plugin-transform-optional-chaining@^7.24.5":
- version "7.24.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz#a6334bebd7f9dd3df37447880d0bd64b778e600f"
- integrity sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==
+"@babel/plugin-transform-optional-chaining@^7.24.1":
+ version "7.24.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz#26e588acbedce1ab3519ac40cc748e380c5291e6"
+ integrity sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==
dependencies:
- "@babel/helper-plugin-utils" "^7.24.5"
+ "@babel/helper-plugin-utils" "^7.24.0"
"@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
+"@babel/plugin-transform-optional-chaining@^7.24.5":
+ version "7.25.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.8.tgz#f46283b78adcc5b6ab988a952f989e7dce70653f"
+ integrity sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.7"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7"
+
"@babel/plugin-transform-parameters@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz#5c3b23f3a6b8fed090f9b98f2926896d3153cc62"
@@ -839,7 +1026,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.5"
-"@babel/plugin-transform-private-methods@^7.22.5", "@babel/plugin-transform-private-methods@^7.24.1":
+"@babel/plugin-transform-private-methods@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a"
integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==
@@ -977,6 +1164,17 @@
"@babel/helper-plugin-utils" "^7.24.5"
"@babel/plugin-syntax-typescript" "^7.24.1"
+"@babel/plugin-transform-typescript@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.7.tgz#8fc7c3d28ddd36bce45b9b48594129d0e560cfbe"
+ integrity sha512-VKlgy2vBzj8AmEzunocMun2fF06bsSWV+FvVXohtL6FGve/+L217qhHxRTVGHEDO/YR8IANcjzgJsd04J8ge5Q==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.7"
+ "@babel/helper-create-class-features-plugin" "^7.25.7"
+ "@babel/helper-plugin-utils" "^7.25.7"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7"
+ "@babel/plugin-syntax-typescript" "^7.25.7"
+
"@babel/plugin-transform-unicode-escapes@^7.24.1":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4"
@@ -1095,15 +1293,6 @@
core-js-compat "^3.31.0"
semver "^6.3.1"
-"@babel/preset-flow@^7.22.15":
- version "7.24.1"
- resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.24.1.tgz#da7196c20c2d7dd4e98cfd8b192fe53b5eb6f0bb"
- integrity sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.24.0"
- "@babel/helper-validator-option" "^7.23.5"
- "@babel/plugin-transform-flow-strip-types" "^7.24.1"
-
"@babel/preset-modules@0.1.6-no-external-plugins":
version "0.1.6-no-external-plugins"
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a"
@@ -1125,7 +1314,7 @@
"@babel/plugin-transform-react-jsx-development" "^7.22.5"
"@babel/plugin-transform-react-pure-annotations" "^7.24.1"
-"@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.23.0", "@babel/preset-typescript@^7.23.3", "@babel/preset-typescript@^7.24.1":
+"@babel/preset-typescript@^7.18.6":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec"
integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==
@@ -1136,16 +1325,16 @@
"@babel/plugin-transform-modules-commonjs" "^7.24.1"
"@babel/plugin-transform-typescript" "^7.24.1"
-"@babel/register@^7.22.15":
- version "7.23.7"
- resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.23.7.tgz#485a5e7951939d21304cae4af1719fdb887bc038"
- integrity sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==
+"@babel/preset-typescript@^7.23.3", "@babel/preset-typescript@^7.24.1":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.25.7.tgz#43c5b68eccb856ae5b52274b77b1c3c413cde1b7"
+ integrity sha512-rkkpaXJZOFN45Fb+Gki0c+KMIglk4+zZXOoMJuyEK8y8Kkc8Jd3BDmP7qPsz0zQMJj+UD7EprF+AqAXcILnexw==
dependencies:
- clone-deep "^4.0.1"
- find-cache-dir "^2.0.0"
- make-dir "^2.1.0"
- pirates "^4.0.6"
- source-map-support "^0.5.16"
+ "@babel/helper-plugin-utils" "^7.25.7"
+ "@babel/helper-validator-option" "^7.25.7"
+ "@babel/plugin-syntax-jsx" "^7.25.7"
+ "@babel/plugin-transform-modules-commonjs" "^7.25.7"
+ "@babel/plugin-transform-typescript" "^7.25.7"
"@babel/regjsgen@^0.8.0":
version "0.8.0"
@@ -1159,14 +1348,21 @@
dependencies:
regenerator-runtime "^0.13.11"
-"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.24.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
dependencies:
regenerator-runtime "^0.14.0"
-"@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.5":
+"@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6"
+ integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
+"@babel/runtime@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb"
integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==
@@ -1191,6 +1387,15 @@
"@babel/parser" "^7.25.0"
"@babel/types" "^7.25.0"
+"@babel/template@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769"
+ integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==
+ dependencies:
+ "@babel/code-frame" "^7.25.7"
+ "@babel/parser" "^7.25.7"
+ "@babel/types" "^7.25.7"
+
"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.1", "@babel/traverse@^7.24.5":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8"
@@ -1220,6 +1425,19 @@
debug "^4.3.1"
globals "^11.1.0"
+"@babel/traverse@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8"
+ integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==
+ dependencies:
+ "@babel/code-frame" "^7.25.7"
+ "@babel/generator" "^7.25.7"
+ "@babel/parser" "^7.25.7"
+ "@babel/template" "^7.25.7"
+ "@babel/types" "^7.25.7"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
"@babel/types@^7.0.0", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7"
@@ -1238,6 +1456,15 @@
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.25.7", "@babel/types@^7.25.8":
+ version "7.25.8"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1"
+ integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.25.7"
+ "@babel/helper-validator-identifier" "^7.25.7"
+ to-fast-properties "^2.0.0"
+
"@base2/pretty-print-object@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4"
@@ -1374,11 +1601,6 @@
dependencies:
"@date-io/core" "^2.17.0"
-"@discoveryjs/json-ext@^0.5.3":
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
- integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
-
"@ducanh2912/next-pwa@^9.7.1":
version "9.7.2"
resolved "https://registry.yarnpkg.com/@ducanh2912/next-pwa/-/next-pwa-9.7.2.tgz#72160e294e787cf5df5c59de2bfa8d9807e025ee"
@@ -1393,10 +1615,10 @@
workbox-webpack-plugin "7.0.0"
workbox-window "7.0.0"
-"@emnapi/runtime@^1.1.1":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.2.0.tgz#71d018546c3a91f3b51106530edbc056b9f2f2e3"
- integrity sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==
+"@emnapi/runtime@^1.2.0":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60"
+ integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==
dependencies:
tslib "^2.4.0"
@@ -1417,15 +1639,15 @@
source-map "^0.5.7"
stylis "4.2.0"
-"@emotion/cache@^11.11.0":
- version "11.11.0"
- resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff"
- integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==
+"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.1":
+ version "11.13.1"
+ resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7"
+ integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==
dependencies:
- "@emotion/memoize" "^0.8.1"
- "@emotion/sheet" "^1.2.2"
- "@emotion/utils" "^1.2.1"
- "@emotion/weak-memoize" "^0.3.1"
+ "@emotion/memoize" "^0.9.0"
+ "@emotion/sheet" "^1.4.0"
+ "@emotion/utils" "^1.4.0"
+ "@emotion/weak-memoize" "^0.4.0"
stylis "4.2.0"
"@emotion/hash@^0.9.1":
@@ -1485,10 +1707,10 @@
multipipe "^1.0.2"
through "^2.3.8"
-"@emotion/sheet@^1.2.2":
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec"
- integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
+"@emotion/sheet@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c"
+ integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
"@emotion/styled@^11.11.0":
version "11.11.5"
@@ -1512,16 +1734,21 @@
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963"
integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==
-"@emotion/utils@^1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4"
- integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==
+"@emotion/utils@^1.2.1", "@emotion/utils@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd"
+ integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==
"@emotion/weak-memoize@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
+"@emotion/weak-memoize@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
+ integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
+
"@emurgo/cardano-serialization-lib-browser@^11.5.0":
version "11.5.0"
resolved "https://registry.yarnpkg.com/@emurgo/cardano-serialization-lib-browser/-/cardano-serialization-lib-browser-11.5.0.tgz#f2d15b538add436e0aa8b67a1d00ca654a680006"
@@ -1542,6 +1769,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
+"@esbuild/aix-ppc64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353"
+ integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==
+
"@esbuild/android-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4"
@@ -1552,6 +1784,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
+"@esbuild/android-arm64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018"
+ integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==
+
"@esbuild/android-arm@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824"
@@ -1562,6 +1799,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
+"@esbuild/android-arm@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee"
+ integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==
+
"@esbuild/android-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d"
@@ -1572,6 +1814,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
+"@esbuild/android-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517"
+ integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==
+
"@esbuild/darwin-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e"
@@ -1582,6 +1829,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
+"@esbuild/darwin-arm64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16"
+ integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==
+
"@esbuild/darwin-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd"
@@ -1592,6 +1844,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
+"@esbuild/darwin-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931"
+ integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==
+
"@esbuild/freebsd-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487"
@@ -1602,6 +1859,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
+"@esbuild/freebsd-arm64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc"
+ integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==
+
"@esbuild/freebsd-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c"
@@ -1612,6 +1874,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
+"@esbuild/freebsd-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730"
+ integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==
+
"@esbuild/linux-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b"
@@ -1622,6 +1889,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
+"@esbuild/linux-arm64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383"
+ integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==
+
"@esbuild/linux-arm@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef"
@@ -1632,6 +1904,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
+"@esbuild/linux-arm@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771"
+ integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==
+
"@esbuild/linux-ia32@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601"
@@ -1642,6 +1919,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
+"@esbuild/linux-ia32@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333"
+ integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==
+
"@esbuild/linux-loong64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299"
@@ -1652,6 +1934,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
+"@esbuild/linux-loong64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac"
+ integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==
+
"@esbuild/linux-mips64el@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec"
@@ -1662,6 +1949,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
+"@esbuild/linux-mips64el@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6"
+ integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==
+
"@esbuild/linux-ppc64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8"
@@ -1672,6 +1964,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
+"@esbuild/linux-ppc64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96"
+ integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==
+
"@esbuild/linux-riscv64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf"
@@ -1682,6 +1979,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
+"@esbuild/linux-riscv64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7"
+ integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==
+
"@esbuild/linux-s390x@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8"
@@ -1692,6 +1994,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
+"@esbuild/linux-s390x@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f"
+ integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==
+
"@esbuild/linux-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78"
@@ -1702,6 +2009,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
+"@esbuild/linux-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24"
+ integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==
+
"@esbuild/netbsd-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b"
@@ -1712,6 +2024,16 @@
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
+"@esbuild/netbsd-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653"
+ integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==
+
+"@esbuild/openbsd-arm64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7"
+ integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==
+
"@esbuild/openbsd-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0"
@@ -1722,6 +2044,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
+"@esbuild/openbsd-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273"
+ integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==
+
"@esbuild/sunos-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30"
@@ -1732,6 +2059,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
+"@esbuild/sunos-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403"
+ integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==
+
"@esbuild/win32-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae"
@@ -1742,6 +2074,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
+"@esbuild/win32-arm64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2"
+ integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==
+
"@esbuild/win32-ia32@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67"
@@ -1752,6 +2089,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
+"@esbuild/win32-ia32@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac"
+ integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==
+
"@esbuild/win32-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae"
@@ -1762,6 +2104,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
+"@esbuild/win32-x64@0.23.1":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699"
+ integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==
+
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -1802,7 +2149,7 @@
crc-32 "^1.2.0"
ethereumjs-util "^7.1.4"
-"@ethereumjs/common@2.6.5", "@ethereumjs/common@^2.0.0", "@ethereumjs/common@^2.6.3", "@ethereumjs/common@^2.6.4":
+"@ethereumjs/common@^2.0.0", "@ethereumjs/common@^2.6.3", "@ethereumjs/common@^2.6.4":
version "2.6.5"
resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.5.tgz#0a75a22a046272579d91919cb12d84f2756e8d30"
integrity sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==
@@ -1818,12 +2165,12 @@
"@ethereumjs/util" "^8.1.0"
crc-32 "^1.2.0"
-"@ethereumjs/common@^4.2.0", "@ethereumjs/common@^4.3.0":
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-4.3.0.tgz#5b45eec7dcf521fa4ddaf0b383072fbcf9913553"
- integrity sha512-shBNJ0ewcPNTUfZduHiczPmqkfJDn0Dh/9BR5fq7xUFTuIq7Fu1Vx00XDwQVIrpVL70oycZocOhBM6nDO+4FEQ==
+"@ethereumjs/common@^4.2.0", "@ethereumjs/common@^4.4.0":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-4.4.0.tgz#fba41612f527a815bf304e98653d6b5fc5d6d4de"
+ integrity sha512-Fy5hMqF6GsE6DpYTyqdDIJPJgUtDn4dL120zKw+Pswuo+iLyBsEYuSyzMw6NVzD2vDzcBG9fE4+qX4X2bPc97w==
dependencies:
- "@ethereumjs/util" "^9.0.3"
+ "@ethereumjs/util" "^9.1.0"
"@ethereumjs/rlp@^4.0.1":
version "4.0.1"
@@ -1870,14 +2217,14 @@
ethereum-cryptography "^2.0.0"
"@ethereumjs/tx@^5.2.1":
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-5.3.0.tgz#473f351729ef4e30eaa3a3fb5aaccd4405a7ee41"
- integrity sha512-uv++XYuIfuqYbvymL3/o14hHuC6zX0nRQ1nI2FHsbkkorLZ2ChEIDqVeeVk7Xc9/jQNU/22sk9qZZkRlsveXxw==
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-5.4.0.tgz#6f47894cc3e2d4e63d87c62b41ed7e8180a1de58"
+ integrity sha512-SCHnK7m/AouZ7nyoR0MEXw1OO/tQojSbp88t8oxhwes5iZkZCtfFdUrJaiIb72qIpH2FVw6s1k1uP7LXuH7PsA==
dependencies:
- "@ethereumjs/common" "^4.3.0"
+ "@ethereumjs/common" "^4.4.0"
"@ethereumjs/rlp" "^5.0.2"
- "@ethereumjs/util" "^9.0.3"
- ethereum-cryptography "^2.1.3"
+ "@ethereumjs/util" "^9.1.0"
+ ethereum-cryptography "^2.2.1"
"@ethereumjs/util@^8.1.0":
version "8.1.0"
@@ -1888,13 +2235,13 @@
ethereum-cryptography "^2.0.0"
micro-ftch "^0.3.1"
-"@ethereumjs/util@^9.0.3":
- version "9.0.3"
- resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-9.0.3.tgz#c2709e6127a85bbe23a71937ac78358ac93e7241"
- integrity sha512-PmwzWDflky+7jlZIFqiGsBPap12tk9zK5SVH9YW2OEnDN7OEhCjUOMzbOqwuClrbkSIkM2ERivd7sXZ48Rh/vg==
+"@ethereumjs/util@^9.1.0":
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-9.1.0.tgz#75e3898a3116d21c135fa9e29886565609129bce"
+ integrity sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==
dependencies:
"@ethereumjs/rlp" "^5.0.2"
- ethereum-cryptography "^2.1.3"
+ ethereum-cryptography "^2.2.1"
"@ethersproject/abi@5.5.0":
version "5.5.0"
@@ -1911,7 +2258,7 @@
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
-"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0":
+"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449"
integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==
@@ -2484,7 +2831,7 @@
"@ethersproject/rlp" "^5.5.0"
"@ethersproject/signing-key" "^5.5.0"
-"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.5.0", "@ethersproject/transactions@^5.6.2", "@ethersproject/transactions@^5.7.0":
+"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.5.0", "@ethersproject/transactions@^5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b"
integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==
@@ -2696,11 +3043,6 @@
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451"
integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==
-"@fal-works/esbuild-plugin-global-externals@^2.1.2":
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4"
- integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==
-
"@fastify/busboy@^2.0.0":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
@@ -3189,7 +3531,7 @@
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc"
integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw==
-"@gnosis.pm/zodiac@^4.0.1":
+"@gnosis.pm/zodiac@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@gnosis.pm/zodiac/-/zodiac-4.0.3.tgz#3323f496cda279e2a68ca2bf726ad840388e8229"
integrity sha512-yliHytBRlb4wNkeiYsaaAvYZC5VwiUBVfJ+HLEmjnWPG2EHB5tvAOUboDU7XvSQ4GUJAz8h2YUrph0XkH+/xfA==
@@ -3249,118 +3591,123 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
-"@img/sharp-darwin-arm64@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz#a1cf4a7febece334f16e0328b9689f05797d7aec"
- integrity sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==
+"@img/sharp-darwin-arm64@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08"
+ integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==
optionalDependencies:
- "@img/sharp-libvips-darwin-arm64" "1.0.2"
+ "@img/sharp-libvips-darwin-arm64" "1.0.4"
-"@img/sharp-darwin-x64@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz#f77be2d7c3609d3e77cd337b199a772e07b87bd2"
- integrity sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==
+"@img/sharp-darwin-x64@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61"
+ integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==
optionalDependencies:
- "@img/sharp-libvips-darwin-x64" "1.0.2"
+ "@img/sharp-libvips-darwin-x64" "1.0.4"
-"@img/sharp-libvips-darwin-arm64@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz#b69f49fecbe9572378675769b189410721b0fa53"
- integrity sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==
+"@img/sharp-libvips-darwin-arm64@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f"
+ integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==
-"@img/sharp-libvips-darwin-x64@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz#5665da7360d8e5ed7bee314491c8fe736b6a3c39"
- integrity sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==
+"@img/sharp-libvips-darwin-x64@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062"
+ integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==
-"@img/sharp-libvips-linux-arm64@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz#8a05e5e9e9b760ff46561e32f19bd5e035fa881c"
- integrity sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==
+"@img/sharp-libvips-linux-arm64@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704"
+ integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==
-"@img/sharp-libvips-linux-arm@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz#0fd33b9bf3221948ce0ca7a5a725942626577a03"
- integrity sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==
+"@img/sharp-libvips-linux-arm@1.0.5":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197"
+ integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==
-"@img/sharp-libvips-linux-s390x@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz#4b89150ec91b256ee2cbb5bb125321bf029a4770"
- integrity sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==
+"@img/sharp-libvips-linux-s390x@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce"
+ integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==
-"@img/sharp-libvips-linux-x64@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz#947ccc22ca5bc8c8cfe921b39a5fdaebc5e39f3f"
- integrity sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==
+"@img/sharp-libvips-linux-x64@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0"
+ integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==
-"@img/sharp-libvips-linuxmusl-arm64@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz#821d58ce774f0f8bed065b69913a62f65d512f2f"
- integrity sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==
+"@img/sharp-libvips-linuxmusl-arm64@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5"
+ integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==
-"@img/sharp-libvips-linuxmusl-x64@1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz#4309474bd8b728a61af0b3b4fad0c476b5f3ccbe"
- integrity sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==
+"@img/sharp-libvips-linuxmusl-x64@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff"
+ integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==
-"@img/sharp-linux-arm64@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz#bd390113e256487041411b988ded13a26cfc5f95"
- integrity sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==
+"@img/sharp-linux-arm64@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22"
+ integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==
optionalDependencies:
- "@img/sharp-libvips-linux-arm64" "1.0.2"
+ "@img/sharp-libvips-linux-arm64" "1.0.4"
-"@img/sharp-linux-arm@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz#14ecc81f38f75fb4cd7571bc83311746d6745fca"
- integrity sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==
+"@img/sharp-linux-arm@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff"
+ integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==
optionalDependencies:
- "@img/sharp-libvips-linux-arm" "1.0.2"
+ "@img/sharp-libvips-linux-arm" "1.0.5"
-"@img/sharp-linux-s390x@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz#119e8081e2c6741b5ac908fe02244e4c559e525f"
- integrity sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==
+"@img/sharp-linux-s390x@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667"
+ integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==
optionalDependencies:
- "@img/sharp-libvips-linux-s390x" "1.0.2"
+ "@img/sharp-libvips-linux-s390x" "1.0.4"
-"@img/sharp-linux-x64@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz#21d4c137b8da9a313b069ff5c920ded709f853d7"
- integrity sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==
+"@img/sharp-linux-x64@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb"
+ integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==
optionalDependencies:
- "@img/sharp-libvips-linux-x64" "1.0.2"
+ "@img/sharp-libvips-linux-x64" "1.0.4"
-"@img/sharp-linuxmusl-arm64@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz#f3fde68fd67b85a32da6f1155818c3b58b8e7ae0"
- integrity sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==
+"@img/sharp-linuxmusl-arm64@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b"
+ integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==
optionalDependencies:
- "@img/sharp-libvips-linuxmusl-arm64" "1.0.2"
+ "@img/sharp-libvips-linuxmusl-arm64" "1.0.4"
-"@img/sharp-linuxmusl-x64@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz#44373724aecd7b69900e0578228144e181db7892"
- integrity sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==
+"@img/sharp-linuxmusl-x64@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48"
+ integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==
optionalDependencies:
- "@img/sharp-libvips-linuxmusl-x64" "1.0.2"
+ "@img/sharp-libvips-linuxmusl-x64" "1.0.4"
-"@img/sharp-wasm32@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz#88e3f18d7e7cd8cfe1af98e9963db4d7b6491435"
- integrity sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==
+"@img/sharp-wasm32@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1"
+ integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==
dependencies:
- "@emnapi/runtime" "^1.1.1"
+ "@emnapi/runtime" "^1.2.0"
-"@img/sharp-win32-ia32@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz#b1c772dd2952e983980b1eb85808fa8129484d46"
- integrity sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==
+"@img/sharp-win32-ia32@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9"
+ integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==
-"@img/sharp-win32-x64@0.33.4":
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz#106f911134035b4157ec92a0c154a6b6f88fa4c1"
- integrity sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==
+"@img/sharp-win32-x64@0.33.5":
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342"
+ integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==
+
+"@ioredis/commands@^1.1.1":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11"
+ integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==
"@isaacs/cliui@^8.0.2":
version "8.0.2"
@@ -3858,7 +4205,44 @@
dependencies:
"@lit-labs/ssr-dom-shim" "^1.0.0"
-"@mdx-js/react@^3.0.0":
+"@mdx-js/loader@^3.0.1":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-3.0.1.tgz#d21e5bd50b38a4713559586dcdaa987ef9dc02c9"
+ integrity sha512-YbYUt7YyEOdFxhyuCWmLKf5vKhID/hJAojEUnheJk4D8iYVLFQw+BAoBWru/dHGch1omtmZOPstsmKPyBF68Tw==
+ dependencies:
+ "@mdx-js/mdx" "^3.0.0"
+ source-map "^0.7.0"
+
+"@mdx-js/mdx@^3.0.0":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.0.1.tgz#617bd2629ae561fdca1bb88e3badd947f5a82191"
+ integrity sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdx" "^2.0.0"
+ collapse-white-space "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-build-jsx "^3.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ estree-util-to-js "^2.0.0"
+ estree-walker "^3.0.0"
+ hast-util-to-estree "^3.0.0"
+ hast-util-to-jsx-runtime "^2.0.0"
+ markdown-extensions "^2.0.0"
+ periscopic "^3.0.0"
+ remark-mdx "^3.0.0"
+ remark-parse "^11.0.0"
+ remark-rehype "^11.0.0"
+ source-map "^0.7.0"
+ unified "^11.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ unist-util-stringify-position "^4.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
+"@mdx-js/react@^3.0.0", "@mdx-js/react@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.0.1.tgz#997a19b3a5b783d936c75ae7c47cfe62f967f746"
integrity sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==
@@ -4003,15 +4387,7 @@
hey-listen "^1.0.8"
tslib "^2.3.1"
-"@motionone/easing@^10.17.0":
- version "10.17.0"
- resolved "https://registry.yarnpkg.com/@motionone/easing/-/easing-10.17.0.tgz#d66cecf7e3ee30104ad00389fb3f0b2282d81aa9"
- integrity sha512-Bxe2wSuLu/qxqW4rBFS5m9tMLOw+QBh8v5A7Z5k4Ul4sTj5jAOfZG5R0bn5ywmk+Fs92Ij1feZ5pmC4TeXA8Tg==
- dependencies:
- "@motionone/utils" "^10.17.0"
- tslib "^2.3.1"
-
-"@motionone/easing@^10.18.0":
+"@motionone/easing@^10.16.3", "@motionone/easing@^10.18.0":
version "10.18.0"
resolved "https://registry.yarnpkg.com/@motionone/easing/-/easing-10.18.0.tgz#7b82f6010dfee3a1bb0ee83abfbaff6edae0c708"
integrity sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==
@@ -4041,7 +4417,7 @@
resolved "https://registry.yarnpkg.com/@motionone/types/-/types-10.17.0.tgz#179571ce98851bac78e19a1c3974767227f08ba3"
integrity sha512-EgeeqOZVdRUTEHq95Z3t8Rsirc7chN5xFAPMYFobx8TPubkEfRSm5xihmMUkbaR2ErKJTUw3347QDPTHIW12IA==
-"@motionone/types@^10.17.1":
+"@motionone/types@^10.16.3", "@motionone/types@^10.17.1":
version "10.17.1"
resolved "https://registry.yarnpkg.com/@motionone/types/-/types-10.17.1.tgz#cf487badbbdc9da0c2cb86ffc1e5d11147c6e6fb"
integrity sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==
@@ -4055,7 +4431,7 @@
hey-listen "^1.0.8"
tslib "^2.3.1"
-"@motionone/utils@^10.18.0":
+"@motionone/utils@^10.16.3", "@motionone/utils@^10.18.0":
version "10.18.0"
resolved "https://registry.yarnpkg.com/@motionone/utils/-/utils-10.18.0.tgz#a59ff8932ed9009624bca07c56b28ef2bb2f885e"
integrity sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==
@@ -4186,15 +4562,6 @@
resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121"
integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==
-"@ndelangen/get-tarball@^3.0.7":
- version "3.0.9"
- resolved "https://registry.yarnpkg.com/@ndelangen/get-tarball/-/get-tarball-3.0.9.tgz#727ff4454e65f34707e742a59e5e6b1f525d8964"
- integrity sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==
- dependencies:
- gunzip-maybe "^1.4.2"
- pump "^3.0.0"
- tar-fs "^2.1.1"
-
"@next/bundle-analyzer@^13.5.6":
version "13.5.6"
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-13.5.6.tgz#3c73f2e15ff5507317b37b87ce984bac5a5d7ad0"
@@ -4202,10 +4569,10 @@
dependencies:
webpack-bundle-analyzer "4.7.0"
-"@next/env@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.1.tgz#80150a8440eb0022a73ba353c6088d419b908bac"
- integrity sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==
+"@next/env@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.13.tgz#ba341ba9eb70db428fc1c754f49c3c516f7bab47"
+ integrity sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==
"@next/eslint-plugin-next@14.2.3":
version "14.2.3"
@@ -4214,50 +4581,57 @@
dependencies:
glob "10.3.10"
-"@next/swc-darwin-arm64@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz#b74ba7c14af7d05fa2848bdeb8ee87716c939b64"
- integrity sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==
-
-"@next/swc-darwin-x64@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz#82c3e67775e40094c66e76845d1a36cc29c9e78b"
- integrity sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==
-
-"@next/swc-linux-arm64-gnu@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz#4f4134457b90adc5c3d167d07dfb713c632c0caa"
- integrity sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==
-
-"@next/swc-linux-arm64-musl@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz#594bedafaeba4a56db23a48ffed2cef7cd09c31a"
- integrity sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==
-
-"@next/swc-linux-x64-gnu@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz#cb4e75f1ff2b9bcadf2a50684605928ddfc58528"
- integrity sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==
-
-"@next/swc-linux-x64-musl@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz#15f26800df941b94d06327f674819ab64b272e25"
- integrity sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==
-
-"@next/swc-win32-arm64-msvc@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz#060c134fa7fa843666e3e8574972b2b723773dd9"
- integrity sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==
-
-"@next/swc-win32-ia32-msvc@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz#5c06889352b1f77e3807834a0d0afd7e2d2d1da2"
- integrity sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==
-
-"@next/swc-win32-x64-msvc@14.1.1":
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz#d38c63a8f9b7f36c1470872797d3735b4a9c5c52"
- integrity sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==
+"@next/mdx@^14.2.11":
+ version "14.2.11"
+ resolved "https://registry.yarnpkg.com/@next/mdx/-/mdx-14.2.11.tgz#d68b7558186794147a45b7b6acdb27a69c904243"
+ integrity sha512-aTs8U7N5FLXArb1YfHMsomMtHa0sulAWrfbPdZKDIpF9DUNwY8tbRVpHLz/AbIwoJk/4oDhDwDSJBFZXYxrjzw==
+ dependencies:
+ source-map "^0.7.0"
+
+"@next/swc-darwin-arm64@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.13.tgz#76f08d78360c4d27d444df7f35a56f59a48f4808"
+ integrity sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==
+
+"@next/swc-darwin-x64@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.13.tgz#1d4821d54bb01dacc6a6c32408f8468a4f4af269"
+ integrity sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==
+
+"@next/swc-linux-arm64-gnu@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.13.tgz#79d9af8d3408df9990c8911889eca1ca6a308f19"
+ integrity sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==
+
+"@next/swc-linux-arm64-musl@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.13.tgz#b13180645865b120591db2f1e831743ebc02ab36"
+ integrity sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==
+
+"@next/swc-linux-x64-gnu@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.13.tgz#8cb8480dfeee512648e4e08c2095aac0461b876f"
+ integrity sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==
+
+"@next/swc-linux-x64-musl@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.13.tgz#df5ca922fa1e1ee81b15a06a2d3d3ace0efd2bd7"
+ integrity sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==
+
+"@next/swc-win32-arm64-msvc@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.13.tgz#8a7db6e71f526212587975f743b28e4d1cb829d1"
+ integrity sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==
+
+"@next/swc-win32-ia32-msvc@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.13.tgz#6aa664f36f2d70c5ae6ffcbbc56784d33f24522d"
+ integrity sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==
+
+"@next/swc-win32-x64-msvc@14.2.13":
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.13.tgz#5a920eea82a58affa6146192586716cec6c87fed"
+ integrity sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==
"@ngraveio/bc-ur@^1.0.0", "@ngraveio/bc-ur@^1.1.5":
version "1.1.13"
@@ -4279,35 +4653,35 @@
dependencies:
"@noble/hashes" "1.3.2"
-"@noble/curves@1.3.0", "@noble/curves@~1.3.0":
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e"
- integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==
- dependencies:
- "@noble/hashes" "1.3.3"
-
-"@noble/curves@^1.4.0":
+"@noble/curves@1.4.2", "@noble/curves@~1.4.0":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9"
integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==
dependencies:
"@noble/hashes" "1.4.0"
+"@noble/curves@^1.4.2":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd"
+ integrity sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==
+ dependencies:
+ "@noble/hashes" "1.4.0"
+
"@noble/hashes@1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==
-"@noble/hashes@1.3.3", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
- integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
-
-"@noble/hashes@1.4.0", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0":
+"@noble/hashes@1.4.0", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==
+"@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2":
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
+ integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -4334,7 +4708,7 @@
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.2.tgz#3e5321a2ecdd0b206064356798c21225b6ec7105"
integrity sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ==
-"@openzeppelin/contracts@^4.9.2":
+"@openzeppelin/contracts@^4.9.6":
version "4.9.6"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677"
integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==
@@ -4664,15 +5038,15 @@
resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91"
integrity sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==
-"@reduxjs/toolkit@^1.9.5":
- version "1.9.7"
- resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.7.tgz#7fc07c0b0ebec52043f8cb43510cf346405f78a6"
- integrity sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==
+"@reduxjs/toolkit@^2.2.6":
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.2.6.tgz#4a8356dad9d0c1ab255607a555d492168e0e3bc1"
+ integrity sha512-kH0r495c5z1t0g796eDQAkYbEQ3a1OLYN9o8jQQVZyKyw367pfRGS+qZLkHYvFHiUUdafpoSlQ2QYObIApjPWA==
dependencies:
- immer "^9.0.21"
- redux "^4.2.1"
- redux-thunk "^2.4.2"
- reselect "^4.1.8"
+ immer "^10.0.3"
+ redux "^5.0.1"
+ redux-thunk "^3.1.0"
+ reselect "^5.1.0"
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
@@ -4716,29 +5090,29 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz#391d528054f758f81e53210f1a1eebcf1a8b1d20"
integrity sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==
-"@safe-global/api-kit@^2.3.2":
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/@safe-global/api-kit/-/api-kit-2.3.2.tgz#37cab964faf79f98eb5f1a86004a56c0fb0eef21"
- integrity sha512-pnbP23t3XQOWZv5Y35CoDpBAajSeB0ZY9ahajruNYt6pyPYxleQVuRueCQ+FDZHNDGS0SK7dlUYyUScs/GsTZQ==
+"@safe-global/api-kit@^2.4.6":
+ version "2.4.6"
+ resolved "https://registry.yarnpkg.com/@safe-global/api-kit/-/api-kit-2.4.6.tgz#b1377ee16d9af2db29f59bce5262ffad59f61b82"
+ integrity sha512-57lXrqXnmdUdQ12ssWSVDZhpIY2HcJzDvR4w6edT8xebEaduKx2UpwRJ8U2WVEBrx5K9PYuLAPsPHs+/r0yuGg==
dependencies:
- "@safe-global/protocol-kit" "^3.1.1"
- "@safe-global/safe-core-sdk-types" "^4.1.1"
- ethers "^6.7.1"
+ "@safe-global/protocol-kit" "^4.1.1"
+ "@safe-global/safe-core-sdk-types" "^5.1.0"
+ ethers "^6.13.1"
node-fetch "^2.7.0"
-"@safe-global/protocol-kit@^3.1.1":
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/@safe-global/protocol-kit/-/protocol-kit-3.1.1.tgz#eec3cee3432cd1ad8b159e1911527d93ecc502f9"
- integrity sha512-ai/N1DI6U53CsC46Do8eOyb6IkgVhjoQFhbFIh5rFSAKiuw3B0hTF7nrVRb0jw4NFlNHCcWDAER/uNH0Qy2Pkg==
+"@safe-global/protocol-kit@^4.1.1":
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/@safe-global/protocol-kit/-/protocol-kit-4.1.1.tgz#3c23616578c7e071cca5e5d945a2f995b6903b0d"
+ integrity sha512-11Jui1gIpCOV1sUn5HlT+hVn/+gJnKjik0V0aUPDKIL2zajwc+jReZK43CH5GyOHKOF7gCs2yprMHOrQuKkkqw==
dependencies:
"@noble/hashes" "^1.3.3"
- "@safe-global/safe-deployments" "^1.36.0"
+ "@safe-global/safe-core-sdk-types" "^5.1.0"
+ "@safe-global/safe-deployments" "^1.37.9"
+ "@safe-global/safe-modules-deployments" "^2.2.1"
+ abitype "^1.0.2"
ethereumjs-util "^7.1.5"
- ethers "^6.7.1"
- semver "^7.5.4"
- web3 "^1.10.3"
- web3-core "^1.10.3"
- web3-utils "^1.10.3"
+ ethers "^6.13.1"
+ semver "^7.6.2"
"@safe-global/safe-apps-sdk@^9.1.0":
version "9.1.0"
@@ -4748,42 +5122,53 @@
"@safe-global/safe-gateway-typescript-sdk" "^3.5.3"
viem "^2.1.1"
-"@safe-global/safe-core-sdk-types@^4.1.1":
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/@safe-global/safe-core-sdk-types/-/safe-core-sdk-types-4.1.1.tgz#b75e15a2e6fa50f131629ca6f6f3a98fd6037d30"
- integrity sha512-5NIWG7OjV+C5iquux0yPcu8SHUzg1qJXJ/jAQcPwXGTC7ZVsFawnR43/l2Vg9zEwf0RF0xTm3W8DXkaBYORiEQ==
+"@safe-global/safe-core-sdk-types@^5.0.1":
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-core-sdk-types/-/safe-core-sdk-types-5.0.2.tgz#9552f5793581333c81676986b3eb19697e1c6627"
+ integrity sha512-UK2WL9nXN/8ez4Upx1D0KhtNXwkEjuV5O2XDem4Jchw5IBCLJnLowUBgEq8Tw4xdB7x2rw72VFAJXYZ/iY4qYw==
dependencies:
- "@safe-global/safe-deployments" "^1.36.0"
- ethers "^6.7.1"
- web3-core "^1.10.3"
- web3-utils "^1.10.3"
+ abitype "^1.0.2"
-"@safe-global/safe-deployments@^1.36.0":
- version "1.36.0"
- resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.36.0.tgz#7e5cd470cc1042182d47f65b59831a64ed8feff1"
- integrity sha512-9MbDJveRR64AbmzjIpuUqmDBDtOZpXpvkyhTUs+5UOPT3WgSO375/ZTO7hZpywP7+EmxnjkGc9EoxjGcC4TAyw==
+"@safe-global/safe-core-sdk-types@^5.1.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-core-sdk-types/-/safe-core-sdk-types-5.1.0.tgz#af8d877b9af231242d023c7182f78ff4223bc3f4"
+ integrity sha512-UzXR4zWmVzux25FcIm4H049QhZZpVpIBL5HE+V0p5gHpArZROL+t24fZmsKUf403CtBxIJM5zZSVQL0nFJi+IQ==
dependencies:
- semver "^7.6.0"
+ abitype "^1.0.2"
+
+"@safe-global/safe-deployments@^1.37.8":
+ version "1.37.8"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.37.8.tgz#5d51a57e4c3a9274ce09d8fe7fbe1265a1aaf4c4"
+ integrity sha512-BT34eqSJ1K+4xJgJVY3/Yxg8TRTEvFppkt4wcirIPGCgR4/j06HptHPyDdmmqTuvih8wi8OpFHi0ncP+cGlXWA==
+ dependencies:
+ semver "^7.6.2"
-"@safe-global/safe-gateway-typescript-sdk@3.21.8":
- version "3.21.8"
- resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.21.8.tgz#f90eb668dd620d0c5578f02f7040169610a0eda1"
- integrity sha512-n/fYgiqbuzAQuK0bgny6GBYvb585ETxKURa5Kb9hBV3fa47SvJo/dpGq275fJUn0e3Hh1YqETiLGj4HVJjHiTA==
+"@safe-global/safe-deployments@^1.37.9":
+ version "1.37.10"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.37.10.tgz#2f61a25bd479332821ba2e91a575237d77406ec3"
+ integrity sha512-lcxX9CV+xdcLs4dF6Cx18zDww5JyqaX6RdcvU0o/34IgJ4Wjo3J/RNzJAoMhurCAfTGr+0vyJ9V13Qo50AR6JA==
+ dependencies:
+ semver "^7.6.2"
+
+"@safe-global/safe-gateway-typescript-sdk@3.22.3-beta.15":
+ version "3.22.3-beta.15"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.3-beta.15.tgz#83c7e784b7ee0d08104c2f1b7dc99f6cdc950683"
+ integrity sha512-6vAKyGualYDde0zpNaigDY9H7EMpccWPov9hw/itBwY+7JReWGYO2DC0yma48bqDyeipsAkrhvHBNx5TF2zY4Q==
"@safe-global/safe-gateway-typescript-sdk@^3.5.3":
version "3.21.2"
resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.21.2.tgz#2123a7429c2d9713365f51c359bfc055d4c8e913"
integrity sha512-N9Y2CKPBVbc8FbOKzqepy8TJUY2VILX7bmxV4ruByLJvR9PBnGvGfnOhw975cDn6PmSziXL0RaUWHpSW23rsng==
-"@safe-global/safe-modules-deployments@^1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@safe-global/safe-modules-deployments/-/safe-modules-deployments-1.2.0.tgz#ca871c3f553cd16cbea1aac8f8be16498329a7d3"
- integrity sha512-/pjHIPaYwGRM5oOB7lc+yf28fWEq7twNP5dJxpLFgG/9UR4E3F+XfFdYkpP22eIvmOkBwCJXJZfPfESh9WSF2w==
+"@safe-global/safe-modules-deployments@^2.2.1":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-modules-deployments/-/safe-modules-deployments-2.2.1.tgz#a8b88f2afc6ec04fed09968fe1e4990ed975c86e"
+ integrity sha512-H0XpusyXVcsTuRsQSq0FoBKqRfhZH87/1DrFEmXXPXmI3fJkvxq3KpTaTTqzcqoIe/J+erwVZQUYNfL68EcvAQ==
-"@scure/base@^1.1.3", "@scure/base@~1.1.4":
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30"
- integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==
+"@scure/base@^1.1.3", "@scure/base@~1.1.6":
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1"
+ integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==
"@scure/base@~1.1.0":
version "1.1.3"
@@ -4804,14 +5189,14 @@
"@noble/hashes" "~1.3.2"
"@scure/base" "~1.1.2"
-"@scure/bip32@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8"
- integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==
+"@scure/bip32@1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67"
+ integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==
dependencies:
- "@noble/curves" "~1.3.0"
- "@noble/hashes" "~1.3.2"
- "@scure/base" "~1.1.4"
+ "@noble/curves" "~1.4.0"
+ "@noble/hashes" "~1.4.0"
+ "@scure/base" "~1.1.6"
"@scure/bip39@1.2.1":
version "1.2.1"
@@ -4821,13 +5206,13 @@
"@noble/hashes" "~1.3.0"
"@scure/base" "~1.1.0"
-"@scure/bip39@1.2.2":
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527"
- integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==
+"@scure/bip39@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3"
+ integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==
dependencies:
- "@noble/hashes" "~1.3.2"
- "@scure/base" "~1.1.4"
+ "@noble/hashes" "~1.4.0"
+ "@scure/base" "~1.1.6"
"@sentry-internal/feedback@7.116.0":
version "7.116.0"
@@ -4949,16 +5334,6 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.31.28.tgz#b68831e7bc7d09daac26968ea32f42bedc968ede"
integrity sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==
-"@sindresorhus/is@^4.0.0", "@sindresorhus/is@^4.6.0":
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
- integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
-
-"@sindresorhus/merge-streams@^2.1.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz#719df7fb41766bc143369eaa0dd56d8dc87c9958"
- integrity sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==
-
"@sinonjs/commons@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd"
@@ -4981,12 +5356,12 @@
buffer "~6.0.3"
"@solana/web3.js@^1.90.0":
- version "1.91.8"
- resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.8.tgz#0d5eb69626a92c391b53e15bfbb0bad3f6858e51"
- integrity sha512-USa6OS1jbh8zOapRJ/CBZImZ8Xb7AJjROZl5adql9TpOoBN9BUzyyouS5oPuZHft7S7eB8uJPuXWYjMi6BHgOw==
+ version "1.95.4"
+ resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.4.tgz#771603f60d75cf7556ad867e1fd2efae32f9ad09"
+ integrity sha512-sdewnNEA42ZSMxqkzdwEWi6fDgzwtJHaQa5ndUGEJYtoOnM6X5cvPmjoTUp7/k7bRrVAxfBgDnvQQHD6yhlLYw==
dependencies:
- "@babel/runtime" "^7.24.5"
- "@noble/curves" "^1.4.0"
+ "@babel/runtime" "^7.25.0"
+ "@noble/curves" "^1.4.2"
"@noble/hashes" "^1.4.0"
"@solana/buffer-layout" "^4.0.1"
agentkeepalive "^4.5.0"
@@ -4996,10 +5371,10 @@
bs58 "^4.0.1"
buffer "6.0.3"
fast-stable-stringify "^1.0.0"
- jayson "^4.1.0"
+ jayson "^4.1.1"
node-fetch "^2.7.0"
- rpc-websockets "^7.11.0"
- superstruct "^0.14.2"
+ rpc-websockets "^9.0.2"
+ superstruct "^2.0.2"
"@spindl-xyz/attribution-lite@^1.4.0":
version "1.6.2"
@@ -5131,7 +5506,7 @@
resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36"
integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==
-"@stablelib/x25519@1.0.3", "@stablelib/x25519@^1.0.3":
+"@stablelib/x25519@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@stablelib/x25519/-/x25519-1.0.3.tgz#13c8174f774ea9f3e5e42213cbf9fc68a3c7b7fd"
integrity sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==
@@ -5171,10 +5546,10 @@
lodash "^4.17.21"
ts-dedent "^2.0.0"
-"@storybook/addon-designs@^8.0.0":
- version "8.0.2"
- resolved "https://registry.yarnpkg.com/@storybook/addon-designs/-/addon-designs-8.0.2.tgz#311cb9a82068cf1475ebc2079c84348558dae54a"
- integrity sha512-/ZDVB1dCmb56IZymb0z5uuodjpAx19NxwI2URHFMFZiwv65lwom5AJx2QHdzqyQfXHryGq/eQrUSswNPYVYqwA==
+"@storybook/addon-designs@^8.0.3":
+ version "8.0.3"
+ resolved "https://registry.yarnpkg.com/@storybook/addon-designs/-/addon-designs-8.0.3.tgz#645ffcb4cd4c73f1b7d255951d4a7dbd46f9fe70"
+ integrity sha512-uArLGYDwiRDjgJHgMotOLGGYK4hq1hBb0PfTJrlBnPy6evky9khrqf4KmXrIh4ViOyZ5t01THe1DnBj52DwrEQ==
dependencies:
"@figspec/react" "^1.0.0"
@@ -5324,26 +5699,6 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
-"@storybook/builder-manager@8.1.3":
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-8.1.3.tgz#f48905fb967866734b24a9f1592e72bd446acbc4"
- integrity sha512-VIYgF6PreiteJMGlz716P27yyL/JF1dR7M2htVJij5IP2X6HUgyzFXScElKljX9fETq7vig+UZWksZ2M2Q9dYg==
- dependencies:
- "@fal-works/esbuild-plugin-global-externals" "^2.1.2"
- "@storybook/core-common" "8.1.3"
- "@storybook/manager" "8.1.3"
- "@storybook/node-logger" "8.1.3"
- "@types/ejs" "^3.1.1"
- "@yarnpkg/esbuild-plugin-pnp" "^3.0.0-rc.10"
- browser-assert "^1.2.1"
- ejs "^3.1.10"
- esbuild "^0.18.0 || ^0.19.0 || ^0.20.0"
- esbuild-plugin-alias "^0.2.1"
- express "^4.17.3"
- fs-extra "^11.1.0"
- process "^0.11.10"
- util "^0.12.4"
-
"@storybook/builder-webpack5@8.1.3":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.1.3.tgz#9e054809b5c495eba7e810348668b0df849d5084"
@@ -5395,76 +5750,13 @@
telejson "^7.2.0"
tiny-invariant "^1.3.1"
-"@storybook/cli@8.1.3":
+"@storybook/client-logger@8.1.3":
version "8.1.3"
- resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-8.1.3.tgz#5f2ee1afbfc25b1ba86e2a73fb3bd427048c7b3f"
- integrity sha512-eqzjy7YOIF0WkeUPT5Mv+WKibk3z+IfP0voTKIWzYKAqZ8sD36NQV/lE7bHy0JAPw+rfw1Fq0gMOiFVcx3ZaUQ==
- dependencies:
- "@babel/core" "^7.24.4"
- "@babel/types" "^7.24.0"
- "@ndelangen/get-tarball" "^3.0.7"
- "@storybook/codemod" "8.1.3"
- "@storybook/core-common" "8.1.3"
- "@storybook/core-events" "8.1.3"
- "@storybook/core-server" "8.1.3"
- "@storybook/csf-tools" "8.1.3"
- "@storybook/node-logger" "8.1.3"
- "@storybook/telemetry" "8.1.3"
- "@storybook/types" "8.1.3"
- "@types/semver" "^7.3.4"
- "@yarnpkg/fslib" "2.10.3"
- "@yarnpkg/libzip" "2.3.0"
- chalk "^4.1.0"
- commander "^6.2.1"
- cross-spawn "^7.0.3"
- detect-indent "^6.1.0"
- envinfo "^7.7.3"
- execa "^5.0.0"
- find-up "^5.0.0"
- fs-extra "^11.1.0"
- get-npm-tarball-url "^2.0.3"
- giget "^1.0.0"
- globby "^14.0.1"
- jscodeshift "^0.15.1"
- leven "^3.1.0"
- ora "^5.4.1"
- prettier "^3.1.1"
- prompts "^2.4.0"
- read-pkg-up "^7.0.1"
- semver "^7.3.7"
- strip-json-comments "^3.0.1"
- tempy "^1.0.1"
- tiny-invariant "^1.3.1"
- ts-dedent "^2.0.0"
-
-"@storybook/client-logger@8.1.3":
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.1.3.tgz#dcf6e7dc3d5757bcf2f7aa578a40aa9438c998f5"
- integrity sha512-dX1jZ+HhJ8hVhAKHQ8gs/FalHjIGo5j1Xk+2UqdsGjLoBlwHIHfHzkVbzrc/gCxxXL0juisk7BzbXaz7lME0KA==
+ resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.1.3.tgz#dcf6e7dc3d5757bcf2f7aa578a40aa9438c998f5"
+ integrity sha512-dX1jZ+HhJ8hVhAKHQ8gs/FalHjIGo5j1Xk+2UqdsGjLoBlwHIHfHzkVbzrc/gCxxXL0juisk7BzbXaz7lME0KA==
dependencies:
"@storybook/global" "^5.0.0"
-"@storybook/codemod@8.1.3":
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-8.1.3.tgz#1b59e5c4de57049e51688f52e9553a98775efc1f"
- integrity sha512-U21HQICKKm/xsfLKEODDphJJiBkzq5wFZzKN2DyMPd3vOfLpCWcaPsO9Pi5IX1cekyCz2o+phYt2r9aSRQUbOg==
- dependencies:
- "@babel/core" "^7.24.4"
- "@babel/preset-env" "^7.24.4"
- "@babel/types" "^7.24.0"
- "@storybook/csf" "^0.1.7"
- "@storybook/csf-tools" "8.1.3"
- "@storybook/node-logger" "8.1.3"
- "@storybook/types" "8.1.3"
- "@types/cross-spawn" "^6.0.2"
- cross-spawn "^7.0.3"
- globby "^14.0.1"
- jscodeshift "^0.15.1"
- lodash "^4.17.21"
- prettier "^3.1.1"
- recast "^0.23.5"
- tiny-invariant "^1.3.1"
-
"@storybook/components@8.1.3":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.1.3.tgz#47e753795fd6203c7ecebde93681945779ed3c42"
@@ -5524,58 +5816,6 @@
"@storybook/csf" "^0.1.7"
ts-dedent "^2.0.0"
-"@storybook/core-server@8.1.3":
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-8.1.3.tgz#5437219a2dd5c0479e30ea25153979f872732a1d"
- integrity sha512-bOHbLI5atDFBOsFc5M0V0ikURVw+Kx/jRXGO5dnc6kr5SwW+ZfWooy1hiFKHRnI8hmVpGXcS6YqTHkUbcrAWgA==
- dependencies:
- "@aw-web-design/x-default-browser" "1.4.126"
- "@babel/core" "^7.24.4"
- "@babel/parser" "^7.24.4"
- "@discoveryjs/json-ext" "^0.5.3"
- "@storybook/builder-manager" "8.1.3"
- "@storybook/channels" "8.1.3"
- "@storybook/core-common" "8.1.3"
- "@storybook/core-events" "8.1.3"
- "@storybook/csf" "^0.1.7"
- "@storybook/csf-tools" "8.1.3"
- "@storybook/docs-mdx" "3.1.0-next.0"
- "@storybook/global" "^5.0.0"
- "@storybook/manager" "8.1.3"
- "@storybook/manager-api" "8.1.3"
- "@storybook/node-logger" "8.1.3"
- "@storybook/preview-api" "8.1.3"
- "@storybook/telemetry" "8.1.3"
- "@storybook/types" "8.1.3"
- "@types/detect-port" "^1.3.0"
- "@types/diff" "^5.0.9"
- "@types/node" "^18.0.0"
- "@types/pretty-hrtime" "^1.0.0"
- "@types/semver" "^7.3.4"
- better-opn "^3.0.2"
- chalk "^4.1.0"
- cli-table3 "^0.6.1"
- compression "^1.7.4"
- detect-port "^1.3.0"
- diff "^5.2.0"
- express "^4.17.3"
- fs-extra "^11.1.0"
- globby "^14.0.1"
- ip "^2.0.1"
- lodash "^4.17.21"
- open "^8.4.0"
- pretty-hrtime "^1.0.3"
- prompts "^2.4.0"
- read-pkg-up "^7.0.1"
- semver "^7.3.7"
- telejson "^7.2.0"
- tiny-invariant "^1.3.1"
- ts-dedent "^2.0.0"
- util "^0.12.4"
- util-deprecate "^1.0.2"
- watchpack "^2.2.0"
- ws "^8.2.3"
-
"@storybook/core-webpack@8.1.3":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.1.3.tgz#a2443019582410d7f17d6e844a42d3a69341aa69"
@@ -5587,6 +5827,25 @@
"@types/node" "^18.0.0"
ts-dedent "^2.0.0"
+"@storybook/core@8.3.4":
+ version "8.3.4"
+ resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.3.4.tgz#35c1a2fbb8b31c7015647309c50a08bd20961522"
+ integrity sha512-4PZB91JJpuKfcjeOR2LXj3ABaPLLSd2P/SfYOKNCygrDstsQa/yay3/yN5Z9yi1cIG84KRr6/sUW+0x8HsGLPg==
+ dependencies:
+ "@storybook/csf" "^0.1.11"
+ "@types/express" "^4.17.21"
+ better-opn "^3.0.2"
+ browser-assert "^1.2.1"
+ esbuild "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0"
+ esbuild-register "^3.5.0"
+ express "^4.19.2"
+ jsdoc-type-pratt-parser "^4.0.0"
+ process "^0.11.10"
+ recast "^0.23.5"
+ semver "^7.6.2"
+ util "^0.12.5"
+ ws "^8.2.3"
+
"@storybook/csf-plugin@8.1.3":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.1.3.tgz#8820364f1cd6bd1a73caec7e41681ac51ff2563e"
@@ -5617,18 +5876,13 @@
dependencies:
lodash "^4.17.15"
-"@storybook/csf@^0.1.7":
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.7.tgz#dcc6c16a353bc09c8c619ba1a23ba93b2aab0b9d"
- integrity sha512-53JeLZBibjQxi0Ep+/AJTfxlofJlxy1jXcSKENlnKxHjWEYyHQCumMP5yTFjf7vhNnMjEpV3zx6t23ssFiGRyw==
+"@storybook/csf@^0.1.11", "@storybook/csf@^0.1.7":
+ version "0.1.11"
+ resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.11.tgz#ad685a4fe564a47a6b73571c2e7c07b526f4f71b"
+ integrity sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==
dependencies:
type-fest "^2.19.0"
-"@storybook/docs-mdx@3.1.0-next.0":
- version "3.1.0-next.0"
- resolved "https://registry.yarnpkg.com/@storybook/docs-mdx/-/docs-mdx-3.1.0-next.0.tgz#9567c6eb621110dcf6554923a975238953d06305"
- integrity sha512-t4syFIeSyufieNovZbLruPt2DmRKpbwL4fERCZ1MifWDRIORCKLc4NCEHy+IqvIqd71/SJV2k4B51nF7vlJfmQ==
-
"@storybook/docs-tools@8.1.3":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-8.1.3.tgz#a93045b1547d86575f52a81da7f391e1fa653ee4"
@@ -5687,11 +5941,6 @@
telejson "^7.2.0"
ts-dedent "^2.0.0"
-"@storybook/manager@8.1.3":
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-8.1.3.tgz#4ee8ce6ba9dd8175d7a0f116f8b45da0015b4dff"
- integrity sha512-hmfQJJNLSqlM+jfcCXo5wnhUIugTsCxv6a+2UnRAt2AnF6J746QaV0npMThw1QG/7fi/ofaRY8hPGxgCN9uHRA==
-
"@storybook/nextjs@^8.0.6":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/nextjs/-/nextjs-8.1.3.tgz#4d0b473519d9100d3525439d13f1ef37c7382998"
@@ -5849,21 +6098,7 @@
memoizerific "^1.11.3"
qs "^6.10.0"
-"@storybook/telemetry@8.1.3":
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-8.1.3.tgz#c0dac80ba496f7356e77b57beb0698d2fbc34960"
- integrity sha512-edFj0AJ3DEF8Z6Ym6ue7N8U9HZ2khAfXIcpk6RDgL/8FrpAZKC96XSEBMSnem3BLHxMi2bddQH1UTU6rKXrfBA==
- dependencies:
- "@storybook/client-logger" "8.1.3"
- "@storybook/core-common" "8.1.3"
- "@storybook/csf-tools" "8.1.3"
- chalk "^4.1.0"
- detect-package-manager "^2.0.1"
- fetch-retry "^5.0.2"
- fs-extra "^11.1.0"
- read-pkg-up "^7.0.1"
-
-"@storybook/test@8.1.3", "@storybook/test@^8.0.6":
+"@storybook/test@8.1.3":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.1.3.tgz#17cbe89c6d8fc787121b9068444c213ebcb27d4e"
integrity sha512-9fjigeDBUk1X7el6haYk1Lniak0Se7Ol5f7QSw/64tIbXHp6ucj06FWEK+SfWx1J9GgCdDiFGW5UMmEZOiRCXw==
@@ -5879,6 +6114,23 @@
"@vitest/spy" "^1.3.1"
util "^0.12.4"
+"@storybook/test@^8.0.6":
+ version "8.0.6"
+ resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.0.6.tgz#6c2d38d9189ec6a0640c3267ad68fc64564ef52d"
+ integrity sha512-MctGhJSnD6es5xj8lMDjB4gzXk6Uoaw756CAnQamPoETr+3dkJzf4LOeUwyV3LgT7D3pQ72Po5kTdCKfrPHsDQ==
+ dependencies:
+ "@storybook/client-logger" "8.0.6"
+ "@storybook/core-events" "8.0.6"
+ "@storybook/instrumenter" "8.0.6"
+ "@storybook/preview-api" "8.0.6"
+ "@testing-library/dom" "^9.3.4"
+ "@testing-library/jest-dom" "^6.4.2"
+ "@testing-library/user-event" "^14.5.2"
+ "@vitest/expect" "1.3.1"
+ "@vitest/spy" "^1.3.1"
+ chai "^4.4.1"
+ util "^0.12.4"
+
"@storybook/theming@8.1.3":
version "8.1.3"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.1.3.tgz#b64167d75f62e14b45cd9a5abb284cfa7f6214a1"
@@ -6014,26 +6266,25 @@
"@svgr/plugin-jsx" "^6.5.1"
"@svgr/plugin-svgo" "^6.5.1"
-"@swc/helpers@0.5.2":
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
- integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
- dependencies:
- tslib "^2.4.0"
+"@swc/counter@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
+ integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
-"@szmarczak/http-timer@^4.0.5":
- version "4.0.6"
- resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"
- integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==
+"@swc/helpers@0.5.5":
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.5.tgz#12689df71bfc9b21c4f4ca00ae55f2f16c8b77c0"
+ integrity sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==
dependencies:
- defer-to-connect "^2.0.0"
+ "@swc/counter" "^0.1.3"
+ tslib "^2.4.0"
-"@szmarczak/http-timer@^5.0.1":
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a"
- integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==
+"@swc/helpers@^0.5.11":
+ version "0.5.12"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.12.tgz#37aaca95284019eb5d2207101249435659709f4b"
+ integrity sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==
dependencies:
- defer-to-connect "^2.0.1"
+ tslib "^2.4.0"
"@testing-library/cypress@^8.0.7":
version "8.0.7"
@@ -6316,6 +6567,13 @@
lodash "^4.17.15"
ts-essentials "^7.0.1"
+"@types/acorn@^4.0.0":
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.6.tgz#d61ca5480300ac41a7d973dd5b84d0a591154a22"
+ integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==
+ dependencies:
+ "@types/estree" "*"
+
"@types/aria-query@^5.0.1":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
@@ -6368,7 +6626,7 @@
dependencies:
"@types/node" "*"
-"@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1":
+"@types/bn.js@^5.1.0":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.2.tgz#162f5238c46f4bcbac07a98561724eca1fcf0c5e"
integrity sha512-dkpZu0szUtn9UXTmw+e0AJFd4D2XAxDnsCLdc05SfqpqzPEBft8eQr8uaFitfo/dUUOZERaLec2hHMG87A4Dxg==
@@ -6383,16 +6641,6 @@
"@types/connect" "*"
"@types/node" "*"
-"@types/cacheable-request@^6.0.1", "@types/cacheable-request@^6.0.2":
- version "6.0.3"
- resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183"
- integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==
- dependencies:
- "@types/http-cache-semantics" "*"
- "@types/keyv" "^3.1.4"
- "@types/node" "*"
- "@types/responselike" "^1.0.0"
-
"@types/connect@*", "@types/connect@^3.4.33":
version "3.4.38"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
@@ -6400,30 +6648,13 @@
dependencies:
"@types/node" "*"
-"@types/cross-spawn@^6.0.2":
- version "6.0.6"
- resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.6.tgz#0163d0b79a6f85409e0decb8dcca17147f81fd22"
- integrity sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==
- dependencies:
- "@types/node" "*"
-
-"@types/debug@^4.1.7":
+"@types/debug@^4.0.0", "@types/debug@^4.1.7":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==
dependencies:
"@types/ms" "*"
-"@types/detect-port@^1.3.0":
- version "1.3.5"
- resolved "https://registry.yarnpkg.com/@types/detect-port/-/detect-port-1.3.5.tgz#deecde143245989dee0e82115f3caba5ee0ea747"
- integrity sha512-Rf3/lB9WkDfIL9eEKaSYKc+1L/rNVYBjThk22JTqQw0YozXarX8YljFAz+HCoC6h4B4KwCMsBPZHaFezwT4BNA==
-
-"@types/diff@^5.0.9":
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.2.1.tgz#cceae9c4b2dae5c6b8ab1ce1263601c255d87fb3"
- integrity sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==
-
"@types/doctrine@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.3.tgz#e892d293c92c9c1d3f9af72c15a554fbc7e0895a"
@@ -6434,11 +6665,6 @@
resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.9.tgz#d86a5f452a15e3e3113b99e39616a9baa0f9863f"
integrity sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==
-"@types/ejs@^3.1.1":
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.5.tgz#49d738257cc73bafe45c13cb8ff240683b4d5117"
- integrity sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==
-
"@types/emscripten@^1.39.6":
version "1.39.12"
resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.12.tgz#e43b4fdd4b389861897d6cbb9665532f3afd5abd"
@@ -6449,21 +6675,12 @@
resolved "https://registry.yarnpkg.com/@types/escodegen/-/escodegen-0.0.6.tgz#5230a9ce796e042cda6f086dbf19f22ea330659c"
integrity sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==
-"@types/eslint-scope@^3.7.3":
- version "3.7.7"
- resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5"
- integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==
- dependencies:
- "@types/eslint" "*"
- "@types/estree" "*"
-
-"@types/eslint@*":
- version "8.56.10"
- resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
- integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==
+"@types/estree-jsx@^1.0.0":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18"
+ integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==
dependencies:
"@types/estree" "*"
- "@types/json-schema" "*"
"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.5":
version "1.0.5"
@@ -6490,7 +6707,7 @@
"@types/range-parser" "*"
"@types/send" "*"
-"@types/express@^4.7.0":
+"@types/express@^4.17.21", "@types/express@^4.7.0":
version "4.17.21"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
@@ -6522,24 +6739,11 @@
dependencies:
"@types/unist" "*"
-"@types/hoist-non-react-statics@^3.3.1":
- version "3.3.5"
- resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494"
- integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==
- dependencies:
- "@types/react" "*"
- hoist-non-react-statics "^3.3.0"
-
"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
-"@types/http-cache-semantics@*":
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
- integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==
-
"@types/http-errors@*":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
@@ -6572,7 +6776,7 @@
expect "^29.0.0"
pretty-format "^29.0.0"
-"@types/js-cookie@^3.0.2":
+"@types/js-cookie@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95"
integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==
@@ -6586,7 +6790,7 @@
"@types/tough-cookie" "*"
parse5 "^7.0.0"
-"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -6596,13 +6800,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
-"@types/keyv@^3.1.4":
- version "3.1.4"
- resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
- integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==
- dependencies:
- "@types/node" "*"
-
"@types/lodash@^4.14.136", "@types/lodash@^4.14.167", "@types/lodash@^4.14.182":
version "4.17.4"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7"
@@ -6613,7 +6810,19 @@
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
+"@types/mdast@^4.0.0":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
+ integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
+ dependencies:
+ "@types/unist" "*"
+
"@types/mdx@^2.0.0":
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.12.tgz#38db34cc8999b982beaec01399620bee6c65ef2e"
+ integrity sha512-H9VZ9YqE+H28FQVchC83RCs5xQ2J7mAAv6qdDEaWmXEVl3OpdH+xfrSUzQ1lp7U7oSTRZ0RvW08ASPJsYBi7Cw==
+
+"@types/mdx@^2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd"
integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==
@@ -6655,7 +6864,7 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469"
integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==
-"@types/node@^12.12.54", "@types/node@^12.12.6":
+"@types/node@^12.12.54":
version "12.20.55"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
@@ -6715,11 +6924,6 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f"
integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
-"@types/pretty-hrtime@^1.0.0":
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#ee1bd8c9f7a01b3445786aad0ef23aba5f511a44"
- integrity sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==
-
"@types/prop-types@*", "@types/prop-types@^15.7.11":
version "15.7.12"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
@@ -6742,7 +6946,7 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
-"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.24":
+"@types/react-dom@^18.0.0", "@types/react-dom@^18.3.0":
version "18.3.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0"
integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==
@@ -6761,10 +6965,10 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.75":
- version "18.3.2"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.2.tgz#462ae4904973bc212fa910424d901e3d137dbfcd"
- integrity sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==
+"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.3.10":
+ version "18.3.10"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.10.tgz#6edc26dc22ff8c9c226d3c7bf8357b013c842219"
+ integrity sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
@@ -6781,13 +6985,6 @@
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.6.tgz#e6e60dad29c2c8c206c026e6dd8d6d1bdda850b8"
integrity sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==
-"@types/responselike@^1.0.0":
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50"
- integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==
- dependencies:
- "@types/node" "*"
-
"@types/secp256k1@^4.0.1":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf"
@@ -6854,11 +7051,21 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20"
integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==
+"@types/unist@^2.0.0":
+ version "2.0.11"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4"
+ integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
+
"@types/use-sync-external-store@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
+"@types/uuid@^8.3.4":
+ version "8.3.4"
+ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
+ integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
+
"@types/uuid@^9.0.1":
version "9.0.8"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
@@ -6881,6 +7088,13 @@
dependencies:
"@types/node" "*"
+"@types/ws@^8.2.2":
+ version "8.5.12"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
+ integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==
+ dependencies:
+ "@types/node" "*"
+
"@types/yargs-parser@*":
version "21.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@@ -7154,10 +7368,10 @@
lodash.isequal "4.5.0"
uint8arrays "3.1.0"
-"@walletconnect/core@2.13.1":
- version "2.13.1"
- resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.13.1.tgz#a59646e39a5beaa3f3551d129af43cd404cf4faf"
- integrity sha512-h0MSYKJu9i1VEs5koCTT7c5YeQ1Kj0ncTFiMqANbDnB1r3mBulXn+FKtZ2fCmf1j7KDpgluuUzpSs+sQfPcv4Q==
+"@walletconnect/core@2.16.1":
+ version "2.16.1"
+ resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.16.1.tgz#019b181387792e0d284e75074b961b48193d9b6a"
+ integrity sha512-UlsnEMT5wwFvmxEjX8s4oju7R3zadxNbZgsFeHEsjh7uknY2zgmUe1Lfc5XU6zyPb1Jx7Nqpdx1KN485ee8ogw==
dependencies:
"@walletconnect/heartbeat" "1.2.2"
"@walletconnect/jsonrpc-provider" "1.0.14"
@@ -7166,39 +7380,37 @@
"@walletconnect/jsonrpc-ws-connection" "1.0.14"
"@walletconnect/keyvaluestorage" "1.1.1"
"@walletconnect/logger" "2.1.2"
- "@walletconnect/relay-api" "1.0.10"
+ "@walletconnect/relay-api" "1.0.11"
"@walletconnect/relay-auth" "1.0.4"
"@walletconnect/safe-json" "1.0.2"
"@walletconnect/time" "1.0.2"
- "@walletconnect/types" "2.13.1"
- "@walletconnect/utils" "2.13.1"
+ "@walletconnect/types" "2.16.1"
+ "@walletconnect/utils" "2.16.1"
events "3.3.0"
- isomorphic-unfetch "3.1.0"
lodash.isequal "4.5.0"
uint8arrays "3.1.0"
"@walletconnect/core@^2.10.1":
- version "2.11.3"
- resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.11.3.tgz#c81855722cb9afd411f91f5345c7874f48bade0b"
- integrity sha512-/9m4EqiggFUwkQDv5PDWbcTI+yCVnBd/iYW5iIHEkivg2/mnBr2bQz2r/vtPjp19r/ZK62Dx0+UN3U+BWP8ulQ==
+ version "2.16.0"
+ resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.16.0.tgz#751eb0b9bcd24a0035c87a1be63c830b86dad3de"
+ integrity sha512-B/PPcg0MRb9pqB6nupVqEFGI5cFK5HNXYoi+y6Qaq53JIjHa6KANskNkj567iegETaPUZIniHENry7M8mHN3Bw==
dependencies:
- "@walletconnect/heartbeat" "1.2.1"
- "@walletconnect/jsonrpc-provider" "1.0.13"
- "@walletconnect/jsonrpc-types" "1.0.3"
+ "@walletconnect/heartbeat" "1.2.2"
+ "@walletconnect/jsonrpc-provider" "1.0.14"
+ "@walletconnect/jsonrpc-types" "1.0.4"
"@walletconnect/jsonrpc-utils" "1.0.8"
"@walletconnect/jsonrpc-ws-connection" "1.0.14"
- "@walletconnect/keyvaluestorage" "^1.1.1"
- "@walletconnect/logger" "^2.0.1"
- "@walletconnect/relay-api" "^1.0.9"
- "@walletconnect/relay-auth" "^1.0.4"
- "@walletconnect/safe-json" "^1.0.2"
- "@walletconnect/time" "^1.0.2"
- "@walletconnect/types" "2.11.3"
- "@walletconnect/utils" "2.11.3"
- events "^3.3.0"
- isomorphic-unfetch "3.1.0"
+ "@walletconnect/keyvaluestorage" "1.1.1"
+ "@walletconnect/logger" "2.1.2"
+ "@walletconnect/relay-api" "1.0.11"
+ "@walletconnect/relay-auth" "1.0.4"
+ "@walletconnect/safe-json" "1.0.2"
+ "@walletconnect/time" "1.0.2"
+ "@walletconnect/types" "2.16.0"
+ "@walletconnect/utils" "2.16.0"
+ events "3.3.0"
lodash.isequal "4.5.0"
- uint8arrays "^3.1.0"
+ uint8arrays "3.1.0"
"@walletconnect/environment@^1.0.1":
version "1.0.1"
@@ -7231,15 +7443,6 @@
keyvaluestorage-interface "^1.0.0"
tslib "1.14.1"
-"@walletconnect/heartbeat@1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@walletconnect/heartbeat/-/heartbeat-1.2.1.tgz#afaa3a53232ae182d7c9cff41c1084472d8f32e9"
- integrity sha512-yVzws616xsDLJxuG/28FqtZ5rzrTA4gUjdEMTbWB5Y8V1XHRmqq4efAxCw5ie7WjbXFSUyBHaWlMR+2/CpQC5Q==
- dependencies:
- "@walletconnect/events" "^1.0.1"
- "@walletconnect/time" "^1.0.2"
- tslib "1.14.1"
-
"@walletconnect/heartbeat@1.2.2", "@walletconnect/heartbeat@^1.2.1":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@walletconnect/heartbeat/-/heartbeat-1.2.2.tgz#e8dc5179db7769950c6f9cf59b23516d9b95227d"
@@ -7268,14 +7471,6 @@
"@walletconnect/safe-json" "^1.0.2"
events "^3.3.0"
-"@walletconnect/jsonrpc-types@1.0.3", "@walletconnect/jsonrpc-types@^1.0.2", "@walletconnect/jsonrpc-types@^1.0.3":
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.3.tgz#65e3b77046f1a7fa8347ae02bc1b841abe6f290c"
- integrity sha512-iIQ8hboBl3o5ufmJ8cuduGad0CQm3ZlsHtujv9Eu16xq89q+BG7Nh5VLxxUgmtpnrePgFkTwXirCTkwJH1v+Yw==
- dependencies:
- keyvaluestorage-interface "^1.0.0"
- tslib "1.14.1"
-
"@walletconnect/jsonrpc-types@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz#ce1a667d79eadf2a2d9d002c152ceb68739c230c"
@@ -7284,6 +7479,14 @@
events "^3.3.0"
keyvaluestorage-interface "^1.0.0"
+"@walletconnect/jsonrpc-types@^1.0.2", "@walletconnect/jsonrpc-types@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.3.tgz#65e3b77046f1a7fa8347ae02bc1b841abe6f290c"
+ integrity sha512-iIQ8hboBl3o5ufmJ8cuduGad0CQm3ZlsHtujv9Eu16xq89q+BG7Nh5VLxxUgmtpnrePgFkTwXirCTkwJH1v+Yw==
+ dependencies:
+ keyvaluestorage-interface "^1.0.0"
+ tslib "1.14.1"
+
"@walletconnect/jsonrpc-utils@1.0.8", "@walletconnect/jsonrpc-utils@^1.0.6", "@walletconnect/jsonrpc-utils@^1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz#82d0cc6a5d6ff0ecc277cb35f71402c91ad48d72"
@@ -7303,7 +7506,7 @@
events "^3.3.0"
ws "^7.5.1"
-"@walletconnect/keyvaluestorage@1.1.1", "@walletconnect/keyvaluestorage@^1.1.1":
+"@walletconnect/keyvaluestorage@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz#dd2caddabfbaf80f6b8993a0704d8b83115a1842"
integrity sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==
@@ -7312,7 +7515,7 @@
idb-keyval "^6.2.1"
unstorage "^1.9.0"
-"@walletconnect/logger@2.1.2":
+"@walletconnect/logger@2.1.2", "@walletconnect/logger@^2.0.1":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@walletconnect/logger/-/logger-2.1.2.tgz#813c9af61b96323a99f16c10089bfeb525e2a272"
integrity sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw==
@@ -7320,14 +7523,6 @@
"@walletconnect/safe-json" "^1.0.2"
pino "7.11.0"
-"@walletconnect/logger@^2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@walletconnect/logger/-/logger-2.0.1.tgz#7f489b96e9a1ff6bf3e58f0fbd6d69718bf844a8"
- integrity sha512-SsTKdsgWm+oDTBeNE/zHxxr5eJfZmE9/5yp/Ku+zJtcTAjELb3DXueWkDXmE9h8uHIbJzIb5wj5lPdzyrjT6hQ==
- dependencies:
- pino "7.11.0"
- tslib "1.14.1"
-
"@walletconnect/modal-core@2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@walletconnect/modal-core/-/modal-core-2.6.2.tgz#d73e45d96668764e0c8668ea07a45bb8b81119e9"
@@ -7360,13 +7555,12 @@
dependencies:
"@walletconnect/jsonrpc-types" "^1.0.2"
-"@walletconnect/relay-api@^1.0.9":
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/@walletconnect/relay-api/-/relay-api-1.0.9.tgz#f8c2c3993dddaa9f33ed42197fc9bfebd790ecaf"
- integrity sha512-Q3+rylJOqRkO1D9Su0DPE3mmznbAalYapJ9qmzDgK28mYF9alcP3UwG/og5V7l7CFOqzCLi7B8BvcBUrpDj0Rg==
+"@walletconnect/relay-api@1.0.11":
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/@walletconnect/relay-api/-/relay-api-1.0.11.tgz#80ab7ef2e83c6c173be1a59756f95e515fb63224"
+ integrity sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==
dependencies:
"@walletconnect/jsonrpc-types" "^1.0.2"
- tslib "1.14.1"
"@walletconnect/relay-auth@1.0.4":
version "1.0.4"
@@ -7402,19 +7596,19 @@
"@walletconnect/utils" "2.13.0"
events "3.3.0"
-"@walletconnect/sign-client@2.13.1":
- version "2.13.1"
- resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.13.1.tgz#7bdc9226218fd33caf3aef69dff0b4140abc7fa8"
- integrity sha512-e+dcqcLsedB4ZjnePFM5Cy8oxu0dyz5iZfhfKH/MOrQV/hyhZ+hJwh4MmkO2QyEu2PERKs9o2Uc6x8RZdi0UAQ==
+"@walletconnect/sign-client@2.16.1":
+ version "2.16.1"
+ resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.16.1.tgz#94a2f630ba741bd180f540c53576c5ceaace4857"
+ integrity sha512-s2Tx2n2duxt+sHtuWXrN9yZVaHaYqcEcjwlTD+55/vs5NUPlISf+fFmZLwSeX1kUlrSBrAuxPUcqQuRTKcjLOA==
dependencies:
- "@walletconnect/core" "2.13.1"
+ "@walletconnect/core" "2.16.1"
"@walletconnect/events" "1.0.1"
"@walletconnect/heartbeat" "1.2.2"
"@walletconnect/jsonrpc-utils" "1.0.8"
"@walletconnect/logger" "2.1.2"
"@walletconnect/time" "1.0.2"
- "@walletconnect/types" "2.13.1"
- "@walletconnect/utils" "2.13.1"
+ "@walletconnect/types" "2.16.1"
+ "@walletconnect/utils" "2.16.1"
events "3.3.0"
"@walletconnect/time@1.0.2", "@walletconnect/time@^1.0.2":
@@ -7424,18 +7618,6 @@
dependencies:
tslib "1.14.1"
-"@walletconnect/types@2.11.3":
- version "2.11.3"
- resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.11.3.tgz#8ce43cb77e8fd9d5269847cdd73bcfa7cce7dd1a"
- integrity sha512-JY4wA9MVosDW9dcJMTpnwliste0aJGJ1X6Q4ulLsQsgWRSEBRkLila0oUT01TDBW9Yq8uUp7uFOUTaKx6KWVAg==
- dependencies:
- "@walletconnect/events" "^1.0.1"
- "@walletconnect/heartbeat" "1.2.1"
- "@walletconnect/jsonrpc-types" "1.0.3"
- "@walletconnect/keyvaluestorage" "^1.1.1"
- "@walletconnect/logger" "^2.0.1"
- events "^3.3.0"
-
"@walletconnect/types@2.13.0":
version "2.13.0"
resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.13.0.tgz#cdac083651f5897084fe9ed62779f11810335ac6"
@@ -7448,10 +7630,22 @@
"@walletconnect/logger" "2.1.2"
events "3.3.0"
-"@walletconnect/types@2.13.1", "@walletconnect/types@^2.13.1":
- version "2.13.1"
- resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.13.1.tgz#393e3bd4d60a755f3a70cbe769b58cf153450310"
- integrity sha512-CIrdt66d38xdunGCy5peOOP17EQkCEGKweXc3+Gn/RWeSiRU35I7wjC/Bp4iWcgAQ6iBTZv4jGGST5XyrOp+Pg==
+"@walletconnect/types@2.16.0":
+ version "2.16.0"
+ resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.16.0.tgz#1719bbde7bd6316e8167ab47e6cbb441c4f168dc"
+ integrity sha512-n+vpli0eonAzG9wtOgmjYbJEMGz2gvOttU2VnzNkeVmvDz7otVuPgtk+7BNyRofZ6rBW6rriW5cCKtmbzvUSzA==
+ dependencies:
+ "@walletconnect/events" "1.0.1"
+ "@walletconnect/heartbeat" "1.2.2"
+ "@walletconnect/jsonrpc-types" "1.0.4"
+ "@walletconnect/keyvaluestorage" "1.1.1"
+ "@walletconnect/logger" "2.1.2"
+ events "3.3.0"
+
+"@walletconnect/types@2.16.1", "@walletconnect/types@^2.16.1":
+ version "2.16.1"
+ resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.16.1.tgz#6583d458d3f7b1919d482ba516ccb7878ec8c91f"
+ integrity sha512-9P4RG4VoDEF+yBF/n2TF12gsvT/aTaeZTVDb/AOayafqiPnmrQZMKmNCJJjq1sfdsDcHXFcZWMGsuCeSJCmrXA==
dependencies:
"@walletconnect/events" "1.0.1"
"@walletconnect/heartbeat" "1.2.2"
@@ -7495,59 +7689,63 @@
query-string "7.1.3"
uint8arrays "3.1.0"
-"@walletconnect/utils@2.13.1", "@walletconnect/utils@^2.13.1":
- version "2.13.1"
- resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.13.1.tgz#f44e81028754c6e056dba588ad9b9fa5ad047645"
- integrity sha512-EcooXXlqy5hk9hy/nK2wBF/qxe7HjH0K8ZHzjKkXRkwAE5pCvy0IGXIXWmUR9sw8LFJEqZyd8rZdWLKNUe8hqA==
+"@walletconnect/utils@2.16.0", "@walletconnect/utils@^2.10.1":
+ version "2.16.0"
+ resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.16.0.tgz#0696655035dce9efcc209e5c789b7e22b085a70d"
+ integrity sha512-ASh9yyqzP4GLIU400rKEvBefcM5GFJbi2Iw7p0SFDyFC5WZYYyllU3WopU+YV/7bbukCMkthEIdioWYlpmjERQ==
dependencies:
"@stablelib/chacha20poly1305" "1.0.1"
"@stablelib/hkdf" "1.0.1"
"@stablelib/random" "1.0.2"
"@stablelib/sha256" "1.0.1"
"@stablelib/x25519" "1.0.3"
- "@walletconnect/relay-api" "1.0.10"
+ "@walletconnect/relay-api" "1.0.11"
+ "@walletconnect/relay-auth" "1.0.4"
"@walletconnect/safe-json" "1.0.2"
"@walletconnect/time" "1.0.2"
- "@walletconnect/types" "2.13.1"
+ "@walletconnect/types" "2.16.0"
"@walletconnect/window-getters" "1.0.1"
"@walletconnect/window-metadata" "1.0.1"
detect-browser "5.3.0"
+ elliptic "^6.5.7"
query-string "7.1.3"
uint8arrays "3.1.0"
-"@walletconnect/utils@^2.10.1":
- version "2.11.3"
- resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.11.3.tgz#3731809b54902655cf202e0bf0e8f268780e8b54"
- integrity sha512-jsdNkrl/IcTkzWFn0S2d0urzBXg6RxVJtUYRsUx3qI3wzOGiABP9ui3yiZ3SgZOv9aRe62PaNp1qpbYZ+zPb8Q==
+"@walletconnect/utils@2.16.1", "@walletconnect/utils@^2.16.1":
+ version "2.16.1"
+ resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.16.1.tgz#2099cc2bd16b0edc32022f64aa2c2c323b45d1d4"
+ integrity sha512-aoQirVoDoiiEtYeYDtNtQxFzwO/oCrz9zqeEEXYJaAwXlGVTS34KFe7W3/Rxd/pldTYKFOZsku2EzpISfH8Wsw==
dependencies:
"@stablelib/chacha20poly1305" "1.0.1"
"@stablelib/hkdf" "1.0.1"
- "@stablelib/random" "^1.0.2"
+ "@stablelib/random" "1.0.2"
"@stablelib/sha256" "1.0.1"
- "@stablelib/x25519" "^1.0.3"
- "@walletconnect/relay-api" "^1.0.9"
- "@walletconnect/safe-json" "^1.0.2"
- "@walletconnect/time" "^1.0.2"
- "@walletconnect/types" "2.11.3"
- "@walletconnect/window-getters" "^1.0.1"
- "@walletconnect/window-metadata" "^1.0.1"
+ "@stablelib/x25519" "1.0.3"
+ "@walletconnect/relay-api" "1.0.11"
+ "@walletconnect/relay-auth" "1.0.4"
+ "@walletconnect/safe-json" "1.0.2"
+ "@walletconnect/time" "1.0.2"
+ "@walletconnect/types" "2.16.1"
+ "@walletconnect/window-getters" "1.0.1"
+ "@walletconnect/window-metadata" "1.0.1"
detect-browser "5.3.0"
+ elliptic "^6.5.7"
query-string "7.1.3"
- uint8arrays "^3.1.0"
+ uint8arrays "3.1.0"
-"@walletconnect/web3wallet@^1.12.1":
- version "1.12.1"
- resolved "https://registry.yarnpkg.com/@walletconnect/web3wallet/-/web3wallet-1.12.1.tgz#efe7863c6518b2262bca1ea01650222986963cc4"
- integrity sha512-34h7UkWjZvZdtCc/t6tZCSBPjDzJbfG1+OPkJ6FiD1KJP+a0wSwuI7l4LliGgvAdsXfrM+sn3ZEWVWy62zeRDA==
+"@walletconnect/web3wallet@^1.15.1":
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/@walletconnect/web3wallet/-/web3wallet-1.15.1.tgz#47d041c07e2b12824ade85e53ed50c89536ef37b"
+ integrity sha512-EgtdZUgtf0diU98x8Q8tiZslE0Z5comnxv3SqmAIgkdhpXDxaM/goo7BC1yC+Wey/IHOOVYg2SW+La2Txk+6hQ==
dependencies:
"@walletconnect/auth-client" "2.1.2"
- "@walletconnect/core" "2.13.1"
+ "@walletconnect/core" "2.16.1"
"@walletconnect/jsonrpc-provider" "1.0.14"
"@walletconnect/jsonrpc-utils" "1.0.8"
"@walletconnect/logger" "2.1.2"
- "@walletconnect/sign-client" "2.13.1"
- "@walletconnect/types" "2.13.1"
- "@walletconnect/utils" "2.13.1"
+ "@walletconnect/sign-client" "2.16.1"
+ "@walletconnect/types" "2.16.1"
+ "@walletconnect/utils" "2.16.1"
"@walletconnect/window-getters@1.0.1", "@walletconnect/window-getters@^1.0.1":
version "1.0.1"
@@ -7556,7 +7754,7 @@
dependencies:
tslib "1.14.1"
-"@walletconnect/window-metadata@1.0.1", "@walletconnect/window-metadata@^1.0.1":
+"@walletconnect/window-metadata@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz#2124f75447b7e989e4e4e1581d55d25bc75f7be5"
integrity sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==
@@ -7572,14 +7770,13 @@
"@coinbase/wallet-sdk" "^3.9.3"
"@web3-onboard/common" "^2.3.3"
-"@web3-onboard/common@^2.2.3", "@web3-onboard/common@^2.3.3", "@web3-onboard/common@^2.3.4":
- version "2.3.4"
- resolved "https://registry.yarnpkg.com/@web3-onboard/common/-/common-2.3.4.tgz#1ce41e090c19e8ac802c45737de3dda3dabea751"
- integrity sha512-LQM7ZA1LoJ4GirsVEWHkv9KNJcrIT+AplR957BP5O8tll+p/A1GCju89C7jAMnb3+9LShUCwHcbE4o8l8gVb9A==
+"@web3-onboard/common@^2.2.3", "@web3-onboard/common@^2.3.3", "@web3-onboard/common@^2.3.4", "@web3-onboard/common@^2.4.1":
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/@web3-onboard/common/-/common-2.4.2.tgz#ffa7b1a7cb6410d9cc4d7b38d50b97a857b334cb"
+ integrity sha512-3+zkBru5W2jBYFBPPQsnqZ7tuN1GUyM5PzD9/MmhvjCLNhmjFtMQ0MkLzG4Yshodb4UW/DmZpjUVrpjdhEhj/Q==
dependencies:
- bignumber.js "^9.1.0"
- ethers "5.5.4"
joi "17.9.1"
+ viem "2.12.0"
"@web3-onboard/core@^2.21.4":
version "2.21.6"
@@ -7611,12 +7808,12 @@
joi "17.9.1"
rxjs "^7.5.2"
-"@web3-onboard/injected-wallets@^2.10.14":
- version "2.10.17"
- resolved "https://registry.yarnpkg.com/@web3-onboard/injected-wallets/-/injected-wallets-2.10.17.tgz#7234962a6dec68352eb868cc778c98c68b83ccd5"
- integrity sha512-jq7ZqzTWWUPD52jh0oQWz9FpCWi7LY0eLYxXU9FKB+7aZx4S4v/5yHoX4uhS0pHH8SdDzGfLwZqgIKHkC1OMUg==
+"@web3-onboard/injected-wallets@^2.11.2":
+ version "2.11.2"
+ resolved "https://registry.yarnpkg.com/@web3-onboard/injected-wallets/-/injected-wallets-2.11.2.tgz#f39c937f903875cfaad6373e1975dde2660d9e17"
+ integrity sha512-VeI0LTFKAcikBLBOub/avzdvc2SWOh3qLjqw/Yh34YS7ArGG5jrQXSbOuVXLqQoW07TyY4SDHXTI1Yft9MiF4Q==
dependencies:
- "@web3-onboard/common" "^2.3.3"
+ "@web3-onboard/common" "^2.4.1"
joi "17.9.1"
lodash.uniqby "^4.7.0"
@@ -7813,13 +8010,6 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
-"@yarnpkg/esbuild-plugin-pnp@^3.0.0-rc.10":
- version "3.0.0-rc.15"
- resolved "https://registry.yarnpkg.com/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.15.tgz#4e40e7d2eb28825c9a35ab9d04c363931d7c0e67"
- integrity sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==
- dependencies:
- tslib "^2.4.0"
-
"@yarnpkg/fslib@2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-2.10.3.tgz#a8c9893df5d183cf6362680b9f1c6d7504dd5717"
@@ -7859,6 +8049,11 @@ abitype@1.0.0:
resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97"
integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==
+abitype@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6"
+ integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==
+
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -7866,12 +8061,7 @@ abort-controller@^3.0.0:
dependencies:
event-target-shim "^5.0.0"
-abortcontroller-polyfill@^1.7.5:
- version "1.7.5"
- resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed"
- integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==
-
-accepts@~1.3.5, accepts@~1.3.8:
+accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
@@ -7887,12 +8077,12 @@ acorn-globals@^7.0.0:
acorn "^8.1.0"
acorn-walk "^8.0.2"
-acorn-import-assertions@^1.9.0:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
- integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+acorn-import-attributes@^1.9.5:
+ version "1.9.5"
+ resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
+ integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
-acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
+acorn-jsx@^5.0.0, acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
@@ -7917,16 +8107,16 @@ acorn@^7.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
+acorn@^8.0.0:
+ version "8.12.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
+ integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
+
acorn@^8.0.4, acorn@^8.1.0, acorn@^8.11.3, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0:
version "8.11.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
-address@^1.0.1:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e"
- integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==
-
adjust-sourcemap-loader@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99"
@@ -7986,7 +8176,7 @@ ajv-keywords@^5.1.0:
dependencies:
fast-deep-equal "^3.1.3"
-ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
+ajv@^6.12.4, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -8303,10 +8493,10 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
-async-limiter@~1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
- integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
+astring@^1.8.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef"
+ integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==
async-mutex@^0.2.6:
version "0.2.6"
@@ -8378,11 +8568,6 @@ axobject-query@^3.2.1:
dependencies:
dequal "^2.0.3"
-babel-core@^7.0.0-bridge.0:
- version "7.0.0-bridge.0"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
- integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-
babel-jest@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
@@ -8484,12 +8669,17 @@ babel-preset-jest@^29.6.3:
babel-plugin-jest-hoist "^29.6.3"
babel-preset-current-node-syntax "^1.0.0"
+bail@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d"
+ integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-base-x@^3.0.2, base-x@^3.0.8, base-x@^3.0.9:
+base-x@^3.0.2, base-x@^3.0.9:
version "3.0.9"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320"
integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==
@@ -8550,7 +8740,7 @@ big-integer@1.6.36:
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36"
integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==
-big-integer@1.6.x, big-integer@^1.6.44, big-integer@^1.6.48:
+big-integer@1.6.x, big-integer@^1.6.48:
version "1.6.52"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
@@ -8567,7 +8757,7 @@ bigint-buffer@^1.1.5:
dependencies:
bindings "^1.3.0"
-bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.1.0, bignumber.js@^9.1.2:
+bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
@@ -8596,15 +8786,6 @@ bitcoin-ops@^1.3.0, bitcoin-ops@^1.4.1:
resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278"
integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==
-bl@^4.0.3, bl@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
- integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
- dependencies:
- buffer "^5.5.0"
- inherits "^2.0.4"
- readable-stream "^3.4.0"
-
bl@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273"
@@ -8638,17 +8819,12 @@ blob-util@^2.0.2:
resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==
-bluebird@^3.5.0, bluebird@^3.7.2:
+bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-bn.js@4.11.6:
- version "4.11.6"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
- integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==
-
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9:
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
@@ -8668,10 +8844,10 @@ bnc-sdk@^4.6.7:
rxjs "^6.6.3"
sturdy-websocket "^0.1.12"
-body-parser@1.20.2, body-parser@^1.16.0:
- version "1.20.2"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
- integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
+body-parser@1.20.3:
+ version "1.20.3"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
+ integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
@@ -8681,7 +8857,7 @@ body-parser@1.20.2, body-parser@^1.16.0:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
- qs "6.11.0"
+ qs "6.13.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -8719,13 +8895,6 @@ bplist-parser@0.3.1:
dependencies:
big-integer "1.6.x"
-bplist-parser@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e"
- integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==
- dependencies:
- big-integer "^1.6.44"
-
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -8813,13 +8982,6 @@ browserify-sign@^4.0.0:
readable-stream "^2.3.8"
safe-buffer "^5.2.1"
-browserify-zlib@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
- integrity sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==
- dependencies:
- pako "~0.2.0"
-
browserify-zlib@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
@@ -8837,6 +8999,16 @@ browserslist@^4.21.10, browserslist@^4.22.2, browserslist@^4.23.0:
node-releases "^2.0.14"
update-browserslist-db "^1.0.13"
+browserslist@^4.24.0:
+ version "4.24.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4"
+ integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==
+ dependencies:
+ caniuse-lite "^1.0.30001663"
+ electron-to-chromium "^1.5.28"
+ node-releases "^2.0.18"
+ update-browserslist-db "^1.1.0"
+
bs58@^4.0.0, bs58@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
@@ -8890,11 +9062,6 @@ buffer-from@~0.1.1:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0"
integrity sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==
-buffer-to-arraybuffer@^0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a"
- integrity sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==
-
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -8908,7 +9075,7 @@ buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3:
base64-js "^1.3.1"
ieee754 "^1.2.1"
-buffer@^5.0.5, buffer@^5.1.0, buffer@^5.5.0, buffer@^5.6.0:
+buffer@^5.1.0, buffer@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
@@ -8940,39 +9107,11 @@ busboy@1.6.0:
dependencies:
streamsearch "^1.1.0"
-bytes@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
- integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
-
bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
-cacheable-lookup@^5.0.3:
- version "5.0.4"
- resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
- integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
-
-cacheable-lookup@^6.0.4:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz#0330a543471c61faa4e9035db583aad753b36385"
- integrity sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==
-
-cacheable-request@^7.0.2:
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817"
- integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==
- dependencies:
- clone-response "^1.0.2"
- get-stream "^5.1.0"
- http-cache-semantics "^4.0.0"
- keyv "^4.0.0"
- lowercase-keys "^2.0.0"
- normalize-url "^6.0.1"
- responselike "^2.0.0"
-
cachedir@^2.3.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d"
@@ -9026,6 +9165,11 @@ caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001587:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz#4adcb443c8b9c8303e04498318f987616b8fea2e"
integrity sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==
+caniuse-lite@^1.0.30001663:
+ version "1.0.30001669"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3"
+ integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==
+
case-sensitive-paths-webpack-plugin@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
@@ -9048,6 +9192,11 @@ cbor-sync@^1.0.4:
resolved "https://registry.yarnpkg.com/cbor-sync/-/cbor-sync-1.0.4.tgz#5a11a1ab75c2a14d1af1b237fd84aa8c1593662f"
integrity sha512-GWlXN4wiz0vdWWXBU71Dvc1q3aBo0HytqwAZnXF1wOwjqNnDWA1vZ1gDMFLlqohak31VQzmhiYfiCX5QSSfagA==
+ccount@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
+ integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
+
chai@^4.3.10:
version "4.4.1"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1"
@@ -9091,6 +9240,26 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
+character-entities-html4@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
+ integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
+
+character-entities-legacy@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
+ integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
+
+character-entities@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
+ integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
+
+character-reference-invalid@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
+ integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
+
check-error@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694"
@@ -9118,20 +9287,10 @@ check-more-types@^2.24.0:
optionalDependencies:
fsevents "~2.3.2"
-chownr@^1.1.1, chownr@^1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
- integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
-
-chownr@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
- integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
-
chromatic@^11.3.2:
- version "11.4.0"
- resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.4.0.tgz#411a51e82599472b2131a08895faf000e0f9a0fa"
- integrity sha512-/O6OwEUckqKTBGbm9KvYsR/eKCXy4s2eelO38yyfimBIJiL8+TS/pVnBqdtzUqO2hVK4GjrFiea9CnZUG9Akzw==
+ version "11.12.6"
+ resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.12.6.tgz#b2feb6d791eea3264f918b0ca0d5602f92c3fd13"
+ integrity sha512-lt6ekbx3LFLCwGheQrBZAkP2EhrXLPpESk7t45PrsV1DSpu0KOH2ZMN/G9QiF84ZGwj9RPC8BwWbnb2/kd66uA==
chrome-trace-event@^1.0.2:
version "1.0.3"
@@ -9143,17 +9302,6 @@ ci-info@^3.2.0, ci-info@^3.7.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
-cids@^0.7.1:
- version "0.7.5"
- resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.5.tgz#60a08138a99bfb69b6be4ceb63bfef7a396b28b2"
- integrity sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==
- dependencies:
- buffer "^5.5.0"
- class-is "^1.1.0"
- multibase "~0.6.0"
- multicodec "^1.0.0"
- multihashes "~0.4.15"
-
cids@^1.0.0, cids@^1.1.5, cids@^1.1.6:
version "1.1.9"
resolved "https://registry.yarnpkg.com/cids/-/cids-1.1.9.tgz#402c26db5c07059377bcd6fb82f2a24e7f2f4a4f"
@@ -9184,12 +9332,7 @@ cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.3:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c"
integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==
-class-is@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825"
- integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==
-
-classnames@^2.3.1:
+classnames@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
@@ -9231,15 +9374,10 @@ cli-cursor@^3.1.0:
dependencies:
restore-cursor "^3.1.0"
-cli-spinners@^2.5.0:
- version "2.9.2"
- resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41"
- integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==
-
-cli-table3@^0.6.1, cli-table3@~0.6.1:
- version "0.6.5"
- resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f"
- integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==
+cli-table3@~0.6.1:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.4.tgz#d1c536b8a3f2e7bec58f67ac9e5769b1b30088b0"
+ integrity sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==
dependencies:
string-width "^4.2.0"
optionalDependencies:
@@ -9285,27 +9423,6 @@ cliui@^8.0.1:
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
-clone-deep@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
- integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
- dependencies:
- is-plain-object "^2.0.4"
- kind-of "^6.0.2"
- shallow-clone "^3.0.0"
-
-clone-response@^1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3"
- integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==
- dependencies:
- mimic-response "^1.0.0"
-
-clone@^1.0.2:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
- integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
-
clsx@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
@@ -9316,6 +9433,11 @@ clsx@^2.1.0:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+cluster-key-slot@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
+ integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -9326,6 +9448,11 @@ code-block-writer@^11.0.0:
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-11.0.3.tgz#9eec2993edfb79bfae845fbc093758c0a0b73b76"
integrity sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==
+collapse-white-space@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca"
+ integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==
+
collect-v8-coverage@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9"
@@ -9383,6 +9510,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
+comma-separated-tokens@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
+ integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
+
command-line-args@^5.1.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e"
@@ -9443,26 +9575,6 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
-compressible@~2.0.16:
- version "2.0.18"
- resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
- integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
- dependencies:
- mime-db ">= 1.43.0 < 2"
-
-compression@^1.7.4:
- version "1.7.4"
- resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
- integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
- dependencies:
- accepts "~1.3.5"
- bytes "3.0.0"
- compressible "~2.0.16"
- debug "2.6.9"
- on-headers "~1.0.2"
- safe-buffer "5.1.2"
- vary "~1.1.2"
-
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -9495,15 +9607,6 @@ content-disposition@0.5.4:
dependencies:
safe-buffer "5.2.1"
-content-hash@^2.5.2:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/content-hash/-/content-hash-2.5.2.tgz#bbc2655e7c21f14fd3bfc7b7d4bfe6e454c9e211"
- integrity sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==
- dependencies:
- cids "^0.7.1"
- multicodec "^0.5.5"
- multihashes "^0.4.15"
-
content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
@@ -9563,14 +9666,6 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
-cors@^2.8.1:
- version "2.8.5"
- resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
- integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
- dependencies:
- object-assign "^4"
- vary "^1"
-
cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
@@ -9797,6 +9892,16 @@ cypress-file-upload@^5.0.8:
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1"
integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==
+cypress-visual-regression@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/cypress-visual-regression/-/cypress-visual-regression-5.0.2.tgz#1681543e0859525cc09baba9de6a367c6c0dbfa5"
+ integrity sha512-Rr/W2uSw1KxG5AE9q6MT8yZfT81GaLVO0z/QyKg9pCZ7XvObUklyh9j5gWe7yUzDnY/2g9XpczZKNKL2zZ2PAg==
+ dependencies:
+ chalk "^4.1.2"
+ pixelmatch "^5.2.1"
+ pngjs "^6.0.0"
+ sanitize-filename "^1.6.3"
+
cypress@^12.15.0:
version "12.17.4"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.4.tgz#b4dadf41673058493fa0d2362faa3da1f6ae2e6c"
@@ -9914,7 +10019,7 @@ dayjs@^1.10.4:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e"
integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==
-debug@2.6.9, debug@^2.2.0:
+debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -9935,6 +10040,13 @@ debug@^3.1.0, debug@^3.2.7:
dependencies:
ms "^2.1.1"
+debug@^4.0.0:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
decamelize-keys@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8"
@@ -9953,25 +10065,18 @@ decimal.js@^10.2.0, decimal.js@^10.4.2:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
-decode-uri-component@^0.2.0, decode-uri-component@^0.2.2:
+decode-named-character-reference@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e"
+ integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==
+ dependencies:
+ character-entities "^2.0.0"
+
+decode-uri-component@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
-decompress-response@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
- integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==
- dependencies:
- mimic-response "^1.0.0"
-
-decompress-response@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
- integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
- dependencies:
- mimic-response "^3.1.0"
-
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
@@ -10028,26 +10133,6 @@ deepmerge@^4.2.2:
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
-default-browser-id@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c"
- integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==
- dependencies:
- bplist-parser "^0.2.0"
- untildify "^4.0.0"
-
-defaults@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a"
- integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==
- dependencies:
- clone "^1.0.2"
-
-defer-to-connect@^2.0.0, defer-to-connect@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
- integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
-
define-data-property@^1.0.1, define-data-property@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
@@ -10113,12 +10198,17 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+denque@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
+ integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
+
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-dequal@^2.0.2, dequal@^2.0.3:
+dequal@^2.0.0, dequal@^2.0.2, dequal@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
@@ -10146,11 +10236,6 @@ detect-browser@5.3.0:
resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca"
integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==
-detect-indent@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
- integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
-
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
@@ -10171,31 +10256,18 @@ detect-node-es@^1.1.0:
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
-detect-package-manager@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/detect-package-manager/-/detect-package-manager-2.0.1.tgz#6b182e3ae5e1826752bfef1de9a7b828cffa50d8"
- integrity sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==
- dependencies:
- execa "^5.1.1"
-
-detect-port@^1.3.0:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.6.1.tgz#45e4073997c5f292b957cb678fb0bb8ed4250a67"
- integrity sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==
+devlop@^1.0.0, devlop@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018"
+ integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
dependencies:
- address "^1.0.1"
- debug "4"
+ dequal "^2.0.0"
diff-sequences@^29.6.3:
version "29.6.3"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
-diff@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531"
- integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==
-
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -10265,11 +10337,6 @@ dom-serializer@^1.0.1:
domhandler "^4.2.0"
entities "^2.0.0"
-dom-walk@^0.1.0:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
- integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
-
domain-browser@^4.22.0:
version "4.23.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.23.0.tgz#427ebb91efcb070f05cffdfb8a4e9a6c25f8c94b"
@@ -10349,16 +10416,6 @@ duplexer@^0.1.2:
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
-duplexify@^3.5.0, duplexify@^3.6.0:
- version "3.7.1"
- resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
- integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
- dependencies:
- end-of-stream "^1.0.0"
- inherits "^2.0.1"
- readable-stream "^2.0.0"
- stream-shift "^1.0.0"
-
duplexify@^4.1.2:
version "4.1.3"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.3.tgz#a07e1c0d0a2c001158563d32592ba58bddb0236f"
@@ -10406,7 +10463,7 @@ eip55@^2.1.1:
dependencies:
keccak "^3.0.3"
-ejs@^3.1.10, ejs@^3.1.6:
+ejs@^3.1.6:
version "3.1.10"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
@@ -10418,6 +10475,11 @@ electron-to-chromium@^1.4.668:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.779.tgz#bb6f08b93092a564421adcadcc4b92c5055c7a77"
integrity sha512-oaTiIcszNfySXVJzKcjxd2YjPxziAd+GmXyb2HbidCeFo6Z88ygOT7EimlrEQhM2U08VhSrbKhLOXP0kKUCZ6g==
+electron-to-chromium@^1.5.28:
+ version "1.5.41"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz#eae1ba6c49a1a61d84cf8263351d3513b2bcc534"
+ integrity sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==
+
elliptic@6.5.4:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@@ -10444,6 +10506,19 @@ elliptic@^6.4.0, elliptic@^6.4.1, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
+elliptic@^6.5.7:
+ version "6.5.7"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
+ integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
+ dependencies:
+ bn.js "^4.11.9"
+ brorand "^1.1.0"
+ hash.js "^1.0.0"
+ hmac-drbg "^1.0.1"
+ inherits "^2.0.4"
+ minimalistic-assert "^1.0.1"
+ minimalistic-crypto-utils "^1.0.1"
+
emittery@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
@@ -10474,7 +10549,12 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
-end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+encodeurl@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+ integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
+end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@@ -10490,10 +10570,10 @@ endent@^2.0.1:
fast-json-parse "^1.0.3"
objectorarray "^1.0.5"
-enhanced-resolve@^5.12.0, enhanced-resolve@^5.16.0, enhanced-resolve@^5.7.0:
- version "5.16.1"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz#e8bc63d51b826d6f1cbc0a150ecb5a8b0c62e567"
- integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==
+enhanced-resolve@^5.12.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0:
+ version "5.17.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
+ integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -10521,11 +10601,6 @@ env-paths@^2.2.1:
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
-envinfo@^7.7.3:
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31"
- integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==
-
err-code@^3.0.0, err-code@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920"
@@ -10688,7 +10763,7 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
-es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.53, es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@^0.10.64, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
+es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.53, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
version "0.10.64"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==
@@ -10707,7 +10782,7 @@ es6-iterator@^2.0.3:
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
-es6-promise@4.2.8, es6-promise@^4.0.3, es6-promise@^4.2.8:
+es6-promise@4.2.8, es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
@@ -10737,11 +10812,6 @@ es6-weak-map@^2.0.3:
es6-iterator "^2.0.3"
es6-symbol "^3.1.1"
-esbuild-plugin-alias@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz#45a86cb941e20e7c2bc68a2bea53562172494fcb"
- integrity sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==
-
esbuild-register@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.5.0.tgz#449613fb29ab94325c722f560f800dd946dc8ea8"
@@ -10778,6 +10848,36 @@ esbuild-register@^3.5.0:
"@esbuild/win32-ia32" "0.20.2"
"@esbuild/win32-x64" "0.20.2"
+"esbuild@^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8"
+ integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.23.1"
+ "@esbuild/android-arm" "0.23.1"
+ "@esbuild/android-arm64" "0.23.1"
+ "@esbuild/android-x64" "0.23.1"
+ "@esbuild/darwin-arm64" "0.23.1"
+ "@esbuild/darwin-x64" "0.23.1"
+ "@esbuild/freebsd-arm64" "0.23.1"
+ "@esbuild/freebsd-x64" "0.23.1"
+ "@esbuild/linux-arm" "0.23.1"
+ "@esbuild/linux-arm64" "0.23.1"
+ "@esbuild/linux-ia32" "0.23.1"
+ "@esbuild/linux-loong64" "0.23.1"
+ "@esbuild/linux-mips64el" "0.23.1"
+ "@esbuild/linux-ppc64" "0.23.1"
+ "@esbuild/linux-riscv64" "0.23.1"
+ "@esbuild/linux-s390x" "0.23.1"
+ "@esbuild/linux-x64" "0.23.1"
+ "@esbuild/netbsd-x64" "0.23.1"
+ "@esbuild/openbsd-arm64" "0.23.1"
+ "@esbuild/openbsd-x64" "0.23.1"
+ "@esbuild/sunos-x64" "0.23.1"
+ "@esbuild/win32-arm64" "0.23.1"
+ "@esbuild/win32-ia32" "0.23.1"
+ "@esbuild/win32-x64" "0.23.1"
+
esbuild@^0.19.2:
version "0.19.12"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04"
@@ -10812,6 +10912,11 @@ escalade@^3.1.1, escalade@^3.1.2:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
+escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -10832,6 +10937,11 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+escape-string-regexp@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
+ integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
+
escodegen@^2.0.0, escodegen@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17"
@@ -11113,6 +11223,52 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+estree-util-attach-comments@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz#344bde6a64c8a31d15231e5ee9e297566a691c2d"
+ integrity sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+estree-util-build-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz#b6d0bced1dcc4f06f25cf0ceda2b2dcaf98168f1"
+ integrity sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ estree-walker "^3.0.0"
+
+estree-util-is-identifier-name@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd"
+ integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==
+
+estree-util-to-js@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz#10a6fb924814e6abb62becf0d2bc4dea51d04f17"
+ integrity sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ astring "^1.8.0"
+ source-map "^0.7.0"
+
+estree-util-value-to-estree@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz#d2f0e5d350a6c181673eb7299743325b86a9bf5c"
+ integrity sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+estree-util-visit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz#13a9a9f40ff50ed0c022f831ddf4b58d05446feb"
+ integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/unist" "^3.0.0"
+
estree-walker@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
@@ -11123,7 +11279,7 @@ estree-walker@^2:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
-estree-walker@^3.0.3:
+estree-walker@^3.0.0, estree-walker@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
@@ -11164,14 +11320,6 @@ eth-crypto@^2.1.0:
ethers "5.7.2"
secp256k1 "5.0.0"
-eth-ens-namehash@2.0.8:
- version "2.0.8"
- resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf"
- integrity sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==
- dependencies:
- idna-uts46-hx "^2.3.1"
- js-sha3 "^0.5.7"
-
eth-json-rpc-filters@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-6.0.1.tgz#0b3e370f017f5c6f58d3e7bd0756d8099ed85c56"
@@ -11183,27 +11331,6 @@ eth-json-rpc-filters@^6.0.0:
json-rpc-engine "^6.1.0"
pify "^5.0.0"
-eth-lib@0.2.8:
- version "0.2.8"
- resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8"
- integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==
- dependencies:
- bn.js "^4.11.6"
- elliptic "^6.4.0"
- xhr-request-promise "^0.1.2"
-
-eth-lib@^0.1.26:
- version "0.1.29"
- resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.29.tgz#0c11f5060d42da9f931eab6199084734f4dbd1d9"
- integrity sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==
- dependencies:
- bn.js "^4.11.6"
- elliptic "^6.4.0"
- nano-json-stream-parser "^0.1.2"
- servify "^0.1.12"
- ws "^3.0.0"
- xhr-request-promise "^0.1.2"
-
eth-query@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/eth-query/-/eth-query-2.1.2.tgz#d6741d9000106b51510c72db92d6365456a6da5e"
@@ -11219,13 +11346,6 @@ eth-rpc-errors@^4.0.2:
dependencies:
fast-safe-stringify "^2.0.6"
-ethereum-bloom-filters@^1.0.6:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.1.0.tgz#b3fc1eb789509ee30db0bf99a2988ccacb8d0397"
- integrity sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==
- dependencies:
- "@noble/hashes" "^1.4.0"
-
ethereum-cryptography@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191"
@@ -11247,15 +11367,15 @@ ethereum-cryptography@^0.1.3:
secp256k1 "^4.0.1"
setimmediate "^1.0.5"
-ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2, ethereum-cryptography@^2.1.3:
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a"
- integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==
+ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz#58f2810f8e020aecb97de8c8c76147600b0b8ccf"
+ integrity sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==
dependencies:
- "@noble/curves" "1.3.0"
- "@noble/hashes" "1.3.3"
- "@scure/bip32" "1.3.3"
- "@scure/bip39" "1.2.2"
+ "@noble/curves" "1.4.2"
+ "@noble/hashes" "1.4.0"
+ "@scure/bip32" "1.4.0"
+ "@scure/bip39" "1.3.0"
ethereumjs-abi@^0.6.8:
version "0.6.8"
@@ -11397,10 +11517,10 @@ ethers@5.7.2:
"@ethersproject/web" "5.7.1"
"@ethersproject/wordlists" "5.7.0"
-ethers@^6.11.1, ethers@^6.7.1, ethers@^6.9.2:
- version "6.12.1"
- resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.12.1.tgz#517ff6d66d4fd5433e38e903051da3e57c87ff37"
- integrity sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw==
+ethers@^6.11.1, ethers@^6.13.1, ethers@^6.9.2:
+ version "6.11.1"
+ resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.11.1.tgz#96aae00b627c2e35f9b0a4d65c7ab658259ee6af"
+ integrity sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==
dependencies:
"@adraffy/ens-normalize" "1.10.1"
"@noble/curves" "1.2.0"
@@ -11410,14 +11530,6 @@ ethers@^6.11.1, ethers@^6.7.1, ethers@^6.9.2:
tslib "2.4.0"
ws "8.5.0"
-ethjs-unit@0.1.6:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699"
- integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==
- dependencies:
- bn.js "4.11.6"
- number-to-bn "1.7.0"
-
ethjs-util@0.1.6, ethjs-util@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536"
@@ -11444,11 +11556,6 @@ eventemitter2@6.4.7:
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d"
integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==
-eventemitter3@4.0.4:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
- integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==
-
eventemitter3@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@@ -11487,7 +11594,7 @@ execa@4.1.0:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
-execa@^5.0.0, execa@^5.1.1:
+execa@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
@@ -11557,37 +11664,74 @@ exponential-backoff@^3.1.0:
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
-express@^4.14.0, express@^4.17.3:
- version "4.19.2"
- resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
- integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
+express@^4.17.3:
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48"
+ integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
- body-parser "1.20.2"
+ body-parser "1.20.3"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
- encodeurl "~1.0.2"
+ encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.2.0"
fresh "0.5.2"
http-errors "2.0.0"
- merge-descriptors "1.0.1"
+ merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
- path-to-regexp "0.1.7"
+ path-to-regexp "0.1.10"
proxy-addr "~2.0.7"
qs "6.11.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
- send "0.18.0"
- serve-static "1.15.0"
+ send "0.19.0"
+ serve-static "1.16.0"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+express@^4.19.2:
+ version "4.21.0"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915"
+ integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==
+ dependencies:
+ accepts "~1.3.8"
+ array-flatten "1.1.1"
+ body-parser "1.20.3"
+ content-disposition "0.5.4"
+ content-type "~1.0.4"
+ cookie "0.6.0"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "2.0.0"
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.3.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ merge-descriptors "1.0.3"
+ methods "~1.1.2"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.10"
+ proxy-addr "~2.0.7"
+ qs "6.13.0"
+ range-parser "~1.2.1"
+ safe-buffer "5.2.1"
+ send "0.19.0"
+ serve-static "1.16.2"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
@@ -11601,7 +11745,14 @@ ext@^1.7.0:
dependencies:
type "^2.7.2"
-extend@~3.0.2:
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -11660,7 +11811,7 @@ fast-glob@3.3.1:
merge2 "^1.3.0"
micromatch "^4.0.4"
-fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.1, fast-glob@^3.3.2:
+fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@@ -11708,6 +11859,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+fault@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c"
+ integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==
+ dependencies:
+ format "^0.2.0"
+
faye-websocket@0.11.4:
version "0.11.4"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
@@ -11729,11 +11887,6 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
-fetch-retry@^5.0.2:
- version "5.0.6"
- resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.6.tgz#17d0bc90423405b7a88b74355bf364acd2a7fa56"
- integrity sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==
-
figures@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -11810,14 +11963,18 @@ finalhandler@1.2.0:
statuses "2.0.1"
unpipe "~1.0.0"
-find-cache-dir@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
- integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
+finalhandler@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
+ integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
dependencies:
- commondir "^1.0.1"
- make-dir "^2.0.0"
- pkg-dir "^3.0.0"
+ debug "2.6.9"
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ statuses "2.0.1"
+ unpipe "~1.0.0"
find-cache-dir@^3.0.0, find-cache-dir@^3.3.1:
version "3.3.2"
@@ -11848,13 +12005,6 @@ find-root@^1.1.0:
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
-find-up@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
- integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
- dependencies:
- locate-path "^3.0.0"
-
find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@@ -11933,15 +12083,10 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
-flow-parser@0.*:
- version "0.236.0"
- resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.236.0.tgz#8e8e6c59ff7e8d196c0ed215b3919320a1c6e332"
- integrity sha512-0OEk9Gr+Yj7wjDW2KgaNYUypKau71jAfFyeLQF5iVtxqc6uJHag/MT7pmaEApf4qM7u86DkBcd4ualddYMfbLw==
-
follow-redirects@^1.15.6:
- version "1.15.6"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
- integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
+ version "1.15.9"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
for-each@^0.3.3:
version "0.3.3"
@@ -11981,11 +12126,6 @@ fork-ts-checker-webpack-plugin@^8.0.0:
semver "^7.3.5"
tapable "^2.2.1"
-form-data-encoder@1.7.1:
- version "1.7.1"
- resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96"
- integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==
-
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@@ -12004,6 +12144,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
+format@^0.2.0:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+ integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
+
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -12014,11 +12159,6 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
-fs-constants@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
- integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
-
fs-extra@11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
@@ -12046,15 +12186,6 @@ fs-extra@^11.1.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
-fs-extra@^4.0.2:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
- integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
- dependencies:
- graceful-fs "^4.1.2"
- jsonfile "^4.0.0"
- universalify "^0.1.0"
-
fs-extra@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
@@ -12074,20 +12205,6 @@ fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
-fs-minipass@^1.2.7:
- version "1.2.7"
- resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
- integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
- dependencies:
- minipass "^2.6.0"
-
-fs-minipass@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
- integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
- dependencies:
- minipass "^3.0.0"
-
fs-monkey@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2"
@@ -12123,10 +12240,10 @@ functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
-fuse.js@^6.6.2:
- version "6.6.2"
- resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111"
- integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==
+fuse.js@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.0.0.tgz#6573c9fcd4c8268e403b4fc7d7131ffcf99a9eb2"
+ integrity sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
@@ -12159,11 +12276,6 @@ get-nonce@^1.0.0:
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
-get-npm-tarball-url@^2.0.3:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/get-npm-tarball-url/-/get-npm-tarball-url-2.1.0.tgz#cbd6bb25884622bc3191c761466c93ac83343213"
- integrity sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==
-
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@@ -12186,7 +12298,7 @@ get-stream@^5.0.0, get-stream@^5.1.0:
dependencies:
pump "^3.0.0"
-get-stream@^6.0.0, get-stream@^6.0.1:
+get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
@@ -12231,20 +12343,6 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
-giget@^1.0.0:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/giget/-/giget-1.2.3.tgz#ef6845d1140e89adad595f7f3bb60aa31c672cb6"
- integrity sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==
- dependencies:
- citty "^0.1.6"
- consola "^3.2.3"
- defu "^6.1.4"
- node-fetch-native "^1.6.3"
- nypm "^0.3.8"
- ohash "^1.1.3"
- pathe "^1.1.2"
- tar "^6.2.0"
-
github-slugger@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-2.0.0.tgz#52cf2f9279a21eb6c59dd385b410f0c0adda8f1a"
@@ -12334,14 +12432,6 @@ global-dirs@^3.0.0:
dependencies:
ini "2.0.0"
-global@~4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
- integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
- dependencies:
- min-document "^2.19.0"
- process "^0.11.10"
-
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -12379,18 +12469,6 @@ globby@^11.0.1, globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
-globby@^14.0.1:
- version "14.0.1"
- resolved "https://registry.yarnpkg.com/globby/-/globby-14.0.1.tgz#a1b44841aa7f4c6d8af2bc39951109d77301959b"
- integrity sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==
- dependencies:
- "@sindresorhus/merge-streams" "^2.1.0"
- fast-glob "^3.3.2"
- ignore "^5.2.4"
- path-type "^5.0.0"
- slash "^5.1.0"
- unicorn-magic "^0.1.0"
-
globby@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
@@ -12414,42 +12492,6 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
-got@12.1.0:
- version "12.1.0"
- resolved "https://registry.yarnpkg.com/got/-/got-12.1.0.tgz#099f3815305c682be4fd6b0ee0726d8e4c6b0af4"
- integrity sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==
- dependencies:
- "@sindresorhus/is" "^4.6.0"
- "@szmarczak/http-timer" "^5.0.1"
- "@types/cacheable-request" "^6.0.2"
- "@types/responselike" "^1.0.0"
- cacheable-lookup "^6.0.4"
- cacheable-request "^7.0.2"
- decompress-response "^6.0.0"
- form-data-encoder "1.7.1"
- get-stream "^6.0.1"
- http2-wrapper "^2.1.10"
- lowercase-keys "^3.0.0"
- p-cancelable "^3.0.0"
- responselike "^2.0.0"
-
-got@^11.8.5:
- version "11.8.6"
- resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a"
- integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==
- dependencies:
- "@sindresorhus/is" "^4.0.0"
- "@szmarczak/http-timer" "^4.0.5"
- "@types/cacheable-request" "^6.0.1"
- "@types/responselike" "^1.0.0"
- cacheable-lookup "^5.0.3"
- cacheable-request "^7.0.2"
- decompress-response "^6.0.0"
- http2-wrapper "^1.0.0-beta.5.2"
- lowercase-keys "^2.0.0"
- p-cancelable "^2.0.0"
- responselike "^2.0.0"
-
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
@@ -12460,17 +12502,15 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
-gunzip-maybe@^1.4.2:
- version "1.4.2"
- resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
- integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==
- dependencies:
- browserify-zlib "^0.1.4"
- is-deflate "^1.0.0"
- is-gzip "^1.0.0"
- peek-stream "^1.1.0"
- pumpify "^1.3.3"
- through2 "^2.0.3"
+gray-matter@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798"
+ integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==
+ dependencies:
+ js-yaml "^3.13.1"
+ kind-of "^6.0.2"
+ section-matter "^1.0.0"
+ strip-bom-string "^1.0.0"
gzip-size@^6.0.0:
version "6.0.0"
@@ -12515,19 +12555,6 @@ handlebars@^4.7.7:
optionalDependencies:
uglify-js "^3.1.4"
-har-schema@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
- integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==
-
-har-validator@~5.1.3:
- version "5.1.5"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
- integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
- dependencies:
- ajv "^6.12.3"
- har-schema "^2.0.0"
-
hard-rejection@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
@@ -12618,6 +12645,49 @@ hast-util-is-element@^3.0.0:
dependencies:
"@types/hast" "^3.0.0"
+hast-util-to-estree@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz#f2afe5e869ddf0cf690c75f9fc699f3180b51b19"
+ integrity sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-attach-comments "^3.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-object "^0.4.0"
+ unist-util-position "^5.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-jsx-runtime@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz#3ed27caf8dc175080117706bf7269404a0aa4f7c"
+ integrity sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-object "^1.0.0"
+ unist-util-position "^5.0.0"
+ vfile-message "^4.0.0"
+
hast-util-to-string@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz#2a131948b4b1b26461a2c8ac876e2c88d02946bd"
@@ -12625,6 +12695,13 @@ hast-util-to-string@^3.0.0:
dependencies:
"@types/hast" "^3.0.0"
+hast-util-whitespace@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621"
+ integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
hdkey@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-2.1.0.tgz#755b30b73f54e93c31919c1b2f19205a8e57cb92"
@@ -12654,7 +12731,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
+hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -12740,11 +12817,6 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
-http-cache-semantics@^4.0.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
- integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
-
http-errors@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
@@ -12756,11 +12828,6 @@ http-errors@2.0.0:
statuses "2.0.1"
toidentifier "1.0.1"
-http-https@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b"
- integrity sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==
-
http-parser-js@>=0.5.1:
version "0.5.8"
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
@@ -12780,15 +12847,6 @@ http-shutdown@^1.2.2:
resolved "https://registry.yarnpkg.com/http-shutdown/-/http-shutdown-1.2.2.tgz#41bc78fc767637c4c95179bc492f312c0ae64c5f"
integrity sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==
-http-signature@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
- integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==
- dependencies:
- assert-plus "^1.0.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
http-signature@~1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9"
@@ -12798,22 +12856,6 @@ http-signature@~1.3.6:
jsprim "^2.0.2"
sshpk "^1.14.1"
-http2-wrapper@^1.0.0-beta.5.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
- integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
- dependencies:
- quick-lru "^5.1.1"
- resolve-alpn "^1.0.0"
-
-http2-wrapper@^2.1.10:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a"
- integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==
- dependencies:
- quick-lru "^5.1.1"
- resolve-alpn "^1.2.0"
-
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@@ -12883,19 +12925,12 @@ idb@7.1.1, idb@^7.0.1:
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b"
integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
-idna-uts46-hx@^2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9"
- integrity sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==
- dependencies:
- punycode "2.1.0"
-
ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1:
+ignore@^5.2.0, ignore@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
@@ -12917,10 +12952,10 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
-immer@^9.0.21:
- version "9.0.21"
- resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
- integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
+immer@^10.0.3:
+ version "10.1.1"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
+ integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
immutable@^4.0.0:
version "4.3.6"
@@ -12971,6 +13006,16 @@ ini@2.0.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
+inline-style-parser@0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
+ integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
+
+inline-style-parser@0.2.4:
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22"
+ integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
+
int64-buffer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-1.0.1.tgz#c78d841b444cadf036cd04f8683696c740f15dca"
@@ -13019,11 +13064,6 @@ ip-address@^9.0.5:
jsbn "1.1.0"
sprintf-js "^1.1.3"
-ip@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105"
- integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==
-
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@@ -13088,6 +13128,19 @@ is-absolute-url@^4.0.0:
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-4.0.1.tgz#16e4d487d4fded05cfe0685e53ec86804a5e94dc"
integrity sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==
+is-alphabetical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b"
+ integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
+
+is-alphanumerical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875"
+ integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
+ dependencies:
+ is-alphabetical "^2.0.0"
+ is-decimal "^2.0.0"
+
is-arguments@^1.0.4, is-arguments@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
@@ -13176,10 +13229,10 @@ is-date-object@^1.0.1, is-date-object@^1.0.5:
dependencies:
has-tostringtag "^1.0.0"
-is-deflate@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14"
- integrity sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==
+is-decimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7"
+ integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
@@ -13191,6 +13244,11 @@ is-docker@^3.0.0:
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200"
integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==
+is-extendable@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+ integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==
+
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -13208,11 +13266,6 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-is-function@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08"
- integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==
-
is-generator-fn@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
@@ -13232,16 +13285,16 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
-is-gzip@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83"
- integrity sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==
-
is-hex-prefixed@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554"
integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==
+is-hexadecimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027"
+ integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
+
is-inside-container@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4"
@@ -13257,12 +13310,12 @@ is-installed-globally@~0.4.0:
global-dirs "^3.0.0"
is-path-inside "^3.0.2"
-is-interactive@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
- integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
+is-map@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
+ integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
-is-map@^2.0.2, is-map@^2.0.3:
+is-map@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
@@ -13336,18 +13389,16 @@ is-plain-obj@^2.1.0:
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+is-plain-obj@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
+ integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
+
is-plain-object@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
-is-plain-object@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
- integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
- dependencies:
- isobject "^3.0.1"
-
is-potential-custom-element-name@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
@@ -13358,6 +13409,13 @@ is-promise@^2.2.2:
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
+is-reference@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.2.tgz#154747a01f45cd962404ee89d43837af2cba247c"
+ integrity sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==
+ dependencies:
+ "@types/estree" "*"
+
is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
@@ -13414,7 +13472,7 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.3:
dependencies:
which-typed-array "^1.1.14"
-is-typedarray@^1.0.0, is-typedarray@~1.0.0:
+is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
@@ -13490,11 +13548,6 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
-isobject@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
- integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
-
isomorphic-unfetch@3.1.0, isomorphic-unfetch@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f"
@@ -13632,10 +13685,10 @@ jake@^10.8.5:
filelist "^1.0.4"
minimatch "^3.1.2"
-jayson@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.0.tgz#60dc946a85197317f2b1439d672a8b0a99cea2f9"
- integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==
+jayson@^4.1.1:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e"
+ integrity sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA==
dependencies:
"@types/connect" "^3.4.33"
"@types/node" "^12.12.54"
@@ -13648,7 +13701,7 @@ jayson@^4.1.0:
isomorphic-ws "^4.0.1"
json-stringify-safe "^5.0.1"
uuid "^8.3.2"
- ws "^7.4.5"
+ ws "^7.5.10"
jest-changed-files@^29.7.0:
version "29.7.0"
@@ -14066,11 +14119,6 @@ js-sha3@0.8.0, js-sha3@^0.8.0:
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
-js-sha3@^0.5.7:
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7"
- integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==
-
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -14096,41 +14144,15 @@ jsbi@^3.1.5:
resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6"
integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==
-jsbn@1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
- integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
-
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
-jscodeshift@^0.15.1:
- version "0.15.2"
- resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.15.2.tgz#145563860360b4819a558c75c545f39683e5a0be"
- integrity sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==
- dependencies:
- "@babel/core" "^7.23.0"
- "@babel/parser" "^7.23.0"
- "@babel/plugin-transform-class-properties" "^7.22.5"
- "@babel/plugin-transform-modules-commonjs" "^7.23.0"
- "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11"
- "@babel/plugin-transform-optional-chaining" "^7.23.0"
- "@babel/plugin-transform-private-methods" "^7.22.5"
- "@babel/preset-flow" "^7.22.15"
- "@babel/preset-typescript" "^7.23.0"
- "@babel/register" "^7.22.15"
- babel-core "^7.0.0-bridge.0"
- chalk "^4.1.2"
- flow-parser "0.*"
- graceful-fs "^4.2.4"
- micromatch "^4.0.4"
- neo-async "^2.5.0"
- node-dir "^0.1.17"
- recast "^0.23.3"
- temp "^0.8.4"
- write-file-atomic "^2.3.0"
+jsdoc-type-pratt-parser@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz#ff6b4a3f339c34a6c188cbf50a16087858d22113"
+ integrity sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==
jsdom@^20.0.0:
version "20.0.3"
@@ -14169,6 +14191,11 @@ jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+jsesc@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
+ integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
+
jsesc@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
@@ -14292,16 +14319,6 @@ jsonschema@1.2.2:
resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.2.tgz#83ab9c63d65bf4d596f91d81195e78772f6452bc"
integrity sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA==
-jsprim@^1.2.2:
- version "1.4.2"
- resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
- integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
- dependencies:
- assert-plus "1.0.0"
- extsprintf "1.3.0"
- json-schema "0.4.0"
- verror "1.10.0"
-
jsprim@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d"
@@ -14336,7 +14353,7 @@ keccak@^3.0.0, keccak@^3.0.3:
node-gyp-build "^4.2.0"
readable-stream "^3.6.0"
-keyv@^4.0.0, keyv@^4.5.3:
+keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
@@ -14348,7 +14365,7 @@ keyvaluestorage-interface@^1.0.0:
resolved "https://registry.yarnpkg.com/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz#13ebdf71f5284ad54be94bd1ad9ed79adad515ff"
integrity sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==
-kind-of@^6.0.2, kind-of@^6.0.3:
+kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
@@ -14532,14 +14549,6 @@ localforage@^1.8.1:
dependencies:
lie "3.1.1"
-locate-path@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
- integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
- dependencies:
- p-locate "^3.0.0"
- path-exists "^3.0.0"
-
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
@@ -14576,6 +14585,11 @@ lodash.defaults@^4.2.0:
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
+lodash.isarguments@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+ integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
+
lodash.isequal@4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@@ -14611,7 +14625,7 @@ lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-log-symbols@^4.0.0, log-symbols@^4.1.0:
+log-symbols@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
@@ -14639,6 +14653,11 @@ long@^5.0.0:
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
+longest-streak@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
+ integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -14660,20 +14679,10 @@ lower-case@^2.0.2:
dependencies:
tslib "^2.0.3"
-lowercase-keys@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
- integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
-
-lowercase-keys@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2"
- integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==
-
lru-cache@^10.2.0:
- version "10.2.2"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878"
- integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
+ integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
lru-cache@^5.1.1:
version "5.1.1"
@@ -14715,7 +14724,7 @@ magic-string@^0.30.5:
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
-make-dir@^2.0.0, make-dir@^2.1.0:
+make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
@@ -14759,6 +14768,16 @@ map-or-similar@^1.5.0:
resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08"
integrity sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==
+markdown-extensions@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4"
+ integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==
+
+markdown-table@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd"
+ integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==
+
markdown-to-jsx@7.3.2:
version "7.3.2"
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.3.2.tgz#f286b4d112dad3028acc1e77dfe1f653b347e131"
@@ -14773,6 +14792,208 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
+mdast-util-find-and-replace@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz#a6fc7b62f0994e973490e45262e4bc07607b04e0"
+ integrity sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ escape-string-regexp "^5.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
+mdast-util-from-markdown@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz#32a6e8f512b416e1f51eb817fc64bd867ebcd9cc"
+ integrity sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark "^4.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-decode-string "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+mdast-util-frontmatter@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz#f5f929eb1eb36c8a7737475c7eb438261f964ee8"
+ integrity sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ escape-string-regexp "^5.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ micromark-extension-frontmatter "^2.0.0"
+
+mdast-util-gfm-autolink-literal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5"
+ integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ ccount "^2.0.0"
+ devlop "^1.0.0"
+ mdast-util-find-and-replace "^3.0.0"
+ micromark-util-character "^2.0.0"
+
+mdast-util-gfm-footnote@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz#25a1753c7d16db8bfd53cd84fe50562bd1e6d6a9"
+ integrity sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.1.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+
+mdast-util-gfm-strikethrough@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16"
+ integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm-table@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38"
+ integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ markdown-table "^3.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm-task-list-item@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936"
+ integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz#3f2aecc879785c3cb6a81ff3a243dc11eca61095"
+ integrity sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==
+ dependencies:
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-gfm-autolink-literal "^2.0.0"
+ mdast-util-gfm-footnote "^2.0.0"
+ mdast-util-gfm-strikethrough "^2.0.0"
+ mdast-util-gfm-table "^2.0.0"
+ mdast-util-gfm-task-list-item "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdx-expression@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096"
+ integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdx-jsx@^3.0.0:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz#76b957b3da18ebcfd0de3a9b4451dcd6fdec2320"
+ integrity sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ devlop "^1.1.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ parse-entities "^4.0.0"
+ stringify-entities "^4.0.0"
+ unist-util-stringify-position "^4.0.0"
+ vfile-message "^4.0.0"
+
+mdast-util-mdx@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz#792f9cf0361b46bee1fdf1ef36beac424a099c41"
+ integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==
+ dependencies:
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdxjs-esm@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97"
+ integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-phrasing@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3"
+ integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ unist-util-is "^6.0.0"
+
+mdast-util-to-hast@^13.0.0:
+ version "13.2.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4"
+ integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ trim-lines "^3.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
+mdast-util-to-markdown@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz#9813f1d6e0cdaac7c244ec8c6dabfdb2102ea2b4"
+ integrity sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-phrasing "^4.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark-util-decode-string "^2.0.0"
+ unist-util-visit "^5.0.0"
+ zwitch "^2.0.0"
+
+mdast-util-to-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814"
+ integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+
mdn-data@2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
@@ -14829,10 +15050,10 @@ meow@^9.0.0:
type-fest "^0.18.0"
yargs-parser "^20.2.3"
-merge-descriptors@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
- integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
+merge-descriptors@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
+ integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-options@^3.0.4:
version "3.0.4"
@@ -14861,6 +15082,385 @@ micro-ftch@^0.3.1:
resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f"
integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==
+micromark-core-commonmark@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz#9a45510557d068605c6e9a80f282b2bb8581e43d"
+ integrity sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-destination "^2.0.0"
+ micromark-factory-label "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-factory-title "^2.0.0"
+ micromark-factory-whitespace "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-html-tag-name "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-frontmatter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz#651c52ffa5d7a8eeed687c513cd869885882d67a"
+ integrity sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==
+ dependencies:
+ fault "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-autolink-literal@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935"
+ integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-footnote@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750"
+ integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-strikethrough@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923"
+ integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-table@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz#5cadedfbb29fca7abf752447967003dc3b6583c9"
+ integrity sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-tagfilter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57"
+ integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-task-list-item@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c"
+ integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b"
+ integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==
+ dependencies:
+ micromark-extension-gfm-autolink-literal "^2.0.0"
+ micromark-extension-gfm-footnote "^2.0.0"
+ micromark-extension-gfm-strikethrough "^2.0.0"
+ micromark-extension-gfm-table "^2.0.0"
+ micromark-extension-gfm-tagfilter "^2.0.0"
+ micromark-extension-gfm-task-list-item "^2.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdx-expression@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz#1407b9ce69916cf5e03a196ad9586889df25302a"
+ integrity sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-mdx-expression "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdx-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz#5abb83da5ddc8e473a374453e6ea56fbd66b59ad"
+ integrity sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==
+ dependencies:
+ "@types/acorn" "^4.0.0"
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ micromark-factory-mdx-expression "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-extension-mdx-md@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz#1d252881ea35d74698423ab44917e1f5b197b92d"
+ integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdxjs-esm@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz#de21b2b045fd2059bd00d36746081de38390d54a"
+ integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-extension-mdxjs@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz#b5a2e0ed449288f3f6f6c544358159557549de18"
+ integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==
+ dependencies:
+ acorn "^8.0.0"
+ acorn-jsx "^5.0.0"
+ micromark-extension-mdx-expression "^3.0.0"
+ micromark-extension-mdx-jsx "^3.0.0"
+ micromark-extension-mdx-md "^2.0.0"
+ micromark-extension-mdxjs-esm "^3.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-destination@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz#857c94debd2c873cba34e0445ab26b74f6a6ec07"
+ integrity sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-label@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz#17c5c2e66ce39ad6f4fc4cbf40d972f9096f726a"
+ integrity sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-mdx-expression@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz#2afaa8ba6d5f63e0cead3e4dee643cad184ca260"
+ integrity sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-factory-space@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz#5e7afd5929c23b96566d0e1ae018ae4fcf81d030"
+ integrity sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-title@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz#726140fc77892af524705d689e1cf06c8a83ea95"
+ integrity sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-whitespace@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz#9e92eb0f5468083381f923d9653632b3cfb5f763"
+ integrity sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-character@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.0.tgz#31320ace16b4644316f6bf057531689c71e2aee1"
+ integrity sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-chunked@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz#e51f4db85fb203a79dbfef23fd41b2f03dc2ef89"
+ integrity sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-classify-character@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz#8c7537c20d0750b12df31f86e976d1d951165f34"
+ integrity sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-combine-extensions@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz#75d6ab65c58b7403616db8d6b31315013bfb7ee5"
+ integrity sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==
+ dependencies:
+ micromark-util-chunked "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-decode-numeric-character-reference@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz#2698bbb38f2a9ba6310e359f99fcb2b35a0d2bd5"
+ integrity sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-decode-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz#7dfa3a63c45aecaa17824e656bcdb01f9737154a"
+ integrity sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-encode@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz#0921ac7953dc3f1fd281e3d1932decfdb9382ab1"
+ integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==
+
+micromark-util-events-to-acorn@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz#4275834f5453c088bd29cd72dfbf80e3327cec07"
+ integrity sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==
+ dependencies:
+ "@types/acorn" "^4.0.0"
+ "@types/estree" "^1.0.0"
+ "@types/unist" "^3.0.0"
+ devlop "^1.0.0"
+ estree-util-visit "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-util-html-tag-name@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz#ae34b01cbe063363847670284c6255bb12138ec4"
+ integrity sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==
+
+micromark-util-normalize-identifier@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz#91f9a4e65fe66cc80c53b35b0254ad67aa431d8b"
+ integrity sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-resolve-all@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz#189656e7e1a53d0c86a38a652b284a252389f364"
+ integrity sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-util-sanitize-uri@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz#ec8fbf0258e9e6d8f13d9e4770f9be64342673de"
+ integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-subtokenize@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz#76129c49ac65da6e479c09d0ec4b5f29ec6eace5"
+ integrity sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-symbol@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz#12225c8f95edf8b17254e47080ce0862d5db8044"
+ integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==
+
+micromark-util-types@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e"
+ integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==
+
+micromark@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.0.tgz#84746a249ebd904d9658cfabc1e8e5f32cbc6249"
+ integrity sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==
+ dependencies:
+ "@types/debug" "^4.0.0"
+ debug "^4.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.7"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5"
@@ -14877,12 +15477,12 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
-mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
+mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
-mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
+mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -14909,23 +15509,6 @@ mimic-fn@^4.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
-mimic-response@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
- integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
-
-mimic-response@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
- integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
-
-min-document@^2.19.0:
- version "2.19.0"
- resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
- integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==
- dependencies:
- dom-walk "^0.1.0"
-
min-indent@^1.0.0, min-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
@@ -14948,7 +15531,7 @@ minimatch@9.0.3:
dependencies:
brace-expansion "^2.0.1"
-minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -14983,71 +15566,12 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8, minimist@~1.
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-minipass@^2.6.0, minipass@^2.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
- integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
- dependencies:
- safe-buffer "^5.1.2"
- yallist "^3.0.0"
-
-minipass@^3.0.0:
- version "3.3.6"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
- integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==
- dependencies:
- yallist "^4.0.0"
-
-minipass@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
- integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
-
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4:
version "7.1.1"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.1.tgz#f7f85aff59aa22f110b20e27692465cf3bf89481"
integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==
-minizlib@^1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
- integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
- dependencies:
- minipass "^2.9.0"
-
-minizlib@^2.1.1:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
- integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
- dependencies:
- minipass "^3.0.0"
- yallist "^4.0.0"
-
-mkdirp-classic@^0.5.2:
- version "0.5.3"
- resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
- integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
-
-mkdirp-promise@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1"
- integrity sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==
- dependencies:
- mkdirp "*"
-
-mkdirp@*:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
- integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
-
-mkdirp@^0.5.5:
- version "0.5.6"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
- integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
- dependencies:
- minimist "^1.2.6"
-
-mkdirp@^1.0.3, mkdirp@^1.0.4:
+mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@@ -15062,11 +15586,6 @@ mlly@^1.6.1, mlly@^1.7.0:
pkg-types "^1.1.0"
ufo "^1.5.3"
-mock-fs@^4.1.0:
- version "4.14.0"
- resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18"
- integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==
-
mockdate@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb"
@@ -15104,19 +15623,11 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-ms@2.1.3, ms@^2.0.0, ms@^2.1.1:
+ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-multibase@^0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b"
- integrity sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==
- dependencies:
- base-x "^3.0.8"
- buffer "^5.5.0"
-
multibase@^4.0.1:
version "4.0.6"
resolved "https://registry.yarnpkg.com/multibase/-/multibase-4.0.6.tgz#6e624341483d6123ca1ede956208cb821b440559"
@@ -15124,29 +15635,6 @@ multibase@^4.0.1:
dependencies:
"@multiformats/base-x" "^4.0.1"
-multibase@~0.6.0:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.6.1.tgz#b76df6298536cc17b9f6a6db53ec88f85f8cc12b"
- integrity sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==
- dependencies:
- base-x "^3.0.8"
- buffer "^5.5.0"
-
-multicodec@^0.5.5:
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-0.5.7.tgz#1fb3f9dd866a10a55d226e194abba2dcc1ee9ffd"
- integrity sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==
- dependencies:
- varint "^5.0.0"
-
-multicodec@^1.0.0:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.4.tgz#46ac064657c40380c28367c90304d8ed175a714f"
- integrity sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==
- dependencies:
- buffer "^5.6.0"
- varint "^5.0.0"
-
multicodec@^3.0.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-3.2.1.tgz#82de3254a0fb163a107c1aab324f2a91ef51efb2"
@@ -15160,15 +15648,6 @@ multiformats@^9.4.2, multiformats@^9.6.4:
resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37"
integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==
-multihashes@^0.4.15, multihashes@~0.4.15:
- version "0.4.21"
- resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.21.tgz#dc02d525579f334a7909ade8a122dabb58ccfcb5"
- integrity sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==
- dependencies:
- buffer "^5.5.0"
- multibase "^0.7.0"
- varint "^5.0.0"
-
multihashes@^4.0.1, multihashes@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-4.0.3.tgz#426610539cd2551edbf533adeac4c06b3b90fb05"
@@ -15222,11 +15701,6 @@ nan@^2.13.2, nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0"
integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==
-nano-json-stream-parser@^0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f"
- integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==
-
nanoid@^3.3.1, nanoid@^3.3.6, nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
@@ -15260,7 +15734,7 @@ negotiator@0.6.3:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
-neo-async@^2.5.0, neo-async@^2.6.2:
+neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
@@ -15270,28 +15744,28 @@ next-tick@1, next-tick@^1.1.0:
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
-next@^14.1.1:
- version "14.1.1"
- resolved "https://registry.yarnpkg.com/next/-/next-14.1.1.tgz#92bd603996c050422a738e90362dff758459a171"
- integrity sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==
+next@^14.2.13:
+ version "14.2.13"
+ resolved "https://registry.yarnpkg.com/next/-/next-14.2.13.tgz#32da2ee0afbe729e2d4a467c3570def90e1c974d"
+ integrity sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==
dependencies:
- "@next/env" "14.1.1"
- "@swc/helpers" "0.5.2"
+ "@next/env" "14.2.13"
+ "@swc/helpers" "0.5.5"
busboy "1.6.0"
caniuse-lite "^1.0.30001579"
graceful-fs "^4.2.11"
postcss "8.4.31"
styled-jsx "5.1.1"
optionalDependencies:
- "@next/swc-darwin-arm64" "14.1.1"
- "@next/swc-darwin-x64" "14.1.1"
- "@next/swc-linux-arm64-gnu" "14.1.1"
- "@next/swc-linux-arm64-musl" "14.1.1"
- "@next/swc-linux-x64-gnu" "14.1.1"
- "@next/swc-linux-x64-musl" "14.1.1"
- "@next/swc-win32-arm64-msvc" "14.1.1"
- "@next/swc-win32-ia32-msvc" "14.1.1"
- "@next/swc-win32-x64-msvc" "14.1.1"
+ "@next/swc-darwin-arm64" "14.2.13"
+ "@next/swc-darwin-x64" "14.2.13"
+ "@next/swc-linux-arm64-gnu" "14.2.13"
+ "@next/swc-linux-arm64-musl" "14.2.13"
+ "@next/swc-linux-x64-gnu" "14.2.13"
+ "@next/swc-linux-x64-musl" "14.2.13"
+ "@next/swc-win32-arm64-msvc" "14.2.13"
+ "@next/swc-win32-ia32-msvc" "14.2.13"
+ "@next/swc-win32-x64-msvc" "14.2.13"
no-case@^3.0.4:
version "3.0.4"
@@ -15326,13 +15800,6 @@ node-addon-api@^7.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb"
integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==
-node-dir@^0.1.17:
- version "0.1.17"
- resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
- integrity sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==
- dependencies:
- minimatch "^3.0.2"
-
node-fetch-native@^1.6.1, node-fetch-native@^1.6.2, node-fetch-native@^1.6.3:
version "1.6.4"
resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e"
@@ -15396,6 +15863,11 @@ node-releases@^2.0.14:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
+node-releases@^2.0.18:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
+ integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
+
normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -15421,11 +15893,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-normalize-url@^6.0.1:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
- integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
-
npm-run-path@^4.0.0, npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@@ -15447,36 +15914,12 @@ nth-check@^2.0.1:
dependencies:
boolbase "^1.0.0"
-number-to-bn@1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0"
- integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==
- dependencies:
- bn.js "4.11.6"
- strip-hex-prefix "1.0.0"
-
nwsapi@^2.2.2:
- version "2.2.10"
- resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.10.tgz#0b77a68e21a0b483db70b11fad055906e867cda8"
- integrity sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==
-
-nypm@^0.3.8:
- version "0.3.8"
- resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.3.8.tgz#a16b078b161be5885351e72cf0b97326973722bf"
- integrity sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==
- dependencies:
- citty "^0.1.6"
- consola "^3.2.3"
- execa "^8.0.1"
- pathe "^1.1.2"
- ufo "^1.4.0"
-
-oauth-sign@~0.9.0:
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
- integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
+ version "2.2.7"
+ resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30"
+ integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==
-object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@@ -15565,13 +16008,6 @@ objectorarray@^1.0.5:
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5"
integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==
-oboe@2.1.5:
- version "2.1.5"
- resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.5.tgz#5554284c543a2266d7a38f17e073821fbde393cd"
- integrity sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==
- dependencies:
- http-https "^1.0.0"
-
ofetch@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.4.tgz#7ea65ced3c592ec2b9906975ae3fe1d26a56f635"
@@ -15582,9 +16018,9 @@ ofetch@^1.3.3:
ufo "^1.5.3"
ohash@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.3.tgz#f12c3c50bfe7271ce3fd1097d42568122ccdcf07"
- integrity sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72"
+ integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==
on-exit-leak-free@^0.2.0:
version "0.2.0"
@@ -15598,11 +16034,6 @@ on-finished@2.4.1:
dependencies:
ee-first "1.1.1"
-on-headers@~1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
- integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
-
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -15632,7 +16063,7 @@ open@^7.4.2:
is-docker "^2.0.0"
is-wsl "^2.1.1"
-open@^8.0.4, open@^8.4.0:
+open@^8.0.4:
version "8.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
@@ -15658,21 +16089,6 @@ optionator@^0.9.3:
type-check "^0.4.0"
word-wrap "^1.2.5"
-ora@^5.4.1:
- version "5.4.1"
- resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
- integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==
- dependencies:
- bl "^4.1.0"
- chalk "^4.1.0"
- cli-cursor "^3.1.0"
- cli-spinners "^2.5.0"
- is-interactive "^1.0.0"
- is-unicode-supported "^0.1.0"
- log-symbols "^4.1.0"
- strip-ansi "^6.0.0"
- wcwidth "^1.0.1"
-
os-browserify@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
@@ -15688,17 +16104,7 @@ ospath@^1.2.2:
resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b"
integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==
-p-cancelable@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf"
- integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
-
-p-cancelable@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050"
- integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==
-
-p-limit@^2.0.0, p-limit@^2.2.0:
+p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
@@ -15719,13 +16125,6 @@ p-limit@^4.0.0:
dependencies:
yocto-queue "^1.0.0"
-p-locate@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
- integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
- dependencies:
- p-limit "^2.0.0"
-
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -15764,11 +16163,6 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
-pako@~0.2.0:
- version "0.2.9"
- resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
- integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
-
pako@~1.0.5:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -15806,10 +16200,19 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.7:
pbkdf2 "^3.1.2"
safe-buffer "^5.2.1"
-parse-headers@^2.0.0:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9"
- integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==
+parse-entities@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.1.tgz#4e2a01111fb1c986549b944af39eeda258fc9e4e"
+ integrity sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ character-entities "^2.0.0"
+ character-entities-legacy "^3.0.0"
+ character-reference-invalid "^2.0.0"
+ decode-named-character-reference "^1.0.0"
+ is-alphanumerical "^2.0.0"
+ is-decimal "^2.0.0"
+ is-hexadecimal "^2.0.0"
parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
@@ -15872,11 +16275,6 @@ path-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
-path-exists@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
- integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
-
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -15920,21 +16318,16 @@ path-scurry@^1.10.1, path-scurry@^1.11.0:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
-path-to-regexp@0.1.7:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
- integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
+path-to-regexp@0.1.10:
+ version "0.1.10"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
+ integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-path-type@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8"
- integrity sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==
-
pathe@^1.1.1, pathe@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
@@ -15956,15 +16349,6 @@ pbkdf2@^3.0.17, pbkdf2@^3.0.3, pbkdf2@^3.1.2:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
-peek-stream@^1.1.0:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67"
- integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==
- dependencies:
- buffer-from "^1.0.0"
- duplexify "^3.5.0"
- through2 "^2.0.3"
-
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
@@ -15975,10 +16359,24 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
-picocolors@^1.0.0, picocolors@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
- integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
+periscopic@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.1.0.tgz#7e9037bf51c5855bd33b48928828db4afa79d97a"
+ integrity sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-walker "^3.0.0"
+ is-reference "^3.0.0"
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picocolors@^1.0.1, picocolors@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1:
version "2.3.1"
@@ -16047,17 +16445,17 @@ pino@7.11.0:
sonic-boom "^2.2.1"
thread-stream "^0.15.1"
-pirates@^4.0.1, pirates@^4.0.4, pirates@^4.0.6:
+pirates@^4.0.1, pirates@^4.0.4:
version "4.0.6"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
-pkg-dir@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
- integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
+pixelmatch@^5.2.1:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.3.0.tgz#5e5321a7abedfb7962d60dbf345deda87cb9560a"
+ integrity sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==
dependencies:
- find-up "^3.0.0"
+ pngjs "^6.0.0"
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
version "4.2.0"
@@ -16103,6 +16501,11 @@ pngjs@^5.0.0:
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
+pngjs@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821"
+ integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==
+
pnp-webpack-plugin@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz#65741384f6d8056f36e2255a8d67ffc20866f5c9"
@@ -16218,7 +16621,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-"prettier-fallback@npm:prettier@^3", prettier@^3.1.1:
+"prettier-fallback@npm:prettier@^3":
version "3.2.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
@@ -16286,7 +16689,7 @@ process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
-prompts@^2.0.1, prompts@^2.4.0:
+prompts@^2.0.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
@@ -16303,6 +16706,11 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
+property-information@^6.0.0:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec"
+ integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==
+
protobufjs@7.2.6:
version "7.2.6"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215"
@@ -16386,7 +16794,7 @@ prr@~1.0.1:
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==
-psl@^1.1.28, psl@^1.1.33:
+psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
@@ -16403,14 +16811,6 @@ public-encrypt@^4.0.0:
randombytes "^2.0.1"
safe-buffer "^5.1.2"
-pump@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
- integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
- dependencies:
- end-of-stream "^1.1.0"
- once "^1.3.1"
-
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
@@ -16419,20 +16819,6 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
-pumpify@^1.3.3:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
- integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
- dependencies:
- duplexify "^3.6.0"
- inherits "^2.0.3"
- pump "^2.0.0"
-
-punycode@2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
- integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==
-
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@@ -16491,10 +16877,17 @@ qs@6.11.0:
dependencies:
side-channel "^1.0.4"
+qs@6.13.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+ integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
+ dependencies:
+ side-channel "^1.0.6"
+
qs@^6.10.0, qs@^6.11.2:
- version "6.12.1"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a"
- integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.0.tgz#edd40c3b823995946a8a0b1f208669c7a200db77"
+ integrity sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==
dependencies:
side-channel "^1.0.6"
@@ -16505,11 +16898,6 @@ qs@~6.10.3:
dependencies:
side-channel "^1.0.4"
-qs@~6.5.2:
- version "6.5.3"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
- integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
-
query-string@7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328"
@@ -16520,15 +16908,6 @@ query-string@7.1.3:
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
-query-string@^5.0.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
- integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==
- dependencies:
- decode-uri-component "^0.2.0"
- object-assign "^4.1.0"
- strict-uri-encode "^1.0.0"
-
querystring-es3@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -16561,11 +16940,6 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
-quick-lru@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
- integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
-
rabin-wasm@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/rabin-wasm/-/rabin-wasm-0.1.5.tgz#5b625ca007d6a2cbc1456c78ae71d550addbc9c9"
@@ -16661,7 +17035,7 @@ react-dom@16.13.1:
prop-types "^15.6.2"
scheduler "^0.19.1"
-"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18.2.0:
+"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@@ -16749,16 +17123,12 @@ react-qr-reader@^2.2.1:
prop-types "^15.7.2"
webrtc-adapter "^7.2.1"
-react-redux@^8.0.5:
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.3.tgz#4fdc0462d0acb59af29a13c27ffef6f49ab4df46"
- integrity sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==
+react-redux@^9.1.2:
+ version "9.1.2"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.1.2.tgz#deba38c64c3403e9abd0c3fbeab69ffd9d8a7e4b"
+ integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
dependencies:
- "@babel/runtime" "^7.12.1"
- "@types/hoist-non-react-statics" "^3.3.1"
"@types/use-sync-external-store" "^0.0.3"
- hoist-non-react-statics "^3.3.2"
- react-is "^18.0.0"
use-sync-external-store "^1.0.0"
react-refresh@^0.14.0:
@@ -16813,7 +17183,7 @@ react@16.13.1:
object-assign "^4.1.1"
prop-types "^15.6.2"
-"react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18.2.0:
+"react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18.2.0, react@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
@@ -16839,7 +17209,7 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
-readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.3.8, readable-stream@~2.3.6:
+readable-stream@^2.0.2, readable-stream@^2.3.8, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
@@ -16903,10 +17273,10 @@ realistic-structured-clone@^3.0.0:
typeson "^6.1.0"
typeson-registry "^1.0.0-alpha.20"
-recast@^0.23.3, recast@^0.23.5:
- version "0.23.7"
- resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.7.tgz#1e08f164e10402b075c904a2b01022b3da039c72"
- integrity sha512-MpQlLZVpqbbxYcqEjwpRWo88sGvjOYoXptySz710RuddNMHx+wPkoNX6YyLZJlXAh5VZr1qmPrTwcTuFMh0Lag==
+recast@^0.23.5:
+ version "0.23.6"
+ resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.6.tgz#198fba74f66143a30acc81929302d214ce4e3bfa"
+ integrity sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ==
dependencies:
ast-types "^0.16.1"
esprima "~4.0.0"
@@ -16922,22 +17292,32 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redis-errors@^1.0.0, redis-errors@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
+ integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==
+
+redis-parser@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
+ integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==
+ dependencies:
+ redis-errors "^1.0.0"
+
reduce-flatten@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27"
integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==
-redux-thunk@^2.4.2:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
- integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
+redux-thunk@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3"
+ integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==
-redux@^4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
- integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
- dependencies:
- "@babel/runtime" "^7.9.2"
+redux@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
+ integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
reflect.getprototypeof@^1.0.4:
version "1.0.6"
@@ -17043,6 +17423,86 @@ relateurl@^0.2.7:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
+remark-frontmatter@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz#b68d61552a421ec412c76f4f66c344627dc187a2"
+ integrity sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-frontmatter "^2.0.0"
+ micromark-extension-frontmatter "^2.0.0"
+ unified "^11.0.0"
+
+remark-gfm@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.0.tgz#aea777f0744701aa288b67d28c43565c7e8c35de"
+ integrity sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-gfm "^3.0.0"
+ micromark-extension-gfm "^3.0.0"
+ remark-parse "^11.0.0"
+ remark-stringify "^11.0.0"
+ unified "^11.0.0"
+
+remark-heading-id@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/remark-heading-id/-/remark-heading-id-1.0.1.tgz#5500b8cbc12ba6cc36fc5ccc2a3ff3d6e5cf981e"
+ integrity sha512-GmJjuCeEkYvwFlvn/Skjc/1Qafj71412gbQnrwUmP/tKskmAf1cMRlZRNoovV+aIvsSRkTb2rCmGv2b9RdoJbQ==
+ dependencies:
+ lodash "^4.17.21"
+ unist-util-visit "^1.4.0"
+
+remark-mdx-frontmatter@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/remark-mdx-frontmatter/-/remark-mdx-frontmatter-5.0.0.tgz#22c48c4758963701595082fd89157586caa5a372"
+ integrity sha512-kI75pshe27TM71R+0iX7C3p4MbGMdygkvSbrk1WYSar88WAwR2JfQilofcDGgDNFAWUo5IwTPyq9XvGpifTwqQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ estree-util-value-to-estree "^3.0.0"
+ toml "^3.0.0"
+ unified "^11.0.0"
+ yaml "^2.0.0"
+
+remark-mdx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-3.0.1.tgz#8f73dd635c1874e44426e243f72c0977cf60e212"
+ integrity sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==
+ dependencies:
+ mdast-util-mdx "^3.0.0"
+ micromark-extension-mdxjs "^3.0.0"
+
+remark-parse@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1"
+ integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unified "^11.0.0"
+
+remark-rehype@^11.0.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.0.tgz#d5f264f42bcbd4d300f030975609d01a1697ccdc"
+ integrity sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ mdast-util-to-hast "^13.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
+remark-stringify@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3"
+ integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ unified "^11.0.0"
+
renderkid@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a"
@@ -17061,32 +17521,6 @@ request-progress@^3.0.0:
dependencies:
throttleit "^1.0.0"
-request@^2.79.0:
- version "2.88.2"
- resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
- integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.8.0"
- caseless "~0.12.0"
- combined-stream "~1.0.6"
- extend "~3.0.2"
- forever-agent "~0.6.1"
- form-data "~2.3.2"
- har-validator "~5.1.3"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.19"
- oauth-sign "~0.9.0"
- performance-now "^2.1.0"
- qs "~6.5.2"
- safe-buffer "^5.1.2"
- tough-cookie "~2.5.0"
- tunnel-agent "^0.6.0"
- uuid "^3.3.2"
-
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -17112,21 +17546,16 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
-reselect@^4.1.8:
- version "4.1.8"
- resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524"
- integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==
+reselect@^5.1.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e"
+ integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
reserved-words@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
integrity sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==
-resolve-alpn@^1.0.0, resolve-alpn@^1.2.0:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
- integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
-
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -17183,13 +17612,6 @@ resolve@^2.0.0-next.5:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
-responselike@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc"
- integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==
- dependencies:
- lowercase-keys "^2.0.0"
-
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -17227,13 +17649,6 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
-rimraf@~2.6.2:
- version "2.6.3"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
- integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
- dependencies:
- glob "^7.1.3"
-
ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -17316,18 +17731,22 @@ rollup-plugin-terser@^7.0.0:
terser "^5.0.0"
rollup@^2.43.1:
- version "2.79.1"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
- integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
+ version "2.79.2"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.2.tgz#f150e4a5db4b121a21a747d762f701e5e9f49090"
+ integrity sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==
optionalDependencies:
fsevents "~2.3.2"
-rpc-websockets@^7.11.0:
- version "7.11.0"
- resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.11.0.tgz#05451975963a7d1a4cf36d54e200bfc4402a56d7"
- integrity sha512-IkLYjayPv6Io8C/TdCL5gwgzd1hFz2vmBZrjMw/SPEXo51ETOhnzgS4Qy5GWi2JQN7HKHa66J3+2mv0fgNh/7w==
+rpc-websockets@^9.0.2:
+ version "9.0.2"
+ resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.2.tgz#4c1568d00b8100f997379a363478f41f8f4b242c"
+ integrity sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw==
dependencies:
- eventemitter3 "^4.0.7"
+ "@swc/helpers" "^0.5.11"
+ "@types/uuid" "^8.3.4"
+ "@types/ws" "^8.2.2"
+ buffer "^6.0.3"
+ eventemitter3 "^5.0.1"
uuid "^8.3.2"
ws "^8.5.0"
optionalDependencies:
@@ -17379,16 +17798,16 @@ safe-array-concat@^1.1.2:
has-symbols "^1.0.3"
isarray "^2.0.5"
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
safe-regex-test@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
@@ -17408,6 +17827,13 @@ safe-stable-stringify@^2.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+sanitize-filename@^1.6.3:
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378"
+ integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==
+ dependencies:
+ truncate-utf8-bytes "^1.0.0"
+
sass-loader@^12.4.0:
version "12.6.0"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb"
@@ -17476,7 +17902,7 @@ schema-utils@^4.0.0:
ajv-formats "^2.1.1"
ajv-keywords "^5.1.0"
-scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1:
+scrypt-js@3.0.1, scrypt-js@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312"
integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==
@@ -17518,6 +17944,14 @@ secp256k1@^4.0.0, secp256k1@^4.0.1:
node-addon-api "^2.0.0"
node-gyp-build "^4.2.0"
+section-matter@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"
+ integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
+ dependencies:
+ extend-shallow "^2.0.1"
+ kind-of "^6.0.0"
+
"semver@2 || 3 || 4 || 5", semver@^5.6.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
@@ -17542,10 +17976,10 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0:
- version "7.6.2"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
- integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
+semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semver@^7.6.3:
+ version "7.6.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
send@0.18.0:
version "0.18.0"
@@ -17566,6 +18000,25 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
+send@0.19.0:
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
+ integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
+ dependencies:
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ mime "1.6.0"
+ ms "2.1.3"
+ on-finished "2.4.1"
+ range-parser "~1.2.1"
+ statuses "2.0.1"
+
serialize-javascript@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
@@ -17580,26 +18033,25 @@ serialize-javascript@^6.0.1:
dependencies:
randombytes "^2.1.0"
-serve-static@1.15.0:
- version "1.15.0"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
- integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
+serve-static@1.16.0:
+ version "1.16.0"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
+ integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.18.0"
-servify@^0.1.12:
- version "0.1.12"
- resolved "https://registry.yarnpkg.com/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95"
- integrity sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==
+serve-static@1.16.2:
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
+ integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
dependencies:
- body-parser "^1.16.0"
- cors "^2.8.1"
- express "^4.14.0"
- request "^2.79.0"
- xhr "^2.3.3"
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.19.0"
set-blocking@^2.0.0:
version "2.0.0"
@@ -17646,41 +18098,34 @@ sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
-shallow-clone@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
- integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
- dependencies:
- kind-of "^6.0.2"
-
sharp@^0.33.3:
- version "0.33.4"
- resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.4.tgz#b88e6e843e095c6ab5e1a0c59c4885e580cd8405"
- integrity sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==
+ version "0.33.5"
+ resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e"
+ integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==
dependencies:
color "^4.2.3"
detect-libc "^2.0.3"
- semver "^7.6.0"
+ semver "^7.6.3"
optionalDependencies:
- "@img/sharp-darwin-arm64" "0.33.4"
- "@img/sharp-darwin-x64" "0.33.4"
- "@img/sharp-libvips-darwin-arm64" "1.0.2"
- "@img/sharp-libvips-darwin-x64" "1.0.2"
- "@img/sharp-libvips-linux-arm" "1.0.2"
- "@img/sharp-libvips-linux-arm64" "1.0.2"
- "@img/sharp-libvips-linux-s390x" "1.0.2"
- "@img/sharp-libvips-linux-x64" "1.0.2"
- "@img/sharp-libvips-linuxmusl-arm64" "1.0.2"
- "@img/sharp-libvips-linuxmusl-x64" "1.0.2"
- "@img/sharp-linux-arm" "0.33.4"
- "@img/sharp-linux-arm64" "0.33.4"
- "@img/sharp-linux-s390x" "0.33.4"
- "@img/sharp-linux-x64" "0.33.4"
- "@img/sharp-linuxmusl-arm64" "0.33.4"
- "@img/sharp-linuxmusl-x64" "0.33.4"
- "@img/sharp-wasm32" "0.33.4"
- "@img/sharp-win32-ia32" "0.33.4"
- "@img/sharp-win32-x64" "0.33.4"
+ "@img/sharp-darwin-arm64" "0.33.5"
+ "@img/sharp-darwin-x64" "0.33.5"
+ "@img/sharp-libvips-darwin-arm64" "1.0.4"
+ "@img/sharp-libvips-darwin-x64" "1.0.4"
+ "@img/sharp-libvips-linux-arm" "1.0.5"
+ "@img/sharp-libvips-linux-arm64" "1.0.4"
+ "@img/sharp-libvips-linux-s390x" "1.0.4"
+ "@img/sharp-libvips-linux-x64" "1.0.4"
+ "@img/sharp-libvips-linuxmusl-arm64" "1.0.4"
+ "@img/sharp-libvips-linuxmusl-x64" "1.0.4"
+ "@img/sharp-linux-arm" "0.33.5"
+ "@img/sharp-linux-arm64" "0.33.5"
+ "@img/sharp-linux-s390x" "0.33.5"
+ "@img/sharp-linux-x64" "0.33.5"
+ "@img/sharp-linuxmusl-arm64" "0.33.5"
+ "@img/sharp-linuxmusl-x64" "0.33.5"
+ "@img/sharp-wasm32" "0.33.5"
+ "@img/sharp-win32-ia32" "0.33.5"
+ "@img/sharp-win32-x64" "0.33.5"
shebang-command@^2.0.0:
version "2.0.0"
@@ -17714,20 +18159,6 @@ signal-exit@^4.0.1, signal-exit@^4.1.0:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
-simple-concat@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
- integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
-
-simple-get@^2.7.0:
- version "2.8.2"
- resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.2.tgz#5708fb0919d440657326cd5fe7d2599d07705019"
- integrity sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==
- dependencies:
- decompress-response "^3.3.0"
- once "^1.3.1"
- simple-concat "^1.0.0"
-
simple-plist@^1.1.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017"
@@ -17768,11 +18199,6 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-slash@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"
- integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
-
slice-ansi@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
@@ -17843,7 +18269,7 @@ source-map-support@0.5.13:
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map-support@^0.5.16, source-map-support@~0.5.20:
+source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
@@ -17861,7 +18287,7 @@ source-map@^0.5.7:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
-source-map@^0.7.3:
+source-map@^0.7.0, source-map@^0.7.3:
version "0.7.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
@@ -17924,20 +18350,15 @@ split2@^4.0.0:
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
-sprintf-js@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
- integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
-
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
-sshpk@^1.14.1, sshpk@^1.7.0:
- version "1.18.0"
- resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028"
- integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==
+sshpk@^1.14.1:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
+ integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
@@ -17966,6 +18387,11 @@ stackframe@^1.3.4:
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==
+standard-as-callback@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
+ integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
+
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
@@ -17988,12 +18414,12 @@ store2@^2.14.2:
resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.3.tgz#24077d7ba110711864e4f691d2af941ec533deb5"
integrity sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==
-storybook@^8.0.6:
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.1.3.tgz#f047b2c945df1c98452bc5756203897e57baca78"
- integrity sha512-djsH1nPnX3G84hWR/HmofrfiZ8mN7dyP7uDYkR8O2rd/pfZ3fMI6iaKKWL73Z+WGAiK2Ax9oSmaZSGwgS6k3Rg==
+storybook@^8.3.4:
+ version "8.3.4"
+ resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.3.4.tgz#caca649de88372679c126a9bf8ae4ed14406e9a3"
+ integrity sha512-nzvuK5TsEgJwcWGLGgafabBOxKn37lfJVv7ZoUVPgJIjk2mNRyJDFwYRJzUZaD37eiR/c/lQ6MoaeqlGwiXoxw==
dependencies:
- "@storybook/cli" "8.1.3"
+ "@storybook/core" "8.3.4"
stream-browserify@^3.0.0:
version "3.0.0"
@@ -18018,7 +18444,7 @@ stream-http@^3.2.0:
readable-stream "^3.6.0"
xtend "^4.0.2"
-stream-shift@^1.0.0, stream-shift@^1.0.2:
+stream-shift@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b"
integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==
@@ -18028,11 +18454,6 @@ streamsearch@^1.1.0:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
-strict-uri-encode@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
- integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==
-
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
@@ -18051,7 +18472,16 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -18134,6 +18564,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
+stringify-entities@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3"
+ integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==
+ dependencies:
+ character-entities-html4 "^2.0.0"
+ character-entities-legacy "^3.0.0"
+
stringify-object@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
@@ -18143,7 +18581,14 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -18157,6 +18602,11 @@ strip-ansi@^7.0.1, strip-ansi@^7.1.0:
dependencies:
ansi-regex "^6.0.1"
+strip-bom-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
+ integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==
+
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -18203,7 +18653,7 @@ strip-indent@^4.0.0:
dependencies:
min-indent "^1.0.1"
-strip-json-comments@^3.0.1, strip-json-comments@^3.1.1:
+strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@@ -18220,6 +18670,20 @@ style-loader@^3.3.1:
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7"
integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==
+style-to-object@^0.4.0:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.4.4.tgz#266e3dfd56391a7eefb7770423612d043c3f33ec"
+ integrity sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==
+ dependencies:
+ inline-style-parser "0.1.1"
+
+style-to-object@^1.0.0:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292"
+ integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==
+ dependencies:
+ inline-style-parser "0.2.4"
+
styled-jsx@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
@@ -18256,15 +18720,15 @@ sucrase@3.34.0:
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
-superstruct@^0.14.2:
- version "0.14.2"
- resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b"
- integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==
-
superstruct@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca"
- integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046"
+ integrity sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==
+
+superstruct@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54"
+ integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==
supports-color@^5.3.0:
version "5.5.0"
@@ -18328,23 +18792,6 @@ svgo@^2.8.0:
picocolors "^1.0.0"
stable "^0.1.8"
-swarm-js@^0.1.40:
- version "0.1.42"
- resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.42.tgz#497995c62df6696f6e22372f457120e43e727979"
- integrity sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==
- dependencies:
- bluebird "^3.5.0"
- buffer "^5.0.5"
- eth-lib "^0.1.26"
- fs-extra "^4.0.2"
- got "^11.8.5"
- mime-types "^2.1.16"
- mkdirp-promise "^5.0.1"
- mock-fs "^4.1.0"
- setimmediate "^1.0.5"
- tar "^4.0.2"
- xhr-request "^1.0.1"
-
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@@ -18370,52 +18817,6 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
-tar-fs@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
- integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
- dependencies:
- chownr "^1.1.1"
- mkdirp-classic "^0.5.2"
- pump "^3.0.0"
- tar-stream "^2.1.4"
-
-tar-stream@^2.1.4:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
- integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
- dependencies:
- bl "^4.0.3"
- end-of-stream "^1.4.1"
- fs-constants "^1.0.0"
- inherits "^2.0.3"
- readable-stream "^3.1.1"
-
-tar@^4.0.2:
- version "4.4.19"
- resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3"
- integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==
- dependencies:
- chownr "^1.1.4"
- fs-minipass "^1.2.7"
- minipass "^2.9.0"
- minizlib "^1.3.3"
- mkdirp "^0.5.5"
- safe-buffer "^5.2.1"
- yallist "^3.1.1"
-
-tar@^6.2.0:
- version "6.2.1"
- resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
- integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
- dependencies:
- chownr "^2.0.0"
- fs-minipass "^2.0.0"
- minipass "^5.0.0"
- minizlib "^2.1.1"
- mkdirp "^1.0.3"
- yallist "^4.0.0"
-
telejson@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/telejson/-/telejson-7.2.0.tgz#3994f6c9a8f8d7f2dba9be2c7c5bbb447e876f32"
@@ -18428,13 +18829,6 @@ temp-dir@^2.0.0:
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==
-temp@^0.8.4:
- version "0.8.4"
- resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2"
- integrity sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==
- dependencies:
- rimraf "~2.6.2"
-
tempy@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.6.0.tgz#65e2c35abc06f1124a97f387b08303442bde59f3"
@@ -18554,11 +18948,6 @@ through2@~0.4.1:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
-timed-out@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
- integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==
-
timers-browserify@^2.0.12:
version "2.0.12"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee"
@@ -18642,6 +19031,11 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+toml@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee"
+ integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==
+
totalist@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
@@ -18657,14 +19051,6 @@ tough-cookie@^4.1.2, tough-cookie@^4.1.3:
universalify "^0.2.0"
url-parse "^1.5.3"
-tough-cookie@~2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
- integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
- dependencies:
- psl "^1.1.28"
- punycode "^2.1.1"
-
tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@@ -18691,16 +19077,33 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+trim-lines@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
+ integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
+
trim-newlines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
+trough@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
+ integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
+
"true-myth@^4.1.0":
version "4.1.1"
resolved "https://registry.yarnpkg.com/true-myth/-/true-myth-4.1.1.tgz#ff4ac9d5130276e34aa338757e2416ec19248ba2"
integrity sha512-rqy30BSpxPznbbTcAcci90oZ1YR4DqvKcNXNerG5gQBU2v4jk0cygheiul5J6ExIMrgDVuanv/MkGfqZbKrNNg==
+truncate-utf8-bytes@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
+ integrity sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==
+ dependencies:
+ utf8-byte-length "^1.0.1"
+
ts-api-utils@^1.0.1, ts-api-utils@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
@@ -18963,13 +19366,6 @@ typed-array-length@^1.0.6:
is-typed-array "^1.1.13"
possible-typed-array-names "^1.0.0"
-typedarray-to-buffer@^3.1.5:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
- integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
- dependencies:
- is-typedarray "^1.0.0"
-
typeforce@^1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
@@ -19042,9 +19438,9 @@ ua-parser-js@^1.0.37:
integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==
ufo@^1.4.0, ufo@^1.5.3:
- version "1.5.3"
- resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344"
- integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"
+ integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==
uglify-js@^3.1.4:
version "3.17.4"
@@ -19072,11 +19468,6 @@ uint8arrays@^3.0.0, uint8arrays@^3.1.0:
dependencies:
multiformats "^9.4.2"
-ultron@~1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
- integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
-
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
@@ -19143,10 +19534,18 @@ unicode-property-aliases-ecmascript@^2.0.0:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
-unicorn-magic@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4"
- integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==
+unified@^11.0.0:
+ version "11.0.5"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1"
+ integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ bail "^2.0.0"
+ devlop "^1.0.0"
+ extend "^3.0.0"
+ is-plain-obj "^4.0.0"
+ trough "^2.0.0"
+ vfile "^6.0.0"
unique-string@^2.0.0:
version "2.0.0"
@@ -19155,6 +19554,11 @@ unique-string@^2.0.0:
dependencies:
crypto-random-string "^2.0.0"
+unist-util-is@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd"
+ integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==
+
unist-util-is@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424"
@@ -19162,6 +19566,34 @@ unist-util-is@^6.0.0:
dependencies:
"@types/unist" "^3.0.0"
+unist-util-position-from-estree@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz#d94da4df596529d1faa3de506202f0c9a23f2200"
+ integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-position@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4"
+ integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-stringify-position@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2"
+ integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-visit-parents@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9"
+ integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==
+ dependencies:
+ unist-util-is "^3.0.0"
+
unist-util-visit-parents@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815"
@@ -19170,6 +19602,13 @@ unist-util-visit-parents@^6.0.0:
"@types/unist" "^3.0.0"
unist-util-is "^6.0.0"
+unist-util-visit@^1.4.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3"
+ integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==
+ dependencies:
+ unist-util-visit-parents "^2.0.0"
+
unist-util-visit@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6"
@@ -19252,6 +19691,14 @@ update-browserslist-db@^1.0.13:
escalade "^3.1.2"
picocolors "^1.0.1"
+update-browserslist-db@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5"
+ integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==
+ dependencies:
+ escalade "^3.2.0"
+ picocolors "^1.1.0"
+
uqr@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/uqr/-/uqr-0.1.2.tgz#5c6cd5dcff9581f9bb35b982cb89e2c483a41d7d"
@@ -19272,11 +19719,6 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
-url-set-query@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339"
- integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==
-
url@^0.11.0:
version "0.11.3"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.3.tgz#6f495f4b935de40ce4a0a52faee8954244f3d3ad"
@@ -19326,10 +19768,10 @@ utf-8-validate@^5.0.2:
dependencies:
node-gyp-build "^4.3.0"
-utf8@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
- integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
+utf8-byte-length@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e"
+ integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
@@ -19362,11 +19804,6 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
-uuid@^3.3.2:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
- integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
-
uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
@@ -19407,7 +19844,7 @@ valtio@1.11.2:
proxy-compare "2.5.1"
use-sync-external-store "1.2.0"
-varint@^5.0.0, varint@^5.0.2:
+varint@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4"
integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==
@@ -19424,7 +19861,7 @@ varuint-bitcoin@^1.1.2:
dependencies:
safe-buffer "^5.1.1"
-vary@^1, vary@~1.1.2:
+vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
@@ -19438,6 +19875,36 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
+vfile-message@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181"
+ integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+vfile@^6.0.0:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab"
+ integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile-message "^4.0.0"
+
+viem@2.12.0:
+ version "2.12.0"
+ resolved "https://registry.yarnpkg.com/viem/-/viem-2.12.0.tgz#699ba326a1ce0df81042dc8b6f22fa751f9cefce"
+ integrity sha512-XBvORspE4x2/gfy7idH6IVFwkJiXirygFCU3lxUH6fttsj8zufLtgiokfvZF/LAZUEDvdxSgL08whSYgffM2fw==
+ dependencies:
+ "@adraffy/ens-normalize" "1.10.0"
+ "@noble/curves" "1.2.0"
+ "@noble/hashes" "1.3.2"
+ "@scure/bip32" "1.3.2"
+ "@scure/bip39" "1.2.1"
+ abitype "1.0.0"
+ isows "1.0.4"
+ ws "8.13.0"
+
viem@^2.1.1:
version "2.13.8"
resolved "https://registry.yarnpkg.com/viem/-/viem-2.13.8.tgz#d6aaeecc84e5ee5cac1566f103ed4cf0335ef811"
@@ -19478,7 +19945,7 @@ warning@^4.0.3:
dependencies:
loose-envify "^1.0.0"
-watchpack@^2.2.0, watchpack@^2.4.1:
+watchpack@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff"
integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==
@@ -19486,270 +19953,6 @@ watchpack@^2.2.0, watchpack@^2.4.1:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
-wcwidth@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
- integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==
- dependencies:
- defaults "^1.0.3"
-
-web3-bzz@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.10.4.tgz#dcc787970767d9004c73d11d0eeef774ce16b880"
- integrity sha512-ZZ/X4sJ0Uh2teU9lAGNS8EjveEppoHNQiKlOXAjedsrdWuaMErBPdLQjXfcrYvN6WM6Su9PMsAxf3FXXZ+HwQw==
- dependencies:
- "@types/node" "^12.12.6"
- got "12.1.0"
- swarm-js "^0.1.40"
-
-web3-core-helpers@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.10.4.tgz#bd2b4140df2016d5dd3bb2b925fc29ad8678677c"
- integrity sha512-r+L5ylA17JlD1vwS8rjhWr0qg7zVoVMDvWhajWA5r5+USdh91jRUYosp19Kd1m2vE034v7Dfqe1xYRoH2zvG0g==
- dependencies:
- web3-eth-iban "1.10.4"
- web3-utils "1.10.4"
-
-web3-core-method@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.10.4.tgz#566b52f006d3cbb13b21b72b8d2108999bf5d6bf"
- integrity sha512-uZTb7flr+Xl6LaDsyTeE2L1TylokCJwTDrIVfIfnrGmnwLc6bmTWCCrm71sSrQ0hqs6vp/MKbQYIYqUN0J8WyA==
- dependencies:
- "@ethersproject/transactions" "^5.6.2"
- web3-core-helpers "1.10.4"
- web3-core-promievent "1.10.4"
- web3-core-subscriptions "1.10.4"
- web3-utils "1.10.4"
-
-web3-core-promievent@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.10.4.tgz#629b970b7934430b03c5033c79f3bb3893027e22"
- integrity sha512-2de5WnJQ72YcIhYwV/jHLc4/cWJnznuoGTJGD29ncFQHAfwW/MItHFSVKPPA5v8AhJe+r6y4Y12EKvZKjQVBvQ==
- dependencies:
- eventemitter3 "4.0.4"
-
-web3-core-requestmanager@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.10.4.tgz#eb1f147e6b9df84e3a37e602162f8925bdb4bb9a"
- integrity sha512-vqP6pKH8RrhT/2MoaU+DY/OsYK9h7HmEBNCdoMj+4ZwujQtw/Mq2JifjwsJ7gits7Q+HWJwx8q6WmQoVZAWugg==
- dependencies:
- util "^0.12.5"
- web3-core-helpers "1.10.4"
- web3-providers-http "1.10.4"
- web3-providers-ipc "1.10.4"
- web3-providers-ws "1.10.4"
-
-web3-core-subscriptions@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.10.4.tgz#2f4dcb404237e92802a563265d11a33934dc38e6"
- integrity sha512-o0lSQo/N/f7/L76C0HV63+S54loXiE9fUPfHFcTtpJRQNDBVsSDdWRdePbWwR206XlsBqD5VHApck1//jEafTw==
- dependencies:
- eventemitter3 "4.0.4"
- web3-core-helpers "1.10.4"
-
-web3-core@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.10.4.tgz#639de68b8b9871d2dc8892e0dd4e380cb1361a98"
- integrity sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww==
- dependencies:
- "@types/bn.js" "^5.1.1"
- "@types/node" "^12.12.6"
- bignumber.js "^9.0.0"
- web3-core-helpers "1.10.4"
- web3-core-method "1.10.4"
- web3-core-requestmanager "1.10.4"
- web3-utils "1.10.4"
-
-web3-core@^1.10.3:
- version "1.10.3"
- resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.10.3.tgz#4aeb8f4b0cb5775d9fa4edf1127864743f1c3ae3"
- integrity sha512-Vbk0/vUNZxJlz3RFjAhNNt7qTpX8yE3dn3uFxfX5OHbuon5u65YEOd3civ/aQNW745N0vGUlHFNxxmn+sG9DIw==
- dependencies:
- "@types/bn.js" "^5.1.1"
- "@types/node" "^12.12.6"
- bignumber.js "^9.0.0"
- web3-core-helpers "1.10.3"
- web3-core-method "1.10.3"
- web3-core-requestmanager "1.10.3"
- web3-utils "1.10.3"
-
-web3-eth-abi@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.10.4.tgz#16c19d0bde0aaf8c1a56cb7743a83156d148d798"
- integrity sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ==
- dependencies:
- "@ethersproject/abi" "^5.6.3"
- web3-utils "1.10.4"
-
-web3-eth-accounts@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.10.4.tgz#df30e85a7cd70e475f8cf52361befba408829e34"
- integrity sha512-ysy5sVTg9snYS7tJjxVoQAH6DTOTkRGR8emEVCWNGLGiB9txj+qDvSeT0izjurS/g7D5xlMAgrEHLK1Vi6I3yg==
- dependencies:
- "@ethereumjs/common" "2.6.5"
- "@ethereumjs/tx" "3.5.2"
- "@ethereumjs/util" "^8.1.0"
- eth-lib "0.2.8"
- scrypt-js "^3.0.1"
- uuid "^9.0.0"
- web3-core "1.10.4"
- web3-core-helpers "1.10.4"
- web3-core-method "1.10.4"
- web3-utils "1.10.4"
-
-web3-eth-contract@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.10.4.tgz#22d39f04e11d9ff4e726e8025a56d78e843a2c3d"
- integrity sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A==
- dependencies:
- "@types/bn.js" "^5.1.1"
- web3-core "1.10.4"
- web3-core-helpers "1.10.4"
- web3-core-method "1.10.4"
- web3-core-promievent "1.10.4"
- web3-core-subscriptions "1.10.4"
- web3-eth-abi "1.10.4"
- web3-utils "1.10.4"
-
-web3-eth-ens@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.10.4.tgz#3d991adac52bc8e598f1f1b8528337fa6291004c"
- integrity sha512-LLrvxuFeVooRVZ9e5T6OWKVflHPFgrVjJ/jtisRWcmI7KN/b64+D/wJzXqgmp6CNsMQcE7rpmf4CQmJCrTdsgg==
- dependencies:
- content-hash "^2.5.2"
- eth-ens-namehash "2.0.8"
- web3-core "1.10.4"
- web3-core-helpers "1.10.4"
- web3-core-promievent "1.10.4"
- web3-eth-abi "1.10.4"
- web3-eth-contract "1.10.4"
- web3-utils "1.10.4"
-
-web3-eth-iban@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.10.4.tgz#bc61b4a1930d19b1df8762c606d669902558e54d"
- integrity sha512-0gE5iNmOkmtBmbKH2aTodeompnNE8jEyvwFJ6s/AF6jkw9ky9Op9cqfzS56AYAbrqEFuClsqB/AoRves7LDELw==
- dependencies:
- bn.js "^5.2.1"
- web3-utils "1.10.4"
-
-web3-eth-personal@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.10.4.tgz#e2ee920f47e84848288e03442659cdbb2c4deea2"
- integrity sha512-BRa/hs6jU1hKHz+AC/YkM71RP3f0Yci1dPk4paOic53R4ZZG4MgwKRkJhgt3/GPuPliwS46f/i5A7fEGBT4F9w==
- dependencies:
- "@types/node" "^12.12.6"
- web3-core "1.10.4"
- web3-core-helpers "1.10.4"
- web3-core-method "1.10.4"
- web3-net "1.10.4"
- web3-utils "1.10.4"
-
-web3-eth@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.10.4.tgz#3a908c635cb5d935bd30473e452f3bd7f2ee66a5"
- integrity sha512-Sql2kYKmgt+T/cgvg7b9ce24uLS7xbFrxE4kuuor1zSCGrjhTJ5rRNG8gTJUkAJGKJc7KgnWmgW+cOfMBPUDSA==
- dependencies:
- web3-core "1.10.4"
- web3-core-helpers "1.10.4"
- web3-core-method "1.10.4"
- web3-core-subscriptions "1.10.4"
- web3-eth-abi "1.10.4"
- web3-eth-accounts "1.10.4"
- web3-eth-contract "1.10.4"
- web3-eth-ens "1.10.4"
- web3-eth-iban "1.10.4"
- web3-eth-personal "1.10.4"
- web3-net "1.10.4"
- web3-utils "1.10.4"
-
-web3-net@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.10.4.tgz#20e12c60e4477d4298979d8d5d66b9abf8e66a09"
- integrity sha512-mKINnhOOnZ4koA+yV2OT5s5ztVjIx7IY9a03w6s+yao/BUn+Luuty0/keNemZxTr1E8Ehvtn28vbOtW7Ids+Ow==
- dependencies:
- web3-core "1.10.4"
- web3-core-method "1.10.4"
- web3-utils "1.10.4"
-
-web3-providers-http@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.10.4.tgz#ca7aa58aeaf8123500c24ffe0595896319f830e8"
- integrity sha512-m2P5Idc8hdiO0l60O6DSCPw0kw64Zgi0pMjbEFRmxKIck2Py57RQMu4bxvkxJwkF06SlGaEQF8rFZBmuX7aagQ==
- dependencies:
- abortcontroller-polyfill "^1.7.5"
- cross-fetch "^4.0.0"
- es6-promise "^4.2.8"
- web3-core-helpers "1.10.4"
-
-web3-providers-ipc@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.10.4.tgz#2e03437909e4e7771d646ff05518efae44b783c3"
- integrity sha512-YRF/bpQk9z3WwjT+A6FI/GmWRCASgd+gC0si7f9zbBWLXjwzYAKG73bQBaFRAHex1hl4CVcM5WUMaQXf3Opeuw==
- dependencies:
- oboe "2.1.5"
- web3-core-helpers "1.10.4"
-
-web3-providers-ws@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.10.4.tgz#55d0c3ba36c6a79d105f02e20a707eb3978e7f82"
- integrity sha512-j3FBMifyuFFmUIPVQR4pj+t5ILhAexAui0opgcpu9R5LxQrLRUZxHSnU+YO25UycSOa/NAX8A+qkqZNpcFAlxA==
- dependencies:
- eventemitter3 "4.0.4"
- web3-core-helpers "1.10.4"
- websocket "^1.0.32"
-
-web3-shh@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.10.4.tgz#9852d6f3d05678e31e49235a60fea10ca7a9e21d"
- integrity sha512-cOH6iFFM71lCNwSQrC3niqDXagMqrdfFW85hC9PFUrAr3PUrIem8TNstTc3xna2bwZeWG6OBy99xSIhBvyIACw==
- dependencies:
- web3-core "1.10.4"
- web3-core-method "1.10.4"
- web3-core-subscriptions "1.10.4"
- web3-net "1.10.4"
-
-web3-utils@1.10.4:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec"
- integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==
- dependencies:
- "@ethereumjs/util" "^8.1.0"
- bn.js "^5.2.1"
- ethereum-bloom-filters "^1.0.6"
- ethereum-cryptography "^2.1.2"
- ethjs-unit "0.1.6"
- number-to-bn "1.7.0"
- randombytes "^2.1.0"
- utf8 "3.0.0"
-
-web3-utils@^1.10.3:
- version "1.10.3"
- resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.3.tgz#f1db99c82549c7d9f8348f04ffe4e0188b449714"
- integrity sha512-OqcUrEE16fDBbGoQtZXWdavsPzbGIDc5v3VrRTZ0XrIpefC/viZ1ZU9bGEemazyS0catk/3rkOOxpzTfY+XsyQ==
- dependencies:
- "@ethereumjs/util" "^8.1.0"
- bn.js "^5.2.1"
- ethereum-bloom-filters "^1.0.6"
- ethereum-cryptography "^2.1.2"
- ethjs-unit "0.1.6"
- number-to-bn "1.7.0"
- randombytes "^2.1.0"
- utf8 "3.0.0"
-
-web3@^1.10.3:
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/web3/-/web3-1.10.4.tgz#5d5e59b976eaf758b060fe1a296da5fe87bdc79c"
- integrity sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA==
- dependencies:
- web3-bzz "1.10.4"
- web3-core "1.10.4"
- web3-eth "1.10.4"
- web3-eth-personal "1.10.4"
- web3-net "1.10.4"
- web3-shh "1.10.4"
- web3-utils "1.10.4"
-
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
@@ -19828,21 +20031,20 @@ webpack-virtual-modules@^0.6.1:
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz#ac6fdb9c5adb8caecd82ec241c9631b7a3681b6f"
integrity sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==
-webpack@5, webpack@^5.88.2:
- version "5.91.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9"
- integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==
+webpack@5, webpack@^5.94.0:
+ version "5.94.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
+ integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
dependencies:
- "@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.5"
"@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.12.1"
"@webassemblyjs/wasm-parser" "^1.12.1"
acorn "^8.7.1"
- acorn-import-assertions "^1.9.0"
+ acorn-import-attributes "^1.9.5"
browserslist "^4.21.10"
chrome-trace-event "^1.0.2"
- enhanced-resolve "^5.16.0"
+ enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"
@@ -19880,18 +20082,6 @@ websocket-extensions@>=0.1.1:
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
-websocket@^1.0.32:
- version "1.0.35"
- resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.35.tgz#374197207d7d4cc4c36cbf8a1bb886ee52a07885"
- integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==
- dependencies:
- bufferutil "^4.0.1"
- debug "^2.2.0"
- es5-ext "^0.10.63"
- typedarray-to-buffer "^3.1.5"
- utf-8-validate "^5.0.2"
- yaeti "^0.0.6"
-
whatwg-encoding@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
@@ -20194,7 +20384,7 @@ workbox-window@7.0.0:
"@types/trusted-types" "^2.0.2"
workbox-core "7.0.0"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -20212,6 +20402,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@@ -20258,16 +20457,12 @@ ws@8.5.0:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
-ws@^3.0.0:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
- integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==
- dependencies:
- async-limiter "~1.0.0"
- safe-buffer "~5.1.0"
- ultron "~1.1.0"
+ws@^7.2.0, ws@^7.3.1, ws@^7.5.1:
+ version "7.5.9"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
+ integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
-ws@^7.2.0, ws@^7.3.1, ws@^7.4.5, ws@^7.5.1:
+ws@^7.5.10:
version "7.5.10"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
@@ -20290,36 +20485,6 @@ xcode@^3.0.1:
simple-plist "^1.1.0"
uuid "^7.0.3"
-xhr-request-promise@^0.1.2:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c"
- integrity sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==
- dependencies:
- xhr-request "^1.1.0"
-
-xhr-request@^1.0.1, xhr-request@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed"
- integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==
- dependencies:
- buffer-to-arraybuffer "^0.0.5"
- object-assign "^4.1.1"
- query-string "^5.0.1"
- simple-get "^2.7.0"
- timed-out "^4.0.1"
- url-set-query "^1.0.0"
- xhr "^2.0.4"
-
-xhr@^2.0.4, xhr@^2.3.3:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d"
- integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==
- dependencies:
- global "~4.4.0"
- is-function "^1.0.1"
- parse-headers "^2.0.0"
- xtend "^4.0.0"
-
xml-name-validator@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
@@ -20353,7 +20518,7 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
-xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
+xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
@@ -20375,12 +20540,7 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-yaeti@^0.0.6:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
- integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==
-
-yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1:
+yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
@@ -20395,10 +20555,15 @@ yaml@^1.10.0, yaml@^1.10.2:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+yaml@^2.0.0:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130"
+ integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==
+
yaml@^2.2.2:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.2.tgz#7a2b30f2243a5fc299e1f14ca58d475ed4bc5362"
- integrity sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.0.tgz#14059ad9d0b1680d0f04d3a60fe00f3a857303c3"
+ integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==
yargs-parser@^18.1.2:
version "18.1.3"
@@ -20466,7 +20631,12 @@ yocto-queue@^1.0.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
-zodiac-roles-deployments@^2.2.2:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/zodiac-roles-deployments/-/zodiac-roles-deployments-2.2.2.tgz#feb7e7544398e1572d2f7fa6ff2be033f2c736ce"
- integrity sha512-6nG6/AuJh9SIrXR1NieRzSfn1+J6k9p2mb3qns3cRYhx3+i4wWIRh1JcE+jueHSYo+H7yKG/jfwRXMVN1L938A==
+zodiac-roles-deployments@^2.2.5:
+ version "2.2.5"
+ resolved "https://registry.yarnpkg.com/zodiac-roles-deployments/-/zodiac-roles-deployments-2.2.5.tgz#4739e54b82d48212711c16c2c4d9b185ea4cf6fe"
+ integrity sha512-fv09LzKoL996W0pY7T2Na2hE3v5VQPQGN7/UckL+aTyBZRAyjz4ZKHbdQDLUzQAzp7l+6PC4JB2WnpeDanC3Hg==
+
+zwitch@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
+ integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==