[Feat] Bridge UI Test Workflow (#814)

* add first test

* create connectMetamaskToDapp fixture

* bump testing framework version

* correctly working connectMetamaskToDapp function

* working metamask e2e test

* ci fix

* fix

* some changes

* working transaction history list test

* try version bump

* try version bump

* work on toggleShowTestNetworksInNativeBridgeForm

* try playwright 1.51.1

* ci file change

* test fixture changes

* try

* try

* try

* try

* test

* Revert "test"

This reverts commit 2592b6835530feb912d537a29eefaba37f74560e.

* try fix

* try fix

* test

* test

* Revert "test"

This reverts commit a21634ca9224b3d025290e215ba61917bec043ff.

* working toggle network e2e test

* working confirm and bridge flow on e2e test

* refactor metamask fixtures

* successful e2e case for bridge eth

* did usdc bridge e2e test

* fix pnpm lock

* refactor

* refactor

* lint and try single text

* try

* test

* test ci change

* test

* test ci change

* run once to get cache

* first unit test for bridge-ui

* redo test

* more test logs

* test

* test

* test

* removed console logs

* address one more edge case

* activate all tests

* try

* more comments

* adjust fixture

* did some unit tests

* add more unit tests

* disable unit tests in ci

* new e2e test

* new comment

* new things

* fix

* comment out blockchain tx tests

* add reporter for playwright

* removed pause

* clean-up

* test

* change get token balance mechanic

* fix import

* fixes

* clean up

* test

* try headless

* redo headful
This commit is contained in:
kyzooghost
2025-03-28 22:35:59 +11:00
committed by GitHub
parent 60101122fb
commit 4f346b558d
25 changed files with 772 additions and 321 deletions

View File

@@ -27,6 +27,6 @@ runs:
node-version: ${{ inputs.node-version }} node-version: ${{ inputs.node-version }}
cache: 'pnpm' cache: 'pnpm'
- name: Npm install - name: pnpm install
run: pnpm i ${{ inputs.pnpm-install-options }} run: pnpm i ${{ inputs.pnpm-install-options }}
shell: bash shell: bash

View File

@@ -24,28 +24,41 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
# Use for testing workflow only (and not publish to Docker)
- uses: actions/cache@v4.2.0
with:
path: |
~/.npm
~/.cache/ms-playwright
~/_work/.pnpm-store/v3
${{ github.workspace }}/bridge-ui/.next/cache
${{ github.workspace }}/bridge-ui/.cache-synpress
key: ${{ runner.os }}-bridge-ui-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/bridge-ui/src/**/*.js', '**/bridge-ui/src/**/*.ts', '**/bridge-ui/src/**/*.tsx') }}
restore-keys: |
${{ runner.os }}-bridge-ui-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Setup nodejs environment - name: Setup nodejs environment
uses: ./.github/actions/setup-nodejs uses: ./.github/actions/setup-nodejs
with: with:
node-version: 18.17.0 node-version: 20.17.0
pnpm-install-options: '--frozen-lockfile --prefer-offline' pnpm-install-options: '--frozen-lockfile --prefer-offline'
- name: Install Playwright - name: Install Playwright
run: pnpm dlx playwright@1.45.3 install --with-deps run: pnpm dlx playwright@1.51.1 install --with-deps chromium
- name: Build Bridge UI - name: Build Bridge UI
run: pnpm run -F bridge-ui build; run: pnpm run -F bridge-ui build
env: env:
NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_ID }} NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_ID }}
NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }} NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: ${{ secrets.PUBLIC_DYNAMIC_ENVIRONMENT_ID }} NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: ${{ secrets.PUBLIC_DYNAMIC_SANDBOX_ENVIRONMENT_ID }}
NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }} NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }}
- name: Install linux dependencies - name: Install linux dependencies
run: | run: |
sudo apt-get install --no-install-recommends -y xvfb sudo apt-get install --no-install-recommends -y xvfb
- name: Build synpress cache - name: Initialize Metamask testing fixture
run: xvfb-run pnpm run -F bridge-ui build:cache run: xvfb-run pnpm run -F bridge-ui build:cache
env: env:
E2E_TEST_PRIVATE_KEY: ${{ secrets.BRIDGE_UI_E2E_TESTS_PRIVATE_KEY }} E2E_TEST_PRIVATE_KEY: ${{ secrets.BRIDGE_UI_E2E_TESTS_PRIVATE_KEY }}
@@ -53,9 +66,21 @@ jobs:
E2E_TEST_WALLET_PASSWORD: "TestPassword!" E2E_TEST_WALLET_PASSWORD: "TestPassword!"
NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }} NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
- name: Run tests - name: Run unit tests
run: DOTENV_CONFIG_PATH=.env.production pnpm run -F bridge-ui test:unit
env:
# Can run unit tests in parallel
CI: "false"
NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
NEXT_PUBLIC_LIFI_API_KEY: "placeholder"
NEXT_PUBLIC_WALLET_CONNECT_ID: "placeholder"
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: "placeholder"
# Prerequisite - Testing wallet must have >0 ETH and USDC on Sepolia
- name: Run E2E tests
run: xvfb-run pnpm run -F bridge-ui test:e2e:headful run: xvfb-run pnpm run -F bridge-ui test:e2e:headful
env: env:
# Do not run E2E tests in parallel. Especially blockchain tx where nonces can collide.
CI: "true" CI: "true"
E2E_TEST_PRIVATE_KEY: ${{ secrets.BRIDGE_UI_E2E_TESTS_PRIVATE_KEY }} E2E_TEST_PRIVATE_KEY: ${{ secrets.BRIDGE_UI_E2E_TESTS_PRIVATE_KEY }}
E2E_TEST_SEED_PHRASE: "test test test test test test test test test test test junk" E2E_TEST_SEED_PHRASE: "test test test test test test test test test test test junk"

View File

@@ -7,17 +7,20 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint && pnpm run lint:test",
"lint:fix": "pnpm run lint:ts:fix", "lint:test": "npx eslint 'test/**/*.{js,ts}'",
"lint:fix": "pnpm run lint:ts:fix && pnpm run lint:test:ts:fix",
"lint:ts:fix": "next lint --fix", "lint:ts:fix": "next lint --fix",
"lint:test:ts:fix": "npx eslint --fix 'test/**/*.{js,ts}'",
"clean": "rimraf node_modules .next .next-env.d.ts", "clean": "rimraf node_modules .next .next-env.d.ts",
"install:playwright": "playwright install --with-deps", "install:playwright": "playwright install --with-deps",
"build:cache": "synpress", "build:cache": "synpress",
"build:cache:force": "synpress --force", "build:cache:force": "synpress --force",
"build:cache:headless": "synpress --headless", "build:cache:headless": "synpress --headless",
"test:e2e:headful": "playwright test", "test:unit": "HEADLESS=true playwright test src",
"test:e2e:headless": "HEADLESS=true playwright test", "test:e2e:headful": "playwright test test/e2e",
"test:e2e:headless:ui": "HEADLESS=true playwright test --ui" "test:e2e:headless": "HEADLESS=true playwright test test/e2e",
"test:e2e:headless:ui": "HEADLESS=true playwright test test/e2e --ui"
}, },
"dependencies": { "dependencies": {
"@consensys/linea-sdk": "0.3.0", "@consensys/linea-sdk": "0.3.0",
@@ -48,15 +51,16 @@
"zustand": "4.5.4" "zustand": "4.5.4"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.45.3", "@playwright/test": "1.51.1",
"@svgr/webpack": "8.1.0", "@svgr/webpack": "8.1.0",
"@synthetixio/synpress": "4.0.0-alpha.7", "@synthetixio/synpress": "4.0.10",
"@types/fs-extra": "11.0.4", "@types/fs-extra": "11.0.4",
"@types/react": "18.3.11", "@types/react": "18.3.11",
"@types/react-dom": "18.3.0", "@types/react-dom": "18.3.0",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.21",
"dotenv": "16.4.7", "dotenv": "16.4.7",
"eslint-config-next": "14.2.15", "eslint-config-next": "14.2.15",
"nock": "14.0.1",
"postcss": "8.5.3" "postcss": "8.5.3"
} }
} }

View File

@@ -1,14 +1,21 @@
import { defineConfig, devices } from "@playwright/test"; import { defineConfig, devices } from "@playwright/test";
import "dotenv/config";
export default defineConfig({ export default defineConfig({
testDir: "./test/e2e", testDir: ".",
timeout: 60_000, testMatch: '**/*.spec.ts',
// Timeout for tests that don't involve blockchain transactions
timeout: 40_000,
fullyParallel: true, fullyParallel: true,
maxFailures: process.env.CI ? 1 : 0, maxFailures: process.env.CI ? 1 : 0,
// To consider - cannot really run E2E tests involving blockchain tx in parallel. There is a high risk of reusing the same tx nonce -> leading to dropped transactions
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI reporter: process.env.CI
? [["html", { open: "never", outputFolder: `playwright-report-${process.env.HEADLESS ? "headless" : "headful"}` }]] ? [
: "html", ["html", { open: "never", outputFolder: `playwright-report-${process.env.HEADLESS ? "headless" : "headful"}` }],
["list"]
]
: [["html"],["list"]],
use: { use: {
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
trace: process.env.CI ? "on" : "retain-on-failure", trace: process.env.CI ? "on" : "retain-on-failure",

View File

@@ -42,7 +42,7 @@ export default function ReceivedAmount() {
return ( return (
<div className={styles.value}> <div className={styles.value}>
<p className={styles.crypto}> <p className={styles.crypto} data-testid="received-amount-text">
{formatBalance(receivedAmount, 6)} {token.symbol} {formatBalance(receivedAmount, 6)} {token.symbol}
</p> </p>
{tokenPrices?.[token[fromChain.layer].toLowerCase()] && {tokenPrices?.[token[fromChain.layer].toLowerCase()] &&

View File

@@ -58,10 +58,11 @@ export default function BridgeForm() {
setIsBridgeOpen(false); setIsBridgeOpen(false);
setIsTransactionHistoryOpen(true); setIsTransactionHistoryOpen(true);
}} }}
data-testid="native-bridge-transaction-history-icon"
> >
<TransactionPaperIcon className={styles["transaction-icon"]} /> <TransactionPaperIcon className={styles["transaction-icon"]} />
</Button> </Button>
<Setting /> <Setting data-testid="native-bridge-form-settings-icon" />
</div> </div>
</div> </div>
<div className={styles["content"]}> <div className={styles["content"]}>

View File

@@ -32,7 +32,7 @@ export default function ConfirmDestinationAddress({ isModalOpen, recipient, onCl
{formatAddress(recipient)} {formatAddress(recipient)}
<UnionIcon /> <UnionIcon />
</Link> </Link>
<Button fullWidth onClick={onConfirm}> <Button fullWidth onClick={onConfirm} data-testid="confirm-and-bridge-btn">
Confirm and bridge Confirm and bridge
</Button> </Button>
</div> </div>

View File

@@ -46,6 +46,7 @@ export default function TokenDetails({ isConnected, token, onTokenClick, tokenPr
return ( return (
<button <button
id={`token-details-${token.symbol}-btn`} id={`token-details-${token.symbol}-btn`}
data-testid={`token-details-${token.symbol.toLowerCase()}-btn`}
className={styles["token-wrapper"]} className={styles["token-wrapper"]}
type="button" type="button"
disabled={tokenNotFromCurrentLayer} disabled={tokenNotFromCurrentLayer}
@@ -60,7 +61,7 @@ export default function TokenDetails({ isConnected, token, onTokenClick, tokenPr
</div> </div>
{isConnected && !tokenNotFromCurrentLayer && ( {isConnected && !tokenNotFromCurrentLayer && (
<div className={styles.rìght}> <div className={styles.rìght}>
<p className={styles["balance"]}> <p className={styles["balance"]} data-testid={`token-details-${token.symbol.toLowerCase()}-amount`}>
{formatBalance(formattedBalance, 8)} {token.symbol} {formatBalance(formattedBalance, 8)} {token.symbol}
</p> </p>
{totalValue !== undefined && ( {totalValue !== undefined && (

View File

@@ -16,7 +16,11 @@ export default function TokenList() {
return ( return (
<div className={styles["wrapper"]}> <div className={styles["wrapper"]}>
{token && ( {token && (
<Button className={styles["token-select-btn"]} onClick={openModal}> <Button
className={styles["token-select-btn"]}
onClick={openModal}
data-testid="native-bridge-open-token-list-modal"
>
<Image src={token.image} alt={token.name} width={24} height={24} /> <Image src={token.image} alt={token.name} width={24} height={24} />
{token.symbol} {token.symbol}
<CaretDownIcon className={styles["arrow-down-icon"]} /> <CaretDownIcon className={styles["arrow-down-icon"]} />

View File

@@ -29,6 +29,7 @@ export default function TransactionHistory() {
setIsTransactionHistoryOpen(false); setIsTransactionHistoryOpen(false);
setIsBridgeOpen(true); setIsBridgeOpen(true);
}} }}
data-testid="transaction-history-close-btn"
> >
<ArrowLeftIcon className={styles["go-back-icon"]} /> <ArrowLeftIcon className={styles["go-back-icon"]} />
</Button> </Button>
@@ -51,6 +52,7 @@ export default function TransactionHistory() {
setIsTransactionHistoryOpen(false); setIsTransactionHistoryOpen(false);
setIsBridgeOpen(true); setIsBridgeOpen(true);
}} }}
data-testid="transaction-history-close-btn"
> >
<ArrowLeftIcon className={styles["go-back-icon"]} /> <ArrowLeftIcon className={styles["go-back-icon"]} />
</Button> </Button>

View File

@@ -21,7 +21,7 @@ export default function ListTransaction({ transactions }: Props) {
}; };
return ( return (
<> <>
<ul className={styles["list"]}> <ul className={styles["list"]} data-testid="native-bridge-transaction-history-list">
{transactions.map((item, index) => ( {transactions.map((item, index) => (
<Transaction key={`transaction-${item.bridgingTx}-${index}`} onClick={handleClickTransaction} {...item} /> <Transaction key={`transaction-${item.bridgingTx}-${index}`} onClick={handleClickTransaction} {...item} />
))} ))}

View File

@@ -3,13 +3,17 @@ import styles from "./setting.module.scss";
import clsx from "clsx"; import clsx from "clsx";
import SettingIcon from "@/assets/icons/setting.svg"; import SettingIcon from "@/assets/icons/setting.svg";
import ToggleSwitch from "@/components/ui/toggle-switch"; import ToggleSwitch from "@/components/ui/toggle-switch";
import { useEffect, useRef, useState } from "react"; import { HTMLAttributes, useEffect, useRef, useState } from "react";
import CurrencyDropdown from "@/components/bridge/currency-dropdown"; import CurrencyDropdown from "@/components/bridge/currency-dropdown";
import { useConfigStore, useChainStore, useFormStore } from "@/stores"; import { useConfigStore, useChainStore, useFormStore } from "@/stores";
import { useChains } from "@/hooks"; import { useChains } from "@/hooks";
import { ChainLayer } from "@/types"; import { ChainLayer } from "@/types";
export default function Setting() { interface SettingProps extends HTMLAttributes<HTMLDivElement> {
"data-testid": string;
}
export default function Setting(props: SettingProps) {
const dropdownRef = useRef<HTMLDivElement | null>(null); const dropdownRef = useRef<HTMLDivElement | null>(null);
const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false); const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false);
const setShowTestnet = useConfigStore.useSetShowTestnet(); const setShowTestnet = useConfigStore.useSetShowTestnet();
@@ -56,6 +60,7 @@ export default function Setting() {
[styles["visible"]]: isDropdownVisible, [styles["visible"]]: isDropdownVisible,
})} })}
onClick={toggleDropdown} onClick={toggleDropdown}
data-testid={props["data-testid"]}
> >
<SettingIcon /> <SettingIcon />
</div> </div>
@@ -76,6 +81,7 @@ export default function Setting() {
onChange={(checked) => { onChange={(checked) => {
setShowTestnet(checked); setShowTestnet(checked);
}} }}
data-testid="native-bridge-test-network-toggle"
/> />
</li> </li>
</ul> </ul>

View File

@@ -8,7 +8,7 @@ type Props = {
disabled?: boolean; disabled?: boolean;
}; };
export default function ToggleSwitch({ checked = false, onChange, disabled }: Props) { export default function ToggleSwitch({ checked = false, onChange, disabled, ...rest }: Props) {
const [isChecked, setIsChecked] = useState<boolean>(checked); const [isChecked, setIsChecked] = useState<boolean>(checked);
useEffect(() => { useEffect(() => {
@@ -32,7 +32,7 @@ export default function ToggleSwitch({ checked = false, onChange, disabled }: Pr
})} })}
> >
<input type="checkbox" checked={isChecked} onChange={handleChange} disabled={disabled} /> <input type="checkbox" checked={isChecked} onChange={handleChange} disabled={disabled} />
<span className={styles["slider"]} /> <span className={styles["slider"]} {...rest} />
</label> </label>
); );
} }

View File

@@ -3,7 +3,7 @@ export enum CctpAttestationMessageStatus {
COMPLETE = "complete", COMPLETE = "complete",
} }
export type CctpAttestationMessage = { export type CctpAttestationMessage = {
attestation: `0x${string}`; attestation: `0x${string}` | "PENDING";
message: `0x${string}`; message: `0x${string}`;
eventNonce: `0x${string}`; eventNonce: `0x${string}`;
cctpVersion: 1 | 2; cctpVersion: 1 | 2;

View File

@@ -0,0 +1,165 @@
import { test } from "@playwright/test";
import nock from "nock";
import { getCctpMessageExpiryBlock, getCctpTransactionStatus } from "./cctp";
import {
CctpAttestationMessage,
CctpAttestationMessageStatus,
CctpV2ReattestationApiResponse,
Chain,
ChainLayer,
TransactionStatus,
} from "@/types";
const { expect, describe } = test;
describe("getCctpMessageExpiryBlock", () => {
test("should return undefined for empty byte string", () => {
const message = "0x";
const resp = getCctpMessageExpiryBlock(message);
expect(resp).toBeUndefined();
});
test("should return 0 if 0 expiryBlock encoded", () => {
const message =
"0x00000001000000000000000b41d77498ae6f504499ff1ead8c1c2a3318d48063b8022f8215f7631153534d210000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000000000000000000000000000000000000000000000000003e8000007d0000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000";
const resp = getCctpMessageExpiryBlock(message);
expect(resp).toBe(BigInt(0));
});
test("should parse encoded expiryBlock", () => {
const message =
"0x00000001000000000000000b41d77498ae6f504499ff1ead8c1c2a3318d48063b8022f8215f7631153534d210000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000000000000000000000000000000000000000000000000003e8000007d0000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000151caa0";
const resp = getCctpMessageExpiryBlock(message);
expect(resp).toBe(BigInt(22137504));
});
});
describe("getCctpTransactionStatus", () => {
const toChainStub: Chain = {
id: 59141,
cctpDomain: 11,
cctpMessageTransmitterV2Address: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
cctpTokenMessengerV2Address: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
gasLimitSurplus: 6000n,
profitMargin: 2n,
testnet: true,
tokenBridgeAddress: "0x93DcAdf238932e6e6a85852caC89cBd71798F463",
iconPath: "/images/logo/linea-sepolia.svg",
messageServiceAddress: "0x971e727e956690b9957be6d51Ec16E73AcAC83A7",
name: "Linea Sepolia",
layer: ChainLayer.L2,
nativeCurrency: {
decimals: 18,
name: "Linea Ether",
symbol: "ETH",
},
blockExplorers: {
default: {
apiUrl: "https://api-sepolia.lineascan.build/api",
name: "Etherscan",
url: "https://sepolia.lineascan.build",
},
},
};
const cctpApiRespPending: CctpAttestationMessage = {
attestation: "PENDING",
message: "0x",
eventNonce: "0xaf75c4d910592bc593c34bf6eb89937b1326ad43d9e3cf45581512efcf3e7da7",
cctpVersion: 2,
status: CctpAttestationMessageStatus.PENDING_CONFIRMATIONS,
};
// Comment out because didn't find a use yet for test, but is a valid API response
// const cctpApiRespReady: CctpAttestationMessage = {
// attestation:
// "0x362dff8a6f0a2c55345242a652bc8bef85dd206d02660c606b34a1f89574c25a58918896870a036f3271da65a9bff30f87d65f50f212dda4a3034fb77d110f511b3d44e4f71c4cdbb53f4e1f6d3f6357b87d056722151259f00f7beaa5af3ee35d21e48a00f932ba0ea02b19f26cb9b5dcd328f491ea91cb6b9ad896f493abe9c91c",
// message:
// "0x00000001000000000000000baf75c4d910592bc593c34bf6eb89937b1326ad43d9e3cf45581512efcf3e7da70000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000000000000000000000000000000000000000000000000003e8000007d0000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
// eventNonce: "0xaf75c4d910592bc593c34bf6eb89937b1326ad43d9e3cf45581512efcf3e7da7",
// cctpVersion: 2,
// status: CctpAttestationMessageStatus.COMPLETE,
// };
const cctpApiRespNoExpiry: CctpAttestationMessage = {
attestation:
"0x06207a5ca18bc3860b5c546e8a18f6032180b3792ece47747774b9de14f62b717c51f439f0263b733b223e507a1aa0e56c823633dbb6931f864598f5c428237b1ca042a1555747bf4d7dac0b4184dad34dfed9be62ce978c18d619c8c537254a2e7b8331068215677ea900e02dd00ffee67fb61a93280c0eec789a52eb03f385b51c",
message:
"0x00000001000000000000000b41d77498ae6f504499ff1ead8c1c2a3318d48063b8022f8215f7631153534d210000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000000000000000000000000000000000000000000000000003e8000007d0000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000558d9534cac58f743a3a9e5382f77575a2595dcb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
eventNonce: "0x41d77498ae6f504499ff1ead8c1c2a3318d48063b8022f8215f7631153534d21",
cctpVersion: 2,
status: CctpAttestationMessageStatus.COMPLETE,
};
// Used on Linea Sepolia
const usedCctpNonce = "0xaf75c4d910592bc593c34bf6eb89937b1326ad43d9e3cf45581512efcf3e7da7";
// Random nonce, should be unused
const randomUnusedNonce = "0x97bce01d925f4152bbdc464a774bb8fcfd161557946b33930f0be0294e97eedf";
test("should return PENDING for an empty message", async () => {
const resp = await getCctpTransactionStatus(toChainStub, cctpApiRespPending, cctpApiRespPending.eventNonce);
expect(resp).toBe(TransactionStatus.PENDING);
});
test("should return COMPLETE for a used nonce", async () => {
const resp = await getCctpTransactionStatus(toChainStub, cctpApiRespNoExpiry, usedCctpNonce);
expect(resp).toBe(TransactionStatus.COMPLETED);
});
test("should return PENDING for a truncated message", async () => {
// Immutable creation of new object
const corruptedCctpApiResp = { ...cctpApiRespNoExpiry };
// Chop off last character. Last 32-bytes of message should be expirationBlock as per https://developers.circle.com/stablecoins/message-format
corruptedCctpApiResp.message = corruptedCctpApiResp.message.slice(0, -1) as `0x${string}`;
const resp = await getCctpTransactionStatus(toChainStub, corruptedCctpApiResp, randomUnusedNonce);
expect(resp).toBe(TransactionStatus.PENDING);
});
// TODO later - Address edge case where 'parseInt("0ILLEGAL", 16) == 0',
test("should return PENDING for a corrupted message", async () => {
const corruptedCctpApiResp = { ...cctpApiRespNoExpiry };
// Replace hex characters with non-hex characters
corruptedCctpApiResp.message = (corruptedCctpApiResp.message.slice(0, -64) +
"ILLEGALILLEGALILLEGALILLEGALILLEGALILLEGALILLEGALILLEGALILLEGALI") as `0x${string}`;
const resp = await getCctpTransactionStatus(toChainStub, corruptedCctpApiResp, randomUnusedNonce);
expect(resp).toBe(TransactionStatus.PENDING);
});
test("should return PENDING if i.) CCTP API response has pending status, ii.) message has no expiry and iii.) nonce unused", async () => {
const stubbedCctpApiResp = { ...cctpApiRespNoExpiry };
stubbedCctpApiResp.status = CctpAttestationMessageStatus.PENDING_CONFIRMATIONS;
const resp = await getCctpTransactionStatus(toChainStub, stubbedCctpApiResp, randomUnusedNonce);
expect(resp).toBe(TransactionStatus.PENDING);
});
test("should return READY_TO_CLAIM if i.) CCTP API response has complete status, ii.) message has no expiry and iii.) nonce unused", async () => {
const stubbedCctpApiResp = { ...cctpApiRespNoExpiry };
stubbedCctpApiResp.status = CctpAttestationMessageStatus.COMPLETE;
const resp = await getCctpTransactionStatus(toChainStub, stubbedCctpApiResp, randomUnusedNonce);
expect(resp).toBe(TransactionStatus.READY_TO_CLAIM);
});
test("should return PENDING and call reattest API if i.) message is expired and ii.) nonce unused", async () => {
const expiredCctpApiResp = { ...cctpApiRespNoExpiry };
// Put expiry block of 1
expiredCctpApiResp.message = (expiredCctpApiResp.message.slice(0, -1) + "1") as `0x${string}`;
// Intercept request to CCTP reattest API
const stubbedReattestApiResp: CctpV2ReattestationApiResponse = {
message: "message",
nonce: randomUnusedNonce,
};
const interceptor = nock("https://iris-api-sandbox.circle.com")
.post(/.*/) // Intercept all POST requests
.reply(200, stubbedReattestApiResp);
const resp = await getCctpTransactionStatus(toChainStub, expiredCctpApiResp, randomUnusedNonce);
expect(resp).toBe(TransactionStatus.PENDING);
// Assert we made a request to CCTP reattest API
expect(interceptor.isDone()).toBe(true);
});
});

View File

@@ -1,13 +1,13 @@
import MessageTransmitterV2 from "@/abis/MessageTransmitterV2.json"; import MessageTransmitterV2 from "@/abis/MessageTransmitterV2.json" assert { type: "json" };
import { CctpAttestationMessage, Chain, TransactionStatus, CctpAttestationMessageStatus } from "@/types"; import { CctpAttestationMessage, Chain, TransactionStatus, CctpAttestationMessageStatus } from "@/types";
import { GetPublicClientReturnType } from "@wagmi/core"; import { GetPublicClientReturnType } from "@wagmi/core";
import { fetchCctpAttestationByTxHash, reattestCctpV2PreFinalityMessage } from "@/services/cctp"; import { fetchCctpAttestationByTxHash, reattestCctpV2PreFinalityMessage } from "@/services/cctp";
import { getPublicClient } from "@wagmi/core"; import { getPublicClient } from "@wagmi/core";
import { config as wagmiConfig } from "@/lib/wagmi"; import { config as wagmiConfig } from "@/lib/wagmi";
import { import {
CCTP_V2_MESSAGE_HEADER_LENGTH,
CCTP_V2_EXPIRATION_BLOCK_LENGTH, CCTP_V2_EXPIRATION_BLOCK_LENGTH,
CCTP_V2_EXPIRATION_BLOCK_OFFSET, CCTP_V2_EXPIRATION_BLOCK_OFFSET,
CCTP_V2_MESSAGE_HEADER_LENGTH,
} from "@/constants"; } from "@/constants";
const isCctpNonceUsed = async ( const isCctpNonceUsed = async (
@@ -25,12 +25,14 @@ const isCctpNonceUsed = async (
return resp === 1n; return resp === 1n;
}; };
const getCctpMessageExpiryBlock = (message: string): bigint | undefined => { export const getCctpMessageExpiryBlock = (message: string): bigint | undefined => {
// See CCTPV2 message format at https://developers.circle.com/stablecoins/message-format // See CCTPV2 message format at https://developers.circle.com/stablecoins/message-format
const expiryInHex = message.substring( const expiryInHex = message.substring(
CCTP_V2_EXPIRATION_BLOCK_OFFSET, CCTP_V2_EXPIRATION_BLOCK_OFFSET,
CCTP_V2_EXPIRATION_BLOCK_OFFSET + CCTP_V2_EXPIRATION_BLOCK_LENGTH, CCTP_V2_EXPIRATION_BLOCK_OFFSET + CCTP_V2_EXPIRATION_BLOCK_LENGTH,
); );
// Should be 32-bytes
if (expiryInHex.length !== 64) return undefined;
const expiryInInt = parseInt(expiryInHex, 16); const expiryInInt = parseInt(expiryInHex, 16);
if (Number.isNaN(expiryInInt)) return undefined; if (Number.isNaN(expiryInInt)) return undefined;
// Return bigint because this is also returned by Viem client.getBlockNumber() // Return bigint because this is also returned by Viem client.getBlockNumber()

View File

@@ -1,31 +1,150 @@
import { metaMaskFixtures } from "@synthetixio/synpress"; import { metaMaskFixtures } from "@synthetixio/synpress/playwright";
import setup from "./wallet-setup/metamask.setup"; import setup from "./wallet-setup/metamask.setup";
import { Locator } from "@playwright/test";
import { getNativeBridgeTransactionsCountImpl, selectTokenAndWaitForBalance } from "./utils";
import { LINEA_SEPOLIA_NETWORK, POLLING_INTERVAL } from "./constants";
/**
* NB: There is an issue with Synpress `metaMaskFixtures` extension functions wherein extension functions
* may not be able to reuse other extension functions. This is especially the case when advanced operations
* on the 'Page' object are done. It seems that the 'Page' object does not remain the same in a nested
* extension function call between the different layers of nesting.
*
* Nested `Metamask` object uses however seem ok.
*/
export const test = metaMaskFixtures(setup).extend<{ export const test = metaMaskFixtures(setup).extend<{
initUI: (firstInit?: boolean) => Promise<void>; // Bridge UI Actions
waitForTransactionToConfirm: () => Promise<void>; clickNativeBridgeButton: () => Promise<Locator>;
getBridgeTransactionsCount: () => Promise<number>; openNativeBridgeTransactionHistory: () => Promise<void>;
sendTokens: (amount: string, isETH?: boolean) => Promise<void>; closeNativeBridgeTransactionHistory: () => Promise<void>;
waitForTransactionListUpdate: (txCountBeforeUpdate: number) => Promise<boolean>; openNativeBridgeFormSettings: () => Promise<void>;
selectToken: (tokenName: string) => Promise<void>; toggleShowTestNetworksInNativeBridgeForm: () => Promise<void>;
}>({ getNativeBridgeTransactionsCount: () => Promise<number>;
initUI: async ({ page }, use) => { selectTokenAndInputAmount: (tokenSymbol: string, amount: string) => Promise<void>;
await use(async (firstInit: boolean = false) => { waitForNewTxAdditionToTxList: (txCountBeforeUpdate: number) => Promise<void>;
const nativeBridgeBtn = await page.waitForSelector("#native-bridge-btn"); waitForTxListUpdateForClaimTx: (claimTxCountBeforeUpdate: number) => Promise<void>;
await nativeBridgeBtn.click();
if (firstInit) { // Metamask Actions - Should be ok to reuse within other fixture functions
const agreeTermsBtn = await page.waitForSelector("#agree-terms-btn"); connectMetamaskToDapp: () => Promise<void>;
await agreeTermsBtn.click(); waitForTransactionToConfirm: () => Promise<void>;
} confirmTransactionAndWaitForInclusion: () => Promise<void>;
switchToLineaSepolia: () => Promise<void>;
// Composite Bridge UI + Metamask Actions
doTokenApprovalIfNeeded: () => Promise<void>;
doInitiateBridgeTransaction: () => Promise<void>;
doClaimTransaction: () => Promise<void>;
}>({
// Bridge UI Actions
clickNativeBridgeButton: async ({ page }, use) => {
await use(async () => {
const nativeBridgeBtn = page.getByRole("link", { name: "Native Bridge", exact: true });
await nativeBridgeBtn.click();
return nativeBridgeBtn;
});
},
openNativeBridgeTransactionHistory: async ({ page }, use) => {
await use(async () => {
const txHistoryIconButton = page.getByTestId("native-bridge-transaction-history-icon");
await txHistoryIconButton.click();
});
},
closeNativeBridgeTransactionHistory: async ({ page }, use) => {
await use(async () => {
const backButton = page.getByTestId("transaction-history-close-btn");
await backButton.click();
});
},
openNativeBridgeFormSettings: async ({ page }, use) => {
await use(async () => {
const formSettingsIconButton = page.getByTestId("native-bridge-form-settings-icon");
await formSettingsIconButton.click();
});
},
toggleShowTestNetworksInNativeBridgeForm: async ({ page }, use) => {
await use(async () => {
await page.getByTestId("native-bridge-test-network-toggle").click();
});
},
getNativeBridgeTransactionsCount: async ({ page }, use) => {
await use(async () => {
return await getNativeBridgeTransactionsCountImpl(page);
});
},
selectTokenAndInputAmount: async ({ page }, use) => {
await use(async (tokenSymbol: string, amount: string) => {
// Wait for page to retrieve blockchain token balance
await selectTokenAndWaitForBalance(tokenSymbol, page);
// Input amount
const amountInput = page.getByRole("textbox", { name: "0", exact: true });
await amountInput.fill(amount);
// Wait for "Receive amount" to populate, we need to fetch blockchain data before proceeding
const receivedAmountField = page.getByTestId("received-amount-text");
await receivedAmountField.waitFor({ state: "visible" });
// Check if there are sufficient funds available
const insufficientFundsButton = page.getByRole("button", { name: "Insufficient funds", exact: true });
if ((await insufficientFundsButton.count()) > 0)
throw "Insufficient funds available, please add some funds before running the test";
});
},
waitForNewTxAdditionToTxList: async ({ page }, use) => {
await use(async (txCountBeforeUpdate: number) => {
const maxTries = 10;
let tryCount = 0;
let listUpdated = false;
do {
const newTxCount = await getNativeBridgeTransactionsCountImpl(page);
listUpdated = newTxCount !== txCountBeforeUpdate;
tryCount++;
await page.waitForTimeout(POLLING_INTERVAL);
} while (!listUpdated && tryCount < maxTries);
});
},
waitForTxListUpdateForClaimTx: async ({ page }, use) => {
await use(async (claimTxCountBeforeUpdate: number) => {
const maxTries = 10;
const readyToClaimTx = page.getByRole("listitem").filter({ hasText: "Ready to claim" });
let tryCount = 0;
let listUpdated = false;
do {
const newReadyToClaimCount = await readyToClaimTx.count();
listUpdated = newReadyToClaimCount === claimTxCountBeforeUpdate + 1;
tryCount++;
await page.waitForTimeout(POLLING_INTERVAL);
} while (!listUpdated && tryCount < maxTries);
});
},
// Metamask Actions - Should be ok to reuse within other fixture functions
connectMetamaskToDapp: async ({ page, metamask }, use) => {
await use(async () => {
// Click Connect button
const connectBtn = page.getByRole("button", { name: "Connect", exact: true }).first();
await connectBtn.click();
// Click on 'Metamask' on the wallet dropdown menu
const metamaskBtnInDropdownList = page.getByRole("button").filter({ hasText: "MetaMask" }).first();
await metamaskBtnInDropdownList.click();
await metamask.connectToDapp();
await metamask.goBackToHomePage();
await page.bringToFront();
}); });
}, },
waitForTransactionToConfirm: async ({ metamask }, use) => { waitForTransactionToConfirm: async ({ metamask }, use) => {
await use(async () => { await use(async () => {
await metamask.page.bringToFront(); await metamask.page.bringToFront();
await metamask.page.reload(); await metamask.page.reload();
const activityButton = metamask.page.locator("button", { hasText: "Activity" }); const activityButton = metamask.page.locator("button", { hasText: "Activity" });
await activityButton.waitFor(); await activityButton.waitFor();
// Sometimes a "What's new" modal pops up on Metamask. We assume this becomes visible at the same time as the Activity button
// This modal causes flaky tests because it appears unpredictably, and blocks other actions.
const gotItButton = metamask.page.locator("button", { hasText: "Got it" });
if (await gotItButton.isVisible()) await gotItButton.click();
// Click Activity button
await activityButton.click(); await activityButton.click();
let txCount = await metamask.page let txCount = await metamask.page
@@ -38,74 +157,72 @@ export const test = metaMaskFixtures(setup).extend<{
} }
}); });
}, },
getBridgeTransactionsCount: async ({ page }, use) => { confirmTransactionAndWaitForInclusion: async ({ page, metamask, waitForTransactionToConfirm }, use) => {
await use(async () => { await use(async () => {
const transactionsCount = await page.locator("#transactions-list").locator("ul").count(); await metamask.confirmTransaction();
return transactionsCount; await waitForTransactionToConfirm();
await page.bringToFront();
}); });
}, },
sendTokens: async ({ page, metamask, waitForTransactionToConfirm }, use) => { switchToLineaSepolia: async ({ metamask }, use) => {
await use(async (amount: string, isETH = false) => { await use(async () => {
const amountInput = await page.waitForSelector("#amount-input"); await metamask.switchNetwork(LINEA_SEPOLIA_NETWORK.name, true);
// Sending the smallest amount of USDC
await amountInput.fill(amount);
// Check if there are funds available
const approveBtnDisabled = await page.locator("#approve-btn.btn-disabled").count();
const submitBtnDisabled = await page.locator("#submit-erc-btn.btn-disabled").count();
if (approveBtnDisabled === 1 && submitBtnDisabled === 1) {
throw "No funds available, please add some funds before running the test";
}
const tokenType = isETH ? "eth" : "erc";
// Check that this amount has been approved
if (tokenType === "erc" && submitBtnDisabled === 1) {
//We need to approve the amount first
const approveBtn = await page.waitForSelector(`#approve-btn`);
await approveBtn.click();
await metamask.page.bringToFront();
await metamask.page.reload();
const nextBtn = metamask.page.locator("button", {
hasText: "Next",
});
await nextBtn.waitFor();
await nextBtn.click();
const approveMMBtn = metamask.page.locator("button", { hasText: "Approve" });
await approveMMBtn.waitFor();
await approveMMBtn.click();
await waitForTransactionToConfirm();
await page.bringToFront();
}
const submitBtn = await page.waitForSelector(`#submit-${tokenType}-btn`);
await submitBtn.click();
}); });
}, },
waitForTransactionListUpdate: async ({ getBridgeTransactionsCount }, use) => {
await use(async (txCountBeforeUpdate: number) => {
const maxTries = 20;
let tryCount = 0;
let listUpdated = false;
do {
const newTxCount = await getBridgeTransactionsCount();
listUpdated = newTxCount !== txCountBeforeUpdate; // Composite Bridge UI + Metamask Actions
tryCount++; doTokenApprovalIfNeeded: async ({ page, metamask, waitForTransactionToConfirm }, use) => {
} while (!listUpdated && tryCount < maxTries); await use(async () => {
// Check if approval required
const approvalButton = page.getByRole("button", { name: "Approve Token", exact: true });
if ((await approvalButton.count()) === 0) return;
await approvalButton.click();
return listUpdated; // Handle Metamask approval UI
await metamask.approveTokenPermission();
await waitForTransactionToConfirm();
// Close 'Transaction successful' modal
await page.bringToFront();
const closeModalBtn = page.getByRole("button", { name: "Bridge your token", exact: true });
await closeModalBtn.click();
}); });
}, },
selectToken: async ({ page }, use) => { doInitiateBridgeTransaction: async ({ page, confirmTransactionAndWaitForInclusion }, use) => {
await use(async (tokenName: string) => { await use(async () => {
const tokenETHBtn = await page.waitForSelector("#token-select-btn"); // Click "Bridge" button
await tokenETHBtn.click(); const bridgeButton = page.getByRole("button", { name: "Bridge", exact: true });
const tokenUSDCBtn = await page.waitForSelector(`#token-details-${tokenName}-btn`); await bridgeButton.waitFor();
await tokenUSDCBtn.click(); await bridgeButton.click();
// Click "Confirm and Bridge" button
const confirmAndBridgeButton = page.getByTestId("confirm-and-bridge-btn");
await expect(confirmAndBridgeButton).toBeVisible();
await expect(confirmAndBridgeButton).toBeEnabled();
await confirmAndBridgeButton.click();
// Confirm Metamask Tx and wait for blockchain inclusion
// Should be ok to reuse this fixture function because it doesn't do much on the `Page` object
await confirmTransactionAndWaitForInclusion();
// Click on 'View transactions' button on the 'Transaction confirmed' modal
const viewTxButton = page.getByRole("button", { name: "View transactions", exact: true });
await viewTxButton.click();
});
},
doClaimTransaction: async ({ page, confirmTransactionAndWaitForInclusion }, use) => {
await use(async () => {
// Click on 'Claim' button
const claimButton = page.getByRole("button", { name: "Claim", exact: true });
await expect(claimButton).toBeVisible();
await expect(claimButton).toBeEnabled();
await claimButton.click();
// Confirm Metamask Tx and wait for blockchain inclusion
// Should be ok to reuse this fixture function because it doesn't do much on the `Page` object
await confirmTransactionAndWaitForInclusion();
// Should finish on tx history page
}); });
}, },
}); });

View File

@@ -1,4 +1,3 @@
import "dotenv/config";
import { formatEther, formatUnits } from "viem"; import { formatEther, formatUnits } from "viem";
export const METAMASK_SEED_PHRASE = process.env.E2E_TEST_SEED_PHRASE; export const METAMASK_SEED_PHRASE = process.env.E2E_TEST_SEED_PHRASE;
@@ -15,6 +14,10 @@ export const LINEA_SEPOLIA_NETWORK = {
}; };
export const TEST_URL = "http://localhost:3000/"; export const TEST_URL = "http://localhost:3000/";
export const SEPOLIA_NETWORK_NAME = "Sepolia"; export const WEI_AMOUNT = formatEther(1n).toString();
export const WEI_AMOUNT = formatEther(BigInt(1)).toString(); // Must be > minimum CCTP fee
export const USDC_AMOUNT = formatUnits(BigInt(1), 6).toString(); export const USDC_AMOUNT = formatUnits(10n, 6).toString();
export const ETH_SYMBOL = "ETH";
export const USDC_SYMBOL = "USDC";
export const POLLING_INTERVAL = 250;

View File

@@ -1,189 +1,164 @@
import { testWithSynpress } from "@synthetixio/synpress"; import { testWithSynpress } from "@synthetixio/synpress";
import { test as advancedFixtures } from "../advancedFixtures"; import { test as advancedFixtures } from "../advancedFixtures";
import { SEPOLIA_NETWORK_NAME, TEST_URL, USDC_AMOUNT, WEI_AMOUNT } from "../constants"; import { TEST_URL, USDC_SYMBOL, USDC_AMOUNT, WEI_AMOUNT, ETH_SYMBOL } from "../constants";
const test = testWithSynpress(advancedFixtures); const test = testWithSynpress(advancedFixtures);
const { expect, describe } = test; const { expect, describe } = test;
describe("Bridge L1 > L2", () => { // To consider in a later ticket - Bridge ERC20 tokens case when ERC20 token is available in Sepolia token list
test.skip("should set up the UI and metamask correctly", async ({ page, metamask, initUI }) => { describe("L1 > L2 via Native Bridge", () => {
await initUI(true);
await page.locator("#wallet-connect-btn").click();
await page.locator("wui-list-wallet", { hasText: "MetaMask" }).nth(1).click();
await metamask.connectToDapp();
await page.bringToFront();
});
test("should successfully go to the bridge UI page", async ({ page }) => { test("should successfully go to the bridge UI page", async ({ page }) => {
const pageUrl = page.url(); const pageUrl = page.url();
expect(pageUrl).toEqual(TEST_URL); expect(pageUrl).toEqual(TEST_URL);
}); });
test.skip("should successfully display the correct heading", async ({ page, initUI }) => { test("should have 'Native Bridge' button link on homepage", async ({ clickNativeBridgeButton }) => {
await initUI(true); const nativeBridgeBtn = await clickNativeBridgeButton();
await expect(nativeBridgeBtn).toBeVisible();
const header = "Bridge";
await page.locator("h2", { hasText: header }).waitFor({ state: "visible" });
}); });
test.skip("metamask should be connected to the right network", async ({ page, metamask, initUI }) => { test("should connect MetaMask to dapp correctly", async ({ connectMetamaskToDapp, clickNativeBridgeButton }) => {
await initUI(true); await clickNativeBridgeButton();
await connectMetamaskToDapp();
await page.locator("#wallet-connect-btn").click();
await page.locator("wui-list-wallet", { hasText: "MetaMask" }).nth(1).click();
await metamask.connectToDapp();
await page.bringToFront();
await page
.locator("#active-chain-name", {
hasText: SEPOLIA_NETWORK_NAME,
})
.waitFor();
}); });
test.skip("should be able to reload the transaction history", async ({ page, metamask, initUI }) => { test("should be able to load the transaction history", async ({
await initUI(true);
await page.locator("#wallet-connect-btn").click();
await page.locator("wui-list-wallet", { hasText: "MetaMask" }).nth(1).click();
await metamask.connectToDapp();
const reloadHistoryBtn = await page.waitForSelector("#reload-history-btn");
await reloadHistoryBtn.click();
const reloadConfirmBtn = await page.waitForSelector("#reload-history-confirm-btn");
await reloadConfirmBtn.click();
await page.locator("#transactions-list").locator("ul").nth(1).waitFor({ timeout: 10_000 });
});
test.skip("should be able to switch network", async ({ page, metamask, initUI }) => {
await initUI(true);
await page.locator("#wallet-connect-btn").click();
await page.locator("wui-list-wallet", { hasText: "MetaMask" }).nth(1).click();
await metamask.connectToDapp();
await page.locator("#chain-select").click();
await page.locator("#switch-alternative-chain-btn").click();
await metamask.approveSwitchNetwork();
await page.bringToFront();
await page.locator("#active-chain-name").getByText("Linea Sepolia Testnet").waitFor();
});
test.skip("should be able to claim funds if available", async ({
page, page,
metamask, connectMetamaskToDapp,
initUI, clickNativeBridgeButton,
waitForTransactionToConfirm, openNativeBridgeTransactionHistory,
}) => { }) => {
await initUI(true); await connectMetamaskToDapp();
await page.locator("#wallet-connect-btn").click(); await clickNativeBridgeButton();
await page.locator("wui-list-wallet", { hasText: "MetaMask" }).nth(1).click(); await openNativeBridgeTransactionHistory();
await metamask.connectToDapp(); const txHistoryHeading = page.getByRole("heading").filter({ hasText: "Transaction History" });
await expect(txHistoryHeading).toBeVisible();
// Check if there is a claim button available
const checkClaimBtn = await page.locator("#claim-funds-btn").all();
if (checkClaimBtn.length > 0) {
const claimBtn = page.locator("#claim-funds-btn").nth(1);
await claimBtn.click();
await metamask.confirmTransaction();
await waitForTransactionToConfirm();
} else {
console.warn("Claim funds could not be tested since no funds are waiting to be claimed");
}
}); });
test.skip("should be able to bridge ETH from L1 to L2", async ({ test("should be able to switch to test networks", async ({
page, page,
metamask, connectMetamaskToDapp,
getBridgeTransactionsCount, clickNativeBridgeButton,
sendTokens, openNativeBridgeFormSettings,
waitForTransactionToConfirm, toggleShowTestNetworksInNativeBridgeForm,
waitForTransactionListUpdate,
}) => { }) => {
await page.bringToFront(); await connectMetamaskToDapp();
const txnsLengthBefore = await getBridgeTransactionsCount(); await clickNativeBridgeButton();
await openNativeBridgeFormSettings();
await toggleShowTestNetworksInNativeBridgeForm();
await sendTokens(WEI_AMOUNT, true); // Should have Sepolia text visible
await metamask.confirmTransaction(); const sepoliaText = page.getByText("Sepolia").first();
await expect(sepoliaText).toBeVisible();
// Wait for transaction to finish
await waitForTransactionToConfirm();
await page.bringToFront();
// We check at the end that the transacton list is updated on the bridge UI
const listUpdated = await waitForTransactionListUpdate(txnsLengthBefore);
// Check that that new transaction is added to the list on the UI
expect(listUpdated).toBeTruthy();
}); });
test.skip("should be able to bridge USDC from L1 to L2", async ({ test("should be able to initiate bridging ETH from L1 to L2 in testnet", async ({
page, getNativeBridgeTransactionsCount,
metamask, waitForNewTxAdditionToTxList,
getBridgeTransactionsCount, connectMetamaskToDapp,
selectToken, clickNativeBridgeButton,
sendTokens, openNativeBridgeFormSettings,
waitForTransactionToConfirm, toggleShowTestNetworksInNativeBridgeForm,
waitForTransactionListUpdate, selectTokenAndInputAmount,
doInitiateBridgeTransaction,
openNativeBridgeTransactionHistory,
closeNativeBridgeTransactionHistory,
}) => { }) => {
const txnsLengthBefore = await getBridgeTransactionsCount(); // Code smell that we may need to refactor E2E tests with blockchain tx into another describe block with a separate timeout
test.setTimeout(90_000);
// Select USDC in the token list // Setup testnet UI
await selectToken("USDC"); await connectMetamaskToDapp();
await clickNativeBridgeButton();
await openNativeBridgeFormSettings();
await toggleShowTestNetworksInNativeBridgeForm();
await sendTokens(USDC_AMOUNT); // Get # of txs in txHistory before doing bridge tx, so that we can later confirm that our bridge tx shows up in the txHistory.
await openNativeBridgeTransactionHistory();
const txnsLengthBefore = await getNativeBridgeTransactionsCount();
await closeNativeBridgeTransactionHistory();
await metamask.confirmTransaction(); // // Actual bridging actions
await selectTokenAndInputAmount(ETH_SYMBOL, WEI_AMOUNT);
await doInitiateBridgeTransaction();
// Wait for transaction to finish // Check that our bridge tx shows up in the tx history
await waitForTransactionToConfirm(); await waitForNewTxAdditionToTxList(txnsLengthBefore);
await page.bringToFront();
// We check at the end that the transacton list is updated on the bridge UI
const listUpdated = await waitForTransactionListUpdate(txnsLengthBefore);
// Check that that new transaction is added to the list on the UI
expect(listUpdated).toBeTruthy();
}); });
test.skip("should be able to bridge ERC20 tokens from L1 to L2", async ({ test("should be able to initiate bridging USDC from L1 to L2 in testnet", async ({
page, getNativeBridgeTransactionsCount,
metamask, waitForNewTxAdditionToTxList,
getBridgeTransactionsCount, connectMetamaskToDapp,
selectToken, clickNativeBridgeButton,
sendTokens, openNativeBridgeFormSettings,
waitForTransactionListUpdate, toggleShowTestNetworksInNativeBridgeForm,
waitForTransactionToConfirm, selectTokenAndInputAmount,
doInitiateBridgeTransaction,
openNativeBridgeTransactionHistory,
closeNativeBridgeTransactionHistory,
doTokenApprovalIfNeeded,
}) => { }) => {
const txnsLengthBefore = await getBridgeTransactionsCount(); // At least 2 blockchain tx in this test
test.setTimeout(120_000);
// Select WETH in the token list (Easiest to get) // Setup testnet UI
await selectToken("WETH"); await connectMetamaskToDapp();
await clickNativeBridgeButton();
await openNativeBridgeFormSettings();
await toggleShowTestNetworksInNativeBridgeForm();
await sendTokens(WEI_AMOUNT); // Get # of txs in txHistory before doing bridge tx, so that we can later confirm that our bridge tx shows up in the txHistory.
await metamask.confirmTransaction(); await openNativeBridgeTransactionHistory();
const txnsLengthBefore = await getNativeBridgeTransactionsCount();
await closeNativeBridgeTransactionHistory();
// Wait for transaction to finish // Actual bridging actions
await waitForTransactionToConfirm(); await selectTokenAndInputAmount(USDC_SYMBOL, USDC_AMOUNT);
await doTokenApprovalIfNeeded();
await doInitiateBridgeTransaction();
await page.bringToFront(); // Check that our bridge tx shows up in the tx history
// We check at the end that the transacton list is updated on the bridge UI await waitForNewTxAdditionToTxList(txnsLengthBefore);
const listUpdated = await waitForTransactionListUpdate(txnsLengthBefore); });
// Check that that new transaction is added to the list on the UI test("should be able to claim if available READY_TO_CLAIM transactions", async ({
expect(listUpdated).toBeTruthy(); page,
connectMetamaskToDapp,
clickNativeBridgeButton,
openNativeBridgeFormSettings,
toggleShowTestNetworksInNativeBridgeForm,
openNativeBridgeTransactionHistory,
getNativeBridgeTransactionsCount,
switchToLineaSepolia,
doClaimTransaction,
waitForTxListUpdateForClaimTx,
}) => {
test.setTimeout(90_000);
await connectMetamaskToDapp();
await clickNativeBridgeButton();
await openNativeBridgeFormSettings();
await toggleShowTestNetworksInNativeBridgeForm();
// Switch to L2 network
await switchToLineaSepolia();
// Load tx history
await openNativeBridgeTransactionHistory();
await getNativeBridgeTransactionsCount();
// Find and click READY_TO_CLAIM TX
const readyToClaimTx = page.getByRole("listitem").filter({ hasText: "Ready to claim" });
const readyToClaimCount = await readyToClaimTx.count();
if (readyToClaimCount === 0) return;
await readyToClaimTx.first().click();
await doClaimTransaction();
// Check that tx history has updated accordingly
await waitForTxListUpdateForClaimTx(readyToClaimCount);
}); });
}); });

View File

@@ -0,0 +1,15 @@
import { Page } from "@playwright/test";
export async function getNativeBridgeTransactionsCountImpl(page: Page): Promise<number> {
const txList = page.getByTestId("native-bridge-transaction-history-list");
const noTransactionsYetText = page.getByText("No transactions yet");
// Either `txList` or `noTransactionsYetText` will appear. Should be mutually exclusive.
await Promise.race([txList.waitFor({ state: "visible" }), noTransactionsYetText.waitFor({ state: "visible" })]);
// Check which element is actually visible
const isTxListVisible = await txList.isVisible();
if (!isTxListVisible) return 0;
const txs = txList.getByRole("listitem");
const txCount = txs.count();
return txCount;
}

View File

@@ -0,0 +1,2 @@
export { selectTokenAndWaitForBalance } from "./selectTokenAndWaitForBalance";
export { getNativeBridgeTransactionsCountImpl } from "./getNativeBridgeTransactionsCountImpl";

View File

@@ -0,0 +1,24 @@
import { Page } from "@playwright/test";
import { POLLING_INTERVAL } from "../constants";
export async function selectTokenAndWaitForBalance(tokenSymbol: string, page: Page) {
const openModalBtn = page.getByTestId("native-bridge-open-token-list-modal");
await openModalBtn.click();
// Wait for API request to retrieve blockchain balance.
const tokenBalance = page.getByTestId(`token-details-${tokenSymbol.toLowerCase()}-amount`);
console.log(`Fetching token balance for ${tokenSymbol}`);
// Timeout implementation
const fetchTokenBalanceTimeout = 5000;
let fetchTokenTimeUsed = 0;
while ((await tokenBalance.textContent()) === `0 ${tokenSymbol}`) {
if (fetchTokenTimeUsed >= fetchTokenBalanceTimeout)
throw `Could not find any balance for ${tokenSymbol}, does the testing wallet have funds?`;
await page.waitForTimeout(POLLING_INTERVAL);
fetchTokenTimeUsed += POLLING_INTERVAL;
}
console.log(`Selected token balance: ${await tokenBalance.textContent()}`);
// Select token
await page.getByTestId(`token-details-${tokenSymbol.toLowerCase()}-btn`).click();
}

View File

@@ -1,5 +1,6 @@
import { MetaMask, defineWalletSetup, getExtensionId } from "@synthetixio/synpress";
import { LINEA_SEPOLIA_NETWORK, METAMASK_PASSWORD, METAMASK_SEED_PHRASE, TEST_PRIVATE_KEY } from "../constants"; import { LINEA_SEPOLIA_NETWORK, METAMASK_PASSWORD, METAMASK_SEED_PHRASE, TEST_PRIVATE_KEY } from "../constants";
import { defineWalletSetup } from "@synthetixio/synpress";
import { MetaMask, getExtensionId } from "@synthetixio/synpress/playwright";
export default defineWalletSetup(METAMASK_PASSWORD, async (context, walletPage) => { export default defineWalletSetup(METAMASK_PASSWORD, async (context, walletPage) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -8,7 +9,6 @@ export default defineWalletSetup(METAMASK_PASSWORD, async (context, walletPage)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
const metamask = new MetaMask(context, walletPage, METAMASK_PASSWORD, extensionId); const metamask = new MetaMask(context, walletPage, METAMASK_PASSWORD, extensionId);
await metamask.importWallet(METAMASK_SEED_PHRASE); await metamask.importWallet(METAMASK_SEED_PHRASE);
await metamask.importWalletFromPrivateKey(TEST_PRIVATE_KEY); await metamask.importWalletFromPrivateKey(TEST_PRIVATE_KEY);

215
pnpm-lock.yaml generated
View File

@@ -88,10 +88,10 @@ importers:
version: 1.9.2 version: 1.9.2
next: next:
specifier: 14.2.25 specifier: 14.2.25
version: 14.2.25(@babel/core@7.25.7)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0) version: 14.2.25(@babel/core@7.25.7)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0)
next-seo: next-seo:
specifier: 6.6.0 specifier: 6.6.0
version: 6.6.0(next@14.2.25(@babel/core@7.25.7)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 6.6.0(next@14.2.25(@babel/core@7.25.7)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
pino-pretty: pino-pretty:
specifier: 13.0.0 specifier: 13.0.0
version: 13.0.0 version: 13.0.0
@@ -124,14 +124,14 @@ importers:
version: 4.5.4(@types/react@18.3.11)(react@18.3.1) version: 4.5.4(@types/react@18.3.11)(react@18.3.1)
devDependencies: devDependencies:
'@playwright/test': '@playwright/test':
specifier: 1.45.3 specifier: 1.51.1
version: 1.45.3 version: 1.51.1
'@svgr/webpack': '@svgr/webpack':
specifier: 8.1.0 specifier: 8.1.0
version: 8.1.0(typescript@5.4.5) version: 8.1.0(typescript@5.4.5)
'@synthetixio/synpress': '@synthetixio/synpress':
specifier: 4.0.0-alpha.7 specifier: 4.0.10
version: 4.0.0-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2) version: 4.0.10(@depay/solana-web3.js@1.27.0)(@depay/web3-blockchains@9.6.7)(@playwright/test@1.51.1)(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(playwright-core@1.51.1)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2)
'@types/fs-extra': '@types/fs-extra':
specifier: 11.0.4 specifier: 11.0.4
version: 11.0.4 version: 11.0.4
@@ -150,6 +150,9 @@ importers:
eslint-config-next: eslint-config-next:
specifier: 14.2.15 specifier: 14.2.15
version: 14.2.15(eslint@8.57.0)(typescript@5.4.5) version: 14.2.15(eslint@8.57.0)(typescript@5.4.5)
nock:
specifier: 14.0.1
version: 14.0.1
postcss: postcss:
specifier: 8.5.3 specifier: 8.5.3
version: 8.5.3 version: 8.5.3
@@ -248,8 +251,6 @@ importers:
specifier: 17.7.2 specifier: 17.7.2
version: 17.7.2 version: 17.7.2
contracts/lib/forge-std: {}
e2e: e2e:
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
@@ -1290,8 +1291,20 @@ packages:
resolution: {integrity: sha512-RqVsm/aURJ2npRm0/0qr/GiMdBsGSbRA2GpzG75Vy7qODYScbYjA/CitMjhw9ktzGjiFN9oh/ooq9GBjPIhEdA==} resolution: {integrity: sha512-RqVsm/aURJ2npRm0/0qr/GiMdBsGSbRA2GpzG75Vy7qODYScbYjA/CitMjhw9ktzGjiFN9oh/ooq9GBjPIhEdA==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@depay/web3-mock@14.17.0': '@depay/web3-client@10.18.6':
resolution: {integrity: sha512-0WCIpHqGUTPmOb5l3iN+4wCY+P3nHnGWd3uyWB+Wrt5DygS6MWI2b50gwtSCgYUCfgmEv9KlRuCnHDC4TDKCeA==} resolution: {integrity: sha512-JeUAZ04/dsIra1ao3mvqAdVhRf4U1YxSoH0mE+XxhcXPgLC7KQTzH6oCLp07tAmoxholEL2cf5Oo20n6q1fZ/w==}
engines: {node: '>=16'}
peerDependencies:
'@depay/solana-web3.js': ^1.25.1
'@depay/web3-blockchains': ^9.3.6
ethers: ^5.7.1
'@depay/web3-mock-evm@14.19.1':
resolution: {integrity: sha512-Gx5n87gwya5dGv4JwDdlJFWshLbM9nDj6co8Z25FTf7/xKsTUD1en971B2QweXqZJxYadnumOPb+n19lgRofpQ==}
engines: {node: '>=16'}
'@depay/web3-mock@14.19.1':
resolution: {integrity: sha512-bBM1J0EWDWXJKVPtzo8YrX7fbGwUATYWN8kaJniQU2z5V+UK3kVhjQi+en0JMF9cCjinkERK7MqoZLaYR+cb+Q==}
engines: {node: '>=16'} engines: {node: '>=16'}
'@dynamic-labs/assert-package-version@4.9.5': '@dynamic-labs/assert-package-version@4.9.5':
@@ -2283,6 +2296,10 @@ packages:
resolution: {integrity: sha512-z10PF9JV6SbjFq+/rYabM+8CVlMokgl8RFGvieSGNTmrkQanfHn+15XBrhG3BgUfvmTeSeyShfOHpG0i9zEdcg==} resolution: {integrity: sha512-z10PF9JV6SbjFq+/rYabM+8CVlMokgl8RFGvieSGNTmrkQanfHn+15XBrhG3BgUfvmTeSeyShfOHpG0i9zEdcg==}
deprecated: Motion One for Vue is deprecated. Use Oku Motion instead https://oku-ui.com/motion deprecated: Motion One for Vue is deprecated. Use Oku Motion instead https://oku-ui.com/motion
'@mswjs/interceptors@0.37.6':
resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==}
engines: {node: '>=18'}
'@mui/core-downloads-tracker@6.4.8': '@mui/core-downloads-tracker@6.4.8':
resolution: {integrity: sha512-vjP4+A1ybyCRhDZC7r5EPWu/gLseFZxaGyPdDl94vzVvk6Yj6gahdaqcjbhkaCrJjdZj90m3VioltWPAnWF/zw==} resolution: {integrity: sha512-vjP4+A1ybyCRhDZC7r5EPWu/gLseFZxaGyPdDl94vzVvk6Yj6gahdaqcjbhkaCrJjdZj90m3VioltWPAnWF/zw==}
@@ -2669,6 +2686,15 @@ packages:
peerDependencies: peerDependencies:
'@oclif/core': '>= 3.0.0' '@oclif/core': '>= 3.0.0'
'@open-draft/deferred-promise@2.2.0':
resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
'@open-draft/logger@0.3.0':
resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
'@openzeppelin/contracts-upgradeable@4.9.6': '@openzeppelin/contracts-upgradeable@4.9.6':
resolution: {integrity: sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==} resolution: {integrity: sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==}
@@ -2799,8 +2825,8 @@ packages:
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@playwright/test@1.45.3': '@playwright/test@1.51.1':
resolution: {integrity: sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==} resolution: {integrity: sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
@@ -3340,32 +3366,32 @@ packages:
'@swc/helpers@0.5.5': '@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
'@synthetixio/ethereum-wallet-mock@0.0.1-alpha.7': '@synthetixio/ethereum-wallet-mock@0.0.11':
resolution: {integrity: sha512-afyAVC2b39OC/mxfVeISFQ7TVleS15GYeCPte6VrdFYWi/2SXPml6myAZx9FlRqRlyaPLhRrtRRjdno44S6/6Q==} resolution: {integrity: sha512-J/8hyHoh5lG5vIQ9iNVApyKKRCMRMqEmuv97s6zZVmqe/gJR0+cSWW+mnYHXcIiWZ82WLYnr4TK4OssqZ/4C3w==}
peerDependencies: peerDependencies:
'@playwright/test': 1.44.0 '@playwright/test': '*'
'@synthetixio/synpress-cache@0.0.1-alpha.7': '@synthetixio/synpress-cache@0.0.11':
resolution: {integrity: sha512-vIR8f4XMJSKsSezmloT8A0yuEZON+Ovbkoy/5ELcrfLFORW1Ssu2pjOioWGgQEVZBCdGTQ4wVIWmo8MbvCrAqA==} resolution: {integrity: sha512-5blQD9RQ/LF6buuNAhok/ouHm0Ch4sNas40J7rWVXrim6UA5sCAHPFfSVdavKpBURGN8t6Zce2+uiZs0srbtwA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
playwright-core: 1.44.0 playwright-core: 1.48.2
'@synthetixio/synpress-core@0.0.1-alpha.7': '@synthetixio/synpress-core@0.0.11':
resolution: {integrity: sha512-El23vK/FlYiliT2JworRc8zXGVdUIvzxwzWtGMZwC/KOKy0RGgNA7rQfwc3VSPhp1ceUd4w3uIRtv6Ccjo8WbA==} resolution: {integrity: sha512-IZtA0KfG1qO0JJVzswTLT6VBmteut7NvqbUcVaEOL12FBPIaZCglyd7gdyTkUCWusp7sHuSA/Bysre0OaBnuQg==}
peerDependencies: peerDependencies:
'@playwright/test': 1.44.0 '@playwright/test': '*'
'@synthetixio/synpress-metamask@0.0.1-alpha.7': '@synthetixio/synpress-metamask@0.0.11':
resolution: {integrity: sha512-xzBsF5wnak25uw7ZO9RJEmCuMtT03W9pyDaEpO7T1VkDcZT8MBxKHs9vR4UKqHHd9IPKXc9EvsUJ/oSwW4saKQ==} resolution: {integrity: sha512-nYNtFYolyeus0WqTqEot6xikyHzSXb/bSK9NP4aQ5vhKFkT6KR+8EjC9G4QKZ/+lM0alBJfvu2BOLDnYtKEOAw==}
peerDependencies: peerDependencies:
'@playwright/test': 1.44.0 '@playwright/test': '*'
'@synthetixio/synpress@4.0.0-alpha.7': '@synthetixio/synpress@4.0.10':
resolution: {integrity: sha512-hdVV+LR/aCO+o7FyeJiFxJ+o797mDqNqkh+huz3Ic4LDbTW18HkLKwLwUzRKt8ngIOf60K/PFL9QyBLujTc76g==} resolution: {integrity: sha512-UXHGb9lylznEdXtDw5TYFR095XsMh5I2iqAHRUfaYgYRJlm+MQAlnFINqREt7g52VqFJ1EzyCeIgrs6Aufk+/g==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@playwright/test': 1.44.0 '@playwright/test': '*'
'@szmarczak/http-timer@4.0.6': '@szmarczak/http-timer@4.0.6':
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
@@ -6568,6 +6594,9 @@ packages:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-node-process@1.2.0:
resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
is-number-object@1.0.7: is-number-object@1.0.7:
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -7576,6 +7605,10 @@ packages:
resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
nock@14.0.1:
resolution: {integrity: sha512-IJN4O9pturuRdn60NjQ7YkFt6Rwei7ZKaOwb1tvUIIqTgeD0SDDAX3vrqZD4wcXczeEy/AsUXxpGpP/yHqV7xg==}
engines: {node: '>=18.20.0 <20 || >=20.12.1'}
node-abi@3.68.0: node-abi@3.68.0:
resolution: {integrity: sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==} resolution: {integrity: sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -7884,6 +7917,9 @@ packages:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
outvariant@1.4.3:
resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
ox@0.6.7: ox@0.6.7:
resolution: {integrity: sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==} resolution: {integrity: sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==}
peerDependencies: peerDependencies:
@@ -8113,13 +8149,13 @@ packages:
pkg-types@1.2.1: pkg-types@1.2.1:
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
playwright-core@1.45.3: playwright-core@1.51.1:
resolution: {integrity: sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==} resolution: {integrity: sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
playwright@1.45.3: playwright@1.51.1:
resolution: {integrity: sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==} resolution: {integrity: sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
@@ -8258,6 +8294,10 @@ packages:
prop-types@15.8.1: prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
propagate@2.0.1:
resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==}
engines: {node: '>= 8'}
proper-lockfile@4.1.2: proper-lockfile@4.1.2:
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
@@ -9043,6 +9083,9 @@ packages:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
strict-event-emitter@0.5.1:
resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
strict-uri-encode@1.1.0: strict-uri-encode@1.1.0:
resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -11497,7 +11540,21 @@ snapshots:
'@depay/web3-blockchains@9.6.7': {} '@depay/web3-blockchains@9.6.7': {}
'@depay/web3-mock@14.17.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': '@depay/web3-client@10.18.6(@depay/solana-web3.js@1.27.0)(@depay/web3-blockchains@9.6.7)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))':
dependencies:
'@depay/solana-web3.js': 1.27.0
'@depay/web3-blockchains': 9.6.7
ethers: 6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)
'@depay/web3-mock-evm@14.19.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
dependencies:
'@depay/web3-blockchains': 9.6.7
ethers: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@depay/web3-mock@14.19.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
dependencies: dependencies:
'@depay/solana-web3.js': 1.27.0 '@depay/solana-web3.js': 1.27.0
'@depay/web3-blockchains': 9.6.7 '@depay/web3-blockchains': 9.6.7
@@ -13027,6 +13084,15 @@ snapshots:
'@motionone/dom': 10.18.0 '@motionone/dom': 10.18.0
tslib: 2.7.0 tslib: 2.7.0
'@mswjs/interceptors@0.37.6':
dependencies:
'@open-draft/deferred-promise': 2.2.0
'@open-draft/logger': 0.3.0
'@open-draft/until': 2.1.0
is-node-process: 1.2.0
outvariant: 1.4.3
strict-event-emitter: 0.5.1
'@mui/core-downloads-tracker@6.4.8': {} '@mui/core-downloads-tracker@6.4.8': {}
'@mui/icons-material@6.0.2(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)': '@mui/icons-material@6.0.2(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)':
@@ -13408,6 +13474,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@open-draft/deferred-promise@2.2.0': {}
'@open-draft/logger@0.3.0':
dependencies:
is-node-process: 1.2.0
outvariant: 1.4.3
'@open-draft/until@2.1.0': {}
'@openzeppelin/contracts-upgradeable@4.9.6': {} '@openzeppelin/contracts-upgradeable@4.9.6': {}
'@openzeppelin/contracts@4.9.6': {} '@openzeppelin/contracts@4.9.6': {}
@@ -13558,9 +13633,9 @@ snapshots:
'@pkgr/core@0.1.1': {} '@pkgr/core@0.1.1': {}
'@playwright/test@1.45.3': '@playwright/test@1.51.1':
dependencies: dependencies:
playwright: 1.45.3 playwright: 1.51.1
'@pnpm/config.env-replace@1.1.0': {} '@pnpm/config.env-replace@1.1.0': {}
@@ -14448,19 +14523,24 @@ snapshots:
'@swc/counter': 0.1.3 '@swc/counter': 0.1.3
tslib: 2.7.0 tslib: 2.7.0
'@synthetixio/ethereum-wallet-mock@0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2)': '@synthetixio/ethereum-wallet-mock@0.0.11(@depay/solana-web3.js@1.27.0)(@depay/web3-blockchains@9.6.7)(@playwright/test@1.51.1)(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2)':
dependencies: dependencies:
'@depay/web3-mock': 14.17.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@depay/web3-client': 10.18.6(@depay/solana-web3.js@1.27.0)(@depay/web3-blockchains@9.6.7)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))
'@playwright/test': 1.45.3 '@depay/web3-mock': 14.19.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
'@synthetixio/synpress-core': 0.0.1-alpha.7(@playwright/test@1.45.3) '@depay/web3-mock-evm': 14.19.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
'@playwright/test': 1.51.1
'@synthetixio/synpress-core': 0.0.11(@playwright/test@1.51.1)
viem: 2.9.9(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2) viem: 2.9.9(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2)
transitivePeerDependencies: transitivePeerDependencies:
- '@depay/solana-web3.js'
- '@depay/web3-blockchains'
- bufferutil - bufferutil
- ethers
- typescript - typescript
- utf-8-validate - utf-8-validate
- zod - zod
'@synthetixio/synpress-cache@0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)': '@synthetixio/synpress-cache@0.0.11(playwright-core@1.51.1)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)':
dependencies: dependencies:
axios: 1.6.7 axios: 1.6.7
chalk: 5.3.0 chalk: 5.3.0
@@ -14469,7 +14549,7 @@ snapshots:
fs-extra: 11.2.0 fs-extra: 11.2.0
glob: 10.3.10 glob: 10.3.10
gradient-string: 2.0.2 gradient-string: 2.0.2
playwright-core: 1.45.3 playwright-core: 1.51.1
progress: 2.0.3 progress: 2.0.3
tsup: 8.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5) tsup: 8.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)
unzipper: 0.10.14 unzipper: 0.10.14
@@ -14483,15 +14563,15 @@ snapshots:
- ts-node - ts-node
- typescript - typescript
'@synthetixio/synpress-core@0.0.1-alpha.7(@playwright/test@1.45.3)': '@synthetixio/synpress-core@0.0.11(@playwright/test@1.51.1)':
dependencies: dependencies:
'@playwright/test': 1.45.3 '@playwright/test': 1.51.1
'@synthetixio/synpress-metamask@0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)': '@synthetixio/synpress-metamask@0.0.11(@playwright/test@1.51.1)(bufferutil@4.0.8)(playwright-core@1.51.1)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)':
dependencies: dependencies:
'@playwright/test': 1.45.3 '@playwright/test': 1.51.1
'@synthetixio/synpress-cache': 0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5) '@synthetixio/synpress-cache': 0.0.11(playwright-core@1.51.1)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)
'@synthetixio/synpress-core': 0.0.1-alpha.7(@playwright/test@1.45.3) '@synthetixio/synpress-core': 0.0.11(@playwright/test@1.51.1)
'@viem/anvil': 0.0.7(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@viem/anvil': 0.0.7(bufferutil@4.0.8)(utf-8-validate@5.0.10)
fs-extra: 11.2.0 fs-extra: 11.2.0
zod: 3.22.4 zod: 3.22.4
@@ -14507,18 +14587,21 @@ snapshots:
- typescript - typescript
- utf-8-validate - utf-8-validate
'@synthetixio/synpress@4.0.0-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2)': '@synthetixio/synpress@4.0.10(@depay/solana-web3.js@1.27.0)(@depay/web3-blockchains@9.6.7)(@playwright/test@1.51.1)(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(playwright-core@1.51.1)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2)':
dependencies: dependencies:
'@playwright/test': 1.45.3 '@playwright/test': 1.51.1
'@synthetixio/ethereum-wallet-mock': 0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2) '@synthetixio/ethereum-wallet-mock': 0.0.11(@depay/solana-web3.js@1.27.0)(@depay/web3-blockchains@9.6.7)(@playwright/test@1.51.1)(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.24.2)
'@synthetixio/synpress-cache': 0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5) '@synthetixio/synpress-cache': 0.0.11(playwright-core@1.51.1)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)
'@synthetixio/synpress-core': 0.0.1-alpha.7(@playwright/test@1.45.3) '@synthetixio/synpress-core': 0.0.11(@playwright/test@1.51.1)
'@synthetixio/synpress-metamask': 0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10) '@synthetixio/synpress-metamask': 0.0.11(@playwright/test@1.51.1)(bufferutil@4.0.8)(playwright-core@1.51.1)(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)
transitivePeerDependencies: transitivePeerDependencies:
- '@depay/solana-web3.js'
- '@depay/web3-blockchains'
- '@microsoft/api-extractor' - '@microsoft/api-extractor'
- '@swc/core' - '@swc/core'
- bufferutil - bufferutil
- debug - debug
- ethers
- playwright-core - playwright-core
- postcss - postcss
- supports-color - supports-color
@@ -18810,6 +18893,8 @@ snapshots:
is-negative-zero@2.0.3: {} is-negative-zero@2.0.3: {}
is-node-process@1.2.0: {}
is-number-object@1.0.7: is-number-object@1.0.7:
dependencies: dependencies:
has-tostringtag: 1.0.2 has-tostringtag: 1.0.2
@@ -20193,15 +20278,15 @@ snapshots:
neo-async@2.6.2: {} neo-async@2.6.2: {}
next-seo@6.6.0(next@14.2.25(@babel/core@7.25.7)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): next-seo@6.6.0(next@14.2.25(@babel/core@7.25.7)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
next: 14.2.25(@babel/core@7.25.7)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0) next: 14.2.25(@babel/core@7.25.7)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
next-tick@1.1.0: {} next-tick@1.1.0: {}
next@14.2.25(@babel/core@7.25.7)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0): next@14.2.25(@babel/core@7.25.7)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0):
dependencies: dependencies:
'@next/env': 14.2.25 '@next/env': 14.2.25
'@swc/helpers': 0.5.5 '@swc/helpers': 0.5.5
@@ -20222,7 +20307,7 @@ snapshots:
'@next/swc-win32-arm64-msvc': 14.2.25 '@next/swc-win32-arm64-msvc': 14.2.25
'@next/swc-win32-ia32-msvc': 14.2.25 '@next/swc-win32-ia32-msvc': 14.2.25
'@next/swc-win32-x64-msvc': 14.2.25 '@next/swc-win32-x64-msvc': 14.2.25
'@playwright/test': 1.45.3 '@playwright/test': 1.51.1
sass: 1.86.0 sass: 1.86.0
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
@@ -20235,6 +20320,12 @@ snapshots:
nocache@3.0.4: {} nocache@3.0.4: {}
nock@14.0.1:
dependencies:
'@mswjs/interceptors': 0.37.6
json-stringify-safe: 5.0.1
propagate: 2.0.1
node-abi@3.68.0: node-abi@3.68.0:
dependencies: dependencies:
semver: 7.6.3 semver: 7.6.3
@@ -20480,6 +20571,8 @@ snapshots:
os-tmpdir@1.0.2: {} os-tmpdir@1.0.2: {}
outvariant@1.4.3: {}
ox@0.6.7(typescript@5.4.5)(zod@3.24.2): ox@0.6.7(typescript@5.4.5)(zod@3.24.2):
dependencies: dependencies:
'@adraffy/ens-normalize': 1.11.0 '@adraffy/ens-normalize': 1.11.0
@@ -20718,11 +20811,11 @@ snapshots:
mlly: 1.7.2 mlly: 1.7.2
pathe: 1.1.2 pathe: 1.1.2
playwright-core@1.45.3: {} playwright-core@1.51.1: {}
playwright@1.45.3: playwright@1.51.1:
dependencies: dependencies:
playwright-core: 1.45.3 playwright-core: 1.51.1
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
@@ -20847,6 +20940,8 @@ snapshots:
object-assign: 4.1.1 object-assign: 4.1.1
react-is: 16.13.1 react-is: 16.13.1
propagate@2.0.1: {}
proper-lockfile@4.1.2: proper-lockfile@4.1.2:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -21834,6 +21929,8 @@ snapshots:
streamsearch@1.1.0: {} streamsearch@1.1.0: {}
strict-event-emitter@0.5.1: {}
strict-uri-encode@1.1.0: {} strict-uri-encode@1.1.0: {}
strict-uri-encode@2.0.0: {} strict-uri-encode@2.0.0: {}

View File

@@ -6,3 +6,4 @@ packages:
- 'operations/**' - 'operations/**'
- 'bridge-ui/**' - 'bridge-ui/**'
- 'ts-libs/**' - 'ts-libs/**'
- '!contracts/lib/**'