diff --git a/.env.example b/.env.example index ab53f2b047..a4f3101a82 100644 --- a/.env.example +++ b/.env.example @@ -38,8 +38,8 @@ 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= diff --git a/.github/workflows/build/action.yml b/.github/workflows/build/action.yml index 6b8ef9d45e..ba6ad94d7e 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 }} diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index e7d985b475..b6f304330a 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.5.1 + uses: contributor-assistant/github-action@v2.5.2 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/lint.yml b/.github/workflows/lint.yml index 1f43f7b418..bdbe5727fa 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/README.md b/README.md index 4ca7cf6970..f890fa7578 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 diff --git a/cypress.config.js b/cypress.config.js index b476d90a2d..f04876345f 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,9 +1,13 @@ 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', trashAssetsBeforeRuns: true, @@ -18,6 +22,20 @@ export default defineConfig({ 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) { @@ -34,6 +52,8 @@ export default defineConfig({ } } }) + + return config }, env: { ...process.env, diff --git a/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js b/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js index 21fea79984..f0236a53a0 100644 --- a/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js +++ b/cypress/e2e/happypath/sendfunds_connected_wallet.cy.js @@ -48,7 +48,7 @@ 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_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) main.addToLocalStorage( constants.localStorageKeys.SAFE_v2__tokenlist_onboarding, ls.cookies.acceptedTokenListOnboarding, diff --git a/cypress/e2e/happypath/sendfunds_queue_1.cy.js b/cypress/e2e/happypath/sendfunds_queue_1.cy.js index d7cbd3f490..b1f6e1bd42 100644 --- a/cypress/e2e/happypath/sendfunds_queue_1.cy.js +++ b/cypress/e2e/happypath/sendfunds_queue_1.cy.js @@ -54,7 +54,7 @@ function executeTransactionFlow(fromSafe) { describe('Send funds from queue happy path tests 1', () => { before(async () => { cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) main.addToLocalStorage( constants.localStorageKeys.SAFE_v2__tokenlist_onboarding, ls.cookies.acceptedTokenListOnboarding, diff --git a/cypress/e2e/happypath/sendfunds_relay.cy.js b/cypress/e2e/happypath/sendfunds_relay.cy.js index e32a05f45a..4aaf53648f 100644 --- a/cypress/e2e/happypath/sendfunds_relay.cy.js +++ b/cypress/e2e/happypath/sendfunds_relay.cy.js @@ -49,7 +49,7 @@ function visit(url) { describe('Send funds with relay happy path tests', { defaultCommandTimeout: 300000 }, () => { before(async () => { cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) main.addToLocalStorage( constants.localStorageKeys.SAFE_v2__tokenlist_onboarding, ls.cookies.acceptedTokenListOnboarding, 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 cd7154b31a..39c322ccee 100644 --- a/cypress/e2e/happypath/tx_history_filter_hp_2.cy.js +++ b/cypress/e2e/happypath/tx_history_filter_hp_2.cy.js @@ -9,7 +9,7 @@ let staticSafes = [] describe('Tx history happy path tests 2', () => { before(async () => { cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) main.addToLocalStorage( constants.localStorageKeys.SAFE_v2__tokenlist_onboarding, ls.cookies.acceptedTokenListOnboarding, diff --git a/cypress/e2e/pages/create_tx.pages.js b/cypress/e2e/pages/create_tx.pages.js index 24fc822604..315211344b 100644 --- a/cypress/e2e/pages/create_tx.pages.js +++ b/cypress/e2e/pages/create_tx.pages.js @@ -59,6 +59,7 @@ 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"` @@ -484,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') @@ -505,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') diff --git a/cypress/e2e/pages/create_wallet.pages.js b/cypress/e2e/pages/create_wallet.pages.js index 508733ce49..1c79e430c6 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}`) } diff --git a/cypress/e2e/pages/main.page.js b/cypress/e2e/pages/main.page.js index 5643720ef9..18c4a546aa 100644 --- a/cypress/e2e/pages/main.page.js +++ b/cypress/e2e/pages/main.page.js @@ -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/navigation.page.js b/cypress/e2e/pages/navigation.page.js index 5e5bd0b41c..18971fd50c 100644 --- a/cypress/e2e/pages/navigation.page.js +++ b/cypress/e2e/pages/navigation.page.js @@ -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/prodhealthcheck/add_owner.cy.js b/cypress/e2e/prodhealthcheck/add_owner.cy.js index 23695d0165..636ba0ed65 100644 --- a/cypress/e2e/prodhealthcheck/add_owner.cy.js +++ b/cypress/e2e/prodhealthcheck/add_owner.cy.js @@ -8,7 +8,7 @@ let staticSafes = [] const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS')) const signer = walletCredentials.OWNER_4_PRIVATE_KEY -describe('Add Owners tests', () => { +describe('[PROD] Add Owners tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/create_tx.cy.js b/cypress/e2e/prodhealthcheck/create_tx.cy.js index 9e36b14b7a..d28755c251 100644 --- a/cypress/e2e/prodhealthcheck/create_tx.cy.js +++ b/cypress/e2e/prodhealthcheck/create_tx.cy.js @@ -18,7 +18,7 @@ function happyPathToStepTwo() { createtx.clickOnNextBtn() } -describe.skip('Create transactions tests', () => { +describe.skip('[PROD] Create transactions tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/load_safe.cy.js b/cypress/e2e/prodhealthcheck/load_safe.cy.js index c9e78740ca..a04a88c235 100644 --- a/cypress/e2e/prodhealthcheck/load_safe.cy.js +++ b/cypress/e2e/prodhealthcheck/load_safe.cy.js @@ -21,7 +21,7 @@ const INVALID_ADDRESS_ERROR_MSG = 'Address given is not a valid Safe address' const OWNER_ENS_DEFAULT_NAME = 'test20.eth' const OWNER_ADDRESS = constants.EOA -describe('Load Safe tests', () => { +describe('[PROD] Load Safe tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/messages_onchain.cy.js b/cypress/e2e/prodhealthcheck/messages_onchain.cy.js index 1db0a3c90a..221036d5b5 100644 --- a/cypress/e2e/prodhealthcheck/messages_onchain.cy.js +++ b/cypress/e2e/prodhealthcheck/messages_onchain.cy.js @@ -8,7 +8,7 @@ let staticSafes = [] const typeMessagesOnchain = msg_data.type.onChain -describe('Onchain Messages tests', () => { +describe('[PROD] Onchain Messages tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/nfts.cy.js b/cypress/e2e/prodhealthcheck/nfts.cy.js index 8056c7ac37..da80bca2ae 100644 --- a/cypress/e2e/prodhealthcheck/nfts.cy.js +++ b/cypress/e2e/prodhealthcheck/nfts.cy.js @@ -16,7 +16,7 @@ let nftsSafes, const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS')) const signer = walletCredentials.OWNER_4_PRIVATE_KEY -describe.skip('NFTs tests', () => { +describe.skip('[PROD] NFTs tests', () => { before(() => { getSafes(CATEGORIES.nfts) .then((nfts) => { diff --git a/cypress/e2e/prodhealthcheck/recovery.cy.js b/cypress/e2e/prodhealthcheck/recovery.cy.js index 071b6c4858..e201a66a3b 100644 --- a/cypress/e2e/prodhealthcheck/recovery.cy.js +++ b/cypress/e2e/prodhealthcheck/recovery.cy.js @@ -6,7 +6,7 @@ import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js' let recoverySafes, staticSafes = [] -describe('Production recovery health check tests', { defaultCommandTimeout: 50000 }, () => { +describe('[PROD] Production recovery health check tests', { defaultCommandTimeout: 50000 }, () => { before(() => { getSafes(CATEGORIES.recovery) .then((recoveries) => { diff --git a/cypress/e2e/prodhealthcheck/remove_owner.cy.js b/cypress/e2e/prodhealthcheck/remove_owner.cy.js index f13a21feab..4c888a993b 100644 --- a/cypress/e2e/prodhealthcheck/remove_owner.cy.js +++ b/cypress/e2e/prodhealthcheck/remove_owner.cy.js @@ -10,7 +10,7 @@ let staticSafes = [] const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS')) const signer = walletCredentials.OWNER_4_PRIVATE_KEY -describe('Remove Owners tests', () => { +describe('[PROD] Remove Owners tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/sidebar.cy.js b/cypress/e2e/prodhealthcheck/sidebar.cy.js index 00d642eb0d..9eafb95451 100644 --- a/cypress/e2e/prodhealthcheck/sidebar.cy.js +++ b/cypress/e2e/prodhealthcheck/sidebar.cy.js @@ -9,7 +9,7 @@ let staticSafes = [] const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS')) const signer = walletCredentials.OWNER_4_PRIVATE_KEY -describe('Sidebar tests', () => { +describe('[PROD] Sidebar tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/sidebar_3.cy.js b/cypress/e2e/prodhealthcheck/sidebar_3.cy.js index 28c051d9b6..f5af8a1d01 100644 --- a/cypress/e2e/prodhealthcheck/sidebar_3.cy.js +++ b/cypress/e2e/prodhealthcheck/sidebar_3.cy.js @@ -12,7 +12,7 @@ const signer = walletCredentials.OWNER_4_PRIVATE_KEY const signer1 = walletCredentials.OWNER_1_PRIVATE_KEY const signer2 = walletCredentials.OWNER_3_PRIVATE_KEY -describe.skip('Sidebar tests 3', () => { +describe.skip('[PROD] Sidebar tests 3', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/spending_limits.cy.js b/cypress/e2e/prodhealthcheck/spending_limits.cy.js index a8e4a8364f..5c25c2386f 100644 --- a/cypress/e2e/prodhealthcheck/spending_limits.cy.js +++ b/cypress/e2e/prodhealthcheck/spending_limits.cy.js @@ -15,7 +15,7 @@ const tokenAmount = 0.1 const newTokenAmount = 0.001 const spendingLimitBalance = '(0.15 ETH)' -describe('Spending limits tests', () => { +describe('[PROD] Spending limits tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/swaps_history_2.cy.js b/cypress/e2e/prodhealthcheck/swaps_history_2.cy.js index 845964ea2a..aef8ec2253 100644 --- a/cypress/e2e/prodhealthcheck/swaps_history_2.cy.js +++ b/cypress/e2e/prodhealthcheck/swaps_history_2.cy.js @@ -9,7 +9,7 @@ let staticSafes = [] const swapsHistory = swaps_data.type.history -describe('Swaps history tests 2', () => { +describe('[PROD] Swaps history tests 2', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/swaps_tokens.cy.js b/cypress/e2e/prodhealthcheck/swaps_tokens.cy.js index 83eee82426..3762501087 100644 --- a/cypress/e2e/prodhealthcheck/swaps_tokens.cy.js +++ b/cypress/e2e/prodhealthcheck/swaps_tokens.cy.js @@ -11,7 +11,7 @@ const signer = walletCredentials.OWNER_4_PRIVATE_KEY let iframeSelector = `iframe[src*="${constants.swapWidget}"]` -describe('[SMOKE] Swaps token tests', () => { +describe('[PROD] Swaps token tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) diff --git a/cypress/e2e/prodhealthcheck/tokens.cy.js b/cypress/e2e/prodhealthcheck/tokens.cy.js index b55b0b13c5..ed585c51ed 100644 --- a/cypress/e2e/prodhealthcheck/tokens.cy.js +++ b/cypress/e2e/prodhealthcheck/tokens.cy.js @@ -9,7 +9,7 @@ const FIAT_AMOUNT_COLUMN = 2 let staticSafes = [] -describe('Prod tokens tests', () => { +describe('[PROD] Prod tokens tests', () => { const fiatRegex = assets.fiatRegex before(async () => { diff --git a/cypress/e2e/prodhealthcheck/tx_history.cy.js b/cypress/e2e/prodhealthcheck/tx_history.cy.js index 77482c11f7..2f22b4bd6d 100644 --- a/cypress/e2e/prodhealthcheck/tx_history.cy.js +++ b/cypress/e2e/prodhealthcheck/tx_history.cy.js @@ -14,7 +14,7 @@ const typeDeleteAllowance = data.type.deleteSpendingLimit const typeSideActions = data.type.sideActions const typeGeneral = data.type.general -describe('Tx history tests 1', () => { +describe('[PROD] Tx history tests 1', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) @@ -27,7 +27,7 @@ describe('Tx history tests 1', () => { 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_offset=7200000&trusted=false&cursor=limit=100&offset=1` + 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') diff --git a/cypress/e2e/prodhealthcheck/tx_history_2.cy.js b/cypress/e2e/prodhealthcheck/tx_history_2.cy.js index aa7bd17692..691fc91324 100644 --- a/cypress/e2e/prodhealthcheck/tx_history_2.cy.js +++ b/cypress/e2e/prodhealthcheck/tx_history_2.cy.js @@ -17,7 +17,7 @@ const typeSideActions = data.type.sideActions const typeGeneral = data.type.general const typeUntrustedToken = data.type.untrustedReceivedToken -describe('Tx history tests 2', () => { +describe('[PROD] Tx history tests 2', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) }) @@ -30,7 +30,7 @@ describe('Tx history tests 2', () => { 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_offset=7200000&trusted=false&cursor=limit=100&offset=1` + 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') diff --git a/cypress/e2e/regression/add_owner.cy.js b/cypress/e2e/regression/add_owner.cy.js index 617eb43903..a544c76021 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 () => { @@ -60,4 +64,41 @@ describe('Add Owners tests', () => { owner.clickOnNextBtn() owner.verifyConfirmTransactionWindowDisplayed() }) + + it('Verify creation, confirmation and deletion of Add owner tx. GA tx_confirm', () => { + 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(2) + 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/create_safe_cf.cy.js b/cypress/e2e/regression/create_safe_cf.cy.js index 874c635496..0ab54755d1 100644 --- a/cypress/e2e/regression/create_safe_cf.cy.js +++ b/cypress/e2e/regression/create_safe_cf.cy.js @@ -40,10 +40,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', () => { @@ -88,7 +88,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', () => { @@ -154,8 +154,9 @@ describe('CF Safe regression tests', () => { 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_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 6fd0b63522..2156f218e7 100644 --- a/cypress/e2e/regression/create_safe_simple.cy.js +++ b/cypress/e2e/regression/create_safe_simple.cy.js @@ -134,8 +134,7 @@ describe('Safe creation tests', () => { main.addToLocalStorage(constants.localStorageKeys.SAFE_v2__addressBook, ls.addressBookData.sameOwnerName), ) .then(() => { - createwallet.clickOnContinueWithWalletBtn() - createwallet.clickOnCreateNewSafeBtn() + createwallet.waitForConnectionMsgDisappear() createwallet.clickOnNextBtn() createwallet.clickOnAddNewOwnerBtn() createwallet.clickOnSignerAddressInput(1) diff --git a/cypress/e2e/regression/create_tx.cy.js b/cypress/e2e/regression/create_tx.cy.js index 5ffc74b51b..ed5ef64c34 100644 --- a/cypress/e2e/regression/create_tx.cy.js +++ b/cypress/e2e/regression/create_tx.cy.js @@ -39,7 +39,6 @@ describe('Create transactions tests', () => { createtx.changeNonce(14) cy.wait(1000) createtx.clickOnSignTransactionBtn() - createtx.waitForProposeRequest() createtx.clickViewTransaction() createtx.verifySingleTxPage() createtx.verifyQueueLabel() diff --git a/cypress/e2e/regression/recovery.cy.js b/cypress/e2e/regression/recovery.cy.js index 8b5d9260d1..37d585cc90 100644 --- a/cypress/e2e/regression/recovery.cy.js +++ b/cypress/e2e/regression/recovery.cy.js @@ -147,7 +147,7 @@ describe('Recovery regression tests', { defaultCommandTimeout: 50000 }, () => { recovery.enterRecovererAddress(constants.SEPOLIA_OWNER_2) recovery.agreeToTerms() recovery.clickOnNextBtn() - navigation.clickOnModalCloseBtn() + navigation.clickOnModalCloseBtn(0) recovery.getSetupRecoveryBtn() navigation.clickOnWalletExpandMoreIcon() navigation.clickOnDisconnectBtn() diff --git a/cypress/e2e/regression/replace_owner.cy.js b/cypress/e2e/regression/replace_owner.cy.js index ec4e6cd875..e996ba60e2 100644 --- a/cypress/e2e/regression/replace_owner.cy.js +++ b/cypress/e2e/regression/replace_owner.cy.js @@ -5,10 +5,12 @@ 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' @@ -76,8 +78,16 @@ describe('Replace Owners tests', () => { owner.verifyErrorMsgInvalidAddress(constants.addressBookErrrMsg.alreadyAdded) }) - // TODO: Flaky. Fix ProposeTx request - it.skip("Verify 'Replace' tx is created", () => { + 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_4.slice(6), + }, + ] cy.visit(constants.setupUrl + staticSafes.SEP_STATIC_SAFE_4) wallet.connectSigner(signer) owner.waitForConnectionStatus() @@ -88,8 +98,9 @@ describe('Replace Owners tests', () => { createTx.changeNonce(2) owner.clickOnNextBtn() createTx.clickOnSignTransactionBtn() - createTx.waitForProposeRequest() createTx.clickViewTransaction() createTx.verifyReplacedSigner(ownerName) + getEvents() + checkDataLayerEvents(tx_created) }) }) diff --git a/cypress/e2e/regression/swaps.cy.js b/cypress/e2e/regression/swaps.cy.js index 35883ea1ab..bad6689cbe 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 @@ -197,4 +201,59 @@ describe('Swaps tests', () => { }) }, ) + + 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.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() + }) + 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_2.cy.js b/cypress/e2e/regression/swaps_history_2.cy.js index 03e113208b..ca71f2d32c 100644 --- a/cypress/e2e/regression/swaps_history_2.cy.js +++ b/cypress/e2e/regression/swaps_history_2.cy.js @@ -25,14 +25,7 @@ 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 @@ -70,7 +63,6 @@ describe('Swaps history tests 2', () => { eq, swapsHistory.cow, swapsHistory.cancelled, - swapsHistory.gGpV2, ]) }) @@ -133,7 +125,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/tokens.cy.js b/cypress/e2e/regression/tokens.cy.js index 374d398189..44580c8c2e 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 @@ -17,8 +18,13 @@ describe('Tokens tests', () => { }) beforeEach(() => { cy.visit(constants.BALANCE_URL + staticSafes.SEP_STATIC_SAFE_2) - cy.clearLocalStorage() - main.acceptCookies() + cy.clearLocalStorage().then(() => { + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) + main.addToLocalStorage( + constants.localStorageKeys.SAFE_v2__tokenlist_onboarding, + ls.cookies.acceptedTokenListOnboarding, + ) + }) }) // TODO: Added to prod diff --git a/cypress/e2e/regression/tx_history.cy.js b/cypress/e2e/regression/tx_history.cy.js index eb154b0d6a..d528a9a925 100644 --- a/cypress/e2e/regression/tx_history.cy.js +++ b/cypress/e2e/regression/tx_history.cy.js @@ -27,7 +27,7 @@ describe('Tx history tests 1', () => { 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_offset=7200000&trusted=false&cursor=limit=100&offset=1` + 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') diff --git a/cypress/e2e/regression/tx_history_2.cy.js b/cypress/e2e/regression/tx_history_2.cy.js index 409250449f..4d36ce8c02 100644 --- a/cypress/e2e/regression/tx_history_2.cy.js +++ b/cypress/e2e/regression/tx_history_2.cy.js @@ -30,7 +30,7 @@ describe('Tx history tests 2', () => { 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_offset=7200000&trusted=false&cursor=limit=100&offset=1` + 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') diff --git a/cypress/e2e/safe-apps/apps_list.cy.js b/cypress/e2e/safe-apps/apps_list.cy.js index eb98aea2d4..46e3e44a9b 100644 --- a/cypress/e2e/safe-apps/apps_list.cy.js +++ b/cypress/e2e/safe-apps/apps_list.cy.js @@ -16,7 +16,7 @@ describe('Safe Apps list tests', () => { beforeEach(() => { cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) }) cy.visit(`${constants.appsUrl}?safe=${staticSafes.SEP_STATIC_SAFE_1}`, { failOnStatusCode: false, diff --git a/cypress/e2e/safe-apps/drain_account.spec.cy.js b/cypress/e2e/safe-apps/drain_account.spec.cy.js index ecc989d2c1..749e34094a 100644 --- a/cypress/e2e/safe-apps/drain_account.spec.cy.js +++ b/cypress/e2e/safe-apps/drain_account.spec.cy.js @@ -13,7 +13,7 @@ describe('Drain Account tests', () => { before(async () => { safeAppSafes = await getSafes(CATEGORIES.safeapps) cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) main.addToLocalStorage( constants.localStorageKeys.SAFE_v2__SafeApps__infoModal, ls.appPermissions(constants.safeTestAppurl).infoModalAccepted, @@ -69,7 +69,7 @@ describe('Drain Account tests', () => { 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/preview_drawer.cy.js b/cypress/e2e/safe-apps/preview_drawer.cy.js index 86018f1dd7..debbe9b314 100644 --- a/cypress/e2e/safe-apps/preview_drawer.cy.js +++ b/cypress/e2e/safe-apps/preview_drawer.cy.js @@ -10,7 +10,7 @@ describe('Preview drawer tests', () => { before(async () => { staticSafes = await getSafes(CATEGORIES.static) cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) }) }) diff --git a/cypress/e2e/safe-apps/safe_permissions.cy.js b/cypress/e2e/safe-apps/safe_permissions.cy.js index 037bd374a3..8aa4cd31b3 100644 --- a/cypress/e2e/safe-apps/safe_permissions.cy.js +++ b/cypress/e2e/safe-apps/safe_permissions.cy.js @@ -6,7 +6,7 @@ import * as ls from '../../support/localstorage_data.js' describe('Safe permissions system tests', () => { before(async () => { cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) }) }) diff --git a/cypress/e2e/safe-apps/tx-builder.spec.cy.js b/cypress/e2e/safe-apps/tx-builder.spec.cy.js index 3f4ae3822e..699518dfa5 100644 --- a/cypress/e2e/safe-apps/tx-builder.spec.cy.js +++ b/cypress/e2e/safe-apps/tx-builder.spec.cy.js @@ -1,35 +1,44 @@ 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' let safeAppSafes = [] let iframeSelector -describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { +const walletCredentials = JSON.parse(Cypress.env('CYPRESS_WALLET_CREDENTIALS')) +const signer = walletCredentials.OWNER_4_PRIVATE_KEY +const signer2 = walletCredentials.OWNER_1_PRIVATE_KEY + +describe.skip('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { before(async () => { safeAppSafes = await getSafes(CATEGORIES.safeapps) - cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) - main.addToLocalStorage( + }) + + beforeEach(() => { + cy.clearLocalStorage() + cy.clearCookies() + cy.window().then((win) => { + win.localStorage.setItem(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) + win.localStorage.setItem( constants.localStorageKeys.SAFE_v2__SafeApps__infoModal, ls.appPermissions(constants.safeTestAppurl).infoModalAccepted, ) }) - }) - 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 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() @@ -74,7 +83,7 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { }) cy.get('h4').contains(safeapps.transactionBuilderStr).should('be.visible') cy.findAllByText(safeapps.testBooleanValue).should('have.length', 6) - navigation.clickOnModalCloseBtn() + 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) @@ -115,7 +124,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) @@ -137,7 +146,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) @@ -193,7 +202,7 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { }) cy.get('h4').contains(safeapps.transactionBuilderStr).should('be.visible') cy.findAllByText(safeapps.testAddressValueStr).should('have.length', 4) - navigation.clickOnModalCloseBtn() + 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) @@ -304,4 +313,55 @@ describe('Transaction Builder tests', { defaultCommandTimeout: 20000 }, () => { getBody().findByText(safeapps.failedStr).should('be.visible') }) }) + + // TODO: Fix visibility element + it.skip('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_modal.cy.js b/cypress/e2e/safe-apps/tx_modal.cy.js index d2b9078157..003e147a38 100644 --- a/cypress/e2e/safe-apps/tx_modal.cy.js +++ b/cypress/e2e/safe-apps/tx_modal.cy.js @@ -10,7 +10,7 @@ const confirmTx = 'Confirm transaction' describe('Transaction modal tests', () => { before(async () => { cy.clearLocalStorage().then(() => { - main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies_1_1, ls.cookies.acceptedCookies) + main.addToLocalStorage(constants.localStorageKeys.SAFE_v2_cookies, ls.cookies.acceptedCookies) main.addToLocalStorage( constants.localStorageKeys.SAFE_v2__SafeApps__infoModal, ls.appPermissions(constants.safeTestAppurl).infoModalAccepted, diff --git a/cypress/e2e/smoke/create_safe_cf.cy.js b/cypress/e2e/smoke/create_safe_cf.cy.js index d7fc4cafa4..346d3ca5d0 100644 --- a/cypress/e2e/smoke/create_safe_cf.cy.js +++ b/cypress/e2e/smoke/create_safe_cf.cy.js @@ -3,6 +3,7 @@ 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')) // DO NOT use OWNER_2_PRIVATE_KEY for safe creation. Used for CF safes. @@ -13,8 +14,10 @@ describe('[SMOKE] CF Safe creation tests', () => { cy.visit(constants.welcomeUrl + '?chain=sep') 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() @@ -23,6 +26,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/fixtures/safes/static.json b/cypress/fixtures/safes/static.json index ab7c42513c..83fb7ab50f 100644 --- a/cypress/fixtures/safes/static.json +++ b/cypress/fixtures/safes/static.json @@ -23,5 +23,6 @@ "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_23": "sep:0x589d862CE2d519d5A862066bB923da0564c3D2EA", + "SEP_STATIC_SAFE_24": "sep:0x49DC5764961DA4864DC5469f16BC68a0F765f2F2" } diff --git a/cypress/support/constants.js b/cypress/support/constants.js index 6b5f294dab..b49298c6b3 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -228,13 +228,15 @@ 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_1_1: 'SAFE_v2__cookies_terms_v1.1', + 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', diff --git a/cypress/support/localstorage_data.js b/cypress/support/localstorage_data.js index 9407a1fb30..7b0ac24805 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: { @@ -379,7 +390,7 @@ export const addressBookData = { '0xc2F3645bfd395516d1a18CA6ad9298299d328C01': 'Safe 27', }, }, - cookies: { necessary: true, updates: true, analytics: true }, + cookies: cookieState, } export const safeSettings = { @@ -673,7 +684,7 @@ export const appPermissions = (url) => ({ }) export const cookies = { - acceptedCookies: { necessary: true, updates: true, analytics: true }, + acceptedCookies: cookieState, acceptedTokenListOnboarding: true, } diff --git a/cypress/support/utils/gtag.js b/cypress/support/utils/gtag.js new file mode 100644 index 0000000000..fe6e9fd07d --- /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 c4845de5ff..844501610b 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 8daee1aa2d..957b229f10 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() } }) diff --git a/docs/update-terms.md b/docs/update-terms.md new file mode 100644 index 0000000000..5e69fac010 --- /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 8eb1dc0edf..ebf77f7fc0 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 702bee6130..3a44ca67c0 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 0000000000..cadf08493b --- /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 4f11a03dc6..a4a7b3f5cf 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 f3fc7f9e89..b867dc0049 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 05376d0080..109e72d88c 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.41.5", + "version": "1.43.1", "type": "module", "scripts": { "dev": "next dev", @@ -57,13 +57,13 @@ "@safe-global/api-kit": "^2.4.4", "@safe-global/protocol-kit": "^4.1.0", "@safe-global/safe-apps-sdk": "^9.1.0", - "@safe-global/safe-deployments": "^1.37.3", - "@safe-global/safe-gateway-typescript-sdk": "3.22.1", - "@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.13", + "@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.11.2", @@ -72,33 +72,36 @@ "@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.10", "papaparse": "^5.3.2", "qrcode.react": "^3.1.0", "react": "^18.3.1", - "react-dom": "^18.2.0", + "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": "^9.1.2", - "semver": "^7.5.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", + "@next/mdx": "^14.2.11", "@openzeppelin/contracts": "^4.9.6", "@safe-global/safe-core-sdk-types": "^5.0.1", "@sentry/types": "^7.74.0", @@ -121,14 +124,15 @@ "@types/jest": "^29.5.4", "@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.3.4", - "@types/react-dom": "^18.2.24", + "@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", @@ -141,17 +145,22 @@ "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", "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.0", "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/public/images/common/recovery_coincover.svg b/public/images/common/recovery_coincover.svg deleted file mode 100644 index 94303918f6..0000000000 --- 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 b13483a96f..2644602e59 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 0000000000..2cff53958a --- /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 0000000000..41469d1e83 --- /dev/null +++ b/public/images/common/stake.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/transactions/blockaid-icon.svg b/public/images/transactions/blockaid-icon.svg new file mode 100644 index 0000000000..7c66a098f9 --- /dev/null +++ b/public/images/transactions/blockaid-icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/balances/AssetsTable/index.tsx b/src/components/balances/AssetsTable/index.tsx index c04d795040..077bfed573 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 && } ), diff --git a/src/components/common/BlockedAddress/index.tsx b/src/components/common/BlockedAddress/index.tsx index 4e2dc6d8ee..3d271d4228 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/ChainSwitcher/index.tsx b/src/components/common/ChainSwitcher/index.tsx index 8c70c4f4ae..f0955d13c4 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 ( - ) } diff --git a/src/components/common/CheckWallet/index.test.tsx b/src/components/common/CheckWallet/index.test.tsx index 7172f1e920..35378993b9 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) => }) +// mock useIsWrongChain +jest.mock('@/hooks/useIsWrongChain', () => ({ + __esModule: true, + default: jest.fn(() => false), +})) + +const renderButton = () => + render({(isOk) => }) 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 9da8c9700e..b354fb8f56 100644 --- a/src/components/common/CheckWallet/index.tsx +++ b/src/components/common/CheckWallet/index.tsx @@ -1,9 +1,11 @@ +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 = { @@ -11,6 +13,7 @@ type CheckWalletProps = { allowSpendingLimit?: boolean allowNonOwner?: boolean noTooltip?: boolean + checkNetwork?: boolean } enum Message { @@ -19,17 +22,28 @@ enum Message { 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)) && !isCounterfactualMultiSig + wallet && + (isSafeOwner || allowNonOwner || (isSpendingLimit && allowSpendingLimit) || isDelegate) && + !isCounterfactualMultiSig ? '' : !wallet ? Message.WalletNotConnected @@ -37,8 +51,8 @@ const CheckWallet = ({ children, allowSpendingLimit, allowNonOwner, noTooltip }: ? 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 81bb623685..9cabe3c161 100644 --- a/src/components/common/Chip/index.tsx +++ b/src/components/common/Chip/index.tsx @@ -1,8 +1,33 @@ -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 } from '@mui/material' -export function Chip(props: ChipProps): ReactElement { - return +type ChipProps = { + label?: string + color?: 'primary' | 'secondary' | 'info' | 'warning' | 'success' | 'error' +} + +export function Chip({ color = 'primary', label = 'New' }: ChipProps) { + return ( + + {label} + + } + /> + ) } diff --git a/src/components/common/CookieAndTermBanner/index.tsx b/src/components/common/CookieAndTermBanner/index.tsx index b9b6a5fbb1..e8f650276b 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()) } @@ -75,9 +87,10 @@ export const CookieAndTermBanner = ({ By browsing this page, you accept our{' '} - Terms & Conditions (last updated August 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 + 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 @@ -139,8 +152,8 @@ const CookieBannerPopup = (): ReactElement | null => { 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) { @@ -156,5 +169,4 @@ const CookieBannerPopup = (): ReactElement | null => { ) : null } - export default CookieBannerPopup diff --git a/src/components/common/Header/index.tsx b/src/components/common/Header/index.tsx index 629c62f399..cfa880fd6b 100644 --- a/src/components/common/Header/index.tsx +++ b/src/components/common/Header/index.tsx @@ -8,10 +8,9 @@ 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' @@ -22,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> @@ -37,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) diff --git a/src/components/common/SafeTokenWidget/__tests__/SafeTokenWidget.test.tsx b/src/components/common/SafeTokenWidget/__tests__/SafeTokenWidget.test.tsx index 49090fab72..b92a675f2e 100644 --- a/src/components/common/SafeTokenWidget/__tests__/SafeTokenWidget.test.tsx +++ b/src/components/common/SafeTokenWidget/__tests__/SafeTokenWidget.test.tsx @@ -5,8 +5,10 @@ import SafeTokenWidget from '..' import { toBeHex } from 'ethers' import { AppRoutes } from '@/config/routes' import useSafeTokenAllocation, { useSafeVotingPower } from '@/hooks/useSafeTokenAllocation' +import * as safePass from '@/store/safePass' +import type { CampaignLeaderboardEntry } from '@/store/safePass' -jest.mock('@/hooks/useChainId', () => 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 79c4aa10fb..ee8a9869d1 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 && ( - - - - )} diff --git a/src/components/common/WalletProvider/index.tsx b/src/components/common/WalletProvider/index.tsx index f1c360c68d..6e14266d1a 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 a324af6889..3c5969d784 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/Assets/index.tsx b/src/components/dashboard/Assets/index.tsx index 8df89c1644..12aca61614 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 }) => ( { return ( - {isMultisigExecutionInfo(transaction.executionInfo) && transaction.executionInfo.nonce} + + + {isMultisigExecutionInfo(transaction.executionInfo) && transaction.executionInfo.nonce} + - - - - - + + + - + + + + - + {isMultisigExecutionInfo(transaction.executionInfo) && ( 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 @@ -42,13 +46,17 @@ const Dashboard = (): ReactElement => { {safe.deployed && ( <> - - - + {isSwapFeatureEnabled && ( + + + + )} - - - + {isSAPBannerEnabled && ( + + + + )} diff --git a/src/components/new-safe/create/NetworkWarning/index.tsx b/src/components/new-safe/create/NetworkWarning/index.tsx index 1a7aa01567..d0a87b5c72 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/steps/ReviewStep/index.test.tsx b/src/components/new-safe/create/steps/ReviewStep/index.test.tsx index d369861243..23a7a67032 100644 --- a/src/components/new-safe/create/steps/ReviewStep/index.test.tsx +++ b/src/components/new-safe/create/steps/ReviewStep/index.test.tsx @@ -24,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() }) diff --git a/src/components/new-safe/create/steps/ReviewStep/index.tsx b/src/components/new-safe/create/steps/ReviewStep/index.tsx index 846813120d..dacaf084c3 100644 --- a/src/components/new-safe/create/steps/ReviewStep/index.tsx +++ b/src/components/new-safe/create/steps/ReviewStep/index.tsx @@ -43,17 +43,17 @@ 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 }) => { return ( - + ≈ {totalFee} {chain?.nativeCurrency.symbol} @@ -288,7 +288,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 @@ -321,7 +321,7 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps - + {!willRelay && ( @@ -333,7 +333,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 47bb71ae83..f495e3d665 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 a831ac4d2a..62c9b1b011 100644 --- a/src/components/new-safe/create/steps/SetNameStep/index.tsx +++ b/src/components/new-safe/create/steps/SetNameStep/index.tsx @@ -122,7 +122,10 @@ function SetNameStep({ . - {isWrongChain && } + + + + diff --git a/src/components/safe-apps/AppFrame/index.tsx b/src/components/safe-apps/AppFrame/index.tsx index b2e1155ea6..de48f57e03 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 && ( { - 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) => (
  • { const { safe, safeLoaded } = useSafeInfo() @@ -40,6 +42,8 @@ export const PushNotifications = (): ReactElement => { 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 +62,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 +120,17 @@ export const PushNotifications = (): ReactElement => { {safeLoaded ? ( <> +
    - + {(isOk) => ( { 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 })) } } diff --git a/src/components/sidebar/SidebarList/index.tsx b/src/components/sidebar/SidebarList/index.tsx index 409275b249..ae5e59742f 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 ea7778a259..1dfc50a6ac 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 1a5632c803..c5980ba59a 100644 --- a/src/components/sidebar/SidebarNavigation/index.tsx +++ b/src/components/sidebar/SidebarNavigation/index.tsx @@ -24,6 +24,8 @@ const getSubdirectory = (pathname: string): string => { return pathname.split('/')[1] } +const geoBlockedRoutes = [AppRoutes.swap, AppRoutes.stake] + const Navigation = (): ReactElement => { const chain = useCurrentChain() const router = useRouter() @@ -31,14 +33,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]) @@ -76,14 +78,15 @@ const Navigation = (): ReactElement => { return ( handleNavigationClick(item.href)} + key={item.href} > {item.icon && {item.icon}} diff --git a/src/components/transactions/HexEncodedData/HexEncodedData.test.tsx b/src/components/transactions/HexEncodedData/HexEncodedData.test.tsx new file mode 100644 index 0000000000..6aa788bcd7 --- /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 0000000000..bf3739bfc5 --- /dev/null +++ b/src/components/transactions/HexEncodedData/__snapshots__/HexEncodedData.test.tsx.snap @@ -0,0 +1,187 @@ +// 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... + + +
    +
    +
    +
    +`; + +exports[`HexEncodedData should render the default component markup 1`] = ` +
    +
    +
    +

    + Data (hex-encoded) +

    +
    +
    +
    + + + + + 0xed2ad31e + + d00088fc64... + + +
    +
    +
    +
    +`; diff --git a/src/components/transactions/HexEncodedData/index.tsx b/src/components/transactions/HexEncodedData/index.tsx index 5b54aca3ed..fe635c5dcf 100644 --- a/src/components/transactions/HexEncodedData/index.tsx +++ b/src/components/transactions/HexEncodedData/index.tsx @@ -8,13 +8,14 @@ import FieldsGrid from '@/components/tx/FieldsGrid' interface Props { hexData: string + highlightFirstBytes?: boolean title?: string limit?: number } const FIRST_BYTES = 10 -export const HexEncodedData = ({ hexData, title, limit = 20 }: Props): ReactElement => { +export const HexEncodedData = ({ hexData, title, highlightFirstBytes = true, limit = 20 }: Props): ReactElement => { const [showTxData, setShowTxData] = useState(false) const showExpandBtn = hexData.length > limit @@ -22,12 +23,12 @@ export const HexEncodedData = ({ hexData, title, limit = 20 }: Props): ReactElem setShowTxData((val) => !val) } - const firstBytes = ( + const firstBytes = highlightFirstBytes ? ( {hexData.slice(0, FIRST_BYTES)} - ) - const restBytes = hexData.slice(FIRST_BYTES) + ) : null + const restBytes = highlightFirstBytes ? hexData.slice(FIRST_BYTES) : hexData const content = ( @@ -37,7 +38,13 @@ export const HexEncodedData = ({ hexData, title, limit = 20 }: Props): ReactElem {firstBytes} {showTxData || !showExpandBtn ? restBytes : shortenText(restBytes, limit - FIRST_BYTES)}{' '} {showExpandBtn && ( - + Show {showTxData ? 'less' : 'more'} )} diff --git a/src/components/transactions/MaliciousTxWarning/index.tsx b/src/components/transactions/MaliciousTxWarning/index.tsx index adbb973fb7..a77b14d725 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/TrustedToggle/TrustedToggleButton.tsx b/src/components/transactions/TrustedToggle/TrustedToggleButton.tsx index c5bc393a8d..918d64d91f 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/TxDetails/Summary/TxDataRow/index.tsx b/src/components/transactions/TxDetails/Summary/TxDataRow/index.tsx index cba0c13711..f0b68a02b2 100644 --- a/src/components/transactions/TxDetails/Summary/TxDataRow/index.tsx +++ b/src/components/transactions/TxDetails/Summary/TxDataRow/index.tsx @@ -40,7 +40,7 @@ export const generateDataRowValue = ( ) case 'bytes': - return + return default: return {value} } diff --git a/src/components/transactions/TxDetails/Summary/index.tsx b/src/components/transactions/TxDetails/Summary/index.tsx index 45c92e1b25..bd87099a66 100644 --- a/src/components/transactions/TxDetails/Summary/index.tsx +++ b/src/components/transactions/TxDetails/Summary/index.tsx @@ -2,13 +2,14 @@ import type { ReactElement } from 'react' import React, { useState } from 'react' 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 @@ -30,6 +31,8 @@ const Summary = ({ txDetails, defaultExpanded = false }: Props): ReactElement => refundReceiver = detailedExecutionInfo.refundReceiver?.value } + const isCustom = isCustomTxInfo(txDetails.txInfo) + return ( <> {txHash && ( @@ -67,6 +70,12 @@ const Summary = ({ txDetails, defaultExpanded = false }: Props): ReactElement => {expanded && ( + {!isCustom && ( + + + + )} + {`${txData.operation} (${Operation[txData.operation].toLowerCase()})`} diff --git a/src/components/transactions/TxDetails/TxData/DecodedData/ValueArray/index.tsx b/src/components/transactions/TxDetails/TxData/DecodedData/ValueArray/index.tsx index 408aa7b40a..8bc19b7cf5 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/Transfer/index.test.tsx b/src/components/transactions/TxDetails/TxData/Transfer/index.test.tsx index d904372959..630e6f8f99 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 a19e55bc69..2026fa11b2 100644 --- a/src/components/transactions/TxDetails/TxData/index.tsx +++ b/src/components/transactions/TxDetails/TxData/index.tsx @@ -1,11 +1,15 @@ 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, isMultisigDetailedExecutionInfo, + isOrderTxInfo, isSettingsChangeTxInfo, isSpendingLimitMethod, + isStakingTxDepositInfo, isSupportedSpendingLimitAddress, isTransferTxInfo, } from '@/utils/transaction-guards' @@ -16,6 +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 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, @@ -30,6 +38,22 @@ const TxData = ({ 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 } diff --git a/src/components/transactions/TxDetails/index.tsx b/src/components/transactions/TxDetails/index.tsx index 1d9a4febf4..1387362d0b 100644 --- a/src/components/transactions/TxDetails/index.tsx +++ b/src/components/transactions/TxDetails/index.tsx @@ -33,7 +33,6 @@ 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' @@ -77,14 +76,6 @@ const TxDetailsBlock = ({ txSummary, txDetails }: TxDetailsProps): ReactElement <> {/* /Details */}
    - {isOrderTxInfo(txDetails.txInfo) && ( -
    - Error parsing data
    }> - - -
    - )} -
    diff --git a/src/components/transactions/TxDetails/styles.module.css b/src/components/transactions/TxDetails/styles.module.css index 9d9e8de2b4..5c3bc427dc 100644 --- a/src/components/transactions/TxDetails/styles.module.css +++ b/src/components/transactions/TxDetails/styles.module.css @@ -22,22 +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); } @@ -59,8 +48,7 @@ padding: 0 var(--space-1); } -.multiSend, -.swapOrder { +.multiSend { border-bottom: 1px solid var(--color-border-light); } diff --git a/src/components/transactions/TxInfo/index.tsx b/src/components/transactions/TxInfo/index.tsx index 82d759beb0..c75d7320d7 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, @@ -90,18 +98,18 @@ export const TransferTx = ({ } const CustomTx = ({ info }: { info: Custom }): ReactElement => { - return <>{info.methodName} + return {info.methodName} } const CreationTx = ({ info }: { info: Creation }): ReactElement => { - return <>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' : ''}`} - + ) } @@ -110,9 +118,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 <> } @@ -129,10 +136,6 @@ const TxInfo = ({ info, ...rest }: { info: TransactionInfo; omitSign?: boolean; return } - if (isCustomTxInfo(info)) { - return - } - if (isCreationTxInfo(info)) { return } @@ -141,6 +144,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 0000000000..5e6635d9b5 --- /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 c9bb49c874..339236fb75 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/Warning/index.tsx b/src/components/transactions/Warning/index.tsx index d5123dac47..049ab9842a 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/styles.module.css b/src/components/tx-flow/common/TxLayout/styles.module.css index f8dfb36dfd..5344a505cb 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/flows/ExecuteBatch/ReviewBatch.tsx b/src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx index cd30e55d6d..6739daad92 100644 --- a/src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx +++ b/src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx @@ -1,5 +1,4 @@ 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' @@ -13,7 +12,6 @@ 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' @@ -40,6 +38,7 @@ 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) @@ -110,8 +109,6 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => { overrides.nonce = userNonce - await assertWalletChain(onboard, safe.chainId) - await dispatchBatchExecution( txsWithDetails, multiSendContract, @@ -193,7 +190,7 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => { - + {canRelay ? ( <> @@ -227,7 +224,7 @@ export const ReviewBatch = ({ params }: { params: ExecuteBatchFlowProps }) => { - + {(isOk) => ( + + {(isOk) => ( + + )} + diff --git a/src/components/tx-flow/flows/SignMessage/index.tsx b/src/components/tx-flow/flows/SignMessage/index.tsx index ac9503ef59..6c608ba339 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.tsx b/src/components/tx-flow/flows/SignMessageOnChain/ReviewSignMessageOnChain.tsx index 5817b8ef76..1cc055aa17 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' @@ -112,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)) diff --git a/src/components/tx-flow/flows/TokenTransfer/ReviewSpendingLimitTx.tsx b/src/components/tx-flow/flows/TokenTransfer/ReviewSpendingLimitTx.tsx index 277de59cc1..8c28146da8 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,8 @@ import { type SubmitCallback } from '@/components/tx/SignOrExecuteForm' import { TX_EVENTS, TX_TYPES } from '@/services/analytics/events/transactions' import { isWalletRejection } from '@/utils/wallets' import { safeParseUnits } from '@/utils/formatters' +import CheckWallet from '@/components/common/CheckWallet' +import NetworkWarning from '@/components/new-safe/create/NetworkWarning' export type SpendingLimitTxParams = { safeAddress: string @@ -103,7 +103,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) @@ -140,7 +139,7 @@ const ReviewSpendingLimitTx = ({ - + {submitError && ( Error submitting the transaction. Please try again. @@ -153,9 +152,13 @@ const ReviewSpendingLimitTx = ({ - + + {(isOk) => ( + + )} + diff --git a/src/components/tx/AdvancedParams/AdvancedParamsForm.tsx b/src/components/tx/AdvancedParams/AdvancedParamsForm.tsx index 9e1ae1c838..a01ea2c64a 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/ConfirmationOrder/ConfirmationOrderHeader.tsx b/src/components/tx/ConfirmationOrder/ConfirmationOrderHeader.tsx new file mode 100644 index 0000000000..89b09f0fd3 --- /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 0000000000..33142de06f --- /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/index.tsx b/src/components/tx/DecodedTx/index.tsx index ac3ddfcd8e..44d8c3087a 100644 --- a/src/components/tx/DecodedTx/index.tsx +++ b/src/components/tx/DecodedTx/index.tsx @@ -41,9 +41,9 @@ const DecodedTx = ({ showMultisend = true, showMethodCall = false, }: DecodedTxProps): ReactElement => { - const isMultisend = !!decodedData?.parameters?.[0]?.valueDecoded const chainId = useChainId() - const isMethodCallInAdvanced = !showMethodCall || isMultisend + const isMultisend = !!decodedData?.parameters?.[0]?.valueDecoded + const isMethodCallInAdvanced = !showMethodCall || (isMultisend && showMultisend) const { data: txDetails, diff --git a/src/components/tx/FieldsGrid/index.tsx b/src/components/tx/FieldsGrid/index.tsx index 36c1e4f475..ba0a4785db 100644 --- a/src/components/tx/FieldsGrid/index.tsx +++ b/src/components/tx/FieldsGrid/index.tsx @@ -4,7 +4,7 @@ import { Grid, Typography } from '@mui/material' const minWidth = { xl: '25%', lg: '100px' } const wrap = { flexWrap: { xl: 'nowrap' } } -const FieldsGrid = ({ title, children }: { title: string; children: ReactNode }) => { +const FieldsGrid = ({ title, children }: { title: string | ReactNode; children: ReactNode }) => { return ( diff --git a/src/components/tx/GasParams/index.tsx b/src/components/tx/GasParams/index.tsx index 9aaabf6a94..3510cb1d88 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/SignOrExecuteForm/DelegateForm.tsx b/src/components/tx/SignOrExecuteForm/DelegateForm.tsx new file mode 100644 index 0000000000..3e3f8f24d7 --- /dev/null +++ b/src/components/tx/SignOrExecuteForm/DelegateForm.tsx @@ -0,0 +1,119 @@ +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, +}: 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 { + await signDelegateTx(safeTx) + } 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 ( +
    + + You are creating this transaction as a delegate. It will not have any signatures until it is confirmed by an + owner. + + + {isRejectedByUser && ( + + + + )} + + + + + + {/* Submit button */} + + {(isOk) => ( + + )} + + + + + ) +} + +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 9d4d4f862a..522fd3c771 100644 --- a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx +++ b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx @@ -188,14 +188,14 @@ export const ExecuteForm = ({ {/* Submit button */} - + {(isOk) => ( diff --git a/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/index.tsx b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/index.tsx index 8a4fd9047f..30078edaad 100644 --- a/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/index.tsx +++ b/src/components/tx/SignOrExecuteForm/ExecuteThroughRoleForm/index.tsx @@ -28,7 +28,6 @@ import useOnboard from '@/hooks/wallets/useOnboard' import useWallet from '@/hooks/wallets/useWallet' import useSafeInfo from '@/hooks/useSafeInfo' import { assertOnboard, assertWallet } from '@/utils/helpers' -import { assertWalletChain } from '@/services/tx/tx-sender/sdk' import { dispatchModuleTxExecution } from '@/services/tx/tx-sender' import { Status } from 'zodiac-roles-deployments' @@ -93,8 +92,6 @@ export const ExecuteThroughRoleForm = ({ assertWallet(wallet) assertOnboard(onboard) - await assertWalletChain(onboard, chainId) - setIsRejectedByUser(false) setIsPending(true) setSubmitError(undefined) @@ -227,7 +224,7 @@ export const ExecuteThroughRoleForm = ({ {/* Submit button, also available to non-owner role members */} - + {(isOk) => (
    }> {isApproval && } @@ -167,12 +167,13 @@ export const SignOrExecuteForm = ({ txId={props.txId} decodedData={decodedData} showMultisend={!props.isBatch} - showMethodCall={props.showMethodCall && !showTxDetails && !isSwapOrder && !isApproval} + showMethodCall={ + props.showMethodCall && !showTxDetails && !isApproval && isGenericConfirmation(decodedData) + } /> )} - - {!isCounterfactualSafe && !props.isRejection && } + {!isCounterfactualSafe && !props.isRejection && } {!isCounterfactualSafe && !props.isRejection && } @@ -189,20 +190,20 @@ export const SignOrExecuteForm = ({ )} - {(canExecute || canExecuteThroughRole) && !props.onlyExecute && !isCounterfactualSafe && ( + {(canExecute || canExecuteThroughRole) && !props.onlyExecute && !isCounterfactualSafe && !isDelegate && ( )} - + - + - {isCounterfactualSafe && ( + {isCounterfactualSafe && !isDelegate && ( )} - {!isCounterfactualSafe && willExecute && ( + {!isCounterfactualSafe && willExecute && !isDelegate && ( )} {!isCounterfactualSafe && willExecuteThroughRole && ( @@ -214,7 +215,7 @@ export const SignOrExecuteForm = ({ role={(allowingRole || mostLikelyRole)!} /> )} - {!isCounterfactualSafe && !willExecute && !willExecuteThroughRole && ( + {!isCounterfactualSafe && !willExecute && !willExecuteThroughRole && !isDelegate && ( )} + + {isDelegate && } ) diff --git a/src/components/tx/WrongChainWarning/index.tsx b/src/components/tx/WrongChainWarning/index.tsx deleted file mode 100644 index 35cc18f9a1..0000000000 --- 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 f78c9fde22..92a16d0342 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 2d6a50c4be..71bce871cb 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 0000000000..e006c222ad --- /dev/null +++ b/src/components/tx/security/blockaid/BlockaidHint.tsx @@ -0,0 +1,14 @@ +import { type SecuritySeverity } from '@/services/security/modules/types' +import { List, ListItem, Typography } from '@mui/material' + +export const BlockaidHint = ({ severity, warnings }: { severity: SecuritySeverity; 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 0000000000..5f7d407c5d --- /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) => (_url: string) => { + 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 0000000000..f1c18f9736 --- /dev/null +++ b/src/components/tx/security/blockaid/index.tsx @@ -0,0 +1,228 @@ +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, isRiskIgnored } = + useContext(TxSecurityContext) + const { severity, warnings, 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 75fee7dc1c..da6d72406f 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 0000000000..5ea657de25 --- /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 8030ec9ec4..0000000000 --- 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 99c6b410bf..0000000000 --- a/src/components/tx/security/redefine/index.tsx +++ /dev/null @@ -1,207 +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]) - - if (!severityProps && !isLoading && !error) { - return null - } - - 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{' '} -
- Redefine logo -
-
-
- -
- {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 e77f7cc919..0000000000 --- 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 d1f6d3b620..0000000000 --- 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 62f267b83b..edcaa570ae 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/utils.ts b/src/components/tx/security/utils.ts new file mode 100644 index 0000000000..cf4b91fa88 --- /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/config/constants.ts b/src/config/constants.ts index b8a20080f1..f1039e0143 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -63,6 +63,7 @@ export enum SafeAppsTag { SAFE_GOVERNANCE_APP = 'safe-governance-app', WALLET_CONNECT = 'wallet-connect', ONRAMP = 'onramp', + RECOVERY_SYGNUM = 'recovery-sygnum', } // Help Center @@ -101,9 +102,9 @@ 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' diff --git a/src/config/routes.ts b/src/config/routes.ts index 909e34db7a..8f65fa5bb7 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -4,6 +4,7 @@ export const AppRoutes = { wc: '/wc', terms: '/terms', swap: '/swap', + stake: '/stake', privacy: '/privacy', licenses: '/licenses', index: '/', diff --git a/src/features/counterfactual/ActivateAccountButton.tsx b/src/features/counterfactual/ActivateAccountButton.tsx index d98b890913..7b2c4efa43 100644 --- a/src/features/counterfactual/ActivateAccountButton.tsx +++ b/src/features/counterfactual/ActivateAccountButton.tsx @@ -28,7 +28,7 @@ const ActivateAccountButton = () => { {(isOk) => ( + + {(isOk) => ( + + )} +
diff --git a/src/features/counterfactual/CounterfactualForm.tsx b/src/features/counterfactual/CounterfactualForm.tsx index cf20d005e5..de779ab68c 100644 --- a/src/features/counterfactual/CounterfactualForm.tsx +++ b/src/features/counterfactual/CounterfactualForm.tsx @@ -9,7 +9,6 @@ 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' @@ -84,7 +83,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 }) @@ -179,7 +177,7 @@ export const CounterfactualForm = ({ {/* Submit button */} - + {(isOk) => ( ) : ( - + - + )} diff --git a/src/features/recovery/components/RecoverySigners/index.tsx b/src/features/recovery/components/RecoverySigners/index.tsx index 2e428a07a0..599023a839 100644 --- a/src/features/recovery/components/RecoverySigners/index.tsx +++ b/src/features/recovery/components/RecoverySigners/index.tsx @@ -13,6 +13,7 @@ import { formatDateTime } from '@/utils/date' import type { RecoveryQueueItem } from '@/features/recovery/services/recovery-state' import txSignersCss from '@/components/transactions/TxSigners/styles.module.css' +import NetworkWarning from '@/components/new-safe/create/NetworkWarning' export function RecoverySigners({ item }: { item: RecoveryQueueItem }): ReactElement { const { isExecutable, isExpired, isNext, remainingSeconds } = useRecoveryTxState(item) @@ -70,6 +71,8 @@ export function RecoverySigners({ item }: { item: RecoveryQueueItem }): ReactEle {isNext && }
+ + diff --git a/src/features/recovery/hooks/useIsRecoverySupported.ts b/src/features/recovery/hooks/useIsRecoverySupported.ts index 099578df0c..549070dea7 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/speedup/components/SpeedUpModal.tsx b/src/features/speedup/components/SpeedUpModal.tsx index 6f1a0eca19..50a641b1b1 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,7 +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 @@ -91,7 +92,6 @@ export const SpeedUpModal = ({ try { setWaitingForConfirmation(true) - await assertWalletChain(onboard, chainInfo.chainId) if (pendingTx.txType === PendingTxType.SAFE_TX) { await dispatchSafeTxSpeedUp( @@ -101,6 +101,7 @@ export const SpeedUpModal = ({ chainInfo.chainId, wallet.address, safeAddress, + safeTx.data.nonce, ) const { data: details } = await trigger({ chainId: chainInfo.chainId, txId }) const txType = getTransactionTrackingType(details) @@ -114,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' }) @@ -193,15 +195,28 @@ export const SpeedUpModal = ({ /> )} + + + - + + {(isOk) => ( + + )} + diff --git a/src/features/speedup/utils/__tests__/IsSpeedableTx.test.ts b/src/features/speedup/utils/__tests__/IsSpeedableTx.test.ts index a8089531f3..9f3f84add3 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 0000000000..5dae2afa21 --- /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 0000000000..efe0b38228 --- /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) => ( + + + + )} + + ) +} + +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 0000000000..b42b388f44 --- /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 0000000000..bf523741c3 --- /dev/null +++ b/src/features/stake/components/StakingConfirmationTx/Deposit.tsx @@ -0,0 +1,105 @@ +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, + NativeStakingStatus, +} from '@safe-global/safe-gateway-typescript-sdk' +import ConfirmationOrderHeader from '@/components/tx/ConfirmationOrder/ConfirmationOrderHeader' +import { formatDurationFromSeconds, 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 + + 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 + + + } + > + {order.fee}% + + + + {isOrder ? ( + + You will own{' '} + + {order.numValidators} Ethereum validator{order.numValidators === 1 ? '' : 's'} + + + ) : ( + {order.numValidators} + )} + + {!isOrder && order.status === NativeStakingStatus.VALIDATION_STARTED ? null : ( + {formatDurationFromSeconds(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 0000000000..d05f3c6c59 --- /dev/null +++ b/src/features/stake/components/StakingConfirmationTx/Exit.tsx @@ -0,0 +1,68 @@ +import { Alert, Stack, Typography } from '@mui/material' +import FieldsGrid from '@/components/tx/FieldsGrid' +import type { StakingTxExitInfo } from '@safe-global/safe-gateway-typescript-sdk' +import { formatDurationFromSeconds } 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 | StakingTxExitInfo +} + +const StakingConfirmationTxExit = ({ order }: StakingOrderConfirmationViewProps) => { + const withdrawIn = formatDurationFromSeconds(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 0000000000..8f1325d3df --- /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, + 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 0000000000..c1309fccb4 --- /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 0000000000..d8ae9ab853 --- /dev/null +++ b/src/features/stake/components/StakingStatus/index.tsx @@ -0,0 +1,77 @@ +import { NativeStakingExitStatus, 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 SignatureIcon from '@/public/images/common/document_signature.svg' +import TxStatusChip, { type TxStatusChipProps } from '@/components/transactions/TxStatusChip' + +const ColorIcons: Record< + NativeStakingStatus | NativeStakingExitStatus, + | { + color: TxStatusChipProps['color'] + icon?: React.ComponentType + text: string + } + | undefined +> = { + [NativeStakingStatus.AWAITING_ENTRY]: { + color: 'info', + icon: ClockIcon, + text: 'Activating', + }, + [NativeStakingStatus.REQUESTED_EXIT]: { + color: 'info', + icon: ClockIcon, + text: 'Requested exit', + }, + [NativeStakingStatus.SIGNATURE_NEEDED]: { + color: 'warning', + icon: SignatureIcon, + text: 'Signature needed', + }, + [NativeStakingStatus.AWAITING_EXECUTION]: { + color: 'warning', + icon: ClockIcon, + text: 'Awaiting execution', + }, + [NativeStakingStatus.VALIDATION_STARTED]: { + color: 'success', + icon: CheckIcon, + text: 'Validation started', + }, + [NativeStakingStatus.WITHDRAWN]: { + color: 'success', + icon: CheckIcon, + text: 'Withdrawn', + }, + [NativeStakingExitStatus.READY_TO_WITHDRAW]: { + color: 'success', + icon: CheckIcon, + text: 'Ready to withdraw', + }, + [NativeStakingExitStatus.REQUEST_PENDING]: { + color: 'info', + icon: ClockIcon, + text: 'Request pending', + }, + [NativeStakingStatus.UNKNOWN]: undefined, +} + +const capitalizedStatus = (status: string) => + status + .toLowerCase() + .replace(/_/g, ' ') + .replace(/^\w/g, (l) => l.toUpperCase()) + +const StakingStatus = ({ status }: { status: NativeStakingStatus | NativeStakingExitStatus }) => { + 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 0000000000..1a3e3c9d0a --- /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 0000000000..3ac5774bcc --- /dev/null +++ b/src/features/stake/components/StakingTxDepositInfo/index.tsx @@ -0,0 +1,17 @@ +import type { StakingTxInfo } from '@safe-global/safe-gateway-typescript-sdk' +import TokenAmount from '@/components/common/TokenAmount' + +export const StakingTxDepositInfo = ({ info }: { info: StakingTxInfo }) => { + 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 0000000000..16d3f6563e --- /dev/null +++ b/src/features/stake/components/StakingTxExitDetails/index.tsx @@ -0,0 +1,37 @@ +import { Box } from '@mui/material' +import type { StakingTxExitInfo, TransactionData } from '@safe-global/safe-gateway-typescript-sdk' +import { NativeStakingExitStatus } from '@safe-global/safe-gateway-typescript-sdk' +import FieldsGrid from '@/components/tx/FieldsGrid' +import TokenAmount from '@/components/common/TokenAmount' +import StakingStatus from '@/features/stake/components/StakingStatus' +import { formatDurationFromSeconds } from '@/utils/formatters' + +const StakingTxExitDetails = ({ info }: { info: StakingTxExitInfo; txData?: TransactionData }) => { + const withdrawIn = formatDurationFromSeconds(info.estimatedExitTime + info.estimatedWithdrawalTime, ['days', 'hours']) + return ( + + + + + + + {info.numValidators} Validator{info.numValidators > 1 ? 's' : ''} + + + {info.status !== NativeStakingExitStatus.READY_TO_WITHDRAW && ( + Up to {withdrawIn} + )} + + + + + + ) +} + +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 0000000000..60fa8ca9e0 --- /dev/null +++ b/src/features/stake/components/StakingTxExitInfo/index.tsx @@ -0,0 +1,17 @@ +import type { StakingTxInfo } from '@safe-global/safe-gateway-typescript-sdk' +import TokenAmount from '@/components/common/TokenAmount' + +const StakingTxExitInfo = ({ info }: { info: StakingTxInfo }) => { + return ( + <> + + + ) +} + +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 0000000000..78681f19d0 --- /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 0000000000..19b30b8119 --- /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 0000000000..e06c911b03 --- /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 0000000000..cc4925f5f9 --- /dev/null +++ b/src/features/stake/constants.ts @@ -0,0 +1,11 @@ +export const STAKE_TITLE = 'Stake' + +export const WIDGET_PRODUCTION_URL = 'https://safe.widget.kiln.fi/earn' +export const WIDGET_TESTNET_URL = 'https://safe.widget.testnet.kiln.fi/earn' + +export const widgetAppData = { + url: WIDGET_PRODUCTION_URL, + name: STAKE_TITLE, + iconUrl: '/images/common/stake.svg', + chainIds: ['17000', '11155111', '1', '42161', '137', '56', '8453', '10'], +} diff --git a/src/features/stake/helpers/utils.ts b/src/features/stake/helpers/utils.ts new file mode 100644 index 0000000000..5eae4b3839 --- /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 0000000000..f14c4ef1b1 --- /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 0000000000..964072036a --- /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 0000000000..4566126551 --- /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 e8a186714b..67910f4f2a 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/SwapOrderConfirmationView/OrderFeeConfirmationView.tsx b/src/features/swap/components/SwapOrderConfirmationView/OrderFeeConfirmationView.tsx index 4cc8020a89..8fcf7d3db9 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 8c34512b69..2c986286b5 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 7f5e9b29db..06d9b077a4 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 d42d876498..15d02327a7 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 5dd2ad6751..0000000000 --- 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 c432fab029..c06651486e 100644 --- a/src/features/swap/components/SwapTxInfo/SwapTx.tsx +++ b/src/features/swap/components/SwapTxInfo/SwapTx.tsx @@ -33,7 +33,15 @@ export const SwapTx = ({ info }: { info: Order }): ReactElement => { } return ( - + {from}  to  diff --git a/src/features/swap/components/TwapFallbackHandlerWarning/index.tsx b/src/features/swap/components/TwapFallbackHandlerWarning/index.tsx index 470bb515b4..7c9234a3c7 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/data/stablecoins.ts b/src/features/swap/helpers/data/stablecoins.ts index b818b11d60..6ce272f1d8 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/index.tsx b/src/features/swap/index.tsx index 1960b2761d..9ac20f238d 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,14 +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 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, @@ -81,7 +80,6 @@ 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 [recipientAddress, setRecipientAddress] = useState('') @@ -274,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) @@ -288,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/hooks/Beamer/useBeamer.ts b/src/hooks/Beamer/useBeamer.ts index 52afabba1e..f97a74b9d7 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 f8c7fd7912..99889dc11f 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)) 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__/useTxPendingStatus.test.ts b/src/hooks/__tests__/useTxPendingStatus.test.ts new file mode 100644 index 0000000000..1a3981cc02 --- /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 2b4939484d..1042a254ab 100644 --- a/src/hooks/__tests__/useTxTracking.test.ts +++ b/src/hooks/__tests__/useTxTracking.test.ts @@ -25,6 +25,7 @@ describe('useTxTracking', () => { renderHook(() => useTxTracking()) txDispatch(TxEvent.PROCESSING, { + nonce: 1, txId: '123', txHash: '0x123', signerAddress: faker.finance.ethereumAddress(), @@ -66,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/messages/useSyncSafeMessageSigner.ts b/src/hooks/messages/useSyncSafeMessageSigner.ts index a22a2014b9..cc6e4ca95f 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 f2f57ad413..900b8e18ef 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/useChains.ts b/src/hooks/useChains.ts index 669c99865a..c04e848e01 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 f4fbfd0127..25d7c5cc07 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 0000000000..74c643ce33 --- /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 88e19bcb3b..451fc41607 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' @@ -24,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 }) }, diff --git a/src/hooks/useSafeTokenEnabled.ts b/src/hooks/useSafeTokenEnabled.ts new file mode 100644 index 0000000000..de25364c60 --- /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.tsx b/src/hooks/useTransactionType.tsx index 8b891ce61a..c87da5ef5c 100644 --- a/src/hooks/useTransactionType.tsx +++ b/src/hooks/useTransactionType.tsx @@ -88,6 +88,24 @@ export const getTransactionType = (tx: TransactionSummary, addressBook: AddressB text: TWAP_ORDER_TITLE, } } + case TransactionInfoType.NATIVE_STAKING_DEPOSIT: { + return { + icon: '/images/common/stake.svg', + text: 'Stake', + } + } + case TransactionInfoType.NATIVE_STAKING_VALIDATORS_EXIT: { + return { + icon: '/images/common/stake.svg', + text: 'Withdraw request', + } + } + case TransactionInfoType.NATIVE_STAKING_WITHDRAW: { + return { + icon: '/images/common/stake.svg', + text: 'Claim', + } + } case TransactionInfoType.CUSTOM: { if (isMultiSendTxInfo(tx.txInfo) && !tx.safeAppInfo) { return { @@ -110,6 +128,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, @@ -117,12 +141,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/useTxPendingStatuses.ts b/src/hooks/useTxPendingStatuses.ts index aaffbe7613..776fce6d4f 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/wallets/__tests__/useOnboard.test.ts b/src/hooks/wallets/__tests__/useOnboard.test.ts index 7fa1602c35..a885e21afa 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 69f9146476..583976d5ee 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) diff --git a/src/markdown/terms/terms.md b/src/markdown/terms/terms.md new file mode 100644 index 0000000000..e0b9433cbf --- /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 0000000000..a89eca7930 --- /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/stake.tsx b/src/pages/stake.tsx new file mode 100644 index 0000000000..2b629a2676 --- /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 297affc64e..0a18c061b8 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 9a1d3b629a..b8dffc0304 100644 --- a/src/pages/terms.tsx +++ b/src/pages/terms.tsx @@ -1,700 +1,30 @@ 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' +import SafeTerms from '@/markdown/terms/terms.md' +import type { LinkProps as NextLinkProps } from 'next/link' +import NextLink from 'next/link' +import type { LinkProps as MUILinkProps } from '@mui/material/Link' +import MUILink from '@mui/material/Link' +import type { MDXComponents } from 'mdx/types' -const SafeTerms = () => ( -
- - Terms and Conditions - -

Last updated: August 2024.

- -

1. What is the scope of the Terms?

-
    -
  1. - 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. -
  2. -
  3. - 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. -
  4. -
  5. - 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. -
  6. -
  7. - 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. -
  8. -
- -

2. What do some of the capitalized terms mean in the Agreement?

-
    -
  1. - “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. -
  2. -
  3. - “Transaction” means a change to the data set through a new entry in the continuous Blockchain. -
  4. -
  5. - “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. -
  6. -
  7. - “Token” means a digital asset transferred in a Transaction, including ETH, ERC20, ERC721 and ERC1155 - tokens. -
  8. -
  9. - “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. -
  10. -
  11. - “Recovery Phrase” means a series of secret words used to generate one or more Private Keys and - derived Public Keys. -
  12. -
  13. - “Public Key” means a unique sequence of numbers and letters within the Blockchain to distinguish the - network participants from each other. -
  14. -
  15. - “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. -
  16. -
- -

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”
  2. -
-

- 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. -

-
    -
  1. “Safe App”
  2. -
-

- 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.{' '} -

-
    -
  1. “Third-Party Safe Apps”
  2. -
-

- 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:

-
    -
  1. - activity regulated by the Federal Financial Supervisory Authority (BaFin) or any other regulatory agency in any - jurisdiction; -
  2. -
  3. coverage underwritten by any regulatory agency’s compensation scheme;
  4. -
  5. - 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; -
  6. -
  7. the storage or transmission of fiat currencies;
  8. -
  9. - 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; -
  10. -
  11. - any form of legal, financial, investment, accounting, tax or other professional advice regarding Transactions - and their suitability to you;{' '} -
  12. -
  13. - the responsibility to monitor authorized Transactions or to check the correctness or completeness of - Transactions before you are authorizing them; -
  14. -
  15. notifications about events occurring in or connection with your Safe Account;
  16. -
  17. recovery of your Safe Account;
  18. -
  19. flagging malicious transactions;
  20. -
  21. issuance of the Safe Token and any related functionalities or reward programs.
  22. -
- -

5. What do you need to know about 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. -
  3. - 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. -
  4. -
  5. This means specifically:
  6. -
-
    -
  • - 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 Service. -
  • -
  • - We have not conducted any security audit, bug bounty or formal verification (whether internal or external) of - the 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 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 - 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. -
  • -
  • - 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 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.{' '} -
  • -
  • - 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. -
  • -
- -

6. What are the fees for the Services?

-
    -
  1. - 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. -
  2. -
  3. - The User is only entitled to offset and/or assert rights of retention if his counterclaims are legally - established, undisputed or recognized by CC. -
  4. -
- -

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. -
  3. - 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). -
  4. -
  5. - 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. -
- -

8. Are we responsible for recovering your Safe Account?

-
    -
  1. We shall not be responsible for recovering your Safe Account.
  2. -
  3. You are solely responsible for securing a back-up of your Safe Account access as you see fit.
  4. -
  5. - 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. -
  6. -
  7. The recovery feature is provided free of charge and liability is limited pursuant to Section 18 below.
  8. -
- -

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. -
  3. You are responsible for monitoring Safe Account as you see fit.
  4. -
  5. - 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. -
  6. -
- -

10. Are we responsible for flagging malicious transactions?

-
    -
  1. We shall not be responsible for flagging malicious transactions in our Safe App.
  2. -
  3. - You are solely responsible for checking any transaction, address, Token or other item you interact with via your - Smart Account in our Safe App. -
  4. -
  5. - 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. -
  6. -
- -

- 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. -
  3. - 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. -
  4. -
  5. - 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. -
  6. -
- -

12. Are we responsible for third-party content and services?

-
    -
  1. - 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, -
      -
    1. involve separate fees and charges,
    2. -
    3. include disclaimers or risk warnings,
    4. -
    5. apply a different terms and privacy policy.
    6. -
    -
  2. -
  3. - Third Party Features are provided for your convenience only. We do not verify, curate, or control Third Party - Features.{' '} -
  4. -
  5. - 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. -
  6. -
- -

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 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. -
  2. -
  3. - 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. -
  4. -
- -

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?

-
    -
  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 App or - any content from the Safe App. -
  2. -
  3. - 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.{' '} -
  4. -
- -

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. -
  3. - 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.{' '} -
  4. -
  5. The foregoing provisions will not limit CC’s liability as defined in Clause 18.
  6. -
  7. - 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. -
  8. -
  9. - 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. -
  10. -
  11. - 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. -
  12. -
- -

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. -
  3. - 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. -
  4. -
  5. You have read and understood these Terms and agree to be bound by its terms.
  6. -
  7. - 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. -
  8. -
  9. - 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. -
  10. -
  11. - You understand the functionality, usage, storage, transmission mechanisms and intricacies associated with Tokens - as well as wallet (including Safe Account) and Blockchains. -
  12. -
  13. - 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. -
  14. -
  15. - You will comply with any applicable tax obligations in your jurisdiction arising from your use of the Services. -
  16. -
  17. - 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. -
  18. -
  19. - 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. -
  20. -
  21. - 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. -
  22. -
  23. - You won’t use our Services to store, trade or transmit Tokens that are proceeds of criminal or fraudulent - activity. -
  24. -
  25. - 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. -
  26. -
  27. You are using the Services at your own risk.
  28. -
- -

18. What about our liability to you?

-
    -
  1. - 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. -
  2. -
  3. - 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 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.{' '} -
  4. -
  5. - 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. -
  6. -
  7. - The limitation of liability also applies to the personal liability of the organs, legal representatives, - employees and vicarious agents of CC. -
  8. -
  9. - 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. -
  10. -
  11. - 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. -
  12. -
- -

19. What about viruses, bugs and security vulnerabilities?

-
    -
  1. We endeavor to provide our Service free from material bugs, security vulnerabilities or viruses.
  2. -
  3. - You are responsible for configuring your information technology and computer programmes to access our Services - and to use your own virus protection software. -
  4. -
  5. If you become aware of any exploits, bugs or vulnerabilities, please inform bounty@safe.global.
  6. -
  7. - 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. -
  8. -
- -

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. -
  3. - 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: -
  4. -
-
    -
  • 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.
  • -
-
    -
  1. - 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. -
  2. -
- -

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?

-

- 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?

-

- 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?

-

- 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?

-
    -
  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. -
  3. - You shall not be entitled to assign this Agreement to any third party without our express prior written consent. -
  4. -
- -

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

-

- 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?

-

- 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?

-

- 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:{' '} - - - {HELP_CENTER_URL} - - -
  2. -
  3. - Discord:{' '} - - - {DISCORD_URL} - - -
  4. -
  5. - Twitter:{' '} - - - {TWITTER_URL} - - -
  6. -
- -

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. -

+const CustomLink: React.FC< + React.PropsWithChildren & Pick> +> = ({ href = '', as, children, ...other }) => { + const isExternal = href.toString().startsWith('http') + return ( + + + {children} + + + ) +} -

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. -

-
-) +const overrideComponents: MDXComponents = { + // @ts-expect-error + a: CustomLink, +} const Terms: NextPage = () => { return ( @@ -703,7 +33,7 @@ const Terms: NextPage = () => { {'Safe{Wallet} – Terms'} -
{IS_OFFICIAL_HOST && }
+
{IS_OFFICIAL_HOST && }
) } diff --git a/src/services/analytics/events/modals.ts b/src/services/analytics/events/modals.ts index e90fb9d334..cba11054ec 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/recovery.ts b/src/services/analytics/events/recovery.ts index b6acc7b694..478aa9313b 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/stake.ts b/src/services/analytics/events/stake.ts new file mode 100644 index 0000000000..6754939484 --- /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/tx-tracking.ts b/src/services/analytics/tx-tracking.ts index 9b74293481..b1397a6649 100644 --- a/src/services/analytics/tx-tracking.ts +++ b/src/services/analytics/tx-tracking.ts @@ -8,6 +8,7 @@ import { isCustomTxInfo, isCancellationTxInfo, isSwapOrderTxInfo, + isAnyStakingTxInfo, } from '@/utils/transaction-guards' export const getTransactionTrackingType = (details: TransactionDetails | undefined): string => { @@ -28,6 +29,10 @@ export const getTransactionTrackingType = (details: TransactionDetails | undefin 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/useGtm.ts b/src/services/analytics/useGtm.ts index 95f546f6d5..91fcffda1b 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/security/modules/BlockaidModule/index.ts b/src/services/security/modules/BlockaidModule/index.ts new file mode 100644 index 0000000000..0f85c195c1 --- /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, data } = 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 0000000000..e956e32f87 --- /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/RedefineModule/index.ts b/src/services/security/modules/RedefineModule/index.ts deleted file mode 100644 index b0d7ac93ed..0000000000 --- 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/transactions/index.tests.ts b/src/services/transactions/index.tests.ts index 7b2634e075..d0310a8db0 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 bf4d7a39b1..9712dc9d89 100644 --- a/src/services/transactions/index.ts +++ b/src/services/transactions/index.ts @@ -1,6 +1,6 @@ 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 export const getTxHistory = ( chainId: string, @@ -13,7 +13,7 @@ export const getTxHistory = ( chainId, safeAddress, { - timezone_offset: getTimezoneOffset(), // used for grouping txs by date + 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 diff --git a/src/services/tx/__tests__/txEvents.test.ts b/src/services/tx/__tests__/txEvents.test.ts index d6a11e623e..be2f36b426 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 cc46409c63..8c8847af5c 100644 --- a/src/services/tx/__tests__/txMonitor.test.ts +++ b/src/services/tx/__tests__/txMonitor.test.ts @@ -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,14 +86,14 @@ describe('txMonitor', () => { const mockFetch = jest.spyOn(global, 'fetch') - waitForRelayedTx('0x1', ['0x2'], safeAddress) + waitForRelayedTx('0x1', ['0x2'], safeAddress, 1) await act(() => { jest.advanceTimersByTime(15_000 + 1) }) expect(mockFetch).toHaveBeenCalledTimes(1) - expect(txDispatchSpy).toHaveBeenCalledWith('PROCESSED', { txId: '0x2', safeAddress }) + expect(txDispatchSpy).toHaveBeenCalledWith('PROCESSED', { txId: '0x2', safeAddress, nonce: 1 }) // The relay timeout should have been cancelled txDispatchSpy.mockClear() @@ -112,7 +113,7 @@ describe('txMonitor', () => { const mockFetch = jest.spyOn(global, 'fetch') - waitForRelayedTx('0x1', ['0x2'], safeAddress) + waitForRelayedTx('0x1', ['0x2'], safeAddress, 1) await act(() => { jest.advanceTimersByTime(15_000 + 1) @@ -120,6 +121,7 @@ describe('txMonitor', () => { expect(mockFetch).toHaveBeenCalledTimes(1) expect(txDispatchSpy).toHaveBeenCalledWith('REVERTED', { + nonce: 1, txId: '0x2', error: new Error(`Relayed transaction reverted by EVM.`), }) @@ -142,7 +144,7 @@ describe('txMonitor', () => { const mockFetch = jest.spyOn(global, 'fetch') - waitForRelayedTx('0x1', ['0x2'], safeAddress) + waitForRelayedTx('0x1', ['0x2'], safeAddress, 1) await act(() => { jest.advanceTimersByTime(15_000 + 1) @@ -150,6 +152,7 @@ describe('txMonitor', () => { expect(mockFetch).toHaveBeenCalledTimes(1) expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', { + nonce: 1, txId: '0x2', error: new Error(`Relayed transaction was blacklisted by relay provider.`), }) @@ -172,7 +175,7 @@ describe('txMonitor', () => { const mockFetch = jest.spyOn(global, 'fetch') - waitForRelayedTx('0x1', ['0x2'], safeAddress) + waitForRelayedTx('0x1', ['0x2'], safeAddress, 1) await act(() => { jest.advanceTimersByTime(15_000 + 1) @@ -180,6 +183,7 @@ describe('txMonitor', () => { expect(mockFetch).toHaveBeenCalledTimes(1) expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', { + nonce: 1, txId: '0x2', error: new Error(`Relayed transaction was cancelled by relay provider.`), }) @@ -202,7 +206,7 @@ describe('txMonitor', () => { const mockFetch = jest.spyOn(global, 'fetch') - waitForRelayedTx('0x1', ['0x2'], safeAddress) + waitForRelayedTx('0x1', ['0x2'], safeAddress, 1) await act(() => { jest.advanceTimersByTime(15_000 + 1) @@ -210,6 +214,7 @@ describe('txMonitor', () => { expect(mockFetch).toHaveBeenCalledTimes(1) expect(txDispatchSpy).toHaveBeenCalledWith('FAILED', { + nonce: 1, txId: '0x2', error: new Error(`Relayed transaction was not found.`), }) @@ -230,13 +235,14 @@ describe('txMonitor', () => { } global.fetch = jest.fn().mockImplementation(setupFetchStub(mockData)) - waitForRelayedTx('0x1', ['0x2'], safeAddress) + waitForRelayedTx('0x1', ['0x2'], safeAddress, 1) await 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 6fc690beb4..c40e6862de 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/tokenTransferParams.ts b/src/services/tx/tokenTransferParams.ts index 00fff7e8f2..e64ee02ba8 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 { sameAddress } from '@/utils/addresses' @@ -64,19 +64,23 @@ export const createNftTransferParams = ( } } -export const getNativeTransferData = (data: MetaTransactionData): DecodedDataResponse => { +export const getNativeTransferData = ({ + to, + value, +}: Pick): BaselineConfirmationView => { return { + 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 4dc35facba..b483390ad1 100644 --- a/src/services/tx/tx-sender/__tests__/ts-sender.test.ts +++ b/src/services/tx/tx-sender/__tests__/ts-sender.test.ts @@ -72,7 +72,7 @@ const mockSafeSDK = { signatures: new Map(), addSignature: jest.fn(), data: { - nonce: '1', + nonce: 1, }, })), createRejectionTransaction: jest.fn(() => ({ @@ -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 () => { @@ -445,8 +449,9 @@ describe('txSender', () => { 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 () => { @@ -475,7 +479,7 @@ describe('txSender', () => { ) 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 () => { @@ -494,8 +498,9 @@ describe('txSender', () => { 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] diff --git a/src/services/tx/tx-sender/dispatch.ts b/src/services/tx/tx-sender/dispatch.ts index 6a698e76b9..6ccfe3f826 100644 --- a/src/services/tx/tx-sender/dispatch.ts +++ b/src/services/tx/tx-sender/dispatch.ts @@ -1,4 +1,6 @@ -import { isSmartContractWallet } from '@/utils/wallets' +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, @@ -7,6 +9,7 @@ import { type TransactionDetails, } from '@safe-global/safe-gateway-typescript-sdk' import type { + SafeSignature, SafeTransaction, Transaction, TransactionOptions, @@ -19,7 +22,7 @@ import type { ContractTransactionResponse, Eip1193Provider, Overrides, Transacti 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, @@ -29,7 +32,7 @@ import { 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 { createExistingTx } from './create' @@ -75,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, }) } @@ -108,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 /** @@ -123,7 +144,7 @@ export const dispatchOnChainSigning = async ( ) => { const sdk = await getSafeSDKWithSigner(provider) const safeTxHash = await sdk.getTransactionHash(safeTx) - const eventParams = { txId } + const eventParams = { txId, nonce: safeTx.data.nonce } const options = chainId === chains.zksync ? { gasLimit: ZK_SYNC_ON_CHAIN_SIGNATURE_GAS_LIMIT } : undefined @@ -155,9 +176,10 @@ export const dispatchSafeTxSpeedUp = async ( chainId: SafeInfo['chainId'], signerAddress: string, safeAddress: string, + nonce: number, ) => { const sdk = await getSafeSDKWithSigner(provider) - const eventParams = { txId } + const eventParams = { txId, nonce } const signerNonce = txOptions.nonce const isSmartAccount = await isSmartContractWallet(chainId, signerAddress) @@ -196,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 } @@ -214,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 @@ -239,13 +255,6 @@ export const dispatchCustomTxSpeedUp = async ( txType: 'Custom', }) - 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 } @@ -262,7 +271,7 @@ export const dispatchTxExecution = async ( isSmartAccount: boolean, ): Promise => { const sdk = await getSafeSDKWithSigner(provider) - const eventParams = { txId } + const eventParams = { txId, nonce: safeTx.data.nonce } const signerNonce = txOptions.nonce ?? (await getUserNonce(signerAddress)) @@ -284,7 +293,7 @@ export const dispatchTxExecution = async ( } else { result = await sdk.executeTransaction(safeTx, txOptions) } - txDispatch(TxEvent.EXECUTING, eventParams) + txDispatch(TxEvent.EXECUTING, { ...eventParams }) } catch (error) { txDispatch(TxEvent.FAILED, { ...eventParams, error: asError(error) }) throw error @@ -292,6 +301,7 @@ export const dispatchTxExecution = async ( txDispatch(TxEvent.PROCESSING, { ...eventParams, + nonce: safeTx.data.nonce, txHash: result.hash, signerAddress, signerNonce, @@ -299,14 +309,6 @@ 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 } @@ -358,13 +360,6 @@ export const dispatchBatchExecution = async ( }) }) - 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 } @@ -521,12 +516,12 @@ 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 } } @@ -562,8 +557,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 @@ -571,6 +568,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 5b7e0aa518..ae00cebeb1 100644 --- a/src/services/tx/tx-sender/sdk.ts +++ b/src/services/tx/tx-sender/sdk.ts @@ -247,6 +247,6 @@ export const prepareApproveTxHash = async (hash: string, provider: Eip1193Provid throw new Error('Transaction hashes can only be approved by Safe owners') } - // @ts-expect-error TS2590: Expression produces a union type that is too complex to represent. + // @ts-ignore return safeContract.encode('approveHash', [hash]) } diff --git a/src/services/tx/txEvents.ts b/src/services/tx/txEvents.ts index 165ffd7ce9..562e52135c 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 2931808128..a6a82317dd 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 96837b07ef..d2b62529f4 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__/txHistorySlice.test.ts b/src/store/__tests__/txHistorySlice.test.ts index fc96f898db..f307c2f663 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 6b5ee5032b..939c720c14 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/cookiesAndTermsSlice.ts b/src/store/cookiesAndTermsSlice.ts index b63d31fd07..7f9c2a6410 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.1', - 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 index 97eafa294e..3b027245b9 100644 --- a/src/store/gateway.ts +++ b/src/store/gateway.ts @@ -3,6 +3,8 @@ 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 @@ -36,6 +38,16 @@ export const gatewayApi = createApi({ } }, }), + 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 } + } + }, + }), }), }) @@ -43,4 +55,5 @@ export const { useGetTransactionDetailsQuery, useGetMultipleTransactionDetailsQuery, useLazyGetTransactionDetailsQuery, + useGetDelegatesQuery, } = gatewayApi diff --git a/src/store/index.ts b/src/store/index.ts index 7ac81800a5..70097eec6e 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -14,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, @@ -23,6 +25,8 @@ import { 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, @@ -47,6 +51,7 @@ const rootReducer = combineReducers({ [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, }) @@ -76,6 +81,7 @@ const middleware: Middleware<{}, RootState>[] = [ broadcastState(persistedSlices), listenerMiddlewareInstance.middleware, ofacApi.middleware, + safePassApi.middleware, slices.gatewayApi.middleware, ] const listeners = [safeMessagesListener, txHistoryListener, txQueueListener, swapOrderListener, swapOrderStatusListener] @@ -90,7 +96,20 @@ export const _hydrationReducer: typeof rootReducer = (state, action) => { * * @see https://lodash.com/docs/4.17.15#merge */ - return merge({}, state, action.payload) as RootState + const nextState = merge({}, state, action.payload) as RootState + + // 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) as RootState } diff --git a/src/store/ofac.ts b/src/store/ofac.ts index 125969ff5b..0982ce5c9d 100644 --- a/src/store/ofac.ts +++ b/src/store/ofac.ts @@ -62,6 +62,7 @@ export const ofacApi = createApi({ return { error: { status: 'CUSTOM_ERROR', data: (error as Error).message } } } }, + keepUnusedDataFor: 24 * 60 * 60, // 24 hours }), }), }) diff --git a/src/store/pendingTxsSlice.ts b/src/store/pendingTxsSlice.ts index 7f35406a42..a8d249ec80 100644 --- a/src/store/pendingTxsSlice.ts +++ b/src/store/pendingTxsSlice.ts @@ -22,6 +22,7 @@ const ActivePendingStates = [PendingStatus.RELAYING, PendingStatus.INDEXING, Pen export type PendingTxCommonProps = { chainId: string safeAddress: string + nonce: number groupKey?: string } diff --git a/src/store/safePass.ts b/src/store/safePass.ts new file mode 100644 index 0000000000..875c44f9f3 --- /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/txHistorySlice.ts b/src/store/txHistorySlice.ts index 10cd8908a9..6424131da1 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(([txId, 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 3b324c2692..d9574f20cd 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 0000000000..a655859ec4 --- /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/utils/__tests__/transactions.test.ts b/src/utils/__tests__/transactions.test.ts index 52c47123ac..2fa9ae7559 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 1c06e308f1..a2efaafd7a 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') @@ -395,7 +396,7 @@ describe('tx-history-filter', () => { expect(getIncomingTransfers).toHaveBeenCalledWith( '4', '0x123', - { value: '123', executed: undefined, timezone_offset: 3600000, trusted: false, imitation: true }, + { value: '123', executed: undefined, timezone: getTimezone(), trusted: false, imitation: true }, 'pageUrl1', ) @@ -422,7 +423,7 @@ describe('tx-history-filter', () => { { execution_date__gte: '1970-01-01T00:00:00.000Z', executed: 'true', - timezone_offset: 3600000, + timezone: getTimezone(), trusted: false, imitation: true, }, @@ -446,7 +447,7 @@ describe('tx-history-filter', () => { expect(getModuleTransactions).toHaveBeenCalledWith( '1', '0x789', - { to: '0x123', executed: undefined, timezone_offset: 3600000, trusted: false, imitation: true }, + { to: '0x123', executed: undefined, timezone: getTimezone(), trusted: false, imitation: true }, 'pageUrl3', ) diff --git a/src/utils/chains.ts b/src/utils/chains.ts index 4d3940839e..0e5179df62 100644 --- a/src/utils/chains.ts +++ b/src/utils/chains.ts @@ -34,11 +34,13 @@ export enum FEATURES { 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, } diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts index 253549f8b5..60c5fbea41 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' @@ -94,3 +95,11 @@ export const formatError = (error: Error & { reason?: string }): string => { if (!reason.endsWith('.')) reason += '.' return ` ${capitalize(reason)}` } + +export const formatDurationFromSeconds = ( + seconds: number, + format: Array<'years' | 'months' | 'days' | 'hours' | 'minutes' | 'seconds'> = ['hours', 'minutes'], +) => { + const duration = intervalToDuration({ start: 0, end: seconds * 1000 }) + return formatDuration(duration, { format }) +} diff --git a/src/utils/transaction-guards.ts b/src/utils/transaction-guards.ts index a92fe19a12..38e94421bf 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 a9fc0af7f0..304f5555e1 100644 --- a/src/utils/transactions.ts +++ b/src/utils/transactions.ts @@ -10,7 +10,7 @@ import type { TransactionListPage, TransactionSummary, } from '@safe-global/safe-gateway-typescript-sdk' -import { ConflictType, TransactionListItemType } from '@safe-global/safe-gateway-typescript-sdk' +import { ConflictType, getTransactionDetails, TransactionListItemType } from '@safe-global/safe-gateway-typescript-sdk' import { isERC20Transfer, isModuleDetailedExecutionInfo, @@ -34,6 +34,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 = ({ @@ -186,7 +187,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) @@ -295,3 +296,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 016d1cd0f7..e6f4b8e1db 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 @@ -126,7 +126,7 @@ export const fetchFilteredTxHistory = async ( const fetchPage = () => { const query = { ...filterData.filter, - timezone_offset: getTimezoneOffset(), + 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 d0322e4748..a9c5eb19fd 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/yarn.lock b/yarn.lock index d48d950af0..d424149a6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,13 +49,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.1", "@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" @@ -69,7 +62,7 @@ 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.2", "@babel/core@^7.23.9": +"@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.2": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== @@ -383,13 +376,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.22.5", "@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" @@ -621,14 +607,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" @@ -684,7 +662,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== @@ -726,7 +704,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== @@ -768,7 +746,7 @@ "@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.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== @@ -784,7 +762,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" -"@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== @@ -1040,15 +1018,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" @@ -1070,7 +1039,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.2": +"@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.23.2": 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== @@ -1081,17 +1050,6 @@ "@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== - 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/regjsgen@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" @@ -1296,11 +1254,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.1" resolved "https://registry.yarnpkg.com/@ducanh2912/next-pwa/-/next-pwa-9.7.1.tgz#ae88531d7925e03a8a4b14b003cf1b26e767ae65" @@ -1447,6 +1400,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.4.tgz#74752a09301b8c6b9a415fbda9fb71406a62a7b7" @@ -1457,6 +1415,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.4.tgz#c27363e1e280e577d9b5c8fa7c7a3be2a8d79bf5" @@ -1467,6 +1430,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.4.tgz#6c9ee03d1488973d928618100048b75b147e0426" @@ -1477,6 +1445,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz#64e2ee945e5932cd49812caa80e8896e937e2f8b" @@ -1487,6 +1460,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.4.tgz#d8e26e1b965df284692e4d1263ba69a49b39ac7a" @@ -1497,6 +1475,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.4.tgz#29751a41b242e0a456d89713b228f1da4f45582f" @@ -1507,6 +1490,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.4.tgz#873edc0f73e83a82432460ea59bf568c1e90b268" @@ -1517,6 +1505,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz#659f2fa988d448dbf5010b5cc583be757cc1b914" @@ -1527,6 +1520,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.4.tgz#d5b13a7ec1f1c655ce05c8d319b3950797baee55" @@ -1537,6 +1535,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.4.tgz#878cd8bf24c9847c77acdb5dd1b2ef6e4fa27a82" @@ -1547,6 +1550,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.4.tgz#df890499f6e566b7de3aa2361be6df2b8d5fa015" @@ -1557,6 +1565,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.4.tgz#76eae4e88d2ce9f4f1b457e93892e802851b6807" @@ -1567,6 +1580,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.4.tgz#c49032f4abbcfa3f747b543a106931fe3dce41ff" @@ -1577,6 +1595,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.4.tgz#0f815a090772138503ee0465a747e16865bf94b1" @@ -1587,6 +1610,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.4.tgz#8d2cca20cd4e7c311fde8701d9f1042664f8b92b" @@ -1597,6 +1625,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.4.tgz#f618bec2655de49bff91c588777e37b5e3169d4a" @@ -1607,6 +1640,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.4.tgz#7889744ca4d60f1538d62382b95e90a49687cef2" @@ -1617,6 +1655,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.4.tgz#c3e436eb9271a423d2e8436fcb120e3fd90e2b01" @@ -1627,6 +1675,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.4.tgz#f63f5841ba8c8c1a1c840d073afc99b53e8ce740" @@ -1637,6 +1690,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.4.tgz#80be69cec92da4da7781cf7a8351b95cc5a236b0" @@ -1647,6 +1705,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.4.tgz#15dc0ed83d2794872b05d8edc4a358fecf97eb54" @@ -1657,6 +1720,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.4": version "0.19.4" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.4.tgz#d46a6e220a717f31f39ae80f49477cc3220be0f0" @@ -1667,6 +1735,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" @@ -2451,11 +2524,6 @@ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.1.0.tgz#e14896f1c57af2495e341dc4c7bf04125c8aeafd" integrity sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ== -"@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== - "@figspec/components@^1.0.1": version "1.0.3" resolved "https://registry.yarnpkg.com/@figspec/components/-/components-1.0.3.tgz#6456970a7298f9969d4d329574435050fcac00d9" @@ -3493,7 +3561,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== @@ -3717,15 +3822,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" @@ -3733,10 +3829,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.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.10.tgz#1d3178340028ced2d679f84140877db4f420333c" + integrity sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw== "@next/eslint-plugin-next@14.1.0": version "14.1.0" @@ -3745,50 +3841,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.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.10.tgz#49d10ca4086fbd59ee68e204f75d7136eda2aa80" + integrity sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ== + +"@next/swc-darwin-x64@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.10.tgz#0ebeae3afb8eac433882b79543295ab83624a1a8" + integrity sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA== + +"@next/swc-linux-arm64-gnu@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.10.tgz#7e602916d2fb55a3c532f74bed926a0137c16f20" + integrity sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA== + +"@next/swc-linux-arm64-musl@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.10.tgz#6b143f628ccee490b527562e934f8de578d4be47" + integrity sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ== + +"@next/swc-linux-x64-gnu@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.10.tgz#086f2f16a0678890a1eb46518c4dda381b046082" + integrity sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg== + +"@next/swc-linux-x64-musl@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.10.tgz#1befef10ed8dbcc5047b5d637a25ae3c30a0bfc3" + integrity sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA== + +"@next/swc-win32-arm64-msvc@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.10.tgz#731f52c3ae3c56a26cf21d474b11ae1529531209" + integrity sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ== + +"@next/swc-win32-ia32-msvc@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.10.tgz#32723ef7f04e25be12af357cc72ddfdd42fd1041" + integrity sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg== + +"@next/swc-win32-x64-msvc@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.10.tgz#ee1d036cb5ec871816f96baee7991035bb242455" + integrity sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ== "@ngraveio/bc-ur@^1.0.0", "@ngraveio/bc-ur@^1.1.5": version "1.1.6" @@ -4176,28 +4279,23 @@ dependencies: abitype "^1.0.2" -"@safe-global/safe-deployments@^1.37.3": - version "1.37.3" - resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.37.3.tgz#ded9fa6bb04f0e8972c00c481badcf513d590b0b" - integrity sha512-EtbiOJVGe697+GcbHtfo75NYpp+hTlIIBqL2ETPLGoQBHoxo9HWbGX/6ZkVxsZv/NN4nKawyMi+MvpUkH9VXGg== +"@safe-global/safe-deployments@^1.37.3", "@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.22.1": - version "3.22.1" - resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.1.tgz#4d5dac21c6e044b68b13b53468633ec771f30e3b" - integrity sha512-YApSpx+3h6uejrJVh8PSqXRRAwmsWz8PZERObMGJNC9NPoMhZG/Rvqb2UWmVLrjFh880rqutsB+GrTmJP351PA== +"@safe-global/safe-gateway-typescript-sdk@3.22.3-beta.13": + version "3.22.3-beta.13" + resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.3-beta.13.tgz#e6feaf93b16788ec6237c7f73f0d57f8514dde0c" + integrity sha512-VAoil8BbAsG14cDFG/sSFmCBIGQpJbyQeI7O7LFkPGwCVOFhHhE7SiurDiSkw13QLLxnIqB/Hq6jse+wa7Ny9g== "@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" @@ -4684,26 +4782,6 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-manager@8.0.6": - version "8.0.6" - resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-8.0.6.tgz#ec2fffddbcaa3680380f1ff529b585452289bd0d" - integrity sha512-N61Gh9FKsSYvsbdBy5qFvq1anTIuUAjh2Z+ezDMlxnfMGG77nZP9heuy1NnCaYCTFzl+lq4BsmRfXXDcKtSPRA== - dependencies: - "@fal-works/esbuild-plugin-global-externals" "^2.1.2" - "@storybook/core-common" "8.0.6" - "@storybook/manager" "8.0.6" - "@storybook/node-logger" "8.0.6" - "@types/ejs" "^3.1.1" - "@yarnpkg/esbuild-plugin-pnp" "^3.0.0-rc.10" - browser-assert "^1.2.1" - ejs "^3.1.8" - 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.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.0.6.tgz#53ee640ef9050149ac1303c4ec6fe6cb8ffba341" @@ -4755,48 +4833,6 @@ telejson "^7.2.0" tiny-invariant "^1.3.1" -"@storybook/cli@8.0.6": - version "8.0.6" - resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-8.0.6.tgz#c9b789474be5b51182dc78e30ca06f7b7961b49d" - integrity sha512-gAnl9soQUu1BtB4sANaqaaeTZAt/ThBSwCdzSLut5p21fP4ovi3FeP7hcDCJbyJZ/AvnD4k6leDrqRQxMVPr0A== - dependencies: - "@babel/core" "^7.23.0" - "@babel/types" "^7.23.0" - "@ndelangen/get-tarball" "^3.0.7" - "@storybook/codemod" "8.0.6" - "@storybook/core-common" "8.0.6" - "@storybook/core-events" "8.0.6" - "@storybook/core-server" "8.0.6" - "@storybook/csf-tools" "8.0.6" - "@storybook/node-logger" "8.0.6" - "@storybook/telemetry" "8.0.6" - "@storybook/types" "8.0.6" - "@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 "^11.0.2" - 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.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.0.6.tgz#70a96ad63318dcf856edeca366a8d21576f8cc09" @@ -4804,27 +4840,6 @@ dependencies: "@storybook/global" "^5.0.0" -"@storybook/codemod@8.0.6": - version "8.0.6" - resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-8.0.6.tgz#c6a1f40697a8409b01481042d41b8709f74aa85c" - integrity sha512-IMaTVI+EvmFxkz4leKWKForPC3LFxzfeTmd/QnTNF3nCeyvmIXvP01pQXRjro0+XcGDncEStuxa1d9ClMlac9Q== - dependencies: - "@babel/core" "^7.23.2" - "@babel/preset-env" "^7.23.2" - "@babel/types" "^7.23.0" - "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "8.0.6" - "@storybook/node-logger" "8.0.6" - "@storybook/types" "8.0.6" - "@types/cross-spawn" "^6.0.2" - cross-spawn "^7.0.3" - globby "^11.0.2" - jscodeshift "^0.15.1" - lodash "^4.17.21" - prettier "^3.1.1" - recast "^0.23.5" - tiny-invariant "^1.3.1" - "@storybook/components@8.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.0.6.tgz#62df4e257d5d456df4ce373e89fc3eda2c5ceb57" @@ -4881,55 +4896,6 @@ dependencies: ts-dedent "^2.0.0" -"@storybook/core-server@8.0.6": - version "8.0.6" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-8.0.6.tgz#e51fb7103448e18349e10289f7eb46ae4dfd60dd" - integrity sha512-COmcjrry8vZXDh08ZGbfDz2bFB4of5wnwOwYf8uwlVND6HnhQzV22On1s3/p8qw+dKOpjpwDdHWtMnndnPNuqQ== - dependencies: - "@aw-web-design/x-default-browser" "1.4.126" - "@babel/core" "^7.23.9" - "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-manager" "8.0.6" - "@storybook/channels" "8.0.6" - "@storybook/core-common" "8.0.6" - "@storybook/core-events" "8.0.6" - "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "8.0.6" - "@storybook/docs-mdx" "3.0.0" - "@storybook/global" "^5.0.0" - "@storybook/manager" "8.0.6" - "@storybook/manager-api" "8.0.6" - "@storybook/node-logger" "8.0.6" - "@storybook/preview-api" "8.0.6" - "@storybook/telemetry" "8.0.6" - "@storybook/types" "8.0.6" - "@types/detect-port" "^1.3.0" - "@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" - express "^4.17.3" - fs-extra "^11.1.0" - globby "^11.0.2" - 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.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.0.6.tgz#fe18de9e728437d1f7e48192dbfd559bb9a4b33f" @@ -4941,6 +4907,23 @@ "@types/node" "^18.0.0" ts-dedent "^2.0.0" +"@storybook/core@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.3.0.tgz#c08ff10405fa935044678c8ae92c7be14dd01bdb" + integrity sha512-UeErpD0xRIP2nFA2TjPYxtEyv24O6VRfq2XXU5ki2QPYnxOxAPBbrMHCADjgBwNS4S2NUWTaVBYxybISVbrj+w== + dependencies: + "@storybook/csf" "^0.1.11" + "@types/express" "^4.17.21" + 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" + process "^0.11.10" + recast "^0.23.5" + semver "^7.6.2" + util "^0.12.5" + ws "^8.2.3" + "@storybook/csf-plugin@8.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.0.6.tgz#1e907002b24840e935c1b747b535f20d369ade3d" @@ -4971,6 +4954,13 @@ dependencies: lodash "^4.17.15" +"@storybook/csf@^0.1.11": + 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/csf@^0.1.2": version "0.1.3" resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.3.tgz#79047a4dece94ba7c8e78003723e9bd9e071379a" @@ -4978,11 +4968,6 @@ dependencies: type-fest "^2.19.0" -"@storybook/docs-mdx@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@storybook/docs-mdx/-/docs-mdx-3.0.0.tgz#5c9b5ce35dcb00ad8aa5dddbabf52ad09fab3974" - integrity sha512-NmiGXl2HU33zpwTv1XORe9XG9H+dRUC1Jl11u92L4xr062pZtrShLmD4VKIsOQujxhhOrbxpwhNOt+6TdhyIdQ== - "@storybook/docs-tools@8.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-8.0.6.tgz#691150506d32d389e15ea0cbb5132a6f4a9c8d85" @@ -5040,11 +5025,6 @@ telejson "^7.2.0" ts-dedent "^2.0.0" -"@storybook/manager@8.0.6": - version "8.0.6" - resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-8.0.6.tgz#c2ab7077c6f77eaf7928b7dcd3335127eacd2997" - integrity sha512-wdL3lG72qrCOLkxEUW49+hmwA4fIFXFvAEU7wVgEt4KyRRGWhHa8Dr/5Tnq54CWJrA+BTrTPHaoo/Vu4BAjgow== - "@storybook/nextjs@^8.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/nextjs/-/nextjs-8.0.6.tgz#9fa2589d92c8b5c80cde7491998161e5d91af17b" @@ -5201,20 +5181,6 @@ memoizerific "^1.11.3" qs "^6.10.0" -"@storybook/telemetry@8.0.6": - version "8.0.6" - resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-8.0.6.tgz#5a7cf64fb7fb6f3fc3ca2e9a871e383782415095" - integrity sha512-kzxhhzGRSBYR4oe/Vlp/adKVxD8KWbIDMCgLWaINe14ILfEmpyrC00MXRSjS1tMF1qfrtn600Oe/xkHFQUpivQ== - dependencies: - "@storybook/client-logger" "8.0.6" - "@storybook/core-common" "8.0.6" - "@storybook/csf-tools" "8.0.6" - 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.0.6", "@storybook/test@^8.0.6": version "8.0.6" resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.0.6.tgz#6c2d38d9189ec6a0640c3267ad68fc64564ef52d" @@ -5367,11 +5333,17 @@ "@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== +"@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== + +"@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: + "@swc/counter" "^0.1.3" tslib "^2.4.0" "@swc/helpers@^0.5.11": @@ -5636,6 +5608,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.2" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.2.tgz#6f1225829d89794fd9f891989c9ce667422d7f64" @@ -5710,12 +5689,12 @@ 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== +"@types/debug@^4.0.0": + 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/node" "*" + "@types/ms" "*" "@types/debug@^4.1.7": version "4.1.9" @@ -5724,11 +5703,6 @@ 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/doctrine@^0.0.3": version "0.0.3" resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.3.tgz#e892d293c92c9c1d3f9af72c15a554fbc7e0895a" @@ -5739,11 +5713,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.10" resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.10.tgz#da6e58a6171b46a41d3694f812d845d515c77e18" @@ -5754,21 +5723,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.5" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.5.tgz#e28b09dbb1d9d35fdfa8a884225f00440dfc5a3e" - integrity sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.44.4" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.4.tgz#28eaff82e1ca0a96554ec5bb0188f10ae1a74c2f" - integrity sha512-lOzjyfY/D9QR4hY9oblZ76B90MYTB3RrQ4z2vBIJKj9ROCRqdkYl2gSUx1x1a4IWPjKJZLL4Aw1Zfay7eMnmnA== +"@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" @@ -5795,7 +5755,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== @@ -5878,7 +5838,7 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.15", "@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== @@ -5898,11 +5858,23 @@ 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== + "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -6000,11 +5972,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.11" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" @@ -6027,10 +5994,10 @@ 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": - version "18.2.24" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.24.tgz#8dda8f449ae436a7a6e91efed8035d4ab03ff759" - integrity sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg== +"@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== dependencies: "@types/react" "*" @@ -6132,6 +6099,11 @@ 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" @@ -6449,10 +6421,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" @@ -6461,39 +6433,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" @@ -6554,7 +6524,7 @@ cross-fetch "^3.1.4" tslib "1.14.1" -"@walletconnect/jsonrpc-provider@1.0.13", "@walletconnect/jsonrpc-provider@^1.0.13": +"@walletconnect/jsonrpc-provider@1.0.13": version "1.0.13" resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.13.tgz#9a74da648d015e1fffc745f0c7d629457f53648b" integrity sha512-K73EpThqHnSR26gOyNEL+acEex3P7VWZe6KE12ZwKzAt2H4e5gldZHbjsu2QR9cLeJ8AXuO7kEMOIcRv1QEc7g== @@ -6563,7 +6533,7 @@ "@walletconnect/safe-json" "^1.0.2" tslib "1.14.1" -"@walletconnect/jsonrpc-provider@1.0.14": +"@walletconnect/jsonrpc-provider@1.0.14", "@walletconnect/jsonrpc-provider@^1.0.13": version "1.0.14" resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz#696f3e3b6d728b361f2e8b853cfc6afbdf2e4e3e" integrity sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow== @@ -6616,7 +6586,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== @@ -6624,14 +6594,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" @@ -6657,10 +6619,10 @@ "@walletconnect/modal-core" "2.6.2" "@walletconnect/modal-ui" "2.6.2" -"@walletconnect/relay-api@1.0.10": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@walletconnect/relay-api/-/relay-api-1.0.10.tgz#5aef3cd07c21582b968136179aa75849dcc65499" - integrity sha512-tqrdd4zU9VBNqUaXXQASaexklv6A54yEyQQEXYOCr+Jz8Ket0dmPBDyg19LVSNUN2cipAghQc45/KVmfFJ0cYw== +"@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" @@ -6706,19 +6668,19 @@ "@walletconnect/utils" "2.11.2" 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": @@ -6740,22 +6702,22 @@ "@walletconnect/logger" "^2.0.1" events "^3.3.0" -"@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== +"@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.1" - "@walletconnect/jsonrpc-types" "1.0.3" - "@walletconnect/keyvaluestorage" "^1.1.1" - "@walletconnect/logger" "^2.0.1" - events "^3.3.0" + "@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.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.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" @@ -6799,59 +6761,63 @@ query-string "7.1.3" uint8arrays "^3.1.0" -"@walletconnect/utils@2.11.3", "@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== - 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.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" - detect-browser "5.3.0" - 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/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/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/sha256" "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" + +"@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" @@ -7106,13 +7072,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" @@ -7159,7 +7118,7 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -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== @@ -7175,12 +7134,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== @@ -7205,16 +7164,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.10.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" @@ -7566,6 +7525,11 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +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" resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.2.6.tgz#0d7a3deb978bc2b984d5908a2038e1ae2e54ff40" @@ -7655,11 +7619,6 @@ b4a@^1.6.4: resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== -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" @@ -7761,6 +7720,11 @@ 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" @@ -7841,19 +7805,12 @@ bech32@^2.0.0: resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== -better-opn@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-3.0.2.tgz#f96f35deaaf8f34144a4102651babcf00d1d8817" - integrity sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ== - dependencies: - open "^8.0.4" - big-integer@1.6.36: version "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.44, big-integer@^1.6.48: +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== @@ -7904,7 +7861,7 @@ 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: +bl@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -7971,10 +7928,10 @@ bnc-sdk@^4.6.7: rxjs "^6.6.3" sturdy-websocket "^0.1.12" -body-parser@1.20.2: - 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" @@ -7984,7 +7941,7 @@ body-parser@1.20.2: 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" @@ -8008,13 +7965,6 @@ bowser@^2.11.0: resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== -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" @@ -8102,13 +8052,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" @@ -8231,11 +8174,6 @@ bytebuffer@^5.0.1: dependencies: long "~3" -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" @@ -8316,6 +8254,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, chai@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" @@ -8359,6 +8302,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" @@ -8391,11 +8354,6 @@ chownr@^1.1.1: 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.0: version "11.3.0" resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.3.0.tgz#d46b7aac1a0eaed29a765645eaf93c484220174c" @@ -8429,7 +8387,7 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -citty@^0.1.3, citty@^0.1.4, citty@^0.1.6: +citty@^0.1.3, citty@^0.1.4: version "0.1.6" resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.6.tgz#0f7904da1ed4625e1a9ea7e0fa780981aab7c5e4" integrity sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== @@ -8441,10 +8399,10 @@ cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.3: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== -classnames@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== +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== clean-css@^5.2.2: version "5.3.3" @@ -8483,12 +8441,7 @@ 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: +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== @@ -8537,20 +8490,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@^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.1.0, clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" @@ -8576,6 +8515,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" @@ -8633,6 +8577,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" @@ -8688,26 +8637,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" @@ -9129,6 +9058,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" @@ -9147,6 +9083,13 @@ 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-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" @@ -9215,21 +9158,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" - define-data-property@^1.0.1, define-data-property@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -9239,11 +9167,6 @@ define-data-property@^1.0.1, define-data-property@^1.1.2: es-errors "^1.3.0" gopd "^1.0.1" -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" @@ -9253,7 +9176,7 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -defu@^6.1.2, defu@^6.1.3, defu@^6.1.4: +defu@^6.1.2, defu@^6.1.3: version "6.1.4" resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== @@ -9305,7 +9228,7 @@ depd@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== @@ -9333,11 +9256,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" @@ -9353,20 +9271,12 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -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.5.1" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" - integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== +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" @@ -9521,16 +9431,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.2" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" @@ -9578,7 +9478,7 @@ eip55@^2.1.1: dependencies: keccak "^3.0.3" -ejs@^3.1.6, ejs@^3.1.8: +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== @@ -9616,6 +9516,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" @@ -9646,7 +9559,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== @@ -9662,10 +9580,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.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" - integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== +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" @@ -9688,11 +9606,6 @@ entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -envinfo@^7.7.3: - version "7.12.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.12.0.tgz#b56723b39c2053d67ea5714f026d05d4f5cc7acd" - integrity sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg== - 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" @@ -9898,11 +9811,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" @@ -9939,6 +9847,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.4" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.4.tgz#cdf5c4c684956d550bc3c6d0c01dac7fef6c75b1" @@ -9992,6 +9930,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" @@ -10271,6 +10214,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" @@ -10281,7 +10270,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== @@ -10607,21 +10596,6 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" - integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^8.0.1" - human-signals "^5.0.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^4.1.0" - strip-final-newline "^3.0.0" - executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -10661,36 +10635,73 @@ exponential-backoff@^3.1.0: integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== 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== + 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" @@ -10704,7 +10715,14 @@ ext@^1.1.2: 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== @@ -10805,6 +10823,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" @@ -10826,11 +10851,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" @@ -10907,14 +10927,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" @@ -10945,13 +10969,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" @@ -11022,11 +11039,6 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -flow-parser@0.*: - version "0.233.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.233.0.tgz#b983e65812d5ecae79f08ae3ed8ad2e131a9b966" - integrity sha512-E/mv51GYJfLuRX6fZnw4M52gBxYa8pkHUOgNEZOcQK2RTXS8YXeU5rlalkTcY99UpwbeNVCSUFKaavpOksi/pQ== - follow-redirects@^1.14.8, follow-redirects@^1.15.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -11088,6 +11100,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" @@ -11149,13 +11166,6 @@ fs-extra@^9.0.1, fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.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.5" resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" @@ -11191,10 +11201,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" @@ -11222,11 +11232,6 @@ get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" -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" @@ -11254,11 +11259,6 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -get-stream@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" - integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== - get-symbol-description@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" @@ -11289,20 +11289,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-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -11409,7 +11395,7 @@ globalyzer@0.1.0: resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== -globby@^11.0.1, globby@^11.0.2, globby@^11.1.0: +globby@^11.0.1, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -11444,7 +11430,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -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: +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" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -11454,17 +11440,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" @@ -11597,6 +11581,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" @@ -11604,6 +11631,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" @@ -11781,11 +11815,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -human-signals@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" - integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== - humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -11913,6 +11942,16 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +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" @@ -11968,7 +12007,7 @@ ioredis@^5.3.2: redis-parser "^3.0.0" standard-as-callback "^2.1.0" -ip@^2.0.0, ip@^2.0.1: +ip@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== @@ -12037,6 +12076,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" @@ -12118,16 +12170,21 @@ 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: +is-docker@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +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" @@ -12164,16 +12221,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-installed-globally@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" @@ -12182,11 +12239,6 @@ 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.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" @@ -12261,18 +12313,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" @@ -12283,9 +12333,16 @@ 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-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" +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" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" @@ -12313,11 +12370,6 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -12401,11 +12453,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" @@ -12998,32 +13045,6 @@ jsbn@~0.1.0: 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" - jsdom@^20.0.0: version "20.0.3" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" @@ -13225,7 +13246,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== @@ -13387,14 +13408,6 @@ loader-utils@^3.2.1: resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== -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" @@ -13471,7 +13484,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== @@ -13504,6 +13517,11 @@ long@~3: resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" integrity sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg== +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" @@ -13575,7 +13593,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== @@ -13619,6 +13637,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" @@ -13633,6 +13661,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" @@ -13689,10 +13919,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" @@ -13716,6 +13946,385 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== +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.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -13732,7 +14341,7 @@ 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== @@ -13759,11 +14368,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -13791,7 +14395,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== @@ -13826,37 +14430,17 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1. resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -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.0.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== -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, mkdirp-classic@^0.5.3: 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@^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== @@ -13908,7 +14492,7 @@ 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== @@ -14015,7 +14599,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== @@ -14025,28 +14609,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.10: + version "14.2.10" + resolved "https://registry.yarnpkg.com/next/-/next-14.2.10.tgz#331981a4fecb1ae8af1817d4db98fc9687ee1cb6" + integrity sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww== dependencies: - "@next/env" "14.1.1" - "@swc/helpers" "0.5.2" + "@next/env" "14.2.10" + "@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.10" + "@next/swc-darwin-x64" "14.2.10" + "@next/swc-linux-arm64-gnu" "14.2.10" + "@next/swc-linux-arm64-musl" "14.2.10" + "@next/swc-linux-x64-gnu" "14.2.10" + "@next/swc-linux-x64-musl" "14.2.10" + "@next/swc-win32-arm64-msvc" "14.2.10" + "@next/swc-win32-ia32-msvc" "14.2.10" + "@next/swc-win32-x64-msvc" "14.2.10" no-case@^3.0.4: version "3.0.4" @@ -14093,14 +14677,7 @@ node-addon-api@^7.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.0.0.tgz#8136add2f510997b3b94814f4af1cce0b0e3962e" integrity sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA== -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.4.0, node-fetch-native@^1.4.1, node-fetch-native@^1.6.3: +node-fetch-native@^1.4.0, node-fetch-native@^1.4.1: version "1.6.4" resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== @@ -14202,13 +14779,6 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npm-run-path@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" - integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== - dependencies: - path-key "^4.0.0" - nth-check@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -14221,17 +14791,6 @@ nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== -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" - 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" @@ -14330,11 +14889,6 @@ ofetch@^1.3.3: node-fetch-native "^1.4.0" ufo "^1.3.0" -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== - on-exit-leak-free@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" @@ -14347,11 +14901,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" @@ -14366,22 +14915,6 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -open@^8.0.4, open@^8.4.0: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -14399,21 +14932,6 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" -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" @@ -14424,7 +14942,7 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== -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== @@ -14445,13 +14963,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" @@ -14490,11 +15001,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" @@ -14532,6 +15038,20 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.7: pbkdf2 "^3.1.2" safe-buffer "^5.2.1" +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" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -14572,11 +15092,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" @@ -14602,11 +15117,6 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -14620,17 +15130,17 @@ path-scurry@^1.10.1, path-scurry@^1.10.2: 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== -pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2: +pathe@^1.1.0, pathe@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== @@ -14651,15 +15161,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" @@ -14670,6 +15171,15 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== +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" @@ -14742,7 +15252,7 @@ pino@7.11.0: sonic-boom "^2.2.1" thread-stream "^0.15.1" -pirates@^4.0.4, pirates@^4.0.6: +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== @@ -14754,13 +15264,6 @@ pixelmatch@^5.2.1: dependencies: pngjs "^6.0.0" -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== - dependencies: - find-up "^3.0.0" - pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -14936,11 +15439,6 @@ prettier@^2.3.1, prettier@^2.7.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -prettier@^3.1.1: - version "3.2.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" - integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== - pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -14992,7 +15490,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== @@ -15009,6 +15507,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.4: version "7.2.4" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.4.tgz#3fc1ec0cdc89dd91aef9ba6037ba07408485c3ae" @@ -15109,14 +15612,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" @@ -15125,15 +15620,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@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -15192,6 +15678,13 @@ 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.10.3, qs@^6.11.2: version "6.12.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.0.tgz#edd40c3b823995946a8a0b1f208669c7a200db77" @@ -15358,13 +15851,13 @@ 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: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" - integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== +"react-dom@^16.8.0 || ^17.0.0 || ^18.0.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== dependencies: loose-envify "^1.1.0" - scheduler "^0.23.0" + scheduler "^0.23.2" react-dom@^17.0.2: version "17.0.2" @@ -15521,7 +16014,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== @@ -15585,7 +16078,7 @@ 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: +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== @@ -15734,6 +16227,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" @@ -15880,13 +16453,6 @@ rimraf@^3.0.0, 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" @@ -16036,16 +16602,16 @@ safe-array-concat@^1.1.0: 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" @@ -16122,10 +16688,10 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -scheduler@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" - integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== dependencies: loose-envify "^1.1.0" @@ -16190,6 +16756,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" @@ -16207,17 +16781,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.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== - dependencies: - lru-cache "^6.0.0" - -semver@^7.6.2: - 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" @@ -16238,6 +16805,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" @@ -16252,16 +16838,26 @@ 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" +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: + 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" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -16306,13 +16902,6 @@ 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.32.6: version "0.32.6" resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a" @@ -16354,7 +16943,7 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1, signal-exit@^4.1.0: +signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -16464,7 +17053,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== @@ -16482,7 +17071,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== @@ -16609,12 +17198,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.0.6" - resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.0.6.tgz#8b2616030f94a9708c0771ed532a5cef09a421c2" - integrity sha512-QcQl8Sj77scGl0s9pw+cSPFmXK9DPogEkOceG12B2PqdS23oGkaBt24292Y3W5TTMVNyHtRTRB/FqPwK3FOdmA== +storybook@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.3.0.tgz#172a5d5e415b83bcb08a3a670a2e6f34383dfea1" + integrity sha512-XKU+nem9OKX/juvJPwka1Q7DTpSbOe0IMp8ZyLQWorhFKpquJdUjryl7Z9GiFZyyTykCqH4ItQ7h8PaOmqVMOw== dependencies: - "@storybook/cli" "8.0.6" + "@storybook/core" "8.3.0" stream-browserify@^3.0.0: version "3.0.0" @@ -16751,6 +17340,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" @@ -16774,6 +17371,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" @@ -16794,11 +17396,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - strip-hex-prefix@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" @@ -16820,7 +17417,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== @@ -16842,6 +17439,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" @@ -16957,7 +17568,7 @@ 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.0.0, tar-fs@^2.1.1: +tar-fs@^2.0.0: 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== @@ -16998,18 +17609,6 @@ tar-stream@^3.1.5: fast-fifo "^1.2.0" streamx "^2.15.0" -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" @@ -17022,13 +17621,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" @@ -17212,6 +17804,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" @@ -17253,11 +17850,21 @@ 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" @@ -17593,7 +18200,7 @@ ua-parser-js@^1.0.35: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.36.tgz#a9ab6b9bd3a8efb90bb0816674b412717b7c428c" integrity sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw== -ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2, ufo@^1.4.0: +ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2: version "1.5.3" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344" integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw== @@ -17688,6 +18295,19 @@ 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== +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" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -17695,6 +18315,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" @@ -17702,6 +18327,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" @@ -17710,6 +18363,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" @@ -17949,6 +18609,22 @@ 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" @@ -18003,7 +18679,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== @@ -18011,13 +18687,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" - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -18096,21 +18765,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" @@ -18477,15 +19145,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^2.3.0: - version "2.4.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" - integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - write-file-atomic@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" @@ -18571,6 +19230,11 @@ 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== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" @@ -18641,3 +19305,8 @@ 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==