From ef463b2a43b1aa61e4db7baa0c9e018a0b4e9bb8 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Wed, 22 Apr 2026 09:15:59 +0300 Subject: [PATCH 01/12] [gui-replacement] rewrite e2e: replace guardianui/anvil with wallet helpers --- .github/workflows/e2e.yml | 2 - e2e/extendTest.ts | 11 +- e2e/fixtures/anvil/index.ts | 15 - e2e/fixtures/anvil/setBalance.ts | 93 ------ e2e/fixtures/element/checkLink.ts | 1 - e2e/fixtures/guardian.ts | 115 ------- e2e/fixtures/index.ts | 4 - e2e/fixtures/sdk/deposit.ts | 12 +- e2e/fixtures/sdk/index.ts | 6 +- e2e/fixtures/sdk/mint.ts | 9 +- e2e/fixtures/swap/actions/boost.ts | 2 - .../swap/helpers/checkTokenDropdown.ts | 5 +- e2e/fixtures/swap/setSdkTransactions.ts | 2 +- e2e/fixtures/vault/index.ts | 4 +- e2e/fixtures/vault/setVaultData.ts | 8 +- e2e/fixtures/wallet/chains.ts | 55 ++++ e2e/fixtures/wallet/checkWalletList.ts | 8 +- e2e/fixtures/wallet/connectWithBalance.ts | 36 +-- e2e/fixtures/wallet/helpers/balance.ts | 78 +++++ .../wallet/helpers/eip1193-provider.js | 289 ++++++++++++++++++ e2e/fixtures/wallet/helpers/impersonate.ts | 30 ++ e2e/fixtures/wallet/helpers/index.ts | 5 + e2e/fixtures/wallet/helpers/initProvider.ts | 117 +++++++ e2e/fixtures/wallet/helpers/resetAllChains.ts | 72 +++++ e2e/fixtures/wallet/index.ts | 40 ++- e2e/fixtures/wallet/init.ts | 41 +++ e2e/fixtures/wallet/setBalance.ts | 65 ++-- e2e/fixtures/wallet/setEthBalance.ts | 33 ++ e2e/fixtures/wallet/switchChain.ts | 26 ++ e2e/tests/balance.spec.ts | 5 +- e2e/tests/boost.spec.ts | 5 +- e2e/tests/burn.spec.ts | 5 +- e2e/tests/loader.spec.ts | 5 +- e2e/tests/mint.spec.ts | 5 +- e2e/tests/stake.spec.ts | 5 +- e2e/tests/unboost.spec.ts | 5 +- e2e/tests/unstake.spec.ts | 5 +- e2e/types.d.ts | 8 +- package.json | 1 - playwright.config.ts | 26 +- 40 files changed, 900 insertions(+), 359 deletions(-) delete mode 100644 e2e/fixtures/anvil/index.ts delete mode 100644 e2e/fixtures/anvil/setBalance.ts delete mode 100644 e2e/fixtures/guardian.ts create mode 100644 e2e/fixtures/wallet/chains.ts create mode 100644 e2e/fixtures/wallet/helpers/balance.ts create mode 100644 e2e/fixtures/wallet/helpers/eip1193-provider.js create mode 100644 e2e/fixtures/wallet/helpers/impersonate.ts create mode 100644 e2e/fixtures/wallet/helpers/index.ts create mode 100644 e2e/fixtures/wallet/helpers/initProvider.ts create mode 100644 e2e/fixtures/wallet/helpers/resetAllChains.ts create mode 100644 e2e/fixtures/wallet/init.ts create mode 100644 e2e/fixtures/wallet/setEthBalance.ts create mode 100644 e2e/fixtures/wallet/switchChain.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index afd0eb78..e966f66c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -69,8 +69,6 @@ jobs: WITH_ANVIL: true URL: https://${{ steps.preview.outputs.preview_url }} RPC_URL: ${{ secrets.RPC_URL }} - GUARDIAN_UI_INFURA_API_KEY: ${{ secrets.GUARDIAN_UI_INFURA_API_KEY }} - GUARDIAN_UI_ALCHEMY_API_KEY: ${{ secrets.GUARDIAN_UI_ALCHEMY_API_KEY }} VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} run: pnpm run e2e diff --git a/e2e/extendTest.ts b/e2e/extendTest.ts index 87195eae..9b3610e7 100644 --- a/e2e/extendTest.ts +++ b/e2e/extendTest.ts @@ -1,4 +1,4 @@ -import { test as base } from '@guardianui/test' +import { test as base } from '@playwright/test' import { api, @@ -7,16 +7,15 @@ import { swap, vault, queue, - anvil, wallet, osToken, graphql, element, helpers, settings, - guardian, transactions, } from './fixtures' +import { resetAllChains } from './fixtures/wallet/helpers' const baseTest = base.extend({ @@ -26,14 +25,12 @@ const baseTest = base.extend({ swap, queue, vault, - anvil, wallet, osToken, graphql, element, helpers, settings, - guardian, transactions, }) @@ -55,6 +52,10 @@ const log = { const getFileName = (value: string) => value.replace(/^.*\/(.*)\//gm, '$1/') +baseTest.beforeAll(async () => { + await resetAllChains() +}) + baseTest.afterEach(async ({}, testInfo) => { const { title, error, duration, file, retry } = testInfo diff --git a/e2e/fixtures/anvil/index.ts b/e2e/fixtures/anvil/index.ts deleted file mode 100644 index 79edcacd..00000000 --- a/e2e/fixtures/anvil/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createSetBalance, SetBalance } from './setBalance' - - -export type AnvilFixture = { - setBalance: SetBalance -} - -const anvil: E2E.Fixture = async ({ page }, use) => { - await use({ - setBalance: createSetBalance({ page }), - }) -} - - -export default anvil diff --git a/e2e/fixtures/anvil/setBalance.ts b/e2e/fixtures/anvil/setBalance.ts deleted file mode 100644 index 899e9ecf..00000000 --- a/e2e/fixtures/anvil/setBalance.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Contract, BaseContract, JsonRpcProvider } from 'ethers' - - -type Wrapper = E2E.FixtureMethod - -type Input = { - tokenAddress: string - amount: string -} - -type TransferContract = BaseContract & { - transfer: (address: string, amount: string, overrides: { from: string }) => Promise<{ hash: string }> -} - -export type SetBalance = (values: Input) => Promise - -const impersonateAccount = async (address: string) => { - try { - await provider.send('anvil_impersonateAccount', [ - address, - ]) - } - catch {} -} - -const provider = new JsonRpcProvider('http://localhost:8545', 1) - -const holders = { - osETH: '0x57ba429517c3473b6d34ca9acd56c0e735b94c02', -} - -const impersonatePromises = { - osETH: impersonateAccount(holders.osETH), -} - -const transferAbi = [ - { - inputs: [ - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256', - }, - ], - name: 'transfer', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, -] - -// ATTN fallback for gui.setBalance, that doesn't work in CI -export const createSetBalance: Wrapper = ({ page }) => ( - async (values: Input) => { - const { tokenAddress, amount } = values - - const holder = holders.osETH - const impersonatePromise = impersonatePromises.osETH - - const getAddressPromise = page.evaluate('window.ethereum.signer.address') - - const setBalancePromise = provider.send('anvil_setBalance', [ - holder, - `0x${BigInt(100000000000000000000000).toString(16)}`, - ]) - - const [ address ] = await Promise.all([ - getAddressPromise, - impersonatePromise, - setBalancePromise, - ]) - - const signer = await provider.getSigner(holder) - const tokenContract = new Contract(tokenAddress, transferAbi).connect(signer) as TransferContract - - const receipt = await tokenContract.transfer(address as string, amount, { - from: holder, - }) - - await provider.waitForTransaction(receipt.hash, 6) - } -) diff --git a/e2e/fixtures/element/checkLink.ts b/e2e/fixtures/element/checkLink.ts index 86665580..652a4fc9 100644 --- a/e2e/fixtures/element/checkLink.ts +++ b/e2e/fixtures/element/checkLink.ts @@ -33,7 +33,6 @@ export const createCheckLink: Wrapper = ({ page }) => ( await expect(page).toHaveURL(expectedURL) if (!skipPageHeadingCheck) { - await page.waitForLoadState('networkidle') await expect(page.getByTestId('page-heading')).toBeVisible() } } diff --git a/e2e/fixtures/guardian.ts b/e2e/fixtures/guardian.ts deleted file mode 100644 index b2a67419..00000000 --- a/e2e/fixtures/guardian.ts +++ /dev/null @@ -1,115 +0,0 @@ -import fs from 'fs' -import path from 'path' -import { Wallet } from 'ethers' - - -const entryPoint = require.resolve('@guardianui/test') -const distDir = path.dirname(entryPoint) -const pkgRoot = path.dirname(distDir) - -const scriptPath = path.join(pkgRoot, 'dist', 'provider', 'provider.js') - -const fixScript = ` - const updateBigNumber = (data) => { - const result = {} - - Object.keys(data).forEach((key) => { - const value = data[key] - - result[key] = value?._isBigNumber ? value.toHexString() : value - }) - - return result - } - - // ATTN - this is a hack to fix the issue with update BigNumber, because getFeeData inwardly uses getBlock - const originalGetFeeDataResult = window.ethereum.provider.getFeeData() - window.ethereum.provider.getFeeData = () => { - return originalGetFeeDataResult.then(updateBigNumber) - } - - const originalGetBlock = window.ethereum.provider.getBlock - window.ethereum.provider.getBlock = function () { - return originalGetBlock.bind(this)(...arguments).then(updateBigNumber) - } - - const originalGetNetwork = window.ethereum.provider.getNetwork - window.ethereum.provider.getNetwork = function () { - return originalGetNetwork.bind(this)(...arguments).then(updateBigNumber) - } - - const originalGetTransaction = window.ethereum.provider.getTransaction - - window.ethereum.provider.getTransaction = function (txHash) { - return originalGetTransaction.bind(this)(txHash) - .then((tx) => { - if (tx.blockNumber) { - const updatedTx = updateBigNumber(tx) - - return { - ...updatedTx, - gasUsed: updatedTx.gasLimit, - cumulativeGasUsed: updatedTx.gasLimit, - logs: [], - status: 1, - } - } - - return new Promise((resolve) => setTimeout(resolve, 100)) - .then(() => window.ethereum.provider.getTransaction(txHash)) - }) - } -` - -const protectionScript = ` - (function() { - let originalEthereum = null - - Object.defineProperty(window, 'ethereum', { - get: function() { - return originalEthereum - }, - set: function(value) { - if (!originalEthereum) { - console.log('Setting window.ethereum:', value) - - originalEthereum = value - } - else { - console.warn('Blocked attempt to overwrite window.ethereum:', value) - } - }, - configurable: true - }) - })() -` - -const script = fs.readFileSync(scriptPath, 'utf-8') - .replace('__GUARDIANUI_MOCK__RPC', "http://127.0.0.1:8545") - .replace('__GUARDIANUI_MOCK__CHAIN_ID', '1') - .concat(`setTimeout(() => {${fixScript}}, 0)`) - -const cookieScript = `document.cookie = 'SW_e2e=true'` - -export type GuardianFixture = { - fixProvider: (privateKey?: string) => Promise -} - -const guardian: E2E.Fixture = async ({ page }, use) => { - - const fixProvider = async (privateKey?: string) => { - const currentKey = privateKey || Wallet.createRandom().privateKey - const scriptWithWallet = script.replace('__GUARDIANUI_MOCK__PRIVATE_KEY', currentKey) - - await page.addInitScript(cookieScript) - await page.addInitScript(protectionScript) - await page.addInitScript(scriptWithWallet) - } - - await use({ - fixProvider, - }) -} - - -export default guardian diff --git a/e2e/fixtures/index.ts b/e2e/fixtures/index.ts index f4af88b4..9602e046 100644 --- a/e2e/fixtures/index.ts +++ b/e2e/fixtures/index.ts @@ -4,14 +4,12 @@ export { default as user } from './user' export { default as swap } from './swap' export { default as vault } from './vault' export { default as queue } from './queue' -export { default as anvil } from './anvil' export { default as wallet } from './wallet' export { default as osToken } from './osToken' export { default as graphql } from './graphql' export { default as element } from './element' export { default as helpers } from './helpers' export { default as settings } from './settings' -export { default as guardian } from './guardian' export { default as transactions } from './transactions' @@ -21,12 +19,10 @@ export type { UserFixture } from './user' export type { SwapFixture } from './swap' export type { VaultFixture } from './vault' export type { QueueFixture } from './queue' -export type { AnvilFixture } from './anvil' export type { WalletFixture } from './wallet' export type { OsTokenFixture } from './osToken' export type { ElementFixture } from './element' export type { HelpersFixture } from './helpers' export type { GraphqlFixture } from './graphql' export type { SettingsFixture } from './settings' -export type { GuardianFixture } from './guardian' export type { TransactionsFixture } from './transactions' diff --git a/e2e/fixtures/sdk/deposit.ts b/e2e/fixtures/sdk/deposit.ts index d8aaf695..b4f37858 100644 --- a/e2e/fixtures/sdk/deposit.ts +++ b/e2e/fixtures/sdk/deposit.ts @@ -1,7 +1,7 @@ import { parseEther } from 'ethers' -type Wrapper = E2E.FixtureMethod +type Wrapper = E2E.FixtureMethod export type Deposit = (values: Input) => Promise @@ -10,12 +10,13 @@ export type Input = { assets: string } -export const createDeposit: Wrapper = ({ page }) => ( +// Pull the address from the wallet fixture rather than reading +// `window.ethereum.signer.address` in-page - keeps the SDK call typed and +// removes a compat-shim dependency from the browser evaluate body. +export const createDeposit: Wrapper = ({ page, wallet }) => ( async ({ vaultAddress, assets }: Input) => ( - page.evaluate(async ({ vaultAddress, depositAssets }) => { + page.evaluate(async ({ vaultAddress, depositAssets, userAddress }) => { const sdk = window.e2e.sdk - // @ts-ignore - const userAddress = window.ethereum.signer.address const assets = BigInt(depositAssets) const depositHash = await sdk.vault.deposit({ @@ -28,6 +29,7 @@ export const createDeposit: Wrapper = ({ page }) => ( }, { vaultAddress, depositAssets: parseEther(assets).toString(), + userAddress: wallet.getAddress(), }) ) ) diff --git a/e2e/fixtures/sdk/index.ts b/e2e/fixtures/sdk/index.ts index 82e1ff3d..78c10ec8 100644 --- a/e2e/fixtures/sdk/index.ts +++ b/e2e/fixtures/sdk/index.ts @@ -7,10 +7,10 @@ export type SDKFixture = { deposit: Deposit } -const sdk: E2E.Fixture = async ({ page }, use) => { +const sdk: E2E.Fixture = async ({ page, wallet }, use) => { await use({ - mint: createMint({ page }), - deposit: createDeposit({ page }), + mint: createMint({ page, wallet }), + deposit: createDeposit({ page, wallet }), }) } diff --git a/e2e/fixtures/sdk/mint.ts b/e2e/fixtures/sdk/mint.ts index 33f0b220..85a159d4 100644 --- a/e2e/fixtures/sdk/mint.ts +++ b/e2e/fixtures/sdk/mint.ts @@ -1,7 +1,7 @@ import { formatEther, parseEther } from 'ethers' -type Wrapper = E2E.FixtureMethod +type Wrapper = E2E.FixtureMethod export type Mint = (values: Input) => Promise @@ -10,12 +10,10 @@ export type Input = { assets: string } -export const createMint: Wrapper = ({ page }) => ( +export const createMint: Wrapper = ({ page, wallet }) => ( async ({ vaultAddress, assets }: Input) => { - const shares = await page.evaluate(async ({ vaultAddress, stakeAssets }) => { + const shares = await page.evaluate(async ({ vaultAddress, stakeAssets, userAddress }) => { const sdk = window.e2e.sdk - // @ts-ignore - const userAddress = window.ethereum.signer.address const assets = BigInt(stakeAssets) const shares = await sdk.contracts.base.mintTokenController.convertToShares(assets) @@ -32,6 +30,7 @@ export const createMint: Wrapper = ({ page }) => ( }, { vaultAddress, stakeAssets: parseEther(assets).toString(), + userAddress: wallet.getAddress(), }) return formatEther(shares) diff --git a/e2e/fixtures/swap/actions/boost.ts b/e2e/fixtures/swap/actions/boost.ts index 7f23b5cd..0cdb78a4 100644 --- a/e2e/fixtures/swap/actions/boost.ts +++ b/e2e/fixtures/swap/actions/boost.ts @@ -42,8 +42,6 @@ export const createBoost: Wrapper = ({ page, transactions, api }) => ( checkNotifications: false, }) - await page.waitForLoadState('networkidle') - await transactions.checkTxCompletedModal({ action: 'boost', value: inputAmount, diff --git a/e2e/fixtures/swap/helpers/checkTokenDropdown.ts b/e2e/fixtures/swap/helpers/checkTokenDropdown.ts index c3e9174a..1b049b7e 100644 --- a/e2e/fixtures/swap/helpers/checkTokenDropdown.ts +++ b/e2e/fixtures/swap/helpers/checkTokenDropdown.ts @@ -8,8 +8,6 @@ export const createCheckTokenDropdown: Wrapper = ({ page, element }) => ( await page.getByTestId('amount-input-token').click() - await page.waitForLoadState('networkidle') - await element.checkVisibility({ testId: 'amount-input-input' }) await element.checkVisibility({ testId: 'amount-input-option-USDT' }) @@ -19,8 +17,7 @@ export const createCheckTokenDropdown: Wrapper = ({ page, element }) => ( await page.getByTestId('amount-input-token').click() - await page.waitForLoadState('networkidle') - + await element.checkVisibility({ testId: 'amount-input-input' }) await page.getByTestId('amount-input-input').fill(stakeToken) await element.checkVisibility({ testId: `amount-input-option-${stakeToken}` }) diff --git a/e2e/fixtures/swap/setSdkTransactions.ts b/e2e/fixtures/swap/setSdkTransactions.ts index 88a40875..e425954a 100644 --- a/e2e/fixtures/swap/setSdkTransactions.ts +++ b/e2e/fixtures/swap/setSdkTransactions.ts @@ -31,7 +31,7 @@ export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql }) => ( await graphql.mockAllocatorsData(deposit) await page.reload() - await page.waitForLoadState('networkidle') + await page.waitForLoadState('domcontentloaded') return shares } diff --git a/e2e/fixtures/vault/index.ts b/e2e/fixtures/vault/index.ts index 0619ced5..b74c05a2 100644 --- a/e2e/fixtures/vault/index.ts +++ b/e2e/fixtures/vault/index.ts @@ -5,9 +5,9 @@ export type VaultFixture = { setVaultData: SetVaultData } -const vault: E2E.Fixture = async ({ page }, use) => { +const vault: E2E.Fixture = async ({ page, wallet }, use) => { await use({ - setVaultData: createSetVaultData({ page }), + setVaultData: createSetVaultData({ page, wallet }), }) } diff --git a/e2e/fixtures/vault/setVaultData.ts b/e2e/fixtures/vault/setVaultData.ts index 873f348b..bb685b82 100644 --- a/e2e/fixtures/vault/setVaultData.ts +++ b/e2e/fixtures/vault/setVaultData.ts @@ -7,16 +7,18 @@ export type SetVaultData = (data?: any) => Promise type Output = Store['vault']['base']['data'] -type Wrapper = E2E.FixtureMethod +type Wrapper = E2E.FixtureMethod -export const createSetVaultData: Wrapper = ({ page }) => ( +export const createSetVaultData: Wrapper = ({ page, wallet }) => ( async (data) => { let admin = data?.admin let address = ZeroAddress if (!admin) { + // Fall back to ZeroAddress when no wallet is injected yet: tests often mock + // GraphQL data before `wallet.init`, so the address may not be available. try { - admin = await page.evaluate('window.ethereum.signer.address') + admin = wallet.getAddress() } catch {} } diff --git a/e2e/fixtures/wallet/chains.ts b/e2e/fixtures/wallet/chains.ts new file mode 100644 index 00000000..877440df --- /dev/null +++ b/e2e/fixtures/wallet/chains.ts @@ -0,0 +1,55 @@ +import { configs, Network } from '@stakewise/v3-sdk' + + +const mainnetTokens = configs[Network.Mainnet].addresses.tokens + +type ChainHolders = Record + +export type SupportedNetwork = Network.Mainnet | Network.Gnosis + +export type ChainEntry = { + name: string + rpcUrl: string + defaultPrivateKey: string + holders: ChainHolders +} + +const defaultPrivateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + +const ethereumHolders: ChainHolders = { + [mainnetTokens.mintToken]: '0x927709711794F3De5DdBF1D176bEE2D55Ba13c21', + [mainnetTokens.v2StakedToken]: '0x0A2504b0B4a9d08b699BeaA72D53F0267bCFfFbb', + [mainnetTokens.v2RewardToken]: '0x0A2504b0B4a9d08b699BeaA72D53F0267bCFfFbb', +} + +const gnosisHolders: ChainHolders = {} + +export const chains: Record = { + [Network.Mainnet]: { + defaultPrivateKey, + name: configs[Network.Mainnet].network.name, + rpcUrl: 'http://127.0.0.1:8545', + holders: ethereumHolders, + }, + [Network.Gnosis]: { + defaultPrivateKey, + name: configs[Network.Gnosis].network.name, + rpcUrl: 'http://127.0.0.1:8546', + holders: gnosisHolders, + }, +} + +export const defaultChainId: SupportedNetwork = Network.Mainnet + +export const getAvailableChains = () => { + const entries = Object.entries(chains).map(([ id, entry ]) => { + const chainId = Number(id) as SupportedNetwork + + return [ + configs[chainId].network.hexadecimalChainId, + { rpcUrl: entry.rpcUrl, name: entry.name }, + ] + }) + + return Object.fromEntries(entries) +} diff --git a/e2e/fixtures/wallet/checkWalletList.ts b/e2e/fixtures/wallet/checkWalletList.ts index 53409315..b25510ba 100644 --- a/e2e/fixtures/wallet/checkWalletList.ts +++ b/e2e/fixtures/wallet/checkWalletList.ts @@ -1,9 +1,9 @@ import * as constants from '../../constants' -type Wrapper = E2E.FixtureMethod +type Wrapper = E2E.FixtureMethod -export type checkWalletList = (isMobile?: boolean, withWalletConnect?: boolean) => Promise +export type CheckWalletList = (isMobile?: boolean, withWalletConnect?: boolean) => Promise const walletList = [ { @@ -40,12 +40,12 @@ const mobileList = [ constants.walletNames.coinbase, constants.walletNames.walletConnect, constants.walletNames.monitorAddress, -] as string[] +] const mobileWallets = walletList.filter(({ id }) => mobileList.includes(id)) const desktopWallets = walletList.filter(({ id }) => id !== constants.walletNames.dAppBrowser) -export const createcheckWalletList: Wrapper = ({ page, element }) => ( +export const createCheckWalletList: Wrapper = ({ page, element }) => ( async (isMobile?: boolean, withWalletConnect = true) => { let currentList = isMobile ? mobileWallets : desktopWallets diff --git a/e2e/fixtures/wallet/connectWithBalance.ts b/e2e/fixtures/wallet/connectWithBalance.ts index 265ed811..8b54ec39 100644 --- a/e2e/fixtures/wallet/connectWithBalance.ts +++ b/e2e/fixtures/wallet/connectWithBalance.ts @@ -1,31 +1,27 @@ import * as constants from '../../constants' -import { createSetBalance, Token } from './setBalance' +import type { SetBalance, Token } from './setBalance' -type Wrapper = E2E.FixtureMethod - type Input = Partial> export type ConnectWithBalance = (tokens: Input) => Promise -export const createConnectWithBalance: Wrapper = ({ page, helpers, gui, anvil, graphql }) => ( - async (tokens: Input) => { - const promises: Promise[] = [] - - const setBalance = createSetBalance({ gui, anvil, graphql }) - - Object.keys(tokens).forEach((token) => { - const amount = tokens[token as Token] - - if (amount) { - promises.push( - setBalance(amount, token as Token) - ) - } - }) - - await Promise.all(promises) +type Deps = { + page: E2E.ExtendedTest['page'] + helpers: E2E.ExtendedTest['helpers'] + setBalance: SetBalance +} + +export const createConnectWithBalance = ({ page, helpers, setBalance }: Deps): ConnectWithBalance => ( + async (tokens) => { + const entries = Object.entries(tokens) as [Token, string | undefined][] + + await Promise.all( + entries + .filter(([ , amount ]) => Boolean(amount)) + .map(([ token, amount ]) => setBalance({ token, amount: amount! })) + ) await page.getByTestId('connect-button').click() await page.getByTestId('metaMask-connector-button').click() diff --git a/e2e/fixtures/wallet/helpers/balance.ts b/e2e/fixtures/wallet/helpers/balance.ts new file mode 100644 index 00000000..384e9cca --- /dev/null +++ b/e2e/fixtures/wallet/helpers/balance.ts @@ -0,0 +1,78 @@ +import { Contract, JsonRpcProvider, parseEther, toBeHex } from 'ethers' + +import { chains } from '../chains' +import type { SupportedNetwork } from '../chains' +import { impersonate } from './impersonate' +import type { State } from '../init' + + +type Input = { + token: string + amount: bigint + chainId?: SupportedNetwork +} + +export type Balance = (input: Input) => Promise + +const transferAbi = [ + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transfer', + outputs: [ { internalType: 'bool', name: '', type: 'bool' } ], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +type Wrapper = (deps: { state: State }) => Balance + +const holderEthHex = toBeHex(parseEther('100000')) + +export const createBalance: Wrapper = ({ state }) => ( + async ({ token, amount, chainId }) => { + if (!state.address) { + throw new Error('Wallet not initialized - call wallet.init() first') + } + + const targetChainId = chainId || state.chainId + const chainEntry = chains[targetChainId] + const holder = chainEntry.holders[token] + + if (!holder) { + throw new Error( + `No known holder for token ${token} on chain ${targetChainId}. ` + + `Add it to chains.ts holders map.` + ) + } + + const rpc = new JsonRpcProvider(chainEntry.rpcUrl) + + try { + await impersonate({ rpc, rpcUrl: chainEntry.rpcUrl, address: holder }) + await rpc.send('anvil_setBalance', [ holder, holderEthHex ]) + + const signer = await rpc.getSigner(holder) + const contract = new Contract(token, transferAbi, signer) + + try { + const tx = await contract.transfer(state.address, amount) + + await rpc.waitForTransaction(tx.hash, 1, 30_000) + } + catch (error) { + const message = error instanceof Error ? error.message : String(error) + + throw new Error( + `balance: transfer failed (token=${token}, holder=${holder}, recipient=${state.address}, chainId=${targetChainId}): ${message}`, + { cause: error } + ) + } + } + finally { + rpc.destroy() + } + } +) diff --git a/e2e/fixtures/wallet/helpers/eip1193-provider.js b/e2e/fixtures/wallet/helpers/eip1193-provider.js new file mode 100644 index 00000000..db17625b --- /dev/null +++ b/e2e/fixtures/wallet/helpers/eip1193-provider.js @@ -0,0 +1,289 @@ +/* eslint-env browser */ +/* eslint-disable no-undef */ +/* + * Injected via page.addInitScript(). `__MOCK_*__` tokens are replaced in Node + * (see initProvider.ts) before the script reaches the browser. ESLint cannot + * see those replacements - hence the disable above. + */ +(function() { + const chains = __MOCK_CHAINS_JSON__ + const address = '__MOCK_ADDRESS__' + let currentChainIdHex = '__MOCK_DEFAULT_CHAIN_ID_HEX__' + + const rpcTimeoutMs = 30_000 + const pendingTxMaxAttempts = 300 // 100ms * 300 = 30s total budget + + // `Object.create(null)` guards against prototype pollution. + const listeners = Object.create(null) + + const emit = (event, ...args) => { + (listeners[event] || []).slice().forEach((cb) => { + try { + cb(...args) + } + catch (error) { + console.error(`[e2e wallet] listener for "${event}" threw:`, error) + } + }) + } + + const on = (event, cb) => { + listeners[event] = listeners[event] || [] + listeners[event].push(cb) + } + + const removeListener = (event, cb) => { + const arr = listeners[event] || [] + const index = arr.indexOf(cb) + + if (index !== -1) { + arr.splice(index, 1) + } + } + + const blockIdToTag = (blockId) => { + if (blockId == null) { + return 'latest' + } + + if (typeof blockId === 'string') { + return blockId + } + + return `0x${blockId.toString(16)}` + } + + let rpcId = 0 + + async function rpcCall(method, params) { + const url = chains[currentChainIdHex].rpcUrl + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), rpcTimeoutMs) + + let response + try { + response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: ++rpcId, + method, + params: params || [], + }), + signal: controller.signal, + }) + } + catch (error) { + const isTimeout = error?.name === 'AbortError' + const reason = isTimeout + ? `timed out after ${rpcTimeoutMs}ms` + : error?.message || 'network failure' + + throw new Error(`RPC ${method} ${reason} [${url}]`) + } + finally { + clearTimeout(timeoutId) + } + + if (!response.ok) { + throw new Error(`RPC ${method} HTTP ${response.status} [${url}]`) + } + + let json + try { + json = await response.json() + } + catch (error) { + throw new Error(`RPC ${method} invalid JSON: ${error.message} [${url}]`) + } + + if (json.error) { + const err = new Error(`RPC ${method}: ${json.error.message || 'error'}`) + err.code = json.error.code + err.data = json.error.data + throw err + } + + return json.result + } + + const compatProvider = { + get connection() { + return { url: chains[currentChainIdHex].rpcUrl } + }, + + async getBlock(blockId) { + return rpcCall('eth_getBlockByNumber', [ blockIdToTag(blockId), false ]) + }, + + async getFeeData() { + const [ gasPrice, block ] = await Promise.all([ + rpcCall('eth_gasPrice', []), + rpcCall('eth_getBlockByNumber', [ 'latest', false ]), + ]) + + return { + gasPrice, + maxFeePerGas: block?.baseFeePerGas, + maxPriorityFeePerGas: '0x3b9aca00', + } + }, + + async getNetwork() { + const chainId = await rpcCall('eth_chainId', []) + + return { + chainId: parseInt(chainId, 16), + name: chains[chainId]?.name || 'unknown', + } + }, + + async getTransaction(txHash, attempt = 0) { + const tx = await rpcCall('eth_getTransactionByHash', [ txHash ]) + + if (tx?.blockNumber) { + const receipt = await rpcCall('eth_getTransactionReceipt', [ txHash ]) + + return { + ...tx, + gasUsed: receipt?.gasUsed || tx.gas, + cumulativeGasUsed: receipt?.cumulativeGasUsed || tx.gas, + logs: receipt?.logs || [], + status: receipt?.status || '0x1', + } + } + + if (attempt >= pendingTxMaxAttempts) { + throw new Error(`getTransaction: ${txHash} not mined after ${pendingTxMaxAttempts * 100}ms`) + } + + await new Promise((resolve) => setTimeout(resolve, 100)) + + return compatProvider.getTransaction(txHash, attempt + 1) + }, + } + + const provider = { + // `isConnected` is a method per EIP-1193 but some older libs read it as a bool; + // a function is truthy so both work. + isMetaMask: true, + isConnected: () => true, + providers: [], + + get chainId() { + return currentChainIdHex + }, + get selectedAddress() { + return address + }, + get networkVersion() { + return String(parseInt(currentChainIdHex, 16)) + }, + + provider: compatProvider, + + on, + removeListener, + + async request(args) { + const method = args?.method + const params = args?.params || [] + + switch (method) { + case 'eth_accounts': + case 'eth_requestAccounts': + return [ address ] + + case 'eth_chainId': + return currentChainIdHex + + case 'net_version': + return String(parseInt(currentChainIdHex, 16)) + + case 'wallet_switchEthereumChain': { + const targetChainIdHex = params[0]?.chainId + + if (!chains[targetChainIdHex]) { + const err = new Error('Unrecognized chain ID. Try adding the chain first.') + err.code = 4902 + throw err + } + + currentChainIdHex = targetChainIdHex + emit('chainChanged', currentChainIdHex) + + return null + } + + case 'wallet_addEthereumChain': { + const chainIdHex = params[0]?.chainId + + if (!chains[chainIdHex]) { + const err = new Error('Cannot add unknown chain at runtime') + err.code = 4902 + throw err + } + + return null + } + + case 'eth_sendTransaction': { + const tx = { ...params[0] } + + if (!tx.from) { + tx.from = address + } + + return rpcCall('eth_sendTransaction', [ tx ]) + } + + case 'personal_sign': { + const [ message, addr ] = params + + return window.__sw_e2e_personalSign(addr || address, message) + } + + case 'eth_sign': { + const [ addr, message ] = params + + return window.__sw_e2e_personalSign(addr || address, message) + } + + case 'eth_signTypedData': + case 'eth_signTypedData_v1': { + // Legacy v1 uses an array of typed pairs, not the EIP-712 object viem expects. + throw new Error(`${method} is not supported - use eth_signTypedData_v4`) + } + + case 'eth_signTypedData_v3': + case 'eth_signTypedData_v4': { + const [ addr, data ] = params + + return window.__sw_e2e_signTypedData(addr || address, data, method) + } + + default: + return rpcCall(method, params) + } + }, + + send(method, params) { + if (typeof method === 'string') { + return provider.request({ method, params }) + } + + return provider.sendAsync(method, params) + }, + + sendAsync(payload, callback) { + provider.request({ method: payload.method, params: payload.params }) + .then((result) => callback(null, { id: payload.id, jsonrpc: '2.0', result })) + .catch((error) => callback(error, null)) + }, + } + + window.ethereum = provider + + window.dispatchEvent(new Event('ethereum#initialized')) +})() diff --git a/e2e/fixtures/wallet/helpers/impersonate.ts b/e2e/fixtures/wallet/helpers/impersonate.ts new file mode 100644 index 00000000..483e91ca --- /dev/null +++ b/e2e/fixtures/wallet/helpers/impersonate.ts @@ -0,0 +1,30 @@ +import type { JsonRpcProvider } from 'ethers' + + +type Input = { + rpc: JsonRpcProvider + rpcUrl: string + address: string +} + +const cache: Record> = {} + +export const impersonate = async ({ rpc, rpcUrl, address }: Input) => { + const addressKey = address.toLowerCase() + + if (!cache[rpcUrl]) { + cache[rpcUrl] = {} + } + + if (cache[rpcUrl][addressKey]) { + return + } + + await rpc.send('anvil_impersonateAccount', [ address ]) + + cache[rpcUrl][addressKey] = true +} + +export const clearImpersonatedCache = (rpcUrl: string) => { + delete cache[rpcUrl] +} diff --git a/e2e/fixtures/wallet/helpers/index.ts b/e2e/fixtures/wallet/helpers/index.ts new file mode 100644 index 00000000..f49f65ed --- /dev/null +++ b/e2e/fixtures/wallet/helpers/index.ts @@ -0,0 +1,5 @@ +export { createBalance } from './balance' +export type { Balance } from './balance' +export { initProvider } from './initProvider' +export { impersonate, clearImpersonatedCache } from './impersonate' +export { resetAllChains } from './resetAllChains' diff --git a/e2e/fixtures/wallet/helpers/initProvider.ts b/e2e/fixtures/wallet/helpers/initProvider.ts new file mode 100644 index 00000000..18e538da --- /dev/null +++ b/e2e/fixtures/wallet/helpers/initProvider.ts @@ -0,0 +1,117 @@ +import fs from 'fs' +import path from 'path' +import type { Page } from '@playwright/test' +import { Wallet, JsonRpcProvider, getBytes, isHexString, parseEther, toBeHex } from 'ethers' +import { configs } from '@stakewise/v3-sdk' + +import { chains, getAvailableChains } from '../chains' +import type { SupportedNetwork } from '../chains' +import { impersonate } from './impersonate' + + +const providerScriptPath = path.join(__dirname, 'eip1193-provider.js') +const providerScriptTemplate = fs.readFileSync(providerScriptPath, 'utf-8') + +const initialEthHex = toBeHex(parseEther('10000')) + +const cookieScript = `document.cookie = 'SW_e2e=true'` + +type InitProviderBase = { + page: Page + chainId: SupportedNetwork +} + +type InitProviderInput = + | InitProviderBase & { privateKey: string; address?: never } + | InitProviderBase & { address: string; privateKey?: never } + +const buildProviderScript = (address: string, chainId: SupportedNetwork) => { + const chainMap = getAvailableChains() + + return providerScriptTemplate + .replace('__MOCK_CHAINS_JSON__', () => JSON.stringify(chainMap)) + .replace('__MOCK_DEFAULT_CHAIN_ID_HEX__', () => configs[chainId].network.hexadecimalChainId) + .replace('__MOCK_ADDRESS__', () => address) +} + +const exposeSigner = async (page: Page, privateKey: string) => { + const wallet = new Wallet(privateKey) + + const exposals: Array<[string, (...args: any[]) => Promise]> = [ + [ + '__sw_e2e_signTypedData', + async (_address: string, data: string) => { + const { domain, types, message } = JSON.parse(data) + const { EIP712Domain: _, ...cleanTypes } = types + + return wallet.signTypedData(domain, cleanTypes, message) + }, + ], + [ + '__sw_e2e_personalSign', + async (_address: string, message: string) => { + const payload = isHexString(message) ? getBytes(message) : message + + return wallet.signMessage(payload) + }, + ], + ] + + for (const [ name, fn ] of exposals) { + try { + await page.exposeFunction(name, fn) + } + catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + + if (!/already registered/i.test(errorMessage)) { + throw error + } + } + } +} + +const impersonateOnChain = async (rpcUrl: string, address: string) => { + const rpc = new JsonRpcProvider(rpcUrl) + + try { + await impersonate({ rpc, rpcUrl, address }) + await rpc.send('anvil_setBalance', [ address, initialEthHex ]) + } + finally { + rpc.destroy() + } +} + +export const initProvider = async (input: InitProviderInput) => { + const { page, chainId } = input + + if (!chains[chainId]) { + throw new Error(`Unknown chainId: ${chainId}`) + } + + const address = input.address || new Wallet(input.privateKey).address + const script = buildProviderScript(address, chainId) + + const impersonationTasks = Object.values(chains).map(async (entry) => { + try { + await impersonateOnChain(entry.rpcUrl, address) + } + catch (error) { + console.warn(`[wallet.init] Skipping impersonation on ${entry.name}: ${(error as Error).message}`) + } + }) + + const tasks = [ ...impersonationTasks ] + + if (input.privateKey) { + tasks.push(exposeSigner(page, input.privateKey)) + } + + await Promise.all(tasks) + + await page.addInitScript(cookieScript) + await page.addInitScript(script) + + return { address } +} diff --git a/e2e/fixtures/wallet/helpers/resetAllChains.ts b/e2e/fixtures/wallet/helpers/resetAllChains.ts new file mode 100644 index 00000000..5ddf5718 --- /dev/null +++ b/e2e/fixtures/wallet/helpers/resetAllChains.ts @@ -0,0 +1,72 @@ +import { JsonRpcProvider } from 'ethers' +import { Network } from '@stakewise/v3-sdk' + +import { chains } from '../chains' +import type { SupportedNetwork } from '../chains' +import { impersonate, clearImpersonatedCache } from './impersonate' + + +const forkUrls: Record = { + [Network.Mainnet]: process.env.RPC_URL, + [Network.Gnosis]: process.env.GNOSIS_RPC_URL, +} + +const requiredChains: SupportedNetwork[] = [ Network.Mainnet ] + +const rpcTimeoutMs = 30_000 + +const withTimeout = (promise: Promise, ms: number, label: string): Promise => ( + Promise.race([ + promise, + new Promise((_, reject) => ( + setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms) + )), + ]) +) + +const resetChain = async (chainId: SupportedNetwork) => { + const chainEntry = chains[chainId] + const forkUrl = forkUrls[chainId] + + if (!forkUrl) { + if (requiredChains.includes(chainId)) { + throw new Error(`Required fork URL missing for chain ${chainId} - set RPC_URL in CI secrets`) + } + + console.warn(`[anvil.reset] Skipping chain ${chainId} - no fork URL configured`) + + return + } + + const rpc = new JsonRpcProvider(chainEntry.rpcUrl) + + try { + await withTimeout( + rpc.send('anvil_reset', [ { forking: { jsonRpcUrl: forkUrl } } ]), + rpcTimeoutMs, + `anvil_reset chain=${chainId}` + ) + + clearImpersonatedCache(chainEntry.rpcUrl) + + for (const holder of Object.values(chainEntry.holders)) { + try { + await impersonate({ rpc, rpcUrl: chainEntry.rpcUrl, address: holder }) + } + catch (error) { + const message = error instanceof Error ? error.message : String(error) + + console.warn(`[anvil.reset] impersonate ${holder} on chain ${chainId} failed: ${message}`) + } + } + } + finally { + rpc.destroy() + } +} + +export const resetAllChains = async () => { + const ids = Object.keys(chains).map((id) => Number(id) as SupportedNetwork) + + await Promise.all(ids.map(resetChain)) +} diff --git a/e2e/fixtures/wallet/index.ts b/e2e/fixtures/wallet/index.ts index 637c37d9..ea3c3e0d 100644 --- a/e2e/fixtures/wallet/index.ts +++ b/e2e/fixtures/wallet/index.ts @@ -1,28 +1,54 @@ import { createConnect, Connect } from './connect' import { createDisconnect, Disconnect } from './disconnect' import { createSetBalance, SetBalance } from './setBalance' +import { createSetEthBalance, SetEthBalance } from './setEthBalance' +import { createBalance } from './helpers' import { createMonitorAddress, MonitorAddress } from './monitorAddress' -import { createcheckWalletList, checkWalletList } from './checkWalletList' +import { createCheckWalletList, CheckWalletList } from './checkWalletList' import { createConnectWithBalance, ConnectWithBalance } from './connectWithBalance' +import { createInit, createState, Init } from './init' +import { createSwitchChain, SwitchChain } from './switchChain' export type WalletFixture = { + init: Init + getAddress: () => string + switchChain: SwitchChain connect: Connect disconnect: Disconnect setBalance: SetBalance + setEthBalance: SetEthBalance monitorAddress: MonitorAddress - checkWalletList: checkWalletList + checkWalletList: CheckWalletList connectWithBalance: ConnectWithBalance } -const wallet: E2E.Fixture = async ({ gui, page, anvil, graphql, helpers, element }, use) => { +const wallet: E2E.Fixture = async ({ page, helpers, element, graphql }, use) => { + const state = createState() + + const balance = createBalance({ state }) + const setEthBalance = createSetEthBalance({ state }) + const setBalance = createSetBalance({ balance, setEthBalance, graphql }) + + const getAddress = () => { + if (!state.address) { + throw new Error('Wallet not initialized - call wallet.init() first') + } + + return state.address + } + await use({ + init: createInit({ page, state }), + getAddress, + switchChain: createSwitchChain({ page, state }), connect: createConnect({ page, helpers }), - monitorAddress: createMonitorAddress({ page }), disconnect: createDisconnect({ page, helpers }), - setBalance: createSetBalance({ anvil, gui, graphql }), - checkWalletList: createcheckWalletList({ page, element }), - connectWithBalance: createConnectWithBalance({ gui, page, anvil, graphql, helpers }), + monitorAddress: createMonitorAddress({ page }), + checkWalletList: createCheckWalletList({ page, element }), + connectWithBalance: createConnectWithBalance({ page, helpers, setBalance }), + setEthBalance, + setBalance, }) } diff --git a/e2e/fixtures/wallet/init.ts b/e2e/fixtures/wallet/init.ts new file mode 100644 index 00000000..625e9804 --- /dev/null +++ b/e2e/fixtures/wallet/init.ts @@ -0,0 +1,41 @@ +import { Wallet } from 'ethers' + +import { defaultChainId } from './chains' +import type { SupportedNetwork } from './chains' +import { initProvider } from './helpers' + + +type Input = { + privateKey?: string + address?: string + chainId?: SupportedNetwork +} + +export type Init = (input?: Input) => Promise + +type State = { + address: string | null + chainId: SupportedNetwork +} + +type Wrapper = (deps: { page: E2E.ExtendedTest['page']; state: State }) => Init + +export const createInit: Wrapper = ({ page, state }) => ( + async (input) => { + const chainId = input?.chainId || defaultChainId + + const { address } = input?.address + ? await initProvider({ page, address: input.address, chainId }) + : await initProvider({ page, privateKey: input?.privateKey || Wallet.createRandom().privateKey, chainId }) + + state.address = address + state.chainId = chainId + } +) + +export const createState = (): State => ({ + address: null, + chainId: defaultChainId, +}) + +export type { State } diff --git a/e2e/fixtures/wallet/setBalance.ts b/e2e/fixtures/wallet/setBalance.ts index acba9694..d58829ef 100644 --- a/e2e/fixtures/wallet/setBalance.ts +++ b/e2e/fixtures/wallet/setBalance.ts @@ -1,44 +1,53 @@ +import { configs, Network } from '@stakewise/v3-sdk' import { parseEther } from 'ethers' +import type { SupportedNetwork } from './chains' +import type { Balance } from './helpers' +import type { SetEthBalance } from './setEthBalance' -type Wrapper = E2E.FixtureMethod export type Token = 'ETH' | 'osETH' -export type SetBalance = (amount: string, token: Token) => Promise +type Input = { + token: Token + amount: string + chainId?: SupportedNetwork +} + +export type SetBalance = (input: Input) => Promise -const tokenAddresses = { - 'osETH': '0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38', +const tokenAddresses: Partial> = { + osETH: configs[Network.Mainnet].addresses.tokens.mintToken, } -export const createSetBalance: Wrapper = ({ gui, anvil, graphql }) => ( - async (amount: string, token: Token) => { +type Wrapper = (deps: { + balance: Balance + setEthBalance: SetEthBalance + graphql: E2E.ExtendedTest['graphql'] +}) => SetBalance + +export const createSetBalance: Wrapper = ({ balance, setEthBalance, graphql }) => ( + async ({ token, amount, chainId }) => { const amountWei = parseEther(amount) if (token === 'ETH') { - await gui.setEthBalance(amountWei) + await setEthBalance({ amount: amountWei, chainId }) + + return + } + + const tokenAddress = tokenAddresses[token] + + if (!tokenAddress) { + throw new Error(`setBalance: missing address mapping for token ${token}`) } - else { - const tokenAddress = tokenAddresses[token as keyof typeof tokenAddresses] - - const isAnvil = token !== 'osETH' || process.env.CI - - const promises = [ - isAnvil - ? anvil.setBalance({ - amount: amountWei.toString(), - tokenAddress, - }) - : gui.setBalance(tokenAddress, amountWei), - ] - - if (token === 'osETH') { - promises.push( - graphql.mockMintTokenBalance(amount) - ) - } - - await Promise.all(promises) + + const promises: Promise[] = [ balance({ token: tokenAddress, amount: amountWei, chainId }) ] + + if (token === 'osETH') { + promises.push(graphql.mockMintTokenBalance(amount)) } + + await Promise.all(promises) } ) diff --git a/e2e/fixtures/wallet/setEthBalance.ts b/e2e/fixtures/wallet/setEthBalance.ts new file mode 100644 index 00000000..a8550a8e --- /dev/null +++ b/e2e/fixtures/wallet/setEthBalance.ts @@ -0,0 +1,33 @@ +import { JsonRpcProvider, toBeHex } from 'ethers' + +import { chains } from './chains' +import type { SupportedNetwork } from './chains' +import type { State } from './init' + + +type Input = { + amount: bigint + chainId?: SupportedNetwork +} + +export type SetEthBalance = (input: Input) => Promise + +type Wrapper = (deps: { state: State }) => SetEthBalance + +export const createSetEthBalance: Wrapper = ({ state }) => ( + async ({ amount, chainId }) => { + if (!state.address) { + throw new Error('Wallet not initialized - call wallet.init() first') + } + + const targetChainId = chainId || state.chainId + const rpc = new JsonRpcProvider(chains[targetChainId].rpcUrl) + + try { + await rpc.send('anvil_setBalance', [ state.address, toBeHex(amount) ]) + } + finally { + rpc.destroy() + } + } +) diff --git a/e2e/fixtures/wallet/switchChain.ts b/e2e/fixtures/wallet/switchChain.ts new file mode 100644 index 00000000..b4ee29f5 --- /dev/null +++ b/e2e/fixtures/wallet/switchChain.ts @@ -0,0 +1,26 @@ +import type { Page } from '@playwright/test' +import { configs } from '@stakewise/v3-sdk' + +import type { SupportedNetwork } from './chains' +import type { State } from './init' + + +export type SwitchChain = (chainId: SupportedNetwork) => Promise + +type Wrapper = (deps: { page: Page; state: State }) => SwitchChain + +export const createSwitchChain: Wrapper = ({ page, state }) => ( + async (chainId) => { + await page.evaluate( + async (chainIdHex) => { + await (window as any).ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [ { chainId: chainIdHex } ], + }) + }, + configs[chainId].network.hexadecimalChainId + ) + + state.chainId = chainId + } +) diff --git a/e2e/tests/balance.spec.ts b/e2e/tests/balance.spec.ts index 922170d7..cf0eaffa 100644 --- a/e2e/tests/balance.spec.ts +++ b/e2e/tests/balance.spec.ts @@ -1,9 +1,8 @@ import test from '../extendTest' -test.beforeEach(async ({ gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test.beforeEach(async ({ wallet }) => { + await wallet.init() }) test('Enabled claim', async ({ wallet, swap, queue, user }) => { diff --git a/e2e/tests/boost.spec.ts b/e2e/tests/boost.spec.ts index f81129ae..b604f375 100644 --- a/e2e/tests/boost.spec.ts +++ b/e2e/tests/boost.spec.ts @@ -3,9 +3,8 @@ import { expect } from '@playwright/test' import test from '../extendTest' -test.beforeEach(async ({ gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test.beforeEach(async ({ wallet }) => { + await wallet.init() }) test('Connect button', async ({ swap }) => { diff --git a/e2e/tests/burn.spec.ts b/e2e/tests/burn.spec.ts index 891c4213..ebdbc43b 100644 --- a/e2e/tests/burn.spec.ts +++ b/e2e/tests/burn.spec.ts @@ -3,9 +3,8 @@ import { expect } from '@playwright/test' import test from '../extendTest' -test.beforeEach(async ({ gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test.beforeEach(async ({ wallet }) => { + await wallet.init() }) test('Connect button', async ({ swap }) => { diff --git a/e2e/tests/loader.spec.ts b/e2e/tests/loader.spec.ts index 8b8c0ca0..ff399d61 100644 --- a/e2e/tests/loader.spec.ts +++ b/e2e/tests/loader.spec.ts @@ -4,9 +4,8 @@ import * as constants from '../constants' import test from '../extendTest' -test('Connect wallet | Disconnect wallet', async ({ page, wallet, swap, gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test('Connect wallet | Disconnect wallet', async ({ page, wallet, swap }) => { + await wallet.init() await page.goto('/') await swap.helpers.checkSwapRender() diff --git a/e2e/tests/mint.spec.ts b/e2e/tests/mint.spec.ts index a049b05a..ea8b6f62 100644 --- a/e2e/tests/mint.spec.ts +++ b/e2e/tests/mint.spec.ts @@ -3,9 +3,8 @@ import { expect } from '@playwright/test' import test from '../extendTest' -test.beforeEach(async ({ gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test.beforeEach(async ({ wallet }) => { + await wallet.init() }) test('Connect button', async ({ swap }) => { diff --git a/e2e/tests/stake.spec.ts b/e2e/tests/stake.spec.ts index 07cb671a..62d6f364 100644 --- a/e2e/tests/stake.spec.ts +++ b/e2e/tests/stake.spec.ts @@ -4,9 +4,8 @@ import { expect } from '@playwright/test' import test from '../extendTest' -test.beforeEach(async ({ gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test.beforeEach(async ({ wallet }) => { + await wallet.init() }) test('Connect button', async ({ swap }) => { diff --git a/e2e/tests/unboost.spec.ts b/e2e/tests/unboost.spec.ts index e5b2b57c..3aa21c55 100644 --- a/e2e/tests/unboost.spec.ts +++ b/e2e/tests/unboost.spec.ts @@ -3,9 +3,8 @@ import { expect } from '@playwright/test' import test from '../extendTest' -test.beforeEach(async ({ gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test.beforeEach(async ({ wallet }) => { + await wallet.init() }) test('Connect button', async ({ swap }) => { diff --git a/e2e/tests/unstake.spec.ts b/e2e/tests/unstake.spec.ts index 083a8365..4e190625 100644 --- a/e2e/tests/unstake.spec.ts +++ b/e2e/tests/unstake.spec.ts @@ -3,9 +3,8 @@ import { expect } from '@playwright/test' import test from '../extendTest' -test.beforeEach(async ({ gui, guardian }) => { - await guardian.fixProvider() - await gui.initializeChain(1) +test.beforeEach(async ({ wallet }) => { + await wallet.init() }) test('Connect button', async ({ swap }) => { diff --git a/e2e/types.d.ts b/e2e/types.d.ts index e0139472..1a39ecde 100644 --- a/e2e/types.d.ts +++ b/e2e/types.d.ts @@ -1,5 +1,4 @@ import type { BrowserContext, Page, TestFixture } from '@playwright/test' -import type { GUI } from '@guardianui/test/dist/models/GUI' import type { ApiFixture, @@ -8,14 +7,12 @@ import type { SwapFixture, QueueFixture, VaultFixture, - AnvilFixture, WalletFixture, OsTokenFixture, ElementFixture, HelpersFixture, GraphqlFixture, SettingsFixture, - GuardianFixture, TransactionsFixture, } from './fixtures' @@ -25,7 +22,6 @@ declare global { namespace E2E { interface ExtendedTest { - gui: GUI page: Page api: ApiFixture sdk: SDKFixture @@ -33,7 +29,6 @@ declare global { swap: SwapFixture queue: QueueFixture vault: VaultFixture - anvil: AnvilFixture wallet: WalletFixture osToken: OsTokenFixture context: BrowserContext @@ -41,7 +36,6 @@ declare global { helpers: HelpersFixture graphql: GraphqlFixture settings: SettingsFixture - guardian: GuardianFixture transactions: TransactionsFixture } @@ -56,6 +50,6 @@ declare global { } interface Window { - e2e: any + e2e: Record } } diff --git a/package.json b/package.json index 84a57296..c7c19450 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ }, "devDependencies": { "@eslint/js": "9.28.0", - "@guardianui/test": "1.0.4", "@next/eslint-plugin-next": "16.0.10", "@playwright/test": "1.57.0", "@typechain/ethers-v6": "0.5.1", diff --git a/playwright.config.ts b/playwright.config.ts index d10720f4..2025b2fb 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -46,14 +46,24 @@ const config: PlaywrightTestConfig = { }, }, ], - webServer: process.env.WITH_ANVIL - ? [ - { - command: './scripts/fork-ci.sh', - url: 'http://localhost:8545', - }, - ] - : [], + webServer: [], +} + +if (process.env.WITH_ANVIL) { + config.webServer = [ + { + command: './scripts/fork-ci.sh', + url: 'http://localhost:8545', + }, + ] + + if (process.env.GNOSIS_RPC_URL) { + config.webServer.push({ + command: ':', + url: 'http://localhost:8546', + reuseExistingServer: true, + }) + } } From 651cfbdab8f59b495e62765525250f048ef68b7f Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Wed, 22 Apr 2026 10:47:17 +0300 Subject: [PATCH 02/12] [gui replacement] update VI Signed-off-by: MikeDiam --- e2e/fixtures/wallet/chains.ts | 44 +++++++++++-------- .../wallet/helpers/eip1193-provider.js | 25 ++++++++++- e2e/fixtures/wallet/helpers/initProvider.ts | 13 +++--- e2e/fixtures/wallet/helpers/resetAllChains.ts | 3 +- e2e/fixtures/wallet/setBalance.ts | 4 +- e2e/fixtures/wallet/switchChain.ts | 4 +- 6 files changed, 60 insertions(+), 33 deletions(-) diff --git a/e2e/fixtures/wallet/chains.ts b/e2e/fixtures/wallet/chains.ts index 877440df..46ea658a 100644 --- a/e2e/fixtures/wallet/chains.ts +++ b/e2e/fixtures/wallet/chains.ts @@ -1,25 +1,35 @@ -import { configs, Network } from '@stakewise/v3-sdk' - +export enum Network { + Mainnet = 1, + Gnosis = 100, + Hoodi = 560048, +} -const mainnetTokens = configs[Network.Mainnet].addresses.tokens +export type SupportedNetwork = Network.Mainnet | Network.Gnosis type ChainHolders = Record -export type SupportedNetwork = Network.Mainnet | Network.Gnosis - export type ChainEntry = { name: string rpcUrl: string + hexadecimalChainId: string defaultPrivateKey: string holders: ChainHolders } +export const tokens = { + mainnet: { + mintToken: '0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38', + v2StakedToken: '0xFe2e637202056d30016725477c5da089Ab0A043A', + v2RewardToken: '0x20BC832ca081b91433ff6c17f85701B6e92486c5', + }, +} + const defaultPrivateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' const ethereumHolders: ChainHolders = { - [mainnetTokens.mintToken]: '0x927709711794F3De5DdBF1D176bEE2D55Ba13c21', - [mainnetTokens.v2StakedToken]: '0x0A2504b0B4a9d08b699BeaA72D53F0267bCFfFbb', - [mainnetTokens.v2RewardToken]: '0x0A2504b0B4a9d08b699BeaA72D53F0267bCFfFbb', + [tokens.mainnet.mintToken]: '0x927709711794F3De5DdBF1D176bEE2D55Ba13c21', + [tokens.mainnet.v2StakedToken]: '0x0A2504b0B4a9d08b699BeaA72D53F0267bCFfFbb', + [tokens.mainnet.v2RewardToken]: '0x0A2504b0B4a9d08b699BeaA72D53F0267bCFfFbb', } const gnosisHolders: ChainHolders = {} @@ -27,13 +37,15 @@ const gnosisHolders: ChainHolders = {} export const chains: Record = { [Network.Mainnet]: { defaultPrivateKey, - name: configs[Network.Mainnet].network.name, + name: 'Ethereum', + hexadecimalChainId: '0x1', rpcUrl: 'http://127.0.0.1:8545', holders: ethereumHolders, }, [Network.Gnosis]: { defaultPrivateKey, - name: configs[Network.Gnosis].network.name, + name: 'Gnosis Chain', + hexadecimalChainId: '0x64', rpcUrl: 'http://127.0.0.1:8546', holders: gnosisHolders, }, @@ -42,14 +54,10 @@ export const chains: Record = { export const defaultChainId: SupportedNetwork = Network.Mainnet export const getAvailableChains = () => { - const entries = Object.entries(chains).map(([ id, entry ]) => { - const chainId = Number(id) as SupportedNetwork - - return [ - configs[chainId].network.hexadecimalChainId, - { rpcUrl: entry.rpcUrl, name: entry.name }, - ] - }) + const entries = Object.values(chains).map((entry) => [ + entry.hexadecimalChainId, + { rpcUrl: entry.rpcUrl, name: entry.name }, + ]) return Object.fromEntries(entries) } diff --git a/e2e/fixtures/wallet/helpers/eip1193-provider.js b/e2e/fixtures/wallet/helpers/eip1193-provider.js index db17625b..6d2e7e32 100644 --- a/e2e/fixtures/wallet/helpers/eip1193-provider.js +++ b/e2e/fixtures/wallet/helpers/eip1193-provider.js @@ -108,6 +108,15 @@ return json.result } + const mineImmediately = async (label) => { + try { + await rpcCall('anvil_mine', [ '0x1' ]) + } + catch (error) { + console.warn(`[e2e wallet] anvil_mine after ${label} failed:`, error?.message || error) + } + } + const compatProvider = { get connection() { return { url: chains[currentChainIdHex].rpcUrl } @@ -181,6 +190,8 @@ return String(parseInt(currentChainIdHex, 16)) }, + signer: { address }, + provider: compatProvider, on, @@ -235,7 +246,19 @@ tx.from = address } - return rpcCall('eth_sendTransaction', [ tx ]) + const hash = await rpcCall('eth_sendTransaction', [ tx ]) + + await mineImmediately('eth_sendTransaction') + + return hash + } + + case 'eth_sendRawTransaction': { + const hash = await rpcCall('eth_sendRawTransaction', params) + + await mineImmediately('eth_sendRawTransaction') + + return hash } case 'personal_sign': { diff --git a/e2e/fixtures/wallet/helpers/initProvider.ts b/e2e/fixtures/wallet/helpers/initProvider.ts index 18e538da..24d226ea 100644 --- a/e2e/fixtures/wallet/helpers/initProvider.ts +++ b/e2e/fixtures/wallet/helpers/initProvider.ts @@ -2,7 +2,6 @@ import fs from 'fs' import path from 'path' import type { Page } from '@playwright/test' import { Wallet, JsonRpcProvider, getBytes, isHexString, parseEther, toBeHex } from 'ethers' -import { configs } from '@stakewise/v3-sdk' import { chains, getAvailableChains } from '../chains' import type { SupportedNetwork } from '../chains' @@ -16,21 +15,19 @@ const initialEthHex = toBeHex(parseEther('10000')) const cookieScript = `document.cookie = 'SW_e2e=true'` -type InitProviderBase = { +type InitProviderInput = { page: Page chainId: SupportedNetwork + address?: string + privateKey?: string } -type InitProviderInput = - | InitProviderBase & { privateKey: string; address?: never } - | InitProviderBase & { address: string; privateKey?: never } - const buildProviderScript = (address: string, chainId: SupportedNetwork) => { const chainMap = getAvailableChains() return providerScriptTemplate .replace('__MOCK_CHAINS_JSON__', () => JSON.stringify(chainMap)) - .replace('__MOCK_DEFAULT_CHAIN_ID_HEX__', () => configs[chainId].network.hexadecimalChainId) + .replace('__MOCK_DEFAULT_CHAIN_ID_HEX__', () => chains[chainId].hexadecimalChainId) .replace('__MOCK_ADDRESS__', () => address) } @@ -90,7 +87,7 @@ export const initProvider = async (input: InitProviderInput) => { throw new Error(`Unknown chainId: ${chainId}`) } - const address = input.address || new Wallet(input.privateKey).address + const address = input.address || new Wallet(input.privateKey as string).address const script = buildProviderScript(address, chainId) const impersonationTasks = Object.values(chains).map(async (entry) => { diff --git a/e2e/fixtures/wallet/helpers/resetAllChains.ts b/e2e/fixtures/wallet/helpers/resetAllChains.ts index 5ddf5718..11197b97 100644 --- a/e2e/fixtures/wallet/helpers/resetAllChains.ts +++ b/e2e/fixtures/wallet/helpers/resetAllChains.ts @@ -1,7 +1,6 @@ import { JsonRpcProvider } from 'ethers' -import { Network } from '@stakewise/v3-sdk' -import { chains } from '../chains' +import { chains, Network } from '../chains' import type { SupportedNetwork } from '../chains' import { impersonate, clearImpersonatedCache } from './impersonate' diff --git a/e2e/fixtures/wallet/setBalance.ts b/e2e/fixtures/wallet/setBalance.ts index d58829ef..a19c4b35 100644 --- a/e2e/fixtures/wallet/setBalance.ts +++ b/e2e/fixtures/wallet/setBalance.ts @@ -1,6 +1,6 @@ -import { configs, Network } from '@stakewise/v3-sdk' import { parseEther } from 'ethers' +import { tokens } from './chains' import type { SupportedNetwork } from './chains' import type { Balance } from './helpers' import type { SetEthBalance } from './setEthBalance' @@ -17,7 +17,7 @@ type Input = { export type SetBalance = (input: Input) => Promise const tokenAddresses: Partial> = { - osETH: configs[Network.Mainnet].addresses.tokens.mintToken, + osETH: tokens.mainnet.mintToken, } type Wrapper = (deps: { diff --git a/e2e/fixtures/wallet/switchChain.ts b/e2e/fixtures/wallet/switchChain.ts index b4ee29f5..f0c0e53d 100644 --- a/e2e/fixtures/wallet/switchChain.ts +++ b/e2e/fixtures/wallet/switchChain.ts @@ -1,6 +1,6 @@ import type { Page } from '@playwright/test' -import { configs } from '@stakewise/v3-sdk' +import { chains } from './chains' import type { SupportedNetwork } from './chains' import type { State } from './init' @@ -18,7 +18,7 @@ export const createSwitchChain: Wrapper = ({ page, state }) => ( params: [ { chainId: chainIdHex } ], }) }, - configs[chainId].network.hexadecimalChainId + chains[chainId].hexadecimalChainId ) state.chainId = chainId From d8d0d17dc9c40e59a2296430beeb078a051e74cc Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 09:23:23 +0300 Subject: [PATCH 03/12] [gui replacement] update pnpm lock file Signed-off-by: MikeDiam --- pnpm-lock.yaml | 446 +------------------------------------------------ 1 file changed, 3 insertions(+), 443 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 851f1a85..f3caf88c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,9 +136,6 @@ importers: '@eslint/js': specifier: 9.28.0 version: 9.28.0 - '@guardianui/test': - specifier: 1.0.4 - version: 1.0.4 '@next/eslint-plugin-next': specifier: 16.0.10 version: 16.0.10 @@ -464,150 +461,60 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ethersproject/abi@5.7.0': - resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} - '@ethersproject/abi@5.8.0': resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} - '@ethersproject/abstract-provider@5.7.0': - resolution: {integrity: sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==} - '@ethersproject/abstract-provider@5.8.0': resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} - '@ethersproject/abstract-signer@5.7.0': - resolution: {integrity: sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==} - '@ethersproject/abstract-signer@5.8.0': resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} - '@ethersproject/address@5.7.0': - resolution: {integrity: sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==} - '@ethersproject/address@5.8.0': resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} - '@ethersproject/base64@5.7.0': - resolution: {integrity: sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==} - '@ethersproject/base64@5.8.0': resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} - '@ethersproject/basex@5.7.0': - resolution: {integrity: sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==} - - '@ethersproject/bignumber@5.7.0': - resolution: {integrity: sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==} - '@ethersproject/bignumber@5.8.0': resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} - '@ethersproject/bytes@5.7.0': - resolution: {integrity: sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==} - '@ethersproject/bytes@5.8.0': resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} - '@ethersproject/constants@5.7.0': - resolution: {integrity: sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==} - '@ethersproject/constants@5.8.0': resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} - '@ethersproject/contracts@5.7.0': - resolution: {integrity: sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==} - - '@ethersproject/hash@5.7.0': - resolution: {integrity: sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==} - '@ethersproject/hash@5.8.0': resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} - '@ethersproject/hdnode@5.7.0': - resolution: {integrity: sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==} - - '@ethersproject/json-wallets@5.7.0': - resolution: {integrity: sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==} - - '@ethersproject/keccak256@5.7.0': - resolution: {integrity: sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==} - '@ethersproject/keccak256@5.8.0': resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} - '@ethersproject/logger@5.7.0': - resolution: {integrity: sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==} - '@ethersproject/logger@5.8.0': resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} - '@ethersproject/networks@5.7.1': - resolution: {integrity: sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==} - '@ethersproject/networks@5.8.0': resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} - '@ethersproject/pbkdf2@5.7.0': - resolution: {integrity: sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==} - - '@ethersproject/properties@5.7.0': - resolution: {integrity: sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==} - '@ethersproject/properties@5.8.0': resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} - '@ethersproject/providers@5.7.2': - resolution: {integrity: sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==} - - '@ethersproject/random@5.7.0': - resolution: {integrity: sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==} - - '@ethersproject/rlp@5.7.0': - resolution: {integrity: sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==} - '@ethersproject/rlp@5.8.0': resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} - '@ethersproject/sha2@5.7.0': - resolution: {integrity: sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==} - - '@ethersproject/signing-key@5.7.0': - resolution: {integrity: sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==} - '@ethersproject/signing-key@5.8.0': resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} - '@ethersproject/solidity@5.7.0': - resolution: {integrity: sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==} - - '@ethersproject/strings@5.7.0': - resolution: {integrity: sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==} - '@ethersproject/strings@5.8.0': resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} - '@ethersproject/transactions@5.7.0': - resolution: {integrity: sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==} - '@ethersproject/transactions@5.8.0': resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} - '@ethersproject/units@5.7.0': - resolution: {integrity: sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==} - - '@ethersproject/wallet@5.7.0': - resolution: {integrity: sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==} - - '@ethersproject/web@5.7.1': - resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} - '@ethersproject/web@5.8.0': resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} - '@ethersproject/wordlists@5.7.0': - resolution: {integrity: sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==} - '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -635,9 +542,6 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@guardianui/test@1.0.4': - resolution: {integrity: sha512-vd62SRyyfZujsF5Ns8BrGi1cfI34deI9L7Jt1JaSmfo+5ORQXiTDEsrG3Oy+zq1otlfD8x2P4ww6BN5PDYLeAA==} - '@headlessui/react@2.2.4': resolution: {integrity: sha512-lz+OGcAH1dK93rgSMzXmm1qKOJkBUqZf1L4M8TWLNplftQD3IkoEDdUFNfAn4ylsN6WOTVtWaLmvmaHOUk1dTA==} engines: {node: '>=10'} @@ -1690,9 +1594,6 @@ packages: resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} engines: {node: '>=0.8'} - aes-js@3.0.0: - resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} - aes-js@4.0.0-beta.5: resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} @@ -1917,9 +1818,6 @@ packages: resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} hasBin: true - bech32@1.1.4: - resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} - big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} @@ -2389,9 +2287,6 @@ packages: electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} - elliptic@6.5.4: - resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} - elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -2608,9 +2503,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - ethers@5.7.2: - resolution: {integrity: sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==} - ethers@6.14.3: resolution: {integrity: sha512-qq7ft/oCJohoTcsNPFaXSQUm457MA5iWqkf1Mb11ujONdg7jBI6sAOrHaTi3j0CBqIGFSCeR/RMc+qwRRub7IA==} engines: {node: '>=14.0.0'} @@ -2865,11 +2757,11 @@ packages: glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-modules@2.0.0: resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} @@ -4530,9 +4422,6 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} - scrypt-js@3.0.1: - resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -4876,6 +4765,7 @@ packages: tar@7.5.2: resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me terser-webpack-plugin@5.3.14: resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} @@ -5268,18 +5158,6 @@ packages: resolution: {integrity: sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==} engines: {node: '>=4'} - ws@7.4.6: - resolution: {integrity: sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@7.5.10: resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} @@ -5785,18 +5663,6 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@ethersproject/abi@5.7.0': - dependencies: - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/abi@5.8.0': dependencies: '@ethersproject/address': 5.8.0 @@ -5809,16 +5675,6 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - '@ethersproject/abstract-provider@5.7.0': - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/networks': 5.7.1 - '@ethersproject/properties': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/web': 5.7.1 - '@ethersproject/abstract-provider@5.8.0': dependencies: '@ethersproject/bignumber': 5.8.0 @@ -5829,14 +5685,6 @@ snapshots: '@ethersproject/transactions': 5.8.0 '@ethersproject/web': 5.8.0 - '@ethersproject/abstract-signer@5.7.0': - dependencies: - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/abstract-signer@5.8.0': dependencies: '@ethersproject/abstract-provider': 5.8.0 @@ -5845,14 +5693,6 @@ snapshots: '@ethersproject/logger': 5.8.0 '@ethersproject/properties': 5.8.0 - '@ethersproject/address@5.7.0': - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/rlp': 5.7.0 - '@ethersproject/address@5.8.0': dependencies: '@ethersproject/bignumber': 5.8.0 @@ -5861,72 +5701,24 @@ snapshots: '@ethersproject/logger': 5.8.0 '@ethersproject/rlp': 5.8.0 - '@ethersproject/base64@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/base64@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 - '@ethersproject/basex@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/properties': 5.7.0 - - '@ethersproject/bignumber@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - bn.js: 5.2.2 - '@ethersproject/bignumber@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 bn.js: 5.2.2 - '@ethersproject/bytes@5.7.0': - dependencies: - '@ethersproject/logger': 5.7.0 - '@ethersproject/bytes@5.8.0': dependencies: '@ethersproject/logger': 5.8.0 - '@ethersproject/constants@5.7.0': - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/constants@5.8.0': dependencies: '@ethersproject/bignumber': 5.8.0 - '@ethersproject/contracts@5.7.0': - dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/transactions': 5.7.0 - - '@ethersproject/hash@5.7.0': - dependencies: - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/base64': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/hash@5.8.0': dependencies: '@ethersproject/abstract-signer': 5.8.0 @@ -5939,128 +5731,26 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - '@ethersproject/hdnode@5.7.0': - dependencies: - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/basex': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/pbkdf2': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/wordlists': 5.7.0 - - '@ethersproject/json-wallets@5.7.0': - dependencies: - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/hdnode': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/pbkdf2': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/random': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - aes-js: 3.0.0 - scrypt-js: 3.0.1 - - '@ethersproject/keccak256@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - js-sha3: 0.8.0 - '@ethersproject/keccak256@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 js-sha3: 0.8.0 - '@ethersproject/logger@5.7.0': {} - '@ethersproject/logger@5.8.0': {} - '@ethersproject/networks@5.7.1': - dependencies: - '@ethersproject/logger': 5.7.0 - '@ethersproject/networks@5.8.0': dependencies: '@ethersproject/logger': 5.8.0 - '@ethersproject/pbkdf2@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/sha2': 5.7.0 - - '@ethersproject/properties@5.7.0': - dependencies: - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties@5.8.0': dependencies: '@ethersproject/logger': 5.8.0 - '@ethersproject/providers@5.7.2': - dependencies: - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/base64': 5.7.0 - '@ethersproject/basex': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/networks': 5.7.1 - '@ethersproject/properties': 5.7.0 - '@ethersproject/random': 5.7.0 - '@ethersproject/rlp': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/web': 5.7.1 - bech32: 1.1.4 - ws: 7.4.6 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@ethersproject/random@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - - '@ethersproject/rlp@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/rlp@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 - '@ethersproject/sha2@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - hash.js: 1.1.7 - - '@ethersproject/signing-key@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - bn.js: 5.2.2 - elliptic: 6.5.4 - hash.js: 1.1.7 - '@ethersproject/signing-key@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -6070,39 +5760,12 @@ snapshots: elliptic: 6.6.1 hash.js: 1.1.7 - '@ethersproject/solidity@5.7.0': - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/strings': 5.7.0 - - '@ethersproject/strings@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/strings@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/constants': 5.8.0 '@ethersproject/logger': 5.8.0 - '@ethersproject/transactions@5.7.0': - dependencies: - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/rlp': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - '@ethersproject/transactions@5.8.0': dependencies: '@ethersproject/address': 5.8.0 @@ -6115,38 +5778,6 @@ snapshots: '@ethersproject/rlp': 5.8.0 '@ethersproject/signing-key': 5.8.0 - '@ethersproject/units@5.7.0': - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/logger': 5.7.0 - - '@ethersproject/wallet@5.7.0': - dependencies: - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/hdnode': 5.7.0 - '@ethersproject/json-wallets': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/random': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/wordlists': 5.7.0 - - '@ethersproject/web@5.7.1': - dependencies: - '@ethersproject/base64': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/web@5.8.0': dependencies: '@ethersproject/base64': 5.8.0 @@ -6155,14 +5786,6 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - '@ethersproject/wordlists@5.7.0': - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 @@ -6196,15 +5819,6 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@guardianui/test@1.0.4': - dependencies: - '@playwright/test': 1.57.0 - dotenv: 16.5.0 - ethers: 5.7.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - '@headlessui/react@2.2.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@floating-ui/react': 0.26.28(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -7269,8 +6883,6 @@ snapshots: adler-32@1.3.1: {} - aes-js@3.0.0: {} - aes-js@4.0.0-beta.5: {} ajv-formats@2.1.1(ajv@8.17.1): @@ -7516,8 +7128,6 @@ snapshots: baseline-browser-mapping@2.9.14: {} - bech32@1.1.4: {} - big.js@5.2.2: {} bignumber.js@9.3.0: {} @@ -7999,16 +7609,6 @@ snapshots: electron-to-chromium@1.5.267: {} - elliptic@6.5.4: - dependencies: - bn.js: 4.12.2 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - elliptic@6.6.1: dependencies: bn.js: 4.12.2 @@ -8367,42 +7967,6 @@ snapshots: esutils@2.0.3: {} - ethers@5.7.2: - dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/base64': 5.7.0 - '@ethersproject/basex': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/contracts': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/hdnode': 5.7.0 - '@ethersproject/json-wallets': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/networks': 5.7.1 - '@ethersproject/pbkdf2': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/providers': 5.7.2 - '@ethersproject/random': 5.7.0 - '@ethersproject/rlp': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - '@ethersproject/solidity': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/units': 5.7.0 - '@ethersproject/wallet': 5.7.0 - '@ethersproject/web': 5.7.1 - '@ethersproject/wordlists': 5.7.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - ethers@6.14.3: dependencies: '@adraffy/ens-normalize': 1.10.1 @@ -10345,8 +9909,6 @@ snapshots: ajv-formats: 2.1.1(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1) - scrypt-js@3.0.1: {} - semver@5.7.2: {} semver@6.3.1: {} @@ -11375,8 +10937,6 @@ snapshots: dependencies: mkdirp: 0.5.6 - ws@7.4.6: {} - ws@7.5.10: {} ws@8.17.1: {} From 9912f64df4f9f72b00a47da47c3b17907b1742c6 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 09:47:56 +0300 Subject: [PATCH 04/12] [gui replacement] update VI Signed-off-by: MikeDiam --- e2e/fixtures/swap/setSdkTransactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/fixtures/swap/setSdkTransactions.ts b/e2e/fixtures/swap/setSdkTransactions.ts index e425954a..8ad83b4e 100644 --- a/e2e/fixtures/swap/setSdkTransactions.ts +++ b/e2e/fixtures/swap/setSdkTransactions.ts @@ -31,7 +31,7 @@ export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql }) => ( await graphql.mockAllocatorsData(deposit) await page.reload() - await page.waitForLoadState('domcontentloaded') + await page.waitForSelector('[data-testid="tab-stake"]') return shares } From 2e03a6ec6554292969173cb25e9abc902111d862 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 10:03:29 +0300 Subject: [PATCH 05/12] [gui replacement] improve wallet and vault helpers Signed-off-by: MikeDiam --- e2e/fixtures/element/checkLink.ts | 2 +- e2e/fixtures/element/checkVisibility.ts | 13 ++----------- e2e/fixtures/user/setUnboostQueue.ts | 6 ++++-- e2e/fixtures/vault/setVaultData.ts | 18 +++--------------- e2e/fixtures/wallet/chains.ts | 13 +------------ e2e/fixtures/wallet/connectWithBalance.ts | 1 + e2e/fixtures/wallet/helpers/resetAllChains.ts | 11 +---------- e2e/fixtures/wallet/index.ts | 4 ++++ playwright.config.ts | 8 -------- 9 files changed, 17 insertions(+), 59 deletions(-) diff --git a/e2e/fixtures/element/checkLink.ts b/e2e/fixtures/element/checkLink.ts index 652a4fc9..fd0d4341 100644 --- a/e2e/fixtures/element/checkLink.ts +++ b/e2e/fixtures/element/checkLink.ts @@ -19,7 +19,7 @@ export const createCheckLink: Wrapper = ({ page }) => ( if (isPopup) { const [ newPage ] = await Promise.all([ - page.waitForEvent('popup'), + page.waitForEvent('popup', { timeout: 15_000 }), page.getByTestId(testId).click(), ]) diff --git a/e2e/fixtures/element/checkVisibility.ts b/e2e/fixtures/element/checkVisibility.ts index e5686aad..1257447d 100644 --- a/e2e/fixtures/element/checkVisibility.ts +++ b/e2e/fixtures/element/checkVisibility.ts @@ -1,6 +1,3 @@ -import { expect } from '@playwright/test' - - type Wrapper = E2E.FixtureMethod type Input = { @@ -15,14 +12,8 @@ export const createCheckVisibility: Wrapper = ({ page }) => ( const { testId, isVisible = true } = values const selector = `[data-testid="${testId}"]` + const state = isVisible ? 'visible' : 'hidden' - if (isVisible) { - await page.waitForSelector(selector, { state: 'visible' }) - expect(await page.isVisible(selector)).toBe(true) - } - else { - await page.waitForSelector(selector, { state: 'hidden' }) - expect(await page.isVisible(selector)).toBe(false) - } + await page.waitForSelector(selector, { state }) } ) diff --git a/e2e/fixtures/user/setUnboostQueue.ts b/e2e/fixtures/user/setUnboostQueue.ts index de966697..0de1bd86 100644 --- a/e2e/fixtures/user/setUnboostQueue.ts +++ b/e2e/fixtures/user/setUnboostQueue.ts @@ -1,6 +1,8 @@ import { parseEther } from 'ethers' +const waitingDurationSeconds = 604800 + type Input = { exitingShares: string exitingRewards: string @@ -22,11 +24,11 @@ export const createSetUnboostQueue: Wrapper = ({ page }) => ( isClaimable: Boolean(isClaimable), exitingShares: parseEther(exitingShares), exitingAssets: parseEther(exitingRewards), - duration: isClaimable ? 0 : 210258902756807306422, + duration: isClaimable ? 0 : waitingDurationSeconds, position: isClaimable ? { timestamp: '1730206212', positionTicket: '210258902756807306422', - exitQueueIndex: isClaimable ? '1' : null, + exitQueueIndex: '1', } : null, } diff --git a/e2e/fixtures/vault/setVaultData.ts b/e2e/fixtures/vault/setVaultData.ts index bb685b82..98a057f8 100644 --- a/e2e/fixtures/vault/setVaultData.ts +++ b/e2e/fixtures/vault/setVaultData.ts @@ -11,21 +11,9 @@ type Wrapper = E2E.FixtureMethod export const createSetVaultData: Wrapper = ({ page, wallet }) => ( async (data) => { - let admin = data?.admin - let address = ZeroAddress - - if (!admin) { - // Fall back to ZeroAddress when no wallet is injected yet: tests often mock - // GraphQL data before `wallet.init`, so the address may not be available. - try { - admin = wallet.getAddress() - } - catch {} - } - - if (admin) { - address = admin - } + // Fall back to ZeroAddress when no wallet is injected yet: tests often mock + // GraphQL data before `wallet.init`, so the address may not be available. + const address = data?.admin || wallet.tryGetAddress() || ZeroAddress const vaultAddress = getAddress(data?.address || constants.metaVault) const capacity = data?.capacity || String(constants.maxUint256) diff --git a/e2e/fixtures/wallet/chains.ts b/e2e/fixtures/wallet/chains.ts index 46ea658a..a232540f 100644 --- a/e2e/fixtures/wallet/chains.ts +++ b/e2e/fixtures/wallet/chains.ts @@ -1,10 +1,8 @@ export enum Network { Mainnet = 1, - Gnosis = 100, - Hoodi = 560048, } -export type SupportedNetwork = Network.Mainnet | Network.Gnosis +export type SupportedNetwork = Network.Mainnet type ChainHolders = Record @@ -32,8 +30,6 @@ const ethereumHolders: ChainHolders = { [tokens.mainnet.v2RewardToken]: '0x0A2504b0B4a9d08b699BeaA72D53F0267bCFfFbb', } -const gnosisHolders: ChainHolders = {} - export const chains: Record = { [Network.Mainnet]: { defaultPrivateKey, @@ -42,13 +38,6 @@ export const chains: Record = { rpcUrl: 'http://127.0.0.1:8545', holders: ethereumHolders, }, - [Network.Gnosis]: { - defaultPrivateKey, - name: 'Gnosis Chain', - hexadecimalChainId: '0x64', - rpcUrl: 'http://127.0.0.1:8546', - holders: gnosisHolders, - }, } export const defaultChainId: SupportedNetwork = Network.Mainnet diff --git a/e2e/fixtures/wallet/connectWithBalance.ts b/e2e/fixtures/wallet/connectWithBalance.ts index 8b54ec39..c96154ae 100644 --- a/e2e/fixtures/wallet/connectWithBalance.ts +++ b/e2e/fixtures/wallet/connectWithBalance.ts @@ -24,6 +24,7 @@ export const createConnectWithBalance = ({ page, helpers, setBalance }: Deps): C ) await page.getByTestId('connect-button').click() + await page.getByTestId('metaMask-connector-button').waitFor({ state: 'visible' }) await page.getByTestId('metaMask-connector-button').click() await helpers.checkNotification(`Successfully connected with ${constants.walletTitles.metaMask}`) diff --git a/e2e/fixtures/wallet/helpers/resetAllChains.ts b/e2e/fixtures/wallet/helpers/resetAllChains.ts index 11197b97..085b7af4 100644 --- a/e2e/fixtures/wallet/helpers/resetAllChains.ts +++ b/e2e/fixtures/wallet/helpers/resetAllChains.ts @@ -7,11 +7,8 @@ import { impersonate, clearImpersonatedCache } from './impersonate' const forkUrls: Record = { [Network.Mainnet]: process.env.RPC_URL, - [Network.Gnosis]: process.env.GNOSIS_RPC_URL, } -const requiredChains: SupportedNetwork[] = [ Network.Mainnet ] - const rpcTimeoutMs = 30_000 const withTimeout = (promise: Promise, ms: number, label: string): Promise => ( @@ -28,13 +25,7 @@ const resetChain = async (chainId: SupportedNetwork) => { const forkUrl = forkUrls[chainId] if (!forkUrl) { - if (requiredChains.includes(chainId)) { - throw new Error(`Required fork URL missing for chain ${chainId} - set RPC_URL in CI secrets`) - } - - console.warn(`[anvil.reset] Skipping chain ${chainId} - no fork URL configured`) - - return + throw new Error(`Fork URL missing for chain ${chainId} - set RPC_URL in CI secrets`) } const rpc = new JsonRpcProvider(chainEntry.rpcUrl) diff --git a/e2e/fixtures/wallet/index.ts b/e2e/fixtures/wallet/index.ts index ea3c3e0d..bae260df 100644 --- a/e2e/fixtures/wallet/index.ts +++ b/e2e/fixtures/wallet/index.ts @@ -13,6 +13,7 @@ import { createSwitchChain, SwitchChain } from './switchChain' export type WalletFixture = { init: Init getAddress: () => string + tryGetAddress: () => string | null switchChain: SwitchChain connect: Connect disconnect: Disconnect @@ -30,6 +31,8 @@ const wallet: E2E.Fixture = async ({ page, helpers, element, grap const setEthBalance = createSetEthBalance({ state }) const setBalance = createSetBalance({ balance, setEthBalance, graphql }) + const tryGetAddress = () => state.address || null + const getAddress = () => { if (!state.address) { throw new Error('Wallet not initialized - call wallet.init() first') @@ -41,6 +44,7 @@ const wallet: E2E.Fixture = async ({ page, helpers, element, grap await use({ init: createInit({ page, state }), getAddress, + tryGetAddress, switchChain: createSwitchChain({ page, state }), connect: createConnect({ page, helpers }), disconnect: createDisconnect({ page, helpers }), diff --git a/playwright.config.ts b/playwright.config.ts index 2025b2fb..3fc7bdc8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -56,14 +56,6 @@ if (process.env.WITH_ANVIL) { url: 'http://localhost:8545', }, ] - - if (process.env.GNOSIS_RPC_URL) { - config.webServer.push({ - command: ':', - url: 'http://localhost:8546', - reuseExistingServer: true, - }) - } } From 43e1aa76ae55861a057bc4673283d0cc1d678d14 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 10:54:09 +0300 Subject: [PATCH 06/12] [gui replacement] improve wallet and vault helpers Signed-off-by: MikeDiam --- e2e/fixtures/sdk/deposit.ts | 6 +++++- e2e/fixtures/sdk/mint.ts | 6 +++++- scripts/fork-ci.sh | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/e2e/fixtures/sdk/deposit.ts b/e2e/fixtures/sdk/deposit.ts index b4f37858..7c4f6c3f 100644 --- a/e2e/fixtures/sdk/deposit.ts +++ b/e2e/fixtures/sdk/deposit.ts @@ -25,7 +25,11 @@ export const createDeposit: Wrapper = ({ page, wallet }) => ( assets, }) - await sdk.provider.waitForTransaction(depositHash) + const receipt = await sdk.provider.waitForTransaction(depositHash) + + if (!receipt || receipt.status !== 1) { + throw new Error(`deposit reverted: tx=${depositHash} status=${receipt?.status ?? 'no-receipt'}`) + } }, { vaultAddress, depositAssets: parseEther(assets).toString(), diff --git a/e2e/fixtures/sdk/mint.ts b/e2e/fixtures/sdk/mint.ts index 85a159d4..26dc74aa 100644 --- a/e2e/fixtures/sdk/mint.ts +++ b/e2e/fixtures/sdk/mint.ts @@ -24,7 +24,11 @@ export const createMint: Wrapper = ({ page, wallet }) => ( shares, }) - await sdk.provider.waitForTransaction(mintHash) + const receipt = await sdk.provider.waitForTransaction(mintHash) + + if (!receipt || receipt.status !== 1) { + throw new Error(`mint reverted: tx=${mintHash} status=${receipt?.status ?? 'no-receipt'}`) + } return shares.toString() }, { diff --git a/scripts/fork-ci.sh b/scripts/fork-ci.sh index f0a2e72a..72134ca6 100755 --- a/scripts/fork-ci.sh +++ b/scripts/fork-ci.sh @@ -1,3 +1,3 @@ #!/bin/sh -anvil --fork-url=$RPC_URL --block-time=5 --port=8545 --chain-id=1 --host=0.0.0.0 +anvil --compute-units-per-second=100 --fork-url=$RPC_URL --block-time=5 --port=8545 --chain-id=1 --host=0.0.0.0 From 5d19eeea12392527bf522ecef6eab761c8a9801a Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 12:19:24 +0300 Subject: [PATCH 07/12] [gui replacement] route web3 reads to local anvil in E2E mode Frontend SDK was reading chain state via NEXT_PUBLIC_MAINNET_NETWORK_URL (real Alchemy mainnet) while wallet writes go through anvil. The mismatch made every read-driven assertion (balances, allowances, vault positions) return zero in tests. Override getWeb3Url to point at 127.0.0.1:8545 when the SW_e2e cookie is set. --- src/helpers/methods/apiUrls/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/helpers/methods/apiUrls/index.ts b/src/helpers/methods/apiUrls/index.ts index 042bca67..f6e490ad 100644 --- a/src/helpers/methods/apiUrls/index.ts +++ b/src/helpers/methods/apiUrls/index.ts @@ -1,9 +1,18 @@ import { Network } from 'sdk' +import cookie from 'helpers/cookie' +import * as constants from 'helpers/constants' import data from './data' -const getWeb3Url = (network: Network) => data[network].web3 +const E2E_RPC_URL = 'http://127.0.0.1:8545' +const E2E_MAINNET_URLS: readonly [string, string] = [ E2E_RPC_URL, E2E_RPC_URL ] + +const isE2E = () => typeof window !== 'undefined' && Boolean(cookie.get(constants.cookieNames.e2e)) + +const getWeb3Url = (network: Network) => ( + isE2E() && network === Network.Mainnet ? E2E_MAINNET_URLS : data[network].web3 +) const getBackendUrl = (network: Network) => data[network].backend From f57a644963224c3dcac6bc5ad428754eb9a4cba4 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 12:20:00 +0300 Subject: [PATCH 08/12] [gui replacement] log on-chain osETH balance after sdk mint Captures the post-receipt mintToken.balanceOf so a status=1 multicall that silently mints zero shares fails at the fixture rather than surfacing as a downstream UI assertion miss. --- e2e/fixtures/sdk/mint.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/e2e/fixtures/sdk/mint.ts b/e2e/fixtures/sdk/mint.ts index 26dc74aa..ba25e1d3 100644 --- a/e2e/fixtures/sdk/mint.ts +++ b/e2e/fixtures/sdk/mint.ts @@ -30,6 +30,14 @@ export const createMint: Wrapper = ({ page, wallet }) => ( throw new Error(`mint reverted: tx=${mintHash} status=${receipt?.status ?? 'no-receipt'}`) } + const balance = await sdk.contracts.tokens.mintToken.balanceOf(userAddress) + + console.log(`[e2e mint] user=${userAddress} hash=${mintHash} status=${receipt.status} logs=${receipt.logs.length} balance=${balance}`) + + if (balance === 0n) { + throw new Error(`mint succeeded (status=1) but balance still 0: tx=${mintHash} user=${userAddress} logs=${receipt.logs.length}`) + } + return shares.toString() }, { vaultAddress, From e05c62fa56b81c813905db2e14ac58fd8a5f5c6e Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 13:11:58 +0300 Subject: [PATCH 09/12] [gui replacement] refactor E2E helpers and fixtures Signed-off-by: MikeDiam --- .../swap/helpers/checkTokenDropdown.ts | 15 +++++- e2e/fixtures/swap/index.ts | 2 +- e2e/fixtures/swap/setSdkTransactions.ts | 11 ++++- .../user/balances/setMaxWithdrawAssets.ts | 11 ++--- .../user/balances/setMintTokenData.ts | 49 +++++++++---------- e2e/fixtures/user/balances/setStakeBalance.ts | 29 ++++++----- e2e/tests/stake.spec.ts | 7 +-- src/helpers/methods/apiUrls/index.ts | 15 +++--- src/helpers/methods/getRpcForE2E.ts | 16 ++++++ 9 files changed, 89 insertions(+), 66 deletions(-) create mode 100644 src/helpers/methods/getRpcForE2E.ts diff --git a/e2e/fixtures/swap/helpers/checkTokenDropdown.ts b/e2e/fixtures/swap/helpers/checkTokenDropdown.ts index 1b049b7e..22eec62d 100644 --- a/e2e/fixtures/swap/helpers/checkTokenDropdown.ts +++ b/e2e/fixtures/swap/helpers/checkTokenDropdown.ts @@ -1,12 +1,23 @@ +import { expect, Locator } from '@playwright/test' + + type Wrapper = E2E.FixtureMethod export type CheckTokenDropdown = (network?: string) => Promise +const TRIGGER_TIMEOUT = 15_000 + +const clickTrigger = async (trigger: Locator) => { + await expect(trigger).toBeEnabled({ timeout: TRIGGER_TIMEOUT }) + await trigger.click() +} + export const createCheckTokenDropdown: Wrapper = ({ page, element }) => ( async (network?: string) => { const stakeToken = network === 'gnosis' ? 'GNO' : 'ETH' + const trigger = page.getByTestId('amount-input-token') - await page.getByTestId('amount-input-token').click() + await clickTrigger(trigger) await element.checkVisibility({ testId: 'amount-input-input' }) await element.checkVisibility({ testId: 'amount-input-option-USDT' }) @@ -15,7 +26,7 @@ export const createCheckTokenDropdown: Wrapper = ({ page, element }) => ( await element.checkText({ testId: 'amount-input-token', expectedText: 'USDT' }) - await page.getByTestId('amount-input-token').click() + await clickTrigger(trigger) await element.checkVisibility({ testId: 'amount-input-input' }) await page.getByTestId('amount-input-input').fill(stakeToken) diff --git a/e2e/fixtures/swap/index.ts b/e2e/fixtures/swap/index.ts index 3fba4629..e79a15b2 100644 --- a/e2e/fixtures/swap/index.ts +++ b/e2e/fixtures/swap/index.ts @@ -79,7 +79,7 @@ const swap: E2E.Fixture = async ({ page, graphql, transactions, ele submitAmount: createSubmitAmount({ page }), submit: createSubmit({ page, transactions }), - setSdkTransactions: createSetSdkTransactions({ page, sdk, graphql }), + setSdkTransactions: createSetSdkTransactions({ page, sdk, graphql, user }), actions: { unstake: createUnstake({ page, transactions }), diff --git a/e2e/fixtures/swap/setSdkTransactions.ts b/e2e/fixtures/swap/setSdkTransactions.ts index 8ad83b4e..a0a4ddd6 100644 --- a/e2e/fixtures/swap/setSdkTransactions.ts +++ b/e2e/fixtures/swap/setSdkTransactions.ts @@ -8,9 +8,9 @@ type Input = { export type SetSdkTransactions = (values: Input) => Promise -type Wrapper = E2E.FixtureMethod +type Wrapper = E2E.FixtureMethod -export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql }) => ( +export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql, user }) => ( async (values: Input) => { const { deposit, mint } = values @@ -30,6 +30,13 @@ export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql }) => ( await graphql.mockAllocatorsData(deposit) + await user.balances.setStakeBalance(deposit) + await user.balances.setMaxWithdrawAssets(deposit) + await user.balances.setMintTokenData({ + mintedShares: shares || '0', + stakedAssets: deposit, + }) + await page.reload() await page.waitForSelector('[data-testid="tab-stake"]') diff --git a/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts b/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts index 50675249..1fa40da5 100644 --- a/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts +++ b/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts @@ -1,22 +1,19 @@ import { parseEther } from 'ethers' -type Output = Store['vault']['user']['balances']['maxWithdrawAssets'] - export type SetMaxWithdrawAssets = (asset: string) => Promise type Wrapper = E2E.FixtureMethod export const createSetMaxWithdrawAssets: Wrapper = ({ page }) => ( async (asset: string) => { + const value = parseEther(asset).toString() - const data: Output = parseEther(asset) - - await page.evaluate((payload) => { + await page.addInitScript((payload) => { window.e2e = { ...window.e2e, - ['user/balances/setMaxWithdrawAssets']: payload, + ['user/balances/setMaxWithdrawAssets']: BigInt(payload), } - }, data) + }, value) } ) diff --git a/e2e/fixtures/user/balances/setMintTokenData.ts b/e2e/fixtures/user/balances/setMintTokenData.ts index 15909068..8873c237 100644 --- a/e2e/fixtures/user/balances/setMintTokenData.ts +++ b/e2e/fixtures/user/balances/setMintTokenData.ts @@ -12,45 +12,40 @@ export type SetMintTokenData = (values: Input) => Promise type Wrapper = E2E.FixtureMethod +const LTV_PERCENT = 999900000000000000n +const PRECISION = 1000000000000000000n + export const createSetMintTokenData: Wrapper = ({ page }) => ( async (values: Input) => { const { stakedAssets = '0', mintedShares = '0' } = values - const data = { - stakedAssets: parseEther(stakedAssets), - mintedShares: parseEther(mintedShares), - } - - await page.evaluate(async (payload) => { - const sdk = window.e2e.sdk - - const avgRewardPerSecond = await sdk.contracts.base.mintTokenController.avgRewardPerSecond() - const ltvPercent = 999900000000000000n - - const maxMintedAssets = payload.stakedAssets * ltvPercent / 1000000000000000000n - const maxMintedAssetsHourReward = (maxMintedAssets * avgRewardPerSecond * 3600n) / 1000000000000000000n - const mintedAssets = await sdk.contracts.base.mintTokenController.convertToAssets(payload.mintedShares) + const stakedAssetsBigInt = parseEther(stakedAssets) + const mintedSharesBigInt = parseEther(mintedShares) - const canMintAssets = maxMintedAssets - maxMintedAssetsHourReward - mintedAssets + const mintedAssets = mintedSharesBigInt + const maxMintedAssets = stakedAssetsBigInt * LTV_PERCENT / PRECISION + const maxMintShares = maxMintedAssets > mintedAssets ? maxMintedAssets - mintedAssets : 0n - let maxMintShares = 0n - - if (canMintAssets > 0) { - maxMintShares = await sdk.contracts.base.mintTokenController.convertToShares(canMintAssets) - } + const payload = { + mintedShares: mintedSharesBigInt.toString(), + mintedAssets: mintedAssets.toString(), + maxMintShares: maxMintShares.toString(), + hasMintBalance: mintedSharesBigInt > 0n, + } - const result: Output = { - mintedAssets, - maxMintShares, + await page.addInitScript((data) => { + const result = { + mintedShares: BigInt(data.mintedShares), + mintedAssets: BigInt(data.mintedAssets), + maxMintShares: BigInt(data.maxMintShares), + hasMintBalance: data.hasMintBalance, isDisabled: false, - hasMintBalance: true, - mintedShares: payload.mintedShares, - } + } satisfies Output window.e2e = { ...window.e2e, ['user/balances/setMintTokenData']: result, } - }, data) + }, payload) } ) diff --git a/e2e/fixtures/user/balances/setStakeBalance.ts b/e2e/fixtures/user/balances/setStakeBalance.ts index f1b492dc..8187f971 100644 --- a/e2e/fixtures/user/balances/setStakeBalance.ts +++ b/e2e/fixtures/user/balances/setStakeBalance.ts @@ -2,13 +2,6 @@ import { parseEther } from 'ethers' import * as constants from '../../../constants' -type Output = Pick - export type SetStakeBalance = (amount: string) => Promise type Wrapper = E2E.FixtureMethod @@ -16,19 +9,25 @@ type Wrapper = E2E.FixtureMethod export const createSetStakeBalance: Wrapper = ({ page }) => ( async (amount: string) => { const assets = Number(amount) ? parseEther(amount) : 0n + const stakedAssets = assets > constants.minimalAmount ? assets : 0n - const data: Output = { - totalEarnedAssets: parseEther('220'), - totalBoostEarnedAssets: parseEther('190'), - totalStakeEarnedAssets: parseEther('100'), - stakedAssets: assets > constants.minimalAmount ? assets : 0n, + const payload = { + stakedAssets: stakedAssets.toString(), + totalEarnedAssets: parseEther('220').toString(), + totalBoostEarnedAssets: parseEther('190').toString(), + totalStakeEarnedAssets: parseEther('100').toString(), } - await page.evaluate((payload) => { + await page.addInitScript((data) => { window.e2e = { ...window.e2e, - ['user/balances/setStakeBalance']: payload, + ['user/balances/setStakeBalance']: { + stakedAssets: BigInt(data.stakedAssets), + totalEarnedAssets: BigInt(data.totalEarnedAssets), + totalBoostEarnedAssets: BigInt(data.totalBoostEarnedAssets), + totalStakeEarnedAssets: BigInt(data.totalStakeEarnedAssets), + }, } - }, data) + }, payload) } ) diff --git a/e2e/tests/stake.spec.ts b/e2e/tests/stake.spec.ts index 62d6f364..5abeea1f 100644 --- a/e2e/tests/stake.spec.ts +++ b/e2e/tests/stake.spec.ts @@ -21,13 +21,14 @@ test('Token dropdown', async ({ swap, wallet }) => { await swap.helpers.checkTokenDropdown() }) -test('Max balance minus gas', async ({ swap, wallet, graphql }) => { +test('Max balance minus gas', async ({ swap, wallet }) => { await swap.openPage() await wallet.connectWithBalance({ ETH: '100' }) await swap.input.fill() - await graphql.waitForResponse('HarvestParams') - await graphql.waitForResponse('HarvestParams') + + await expect.poll(async () => Number(await swap.input.value()), { timeout: 10_000 }) + .toBeGreaterThan(0) const value = await swap.input.value() diff --git a/src/helpers/methods/apiUrls/index.ts b/src/helpers/methods/apiUrls/index.ts index f6e490ad..456d165f 100644 --- a/src/helpers/methods/apiUrls/index.ts +++ b/src/helpers/methods/apiUrls/index.ts @@ -1,18 +1,15 @@ import { Network } from 'sdk' -import cookie from 'helpers/cookie' -import * as constants from 'helpers/constants' -import data from './data' +import getRpcForE2E from '../getRpcForE2E' +import data from './data' -const E2E_RPC_URL = 'http://127.0.0.1:8545' -const E2E_MAINNET_URLS: readonly [string, string] = [ E2E_RPC_URL, E2E_RPC_URL ] -const isE2E = () => typeof window !== 'undefined' && Boolean(cookie.get(constants.cookieNames.e2e)) +const getWeb3Url = (network: Network) => { + const e2eRpc = getRpcForE2E() -const getWeb3Url = (network: Network) => ( - isE2E() && network === Network.Mainnet ? E2E_MAINNET_URLS : data[network].web3 -) + return e2eRpc || data[network].web3 +} const getBackendUrl = (network: Network) => data[network].backend diff --git a/src/helpers/methods/getRpcForE2E.ts b/src/helpers/methods/getRpcForE2E.ts new file mode 100644 index 00000000..12d2aba0 --- /dev/null +++ b/src/helpers/methods/getRpcForE2E.ts @@ -0,0 +1,16 @@ +import cookie from 'helpers/cookie' +import * as constants from 'helpers/constants' + + +const getRpcForE2E = () => { + if (typeof window === 'undefined') { + return null + } + + const e2eCookieValue = cookie.get(constants.cookieNames.e2e) + + return e2eCookieValue ? 'http://localhost:8545' : null +} + + +export default getRpcForE2E From a03248e78e4bc1ecd7e32286d17335487bf0d7e0 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 13:41:49 +0300 Subject: [PATCH 10/12] [gui replacement] refactor user balance fixtures Signed-off-by: MikeDiam --- e2e/fixtures/swap/index.ts | 2 +- e2e/fixtures/swap/setSdkTransactions.ts | 11 +---- .../user/balances/setMaxWithdrawAssets.ts | 11 +++-- .../user/balances/setMintTokenData.ts | 49 ++++++++++--------- e2e/fixtures/user/balances/setStakeBalance.ts | 29 +++++------ 5 files changed, 52 insertions(+), 50 deletions(-) diff --git a/e2e/fixtures/swap/index.ts b/e2e/fixtures/swap/index.ts index e79a15b2..3fba4629 100644 --- a/e2e/fixtures/swap/index.ts +++ b/e2e/fixtures/swap/index.ts @@ -79,7 +79,7 @@ const swap: E2E.Fixture = async ({ page, graphql, transactions, ele submitAmount: createSubmitAmount({ page }), submit: createSubmit({ page, transactions }), - setSdkTransactions: createSetSdkTransactions({ page, sdk, graphql, user }), + setSdkTransactions: createSetSdkTransactions({ page, sdk, graphql }), actions: { unstake: createUnstake({ page, transactions }), diff --git a/e2e/fixtures/swap/setSdkTransactions.ts b/e2e/fixtures/swap/setSdkTransactions.ts index a0a4ddd6..8ad83b4e 100644 --- a/e2e/fixtures/swap/setSdkTransactions.ts +++ b/e2e/fixtures/swap/setSdkTransactions.ts @@ -8,9 +8,9 @@ type Input = { export type SetSdkTransactions = (values: Input) => Promise -type Wrapper = E2E.FixtureMethod +type Wrapper = E2E.FixtureMethod -export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql, user }) => ( +export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql }) => ( async (values: Input) => { const { deposit, mint } = values @@ -30,13 +30,6 @@ export const createSetSdkTransactions: Wrapper = ({ page, sdk, graphql, user }) await graphql.mockAllocatorsData(deposit) - await user.balances.setStakeBalance(deposit) - await user.balances.setMaxWithdrawAssets(deposit) - await user.balances.setMintTokenData({ - mintedShares: shares || '0', - stakedAssets: deposit, - }) - await page.reload() await page.waitForSelector('[data-testid="tab-stake"]') diff --git a/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts b/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts index 1fa40da5..50675249 100644 --- a/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts +++ b/e2e/fixtures/user/balances/setMaxWithdrawAssets.ts @@ -1,19 +1,22 @@ import { parseEther } from 'ethers' +type Output = Store['vault']['user']['balances']['maxWithdrawAssets'] + export type SetMaxWithdrawAssets = (asset: string) => Promise type Wrapper = E2E.FixtureMethod export const createSetMaxWithdrawAssets: Wrapper = ({ page }) => ( async (asset: string) => { - const value = parseEther(asset).toString() - await page.addInitScript((payload) => { + const data: Output = parseEther(asset) + + await page.evaluate((payload) => { window.e2e = { ...window.e2e, - ['user/balances/setMaxWithdrawAssets']: BigInt(payload), + ['user/balances/setMaxWithdrawAssets']: payload, } - }, value) + }, data) } ) diff --git a/e2e/fixtures/user/balances/setMintTokenData.ts b/e2e/fixtures/user/balances/setMintTokenData.ts index 8873c237..15909068 100644 --- a/e2e/fixtures/user/balances/setMintTokenData.ts +++ b/e2e/fixtures/user/balances/setMintTokenData.ts @@ -12,40 +12,45 @@ export type SetMintTokenData = (values: Input) => Promise type Wrapper = E2E.FixtureMethod -const LTV_PERCENT = 999900000000000000n -const PRECISION = 1000000000000000000n - export const createSetMintTokenData: Wrapper = ({ page }) => ( async (values: Input) => { const { stakedAssets = '0', mintedShares = '0' } = values - const stakedAssetsBigInt = parseEther(stakedAssets) - const mintedSharesBigInt = parseEther(mintedShares) + const data = { + stakedAssets: parseEther(stakedAssets), + mintedShares: parseEther(mintedShares), + } - const mintedAssets = mintedSharesBigInt - const maxMintedAssets = stakedAssetsBigInt * LTV_PERCENT / PRECISION - const maxMintShares = maxMintedAssets > mintedAssets ? maxMintedAssets - mintedAssets : 0n + await page.evaluate(async (payload) => { + const sdk = window.e2e.sdk - const payload = { - mintedShares: mintedSharesBigInt.toString(), - mintedAssets: mintedAssets.toString(), - maxMintShares: maxMintShares.toString(), - hasMintBalance: mintedSharesBigInt > 0n, - } + const avgRewardPerSecond = await sdk.contracts.base.mintTokenController.avgRewardPerSecond() + const ltvPercent = 999900000000000000n + + const maxMintedAssets = payload.stakedAssets * ltvPercent / 1000000000000000000n + const maxMintedAssetsHourReward = (maxMintedAssets * avgRewardPerSecond * 3600n) / 1000000000000000000n + const mintedAssets = await sdk.contracts.base.mintTokenController.convertToAssets(payload.mintedShares) + + const canMintAssets = maxMintedAssets - maxMintedAssetsHourReward - mintedAssets - await page.addInitScript((data) => { - const result = { - mintedShares: BigInt(data.mintedShares), - mintedAssets: BigInt(data.mintedAssets), - maxMintShares: BigInt(data.maxMintShares), - hasMintBalance: data.hasMintBalance, + let maxMintShares = 0n + + if (canMintAssets > 0) { + maxMintShares = await sdk.contracts.base.mintTokenController.convertToShares(canMintAssets) + } + + const result: Output = { + mintedAssets, + maxMintShares, isDisabled: false, - } satisfies Output + hasMintBalance: true, + mintedShares: payload.mintedShares, + } window.e2e = { ...window.e2e, ['user/balances/setMintTokenData']: result, } - }, payload) + }, data) } ) diff --git a/e2e/fixtures/user/balances/setStakeBalance.ts b/e2e/fixtures/user/balances/setStakeBalance.ts index 8187f971..f1b492dc 100644 --- a/e2e/fixtures/user/balances/setStakeBalance.ts +++ b/e2e/fixtures/user/balances/setStakeBalance.ts @@ -2,6 +2,13 @@ import { parseEther } from 'ethers' import * as constants from '../../../constants' +type Output = Pick + export type SetStakeBalance = (amount: string) => Promise type Wrapper = E2E.FixtureMethod @@ -9,25 +16,19 @@ type Wrapper = E2E.FixtureMethod export const createSetStakeBalance: Wrapper = ({ page }) => ( async (amount: string) => { const assets = Number(amount) ? parseEther(amount) : 0n - const stakedAssets = assets > constants.minimalAmount ? assets : 0n - const payload = { - stakedAssets: stakedAssets.toString(), - totalEarnedAssets: parseEther('220').toString(), - totalBoostEarnedAssets: parseEther('190').toString(), - totalStakeEarnedAssets: parseEther('100').toString(), + const data: Output = { + totalEarnedAssets: parseEther('220'), + totalBoostEarnedAssets: parseEther('190'), + totalStakeEarnedAssets: parseEther('100'), + stakedAssets: assets > constants.minimalAmount ? assets : 0n, } - await page.addInitScript((data) => { + await page.evaluate((payload) => { window.e2e = { ...window.e2e, - ['user/balances/setStakeBalance']: { - stakedAssets: BigInt(data.stakedAssets), - totalEarnedAssets: BigInt(data.totalEarnedAssets), - totalBoostEarnedAssets: BigInt(data.totalBoostEarnedAssets), - totalStakeEarnedAssets: BigInt(data.totalStakeEarnedAssets), - }, + ['user/balances/setStakeBalance']: payload, } - }, payload) + }, data) } ) From 5f4058de36866caaa16ebf43ef85ff4ab3799638 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 14:24:24 +0300 Subject: [PATCH 11/12] [gui replacement] refactor user balance fixtures Signed-off-by: MikeDiam --- e2e/constants.ts | 4 +++ e2e/fixtures/sdk/deposit.ts | 3 -- e2e/fixtures/vault/setVaultData.ts | 2 -- e2e/fixtures/wallet/helpers/balance.ts | 3 +- .../wallet/helpers/eip1193-provider.js | 33 +++---------------- e2e/fixtures/wallet/helpers/initProvider.ts | 29 +++++++++------- e2e/fixtures/wallet/init.ts | 20 +++++++---- e2e/fixtures/wallet/switchChain.ts | 10 ++++-- 8 files changed, 49 insertions(+), 55 deletions(-) diff --git a/e2e/constants.ts b/e2e/constants.ts index 472f69b2..2bc38f49 100644 --- a/e2e/constants.ts +++ b/e2e/constants.ts @@ -1,3 +1,7 @@ +export const cookieNames = { + e2e: 'SW_e2e', +} + export const walletNames = { monitorAddress: 'monitorAddress', walletConnect: 'walletConnect', diff --git a/e2e/fixtures/sdk/deposit.ts b/e2e/fixtures/sdk/deposit.ts index 7c4f6c3f..841d7071 100644 --- a/e2e/fixtures/sdk/deposit.ts +++ b/e2e/fixtures/sdk/deposit.ts @@ -10,9 +10,6 @@ export type Input = { assets: string } -// Pull the address from the wallet fixture rather than reading -// `window.ethereum.signer.address` in-page - keeps the SDK call typed and -// removes a compat-shim dependency from the browser evaluate body. export const createDeposit: Wrapper = ({ page, wallet }) => ( async ({ vaultAddress, assets }: Input) => ( page.evaluate(async ({ vaultAddress, depositAssets, userAddress }) => { diff --git a/e2e/fixtures/vault/setVaultData.ts b/e2e/fixtures/vault/setVaultData.ts index 98a057f8..d7414356 100644 --- a/e2e/fixtures/vault/setVaultData.ts +++ b/e2e/fixtures/vault/setVaultData.ts @@ -11,8 +11,6 @@ type Wrapper = E2E.FixtureMethod export const createSetVaultData: Wrapper = ({ page, wallet }) => ( async (data) => { - // Fall back to ZeroAddress when no wallet is injected yet: tests often mock - // GraphQL data before `wallet.init`, so the address may not be available. const address = data?.admin || wallet.tryGetAddress() || ZeroAddress const vaultAddress = getAddress(data?.address || constants.metaVault) diff --git a/e2e/fixtures/wallet/helpers/balance.ts b/e2e/fixtures/wallet/helpers/balance.ts index 384e9cca..0d5a3019 100644 --- a/e2e/fixtures/wallet/helpers/balance.ts +++ b/e2e/fixtures/wallet/helpers/balance.ts @@ -43,8 +43,7 @@ export const createBalance: Wrapper = ({ state }) => ( if (!holder) { throw new Error( - `No known holder for token ${token} on chain ${targetChainId}. ` + - `Add it to chains.ts holders map.` + `No known holder for token ${token} on chain ${targetChainId}. Add it to chains.ts holders map.` ) } diff --git a/e2e/fixtures/wallet/helpers/eip1193-provider.js b/e2e/fixtures/wallet/helpers/eip1193-provider.js index 6d2e7e32..55e3f24f 100644 --- a/e2e/fixtures/wallet/helpers/eip1193-provider.js +++ b/e2e/fixtures/wallet/helpers/eip1193-provider.js @@ -1,19 +1,14 @@ /* eslint-env browser */ /* eslint-disable no-undef */ -/* - * Injected via page.addInitScript(). `__MOCK_*__` tokens are replaced in Node - * (see initProvider.ts) before the script reaches the browser. ESLint cannot - * see those replacements - hence the disable above. - */ (function() { const chains = __MOCK_CHAINS_JSON__ + const defaultChainIdHex = '__MOCK_DEFAULT_CHAIN_ID_HEX__' const address = '__MOCK_ADDRESS__' - let currentChainIdHex = '__MOCK_DEFAULT_CHAIN_ID_HEX__' + let currentChainIdHex = defaultChainIdHex const rpcTimeoutMs = 30_000 const pendingTxMaxAttempts = 300 // 100ms * 300 = 30s total budget - // `Object.create(null)` guards against prototype pollution. const listeners = Object.create(null) const emit = (event, ...args) => { @@ -56,7 +51,7 @@ let rpcId = 0 async function rpcCall(method, params) { - const url = chains[currentChainIdHex].rpcUrl + const url = (chains[currentChainIdHex] || chains[defaultChainIdHex]).rpcUrl const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), rpcTimeoutMs) @@ -174,8 +169,6 @@ } const provider = { - // `isConnected` is a method per EIP-1193 but some older libs read it as a bool; - // a function is truthy so both work. isMetaMask: true, isConnected: () => true, providers: [], @@ -212,30 +205,15 @@ case 'net_version': return String(parseInt(currentChainIdHex, 16)) + // skip 4902 check for gnosis tests case 'wallet_switchEthereumChain': { - const targetChainIdHex = params[0]?.chainId - - if (!chains[targetChainIdHex]) { - const err = new Error('Unrecognized chain ID. Try adding the chain first.') - err.code = 4902 - throw err - } - - currentChainIdHex = targetChainIdHex + currentChainIdHex = params[0]?.chainId emit('chainChanged', currentChainIdHex) return null } case 'wallet_addEthereumChain': { - const chainIdHex = params[0]?.chainId - - if (!chains[chainIdHex]) { - const err = new Error('Cannot add unknown chain at runtime') - err.code = 4902 - throw err - } - return null } @@ -275,7 +253,6 @@ case 'eth_signTypedData': case 'eth_signTypedData_v1': { - // Legacy v1 uses an array of typed pairs, not the EIP-712 object viem expects. throw new Error(`${method} is not supported - use eth_signTypedData_v4`) } diff --git a/e2e/fixtures/wallet/helpers/initProvider.ts b/e2e/fixtures/wallet/helpers/initProvider.ts index 24d226ea..9ff1d435 100644 --- a/e2e/fixtures/wallet/helpers/initProvider.ts +++ b/e2e/fixtures/wallet/helpers/initProvider.ts @@ -1,11 +1,12 @@ import fs from 'fs' import path from 'path' -import type { Page } from '@playwright/test' import { Wallet, JsonRpcProvider, getBytes, isHexString, parseEther, toBeHex } from 'ethers' +import type { Page } from '@playwright/test' +import * as constants from '../../../constants' +import { impersonate } from './impersonate' import { chains, getAvailableChains } from '../chains' import type { SupportedNetwork } from '../chains' -import { impersonate } from './impersonate' const providerScriptPath = path.join(__dirname, 'eip1193-provider.js') @@ -13,7 +14,7 @@ const providerScriptTemplate = fs.readFileSync(providerScriptPath, 'utf-8') const initialEthHex = toBeHex(parseEther('10000')) -const cookieScript = `document.cookie = 'SW_e2e=true'` +const cookieScript = `document.cookie = '${constants.cookieNames.e2e}=true'` type InitProviderInput = { page: Page @@ -87,17 +88,21 @@ export const initProvider = async (input: InitProviderInput) => { throw new Error(`Unknown chainId: ${chainId}`) } - const address = input.address || new Wallet(input.privateKey as string).address - const script = buildProviderScript(address, chainId) + let address = input.address - const impersonationTasks = Object.values(chains).map(async (entry) => { - try { - await impersonateOnChain(entry.rpcUrl, address) + if (!address) { + if (!input.privateKey) { + throw new Error('initProvider: either address or privateKey must be provided') } - catch (error) { - console.warn(`[wallet.init] Skipping impersonation on ${entry.name}: ${(error as Error).message}`) - } - }) + + address = new Wallet(input.privateKey).address + } + + const script = buildProviderScript(address, chainId) + + const impersonationTasks = Object.values(chains).map((entry) => ( + impersonateOnChain(entry.rpcUrl, address) + )) const tasks = [ ...impersonationTasks ] diff --git a/e2e/fixtures/wallet/init.ts b/e2e/fixtures/wallet/init.ts index 625e9804..b41628d9 100644 --- a/e2e/fixtures/wallet/init.ts +++ b/e2e/fixtures/wallet/init.ts @@ -1,9 +1,10 @@ import { Wallet } from 'ethers' import { defaultChainId } from './chains' -import type { SupportedNetwork } from './chains' import { initProvider } from './helpers' +import type { SupportedNetwork } from './chains' + type Input = { privateKey?: string @@ -22,14 +23,21 @@ type Wrapper = (deps: { page: E2E.ExtendedTest['page']; state: State }) => Init export const createInit: Wrapper = ({ page, state }) => ( async (input) => { - const chainId = input?.chainId || defaultChainId + const params = { + page, + chainId: input?.chainId || defaultChainId, + address: input?.address, + privateKey: input?.privateKey, + } + + if (!params.address && !params.privateKey) { + params.privateKey = Wallet.createRandom().privateKey + } - const { address } = input?.address - ? await initProvider({ page, address: input.address, chainId }) - : await initProvider({ page, privateKey: input?.privateKey || Wallet.createRandom().privateKey, chainId }) + const { address } = await initProvider(params) state.address = address - state.chainId = chainId + state.chainId = params.chainId } ) diff --git a/e2e/fixtures/wallet/switchChain.ts b/e2e/fixtures/wallet/switchChain.ts index f0c0e53d..a3f31ac4 100644 --- a/e2e/fixtures/wallet/switchChain.ts +++ b/e2e/fixtures/wallet/switchChain.ts @@ -1,10 +1,14 @@ import type { Page } from '@playwright/test' import { chains } from './chains' -import type { SupportedNetwork } from './chains' import type { State } from './init' +import type { SupportedNetwork } from './chains' +type EthereumProvider = { + request: (args: { method: string, params: unknown[] }) => Promise +} + export type SwitchChain = (chainId: SupportedNetwork) => Promise type Wrapper = (deps: { page: Page; state: State }) => SwitchChain @@ -13,7 +17,9 @@ export const createSwitchChain: Wrapper = ({ page, state }) => ( async (chainId) => { await page.evaluate( async (chainIdHex) => { - await (window as any).ethereum.request({ + const { ethereum } = window as unknown as { ethereum: EthereumProvider } + + await ethereum.request({ method: 'wallet_switchEthereumChain', params: [ { chainId: chainIdHex } ], }) From 0bc388c1b5ca8f3c78319e21093d0fb5f43c8412 Mon Sep 17 00:00:00 2001 From: MikeDiam Date: Mon, 27 Apr 2026 14:42:19 +0300 Subject: [PATCH 12/12] [gui replacement] refactor user balance fixtures Signed-off-by: MikeDiam --- e2e/fixtures/swap/helpers/checkTokenDropdown.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/e2e/fixtures/swap/helpers/checkTokenDropdown.ts b/e2e/fixtures/swap/helpers/checkTokenDropdown.ts index 22eec62d..44c2a6c8 100644 --- a/e2e/fixtures/swap/helpers/checkTokenDropdown.ts +++ b/e2e/fixtures/swap/helpers/checkTokenDropdown.ts @@ -1,14 +1,16 @@ -import { expect, Locator } from '@playwright/test' +import { expect } from '@playwright/test' + +import type { Locator } from '@playwright/test' type Wrapper = E2E.FixtureMethod export type CheckTokenDropdown = (network?: string) => Promise -const TRIGGER_TIMEOUT = 15_000 +const triggerTimeout = 15_000 const clickTrigger = async (trigger: Locator) => { - await expect(trigger).toBeEnabled({ timeout: TRIGGER_TIMEOUT }) + await expect(trigger).toBeEnabled({ timeout: triggerTimeout }) await trigger.click() }