From b9afdc3dc97d24b92b27cbb4fd893c8d21020d66 Mon Sep 17 00:00:00 2001
From: Nicolas Villanueva <1890113+MexicanAce@users.noreply.github.com>
Date: Sun, 22 Dec 2024 04:16:36 -0800
Subject: [PATCH] fix: auth server and sdk when not using sessions (#45)
---
.github/workflows/ci.yml | 11 +
.gitignore | 4 +
README.md | 14 +-
examples/demo-app/.gitignore | 1 +
examples/demo-app/package.json | 5 +-
examples/demo-app/pages/index.vue | 84 +++++-
examples/demo-app/project.json | 22 +-
.../smart-contracts/DemoPaymaster.sol | 65 ++++
.../demo-app/tests/create-account.spec.ts | 282 +++++++++++++++++-
.../auth-server/components/views/Auth.vue | 2 +-
.../views/confirmation/RequestSession.vue | 2 +-
packages/sdk/src/client/passkey/client.ts | 4 +-
.../src/client/passkey/decorators/wallet.ts | 52 ++++
pnpm-lock.yaml | 77 ++++-
14 files changed, 575 insertions(+), 50 deletions(-)
create mode 100644 examples/demo-app/smart-contracts/DemoPaymaster.sol
create mode 100644 packages/sdk/src/client/passkey/decorators/wallet.ts
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 149d244..dc8a890 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -52,6 +52,17 @@ jobs:
run: pnpm run deploy
working-directory: packages/contracts
+ - name: Install zksync-foundry
+ run: |
+ wget -qc https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz -O - | tar -xz
+ ./forge -V && ./cast -V
+ sudo mv ./forge /usr/local/bin/
+ sudo mv ./cast /usr/local/bin/
+ forge -V && cast -V
+
+ - name: Deploy Demo-App contracts
+ run: pnpm nx deploy-contracts demo-app
+
# Run E2E tests
- name: Install Playwright Chromium Browser
run: pnpm exec playwright install chromium
diff --git a/.gitignore b/.gitignore
index 1411f3c..2425315 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,10 @@ node_modules/
# era-test-node
era_test_node.log
+anvil-zksync.log
+foundryup-zksync
+cache/
+zkout/
package-lock.json
yarn.lock
diff --git a/README.md b/README.md
index 832045b..b29dd57 100644
--- a/README.md
+++ b/README.md
@@ -125,13 +125,19 @@ This monorepo is comprised of the following packages, products, and examples:
[workspace protocol](https://pnpm.io/workspaces#workspace-protocol-workspace)
to link SDK in the new folder.
-3. Start a local node:
+3. Install `foundry-zksync`:
+
+ ```bash
+ curl -L https://raw.githubusercontent.com/matter-labs/foundry-zksync/main/install-foundry-zksync | bash
+ ```
+
+4. Start a local node:
```bash
npx zksync-cli dev start
```
-4. Compile and deploy contracts to the local node:
+5. Compile and deploy contracts to the local node:
```bash
# Compile and deploy contracts
@@ -140,11 +146,9 @@ This monorepo is comprised of the following packages, products, and examples:
pnpm run deploy
```
-5. Start the demo application:
+6. Start the demo application:
```bash
- # Go back to root folder to start demo app
- cd ../..
pnpm nx dev demo-app
```
diff --git a/examples/demo-app/.gitignore b/examples/demo-app/.gitignore
index 4a7f73a..5494e94 100644
--- a/examples/demo-app/.gitignore
+++ b/examples/demo-app/.gitignore
@@ -22,3 +22,4 @@ logs
.env
.env.*
!.env.example
+forge-output.json
diff --git a/examples/demo-app/package.json b/examples/demo-app/package.json
index 954d1ec..b748cce 100644
--- a/examples/demo-app/package.json
+++ b/examples/demo-app/package.json
@@ -7,6 +7,7 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
+ "@matterlabs/zksync-contracts": "^0.6.1",
"@nuxtjs/google-fonts": "^3.2.0",
"@pinia/nuxt": "^0.5.5",
"@simplewebauthn/browser": "^10.0.0",
@@ -19,8 +20,8 @@
"viem": "2.21.14",
"vue": "^3.4.21",
"wagmi": "^2.12.17",
- "zksync-sso": "workspace:*",
- "zksync-ethers": "^6.15.0"
+ "zksync-ethers": "^6.15.0",
+ "zksync-sso": "workspace:*"
},
"devDependencies": {
"@nuxt/eslint": "^0.5.7",
diff --git a/examples/demo-app/pages/index.vue b/examples/demo-app/pages/index.vue
index 0cb7722..a363b22 100644
--- a/examples/demo-app/pages/index.vue
+++ b/examples/demo-app/pages/index.vue
@@ -4,11 +4,18 @@
ZKsync SSO Demo
+
+
{
transport: http(),
});
- await richClient.sendTransaction({
+ let transactionHash = await richClient.sendTransaction({
to: address.value,
value: parseEther("1"),
});
+ // FIXME: When not using sessions, sendTransaction returns a map and not a string
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if ((transactionHash as any).value !== undefined) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ transactionHash = (transactionHash as any).value;
+ }
+
+ await waitForTransactionReceipt(wagmiConfig, {
+ hash: transactionHash,
+ });
};
watchAccount(wagmiConfig, {
@@ -118,11 +148,11 @@ watch(address, async () => {
balance.value = currentBalance;
}, { immediate: true });
-const connectWallet = async () => {
+const connectWallet = async (useSession: boolean) => {
try {
errorMessage.value = "";
connect(wagmiConfig, {
- connector: zksyncConnector,
+ connector: useSession ? zksyncConnectorWithSession : zksyncConnector,
chainId: chain.id,
});
} catch (error) {
@@ -133,25 +163,45 @@ const connectWallet = async () => {
};
const disconnectWallet = async () => {
+ errorMessage.value = "";
await disconnect(wagmiConfig);
};
-const sendTokens = async () => {
+const sendTokens = async (usePaymaster: boolean) => {
if (!address.value) return;
errorMessage.value = "";
isSendingEth.value = true;
try {
- const transactionHash = await sendTransaction(wagmiConfig, {
- to: testTransferTarget,
- value: parseEther("0.1"),
- });
+ let transactionHash;
+ if (usePaymaster) {
+ transactionHash = await sendTransaction(wagmiConfig, {
+ to: testTransferTarget,
+ value: parseEther("0.1"),
+ paymaster: PaymasterContract.deployedTo as `0x${string}`,
+ paymasterInput: getGeneralPaymasterInput({ innerInput: "0x" }),
+ });
+ } else {
+ transactionHash = await sendTransaction(wagmiConfig, {
+ to: testTransferTarget,
+ value: parseEther("0.1"),
+ });
+ }
+
+ // FIXME: When not using sessions, sendTransaction returns a map and not a string
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if ((transactionHash as any).value !== undefined) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ transactionHash = (transactionHash as any).value;
+ }
+
+ const receipt = await waitForTransactionReceipt(wagmiConfig, {
+ hash: transactionHash,
+ });
balance.value = await getBalance(wagmiConfig, {
address: address.value,
});
-
- const receipt = await waitForTransactionReceipt(wagmiConfig, { hash: transactionHash });
if (receipt.status === "reverted") throw new Error("Transaction reverted");
} catch (error) {
// eslint-disable-next-line no-console
@@ -162,6 +212,10 @@ const sendTokens = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transactionFailureDetails = (error as any).cause?.cause?.data?.originalError?.cause?.details;
}
+ if (!transactionFailureDetails) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ transactionFailureDetails = (error as any).cause?.details;
+ }
if (transactionFailureDetails) {
errorMessage.value = transactionFailureDetails;
diff --git a/examples/demo-app/project.json b/examples/demo-app/project.json
index 40454eb..95f48b6 100644
--- a/examples/demo-app/project.json
+++ b/examples/demo-app/project.json
@@ -17,7 +17,8 @@
"prefix": "Demo-App:"
}
]
- }
+ },
+ "dependsOn": ["deploy-contracts"]
},
"build": {
"executor": "nx:run-commands",
@@ -28,8 +29,24 @@
"command": "pnpm nuxt generate"
}
]
+ },
+ "dependsOn": ["deploy-contracts"]
+ },
+ "build-contracts": {
+ "executor": "nx:run-commands",
+ "options": {
+ "cwd": "examples/demo-app",
+ "command": "forge build smart-contracts/DemoPaymaster.sol --root . --zksync"
}
},
+ "deploy-contracts": {
+ "executor": "nx:run-commands",
+ "options": {
+ "cwd": "examples/demo-app",
+ "command": "forge create smart-contracts/DemoPaymaster.sol:DemoPaymaster --private-key 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --rpc-url http://localhost:8011 --root . --chain 260 --zksync --json 2>&1 | sed -n 's/.*\\({.*}\\).*/\\1/p' > forge-output.json && ADDRESS=$(sed -n 's/.*\"deployedTo\":\"\\([^\"]*\\)\".*/\\1/p' forge-output.json) && echo $ADDRESS && cast send --private-key 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 $ADDRESS --rpc-url http://localhost:8011 --value 0.1ether"
+ },
+ "dependsOn": ["build-contracts"]
+ },
"build:local": {
"executor": "nx:run-commands",
"options": {
@@ -60,7 +77,8 @@
"options": {
"cwd": "examples/demo-app",
"command": "pnpm exec playwright install chromium"
- }
+ },
+ "dependsOn": ["deploy-contracts"]
},
"e2e": {
"executor": "nx:run-commands",
diff --git a/examples/demo-app/smart-contracts/DemoPaymaster.sol b/examples/demo-app/smart-contracts/DemoPaymaster.sol
new file mode 100644
index 0000000..3a549c6
--- /dev/null
+++ b/examples/demo-app/smart-contracts/DemoPaymaster.sol
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+/// !!! !!!
+/// !!! THIS IS FOR DEMO PURPOSES ONLY !!!
+/// !!! !!!
+/// !!! DO NOT COPY THIS PAYMASTER !!!
+/// !!! FOR PRODUCTION APPLICATIONS !!!
+/// !!! !!!
+/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+import { IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol";
+import { IPaymasterFlow } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol";
+import { TransactionHelper, Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol";
+
+import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";
+
+/// @author Matter Labs
+/// @notice DO NOT USE THIS FOR PRODUCTION. This contract does not include any validations other than using the paymaster general flow.
+contract DemoPaymaster is IPaymaster {
+ modifier onlyBootloader() {
+ require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Only bootloader can call this method");
+ // Continue execution if called from the bootloader.
+ _;
+ }
+
+ function validateAndPayForPaymasterTransaction(
+ bytes32,
+ bytes32,
+ Transaction calldata _transaction
+ ) external payable onlyBootloader returns (bytes4 magic, bytes memory context) {
+ // By default we consider the transaction as accepted.
+ magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC;
+ require(_transaction.paymasterInput.length >= 4, "The standard paymaster input must be at least 4 bytes long");
+
+ bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]);
+ require(paymasterInputSelector == IPaymasterFlow.general.selector, "Unsupported paymaster flow");
+
+ // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit,
+ // neither paymaster nor account are allowed to access this context variable.
+ uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas;
+
+ // The bootloader never returns any data, so it can safely be ignored here.
+ (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ value: requiredETH }("");
+ require(success, "Failed to transfer tx fee to the Bootloader. Paymaster balance might not be enough.");
+ }
+
+ function postTransaction(
+ bytes calldata _context,
+ Transaction calldata _transaction,
+ bytes32,
+ bytes32,
+ ExecutionResult _txResult,
+ uint256 _maxRefundedGas
+ ) external payable override onlyBootloader {}
+
+ function withdraw(address payable _to) external {
+ uint256 balance = address(this).balance;
+ (bool success, ) = _to.call{ value: balance }("");
+ require(success, "Failed to withdraw funds from paymaster.");
+ }
+
+ receive() external payable {}
+}
diff --git a/examples/demo-app/tests/create-account.spec.ts b/examples/demo-app/tests/create-account.spec.ts
index 5adb422..f2c2399 100644
--- a/examples/demo-app/tests/create-account.spec.ts
+++ b/examples/demo-app/tests/create-account.spec.ts
@@ -44,9 +44,9 @@ test.beforeEach(async ({ page }) => {
await expect(page.getByText("ZKsync SSO Demo")).toBeVisible();
});
-test("Create account, session key, and send ETH", async ({ page }) => {
+test("Create account w/ session and send ETH", async ({ page }) => {
// Click the Connect button
- await page.getByRole("button", { name: "Connect" }).click();
+ await page.getByRole("button", { name: "Connect w/ Session", exact: true }).click();
// Ensure popup is displayed
await page.waitForTimeout(2000);
@@ -64,7 +64,68 @@ test("Create account, session key, and send ETH", async ({ page }) => {
// NOTE: This needs to be done for every page of every test that uses WebAuthn
const client = await popup.context().newCDPSession(popup);
await client.send("WebAuthn.enable");
- const result = await client.send("WebAuthn.addVirtualAuthenticator", {
+ await client.send("WebAuthn.addVirtualAuthenticator", {
+ options: {
+ protocol: "ctap2",
+ transport: "usb",
+ hasResidentKey: true,
+ hasUserVerification: true,
+ isUserVerified: true,
+ automaticPresenceSimulation: true,
+ },
+ });
+
+ // Click Sign Up
+ await popup.getByTestId("signup").click();
+
+ // Add session
+ await expect(popup.getByText("Authorize ZKsync SSO Demo")).toBeVisible();
+ await expect(popup.getByText("Act on your behalf")).toBeVisible();
+ await expect(popup.getByText("Expires tomorrow")).toBeVisible();
+ await expect(popup.getByText("Permissions")).toBeVisible();
+ await popup.getByTestId("connect").click();
+
+ // Waits for session to complete and popup to close
+ await page.waitForTimeout(2000);
+
+ // Check address/balance is shown
+ await expect(page.getByText("Disconnect")).toBeVisible();
+ await expect(page.getByText("Balance:")).toBeVisible();
+ const startBalance = +(await page.getByText("Balance:").innerText())
+ .replace("Balance: ", "")
+ .replace(" ETH", "");
+
+ // Send some eth
+ await page.getByRole("button", { name: "Send 0.1 ETH", exact: true }).click();
+ await expect(page.getByRole("button", { name: "Send 0.1 ETH", exact: true })).toBeEnabled();
+ const endBalance = +(await page.getByText("Balance:").innerText())
+ .replace("Balance: ", "")
+ .replace(" ETH", "");
+ await expect(startBalance, "Balance after transfer should be ~0.1 ETH less")
+ .toBeGreaterThan(endBalance + 0.1);
+});
+
+test("Create account w/ session and send ETH w/ paymaster", async ({ page }) => {
+ // Click the Connect button
+ await page.getByRole("button", { name: "Connect w/ Session", exact: true }).click();
+
+ // Ensure popup is displayed
+ await page.waitForTimeout(2000);
+ const popup = page.context().pages()[1];
+ await expect(popup.getByText("Connect to")).toBeVisible();
+ popup.on("console", (msg) => {
+ if (msg.type() === "error")
+ console.log(`Auth server error console: "${msg.text()}"`);
+ });
+ popup.on("pageerror", (exception) => {
+ console.log(`Auth server uncaught exception: "${exception}"`);
+ });
+
+ // Setup webauthn a Chrome Devtools Protocol session
+ // NOTE: This needs to be done for every page of every test that uses WebAuthn
+ const client = await popup.context().newCDPSession(popup);
+ await client.send("WebAuthn.enable");
+ await client.send("WebAuthn.addVirtualAuthenticator", {
options: {
protocol: "ctap2",
transport: "usb",
@@ -74,8 +135,6 @@ test("Create account, session key, and send ETH", async ({ page }) => {
automaticPresenceSimulation: true,
},
});
- const authenticatorId = result.authenticatorId;
- console.log(`WebAuthn Authenticator ID: ${authenticatorId}`);
// Click Sign Up
await popup.getByTestId("signup").click();
@@ -92,20 +151,221 @@ test("Create account, session key, and send ETH", async ({ page }) => {
// Check address/balance is shown
await expect(page.getByText("Disconnect")).toBeVisible();
- const address = (await page.getByText("Connected Address:").innerText())
- .replace("Connected Address: ", "");
- console.log(`Public Address: ${address}`);
+ await expect(page.getByText("Balance:")).toBeVisible();
+ const startBalance = +(await page.getByText("Balance:").innerText())
+ .replace("Balance: ", "")
+ .replace(" ETH", "");
+
+ // Send some eth w/ paymaster
+ await page.getByRole("button", { name: "Send 0.1 ETH w/ Paymaster", exact: true }).click();
+ await expect(page.getByRole("button", { name: "Send 0.1 ETH w/ Paymaster", exact: true })).toBeEnabled();
+ const endBalance = +(await page.getByText("Balance:").innerText())
+ .replace("Balance: ", "")
+ .replace(" ETH", "");
+ await expect(startBalance, "Balance after transfer should be 0.1 ETH less (no fees)")
+ .toEqual(endBalance + 0.1);
+});
+
+test("Create passkey account and send ETH", async ({ page }) => {
+ // Click the Connect button
+ await page.getByRole("button", { name: "Connect", exact: true }).click();
+
+ // Ensure popup is displayed
+ await page.waitForTimeout(2000);
+ let popup = page.context().pages()[1];
+ await expect(popup.getByText("Connect to")).toBeVisible();
+ popup.on("console", (msg) => {
+ if (msg.type() === "error")
+ console.log(`Auth server error console: "${msg.text()}"`);
+ });
+ popup.on("pageerror", (exception) => {
+ console.log(`Auth server uncaught exception: "${exception}"`);
+ });
+
+ // Setup webauthn a Chrome Devtools Protocol session
+ // NOTE: This needs to be done for every page of every test that uses WebAuthn
+ let client = await popup.context().newCDPSession(popup);
+ await client.send("WebAuthn.enable");
+ await client.send("WebAuthn.addVirtualAuthenticator", {
+ options: {
+ protocol: "ctap2",
+ transport: "usb",
+ hasResidentKey: true,
+ hasUserVerification: true,
+ isUserVerified: true,
+ automaticPresenceSimulation: true,
+ },
+ });
+ let newCredential = null;
+ client.on("WebAuthn.credentialAdded", (credentialAdded) => {
+ console.log("New Passkey credential added");
+ console.log(`Authenticator ID: ${credentialAdded.authenticatorId}`);
+ console.log(`Credential: ${credentialAdded.credential}`);
+ newCredential = credentialAdded.credential;
+ });
+
+ // Click Sign Up
+ await popup.getByTestId("signup").click();
+
+ // Confirm access to your account
+ await expect(popup.getByText("Connect to ZKsync SSO Demo")).toBeVisible();
+ await expect(popup.getByText("localhost:3004")).toBeVisible();
+ await expect(popup.getByText("Let it see your address, balance and activity")).toBeVisible();
+ await popup.getByTestId("connect").click();
+
+ // Waits for session to complete and popup to close
+ await page.waitForTimeout(2000);
+
+ // Check address/balance is shown
+ await expect(page.getByText("Disconnect")).toBeVisible();
await expect(page.getByText("Balance:")).toBeVisible();
const startBalance = +(await page.getByText("Balance:").innerText())
.replace("Balance: ", "")
.replace(" ETH", "");
// Send some eth
- await page.getByRole("button", { name: "Send 0.1 ETH" }).click();
- await expect(page.getByRole("button", { name: "Send 0.1 ETH" })).toBeEnabled();
+ await page.getByRole("button", { name: "Send 0.1 ETH", exact: true }).click();
+
+ // Wait for Auth Server to pop back up
+ await page.waitForTimeout(2000);
+ popup = page.context().pages()[1];
+
+ // We need to recreate the virtual authenticator to match the previous one
+ client = await popup.context().newCDPSession(popup);
+ await client.send("WebAuthn.enable");
+ const result = await client.send("WebAuthn.addVirtualAuthenticator", {
+ options: {
+ protocol: "ctap2",
+ transport: "usb",
+ hasResidentKey: true,
+ hasUserVerification: true,
+ isUserVerified: true,
+ automaticPresenceSimulation: true,
+ },
+ });
+ await expect(newCredential).not.toBeNull();
+ await client.send("WebAuthn.addCredential", {
+ authenticatorId: result.authenticatorId,
+ credential: newCredential!,
+ });
+
+ // Confirm the transfer
+ await expect(popup.getByText("-0.1")).toBeVisible();
+ await expect(popup.getByText("Sending to")).toBeVisible();
+ await expect(popup.getByText("0x55b...4A6")).toBeVisible();
+ await expect(popup.getByText("Fees")).toBeVisible();
+ await popup.getByTestId("confirm").click();
+
+ // Wait for confirmation to complete and popup to close
+ await page.waitForTimeout(2000);
+
+ // Confirm transfer completed and balance updated
+ await expect(page.getByRole("button", { name: "Send 0.1 ETH", exact: true })).toBeEnabled();
const endBalance = +(await page.getByText("Balance:").innerText())
.replace("Balance: ", "")
.replace(" ETH", "");
await expect(startBalance, "Balance after transfer should be ~0.1 ETH less")
- .toBeGreaterThanOrEqual(endBalance + 0.1);
+ .toBeGreaterThan(endBalance + 0.1);
+});
+
+test("Create passkey account and send ETH w/ paymaster", async ({ page }) => {
+ // Click the Connect button
+ await page.getByRole("button", { name: "Connect", exact: true }).click();
+
+ // Ensure popup is displayed
+ await page.waitForTimeout(2000);
+ let popup = page.context().pages()[1];
+ await expect(popup.getByText("Connect to")).toBeVisible();
+ popup.on("console", (msg) => {
+ if (msg.type() === "error")
+ console.log(`Auth server error console: "${msg.text()}"`);
+ });
+ popup.on("pageerror", (exception) => {
+ console.log(`Auth server uncaught exception: "${exception}"`);
+ });
+
+ // Setup webauthn a Chrome Devtools Protocol session
+ // NOTE: This needs to be done for every page of every test that uses WebAuthn
+ let client = await popup.context().newCDPSession(popup);
+ await client.send("WebAuthn.enable");
+ await client.send("WebAuthn.addVirtualAuthenticator", {
+ options: {
+ protocol: "ctap2",
+ transport: "usb",
+ hasResidentKey: true,
+ hasUserVerification: true,
+ isUserVerified: true,
+ automaticPresenceSimulation: true,
+ },
+ });
+ let newCredential = null;
+ client.on("WebAuthn.credentialAdded", (credentialAdded) => {
+ console.log("New Passkey credential added");
+ console.log(`Authenticator ID: ${credentialAdded.authenticatorId}`);
+ console.log(`Credential: ${credentialAdded.credential}`);
+ newCredential = credentialAdded.credential;
+ });
+
+ // Click Sign Up
+ await popup.getByTestId("signup").click();
+
+ // Confirm access to your account
+ await expect(popup.getByText("Connect to ZKsync SSO Demo")).toBeVisible();
+ await expect(popup.getByText("localhost:3004")).toBeVisible();
+ await expect(popup.getByText("Let it see your address, balance and activity")).toBeVisible();
+ await popup.getByTestId("connect").click();
+
+ // Waits for session to complete and popup to close
+ await page.waitForTimeout(2000);
+
+ // Check address/balance is shown
+ await expect(page.getByText("Disconnect")).toBeVisible();
+ await expect(page.getByText("Balance:")).toBeVisible();
+ const startBalance = +(await page.getByText("Balance:").innerText())
+ .replace("Balance: ", "")
+ .replace(" ETH", "");
+
+ // Send some eth w/ paymaster
+ await page.getByRole("button", { name: "Send 0.1 ETH w/ Paymaster", exact: true }).click();
+
+ // Wait for Auth Server to pop back up
+ await page.waitForTimeout(2000);
+ popup = page.context().pages()[1];
+
+ // We need to recreate the virtual authenticator to match the previous one
+ client = await popup.context().newCDPSession(popup);
+ await client.send("WebAuthn.enable");
+ const result = await client.send("WebAuthn.addVirtualAuthenticator", {
+ options: {
+ protocol: "ctap2",
+ transport: "usb",
+ hasResidentKey: true,
+ hasUserVerification: true,
+ isUserVerified: true,
+ automaticPresenceSimulation: true,
+ },
+ });
+ await expect(newCredential).not.toBeNull();
+ await client.send("WebAuthn.addCredential", {
+ authenticatorId: result.authenticatorId,
+ credential: newCredential!,
+ });
+
+ // Confirm the transfer
+ await expect(popup.getByText("-0.1")).toBeVisible();
+ await expect(popup.getByText("Sending to")).toBeVisible();
+ await expect(popup.getByText("0x55b...4A6")).toBeVisible();
+ await expect(popup.getByText("Fees")).toBeVisible();
+ await popup.getByTestId("confirm").click();
+
+ // Wait for confirmation to complete and popup to close
+ await page.waitForTimeout(2000);
+
+ // Confirm transfer completed and balance updated
+ await expect(page.getByRole("button", { name: "Send 0.1 ETH w/ Paymaster", exact: true })).toBeEnabled();
+ const endBalance = +(await page.getByText("Balance:").innerText())
+ .replace("Balance: ", "")
+ .replace(" ETH", "");
+ await expect(startBalance, "Balance after transfer should be 0.1 ETH less (no fees)")
+ .toEqual(endBalance + 0.1);
});
diff --git a/packages/auth-server/components/views/Auth.vue b/packages/auth-server/components/views/Auth.vue
index 2497802..4107724 100644
--- a/packages/auth-server/components/views/Auth.vue
+++ b/packages/auth-server/components/views/Auth.vue
@@ -66,7 +66,7 @@ const registerAccount = async () => {
if (!session.value) {
// no session defined
await createAccount();
- if (!createAccountError) {
+ if (!createAccountError.value) {
navigateTo("/confirm/connect");
}
} else {
diff --git a/packages/auth-server/components/views/confirmation/RequestSession.vue b/packages/auth-server/components/views/confirmation/RequestSession.vue
index 76d8679..e20b800 100644
--- a/packages/auth-server/components/views/confirmation/RequestSession.vue
+++ b/packages/auth-server/components/views/confirmation/RequestSession.vue
@@ -65,7 +65,7 @@
data-testid="connect"
@click="confirmConnection()"
>
- Connect
+ {{ isLoggedIn ? 'Connect' : 'Create' }}
diff --git a/packages/sdk/src/client/passkey/client.ts b/packages/sdk/src/client/passkey/client.ts
index 954dafd..05c38fb 100644
--- a/packages/sdk/src/client/passkey/client.ts
+++ b/packages/sdk/src/client/passkey/client.ts
@@ -5,6 +5,7 @@ import { passkeyHashSignatureResponseFormat } from "../../utils/passkey.js";
import { toPasskeyAccount } from "./account.js";
import { requestPasskeyAuthentication } from "./actions/passkey.js";
import { type ZksyncSsoPasskeyActions, zksyncSsoPasskeyActions } from "./decorators/passkey.js";
+import { zksyncSsoPasskeyWalletActions } from "./decorators/wallet.js";
export function createZksyncPasskeyClient<
transport extends Transport,
@@ -47,7 +48,8 @@ export function createZksyncPasskeyClient<
.extend(publicActions)
.extend(walletActions)
.extend(eip712WalletActions())
- .extend(zksyncSsoPasskeyActions);
+ .extend(zksyncSsoPasskeyActions)
+ .extend(zksyncSsoPasskeyWalletActions);
return client;
}
diff --git a/packages/sdk/src/client/passkey/decorators/wallet.ts b/packages/sdk/src/client/passkey/decorators/wallet.ts
new file mode 100644
index 0000000..066d824
--- /dev/null
+++ b/packages/sdk/src/client/passkey/decorators/wallet.ts
@@ -0,0 +1,52 @@
+import { type Account, bytesToHex, type Chain, formatTransaction, type Transport, type WalletActions } from "viem";
+import { deployContract, getAddresses, getChainId, sendRawTransaction, signMessage, signTypedData, writeContract } from "viem/actions";
+import { signTransaction, type ZksyncEip712Meta } from "viem/zksync";
+
+import { sendEip712Transaction } from "../../session/actions/sendEip712Transaction.js";
+import type { ClientWithZksyncSsoPasskeyData } from "../client.js";
+
+export type ZksyncSsoPasskeyWalletActions
= Omit<
+ WalletActions, "addChain" | "getPermissions" | "requestAddresses" | "requestPermissions" | "switchChain" | "watchAsset" | "prepareTransactionRequest"
+>;
+
+export function zksyncSsoPasskeyWalletActions<
+ transport extends Transport,
+ chain extends Chain,
+ account extends Account,
+>(client: ClientWithZksyncSsoPasskeyData): ZksyncSsoPasskeyWalletActions {
+ return {
+ deployContract: (args) => deployContract(client, args),
+ getAddresses: () => getAddresses(client),
+ getChainId: () => getChainId(client),
+ sendRawTransaction: (args) => sendRawTransaction(client, args),
+ sendTransaction: async (args) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const unformattedTx: any = Object.assign({}, args);
+
+ if ("eip712Meta" in unformattedTx) {
+ const eip712Meta = unformattedTx.eip712Meta as ZksyncEip712Meta;
+ unformattedTx.gasPerPubdata = eip712Meta.gasPerPubdata ? BigInt(eip712Meta.gasPerPubdata) : undefined;
+ unformattedTx.factoryDeps = eip712Meta.factoryDeps;
+ unformattedTx.customSignature = eip712Meta.customSignature;
+ unformattedTx.paymaster = eip712Meta.paymasterParams?.paymaster;
+ unformattedTx.paymasterInput = eip712Meta.paymasterParams?.paymasterInput ? bytesToHex(new Uint8Array(eip712Meta.paymasterParams?.paymasterInput)) : undefined;
+ delete unformattedTx.eip712Meta;
+ }
+
+ const formatters = client.chain?.formatters;
+ const format = formatters?.transaction?.format || formatTransaction;
+
+ const tx = {
+ ...format(unformattedTx),
+ type: "eip712",
+ };
+
+ return await sendEip712Transaction(client, tx);
+ },
+ signMessage: (args) => signMessage(client, args),
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ signTransaction: (args) => signTransaction(client, args as any),
+ signTypedData: (args) => signTypedData(client, args),
+ writeContract: (args) => writeContract(client, args),
+ };
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 475f031..94760d1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -156,6 +156,9 @@ importers:
examples/demo-app:
dependencies:
+ '@matterlabs/zksync-contracts':
+ specifier: ^0.6.1
+ version: 0.6.1(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.1.0)
'@nuxtjs/google-fonts':
specifier: ^3.2.0
version: 3.2.0(magicast@0.3.5)(rollup@4.24.0)
@@ -5006,6 +5009,7 @@ packages:
'@web3modal/core@5.1.11':
resolution: {integrity: sha512-ugUVFVml1vVW+V7yxkn/AYYdrUJzn4ulFbDlxDMpmukKY6sDYLMMGAJ84O8ZC/OPyC7009NYd3mKZurxEyWkHw==}
+ deprecated: Web3Modal is now Reown AppKit. Please follow the upgrade guide at https://docs.reown.com/appkit/upgrade/from-w3m-to-reown
'@web3modal/polyfills@5.1.11':
resolution: {integrity: sha512-BDIDYA2LGTCquahbZ+wyWQy4IBOPeKVSgt4ZpFir1fnVJUPkEluSwZStcKLtCzQvxJgER1sLicUrjJQHF36TOg==}
@@ -5018,9 +5022,11 @@ packages:
'@web3modal/siwe@5.1.11':
resolution: {integrity: sha512-1aKEtMosACyY0SRjHjdcA/g3bRtMojTxlK7S/T6zBk57X/P3xcEZq9J8UM73plmGewjZdLaqGMgv6B/k/WleZQ==}
+ deprecated: Web3Modal is now Reown AppKit. Please follow the upgrade guide at https://docs.reown.com/appkit/upgrade/from-w3m-to-reown
'@web3modal/ui@5.1.11':
resolution: {integrity: sha512-L0L+2YOK+ONx+W7GPtkSdKZuAQ8cjcS5N8kp+WZzKOMUTeDLuXKtSnES4p/ShOVmkpV6qB8r0pPA9xgFh1D3ow==}
+ deprecated: Web3Modal is now Reown AppKit. Please follow the upgrade guide at https://docs.reown.com/appkit/upgrade/from-w3m-to-reown
'@web3modal/wagmi@5.1.11':
resolution: {integrity: sha512-etV1qfBVvh41EMuBHXUpcO/W818jZVNh5/l9Z5kqRPZxlQmBaJbt5mTzw6nw/Lujoe1yYKugGQFhgjfEQK+eyA==}
@@ -13545,7 +13551,7 @@ snapshots:
- encoding
- supports-color
- '@matterlabs/hardhat-zksync-deploy@1.5.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(zksync-ethers@6.15.0(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)))':
+ '@matterlabs/hardhat-zksync-deploy@1.5.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(zksync-ethers@6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)))':
dependencies:
'@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))
chai: 4.5.0
@@ -13558,7 +13564,7 @@ snapshots:
sinon: 18.0.1
sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1)
ts-morph: 22.0.0
- zksync-ethers: 6.15.0(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))
+ zksync-ethers: 6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))
transitivePeerDependencies:
- encoding
- supports-color
@@ -13600,16 +13606,16 @@ snapshots:
- typescript
- utf-8-validate
- '@matterlabs/hardhat-zksync-ethers@1.2.1(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)(zksync-ethers@6.15.0(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)))':
+ '@matterlabs/hardhat-zksync-ethers@1.2.1(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)(zksync-ethers@6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)))':
dependencies:
- '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(zksync-ethers@6.15.0(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
+ '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(zksync-ethers@6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
'@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))
'@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))
chai: 4.5.0
chalk: 4.1.2
ethers: 6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)
hardhat: 2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)
- zksync-ethers: 6.15.0(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))
+ zksync-ethers: 6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))
transitivePeerDependencies:
- bufferutil
- c-kzg
@@ -13730,8 +13736,8 @@ snapshots:
'@matterlabs/hardhat-zksync-upgradable@1.7.0(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)':
dependencies:
- '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(zksync-ethers@6.15.0(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
- '@matterlabs/hardhat-zksync-ethers': 1.2.1(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)(zksync-ethers@6.15.0(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
+ '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(zksync-ethers@6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
+ '@matterlabs/hardhat-zksync-ethers': 1.2.1(bufferutil@4.0.8)(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)(zksync-ethers@6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
'@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))
'@openzeppelin/contracts-hardhat-zksync-upgradable': '@openzeppelin/contracts@5.1.0'
'@openzeppelin/defender-sdk-base-client': 1.15.0
@@ -13887,6 +13893,11 @@ snapshots:
'@openzeppelin/contracts': 4.9.6
'@openzeppelin/contracts-upgradeable': 4.9.6
+ '@matterlabs/zksync-contracts@0.6.1(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.1.0)':
+ dependencies:
+ '@openzeppelin/contracts': 5.1.0
+ '@openzeppelin/contracts-upgradeable': 4.9.6
+
'@metamask/eth-json-rpc-provider@1.0.1':
dependencies:
'@metamask/json-rpc-engine': 7.3.3
@@ -15046,8 +15057,8 @@ snapshots:
dependencies:
'@nuxt/kit': 3.13.2(magicast@0.3.5)(rollup@4.24.0)
'@rollup/plugin-replace': 5.0.7(rollup@4.24.0)
- '@vitejs/plugin-vue': 5.1.4(vite@5.4.10(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))
- '@vitejs/plugin-vue-jsx': 4.0.1(vite@5.4.10(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))
+ '@vitejs/plugin-vue': 5.1.4(vite@5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))
+ '@vitejs/plugin-vue-jsx': 4.0.1(vite@5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))
autoprefixer: 10.4.20(postcss@8.4.47)
clear: 0.1.0
consola: 3.2.3
@@ -15075,7 +15086,7 @@ snapshots:
unplugin: 1.14.1
vite: 5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0)
vite-node: 2.1.3(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0)
- vite-plugin-checker: 0.8.0(eslint@9.11.1(jiti@2.4.1))(optionator@0.9.4)(typescript@5.6.2)(vite@5.4.10(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0))
+ vite-plugin-checker: 0.8.0(eslint@9.11.1(jiti@2.4.1))(optionator@0.9.4)(typescript@5.6.2)(vite@5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0))
vue: 3.5.12(typescript@5.6.2)
vue-bundle-renderer: 2.1.1
transitivePeerDependencies:
@@ -17077,6 +17088,16 @@ snapshots:
- encoding
- supports-color
+ '@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))':
+ dependencies:
+ '@babel/core': 7.26.0
+ '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0)
+ '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0)
+ vite: 5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0)
+ vue: 3.5.12(typescript@5.6.2)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.10(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))':
dependencies:
'@babel/core': 7.26.0
@@ -17087,6 +17108,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitejs/plugin-vue@5.1.4(vite@5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))':
+ dependencies:
+ vite: 5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0)
+ vue: 3.5.12(typescript@5.6.2)
+
'@vitejs/plugin-vue@5.1.4(vite@5.4.10(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.2))':
dependencies:
vite: 5.4.10(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0)
@@ -23096,7 +23122,7 @@ snapshots:
vue: 3.5.12(typescript@5.6.2)
vue-bundle-renderer: 2.1.1
vue-devtools-stub: 0.1.0
- vue-router: 4.4.5(vue@3.5.12(typescript@5.6.2))
+ vue-router: 4.4.5(vue@3.5.13(typescript@5.6.2))
optionalDependencies:
'@parcel/watcher': 2.4.1
'@types/node': 22.8.0
@@ -25494,7 +25520,7 @@ snapshots:
unplugin: 1.14.1
yaml: 2.6.0
optionalDependencies:
- vue-router: 4.4.5(vue@3.5.12(typescript@5.6.2))
+ vue-router: 4.4.5(vue@3.5.13(typescript@5.6.2))
transitivePeerDependencies:
- rollup
- vue
@@ -25673,6 +25699,28 @@ snapshots:
- supports-color
- terser
+ vite-plugin-checker@0.8.0(eslint@9.11.1(jiti@2.4.1))(optionator@0.9.4)(typescript@5.6.2)(vite@5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0)):
+ dependencies:
+ '@babel/code-frame': 7.26.0
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ chokidar: 3.6.0
+ commander: 8.3.0
+ fast-glob: 3.3.2
+ fs-extra: 11.2.0
+ npm-run-path: 4.0.1
+ strip-ansi: 6.0.1
+ tiny-invariant: 1.3.3
+ vite: 5.4.10(@types/node@20.16.10)(sass@1.80.4)(terser@5.36.0)
+ vscode-languageclient: 7.0.0
+ vscode-languageserver: 7.0.0
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-uri: 3.0.8
+ optionalDependencies:
+ eslint: 9.11.1(jiti@2.4.1)
+ optionator: 0.9.4
+ typescript: 5.6.2
+
vite-plugin-checker@0.8.0(eslint@9.11.1(jiti@2.4.1))(optionator@0.9.4)(typescript@5.6.2)(vite@5.4.10(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0)):
dependencies:
'@babel/code-frame': 7.26.0
@@ -25841,6 +25889,11 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.12(typescript@5.6.2)
+ vue-router@4.4.5(vue@3.5.13(typescript@5.6.2)):
+ dependencies:
+ '@vue/devtools-api': 6.6.4
+ vue: 3.5.13(typescript@5.6.2)
+
vue-router@4.5.0(vue@3.5.13(typescript@5.6.2)):
dependencies:
'@vue/devtools-api': 6.6.4