Skip to content

Commit b9afdc3

Browse files
authored
fix: auth server and sdk when not using sessions (#45)
1 parent 412382f commit b9afdc3

File tree

14 files changed

+575
-50
lines changed

14 files changed

+575
-50
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ jobs:
5252
run: pnpm run deploy
5353
working-directory: packages/contracts
5454

55+
- name: Install zksync-foundry
56+
run: |
57+
wget -qc https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz -O - | tar -xz
58+
./forge -V && ./cast -V
59+
sudo mv ./forge /usr/local/bin/
60+
sudo mv ./cast /usr/local/bin/
61+
forge -V && cast -V
62+
63+
- name: Deploy Demo-App contracts
64+
run: pnpm nx deploy-contracts demo-app
65+
5566
# Run E2E tests
5667
- name: Install Playwright Chromium Browser
5768
run: pnpm exec playwright install chromium

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ node_modules/
1010

1111
# era-test-node
1212
era_test_node.log
13+
anvil-zksync.log
14+
foundryup-zksync
15+
cache/
16+
zkout/
1317

1418
package-lock.json
1519
yarn.lock

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,19 @@ This monorepo is comprised of the following packages, products, and examples:
125125
[workspace protocol](https://pnpm.io/workspaces#workspace-protocol-workspace)
126126
to link SDK in the new folder.
127127

128-
3. Start a local node:
128+
3. Install `foundry-zksync`:
129+
130+
```bash
131+
curl -L https://raw.githubusercontent.com/matter-labs/foundry-zksync/main/install-foundry-zksync | bash
132+
```
133+
134+
4. Start a local node:
129135

130136
```bash
131137
npx zksync-cli dev start
132138
```
133139

134-
4. Compile and deploy contracts to the local node:
140+
5. Compile and deploy contracts to the local node:
135141

136142
```bash
137143
# Compile and deploy contracts
@@ -140,11 +146,9 @@ This monorepo is comprised of the following packages, products, and examples:
140146
pnpm run deploy
141147
```
142148

143-
5. Start the demo application:
149+
6. Start the demo application:
144150

145151
```bash
146-
# Go back to root folder to start demo app
147-
cd ../..
148152
pnpm nx dev demo-app
149153
```
150154

examples/demo-app/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ logs
2222
.env
2323
.env.*
2424
!.env.example
25+
forge-output.json

examples/demo-app/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"postinstall": "nuxt prepare"
88
},
99
"dependencies": {
10+
"@matterlabs/zksync-contracts": "^0.6.1",
1011
"@nuxtjs/google-fonts": "^3.2.0",
1112
"@pinia/nuxt": "^0.5.5",
1213
"@simplewebauthn/browser": "^10.0.0",
@@ -19,8 +20,8 @@
1920
"viem": "2.21.14",
2021
"vue": "^3.4.21",
2122
"wagmi": "^2.12.17",
22-
"zksync-sso": "workspace:*",
23-
"zksync-ethers": "^6.15.0"
23+
"zksync-ethers": "^6.15.0",
24+
"zksync-sso": "workspace:*"
2425
},
2526
"devDependencies": {
2627
"@nuxt/eslint": "^0.5.7",

examples/demo-app/pages/index.vue

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44
ZKsync SSO Demo
55
</h1>
66
<button
7-
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
8-
@click="address ? disconnectWallet() : connectWallet()"
7+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-4"
8+
@click="address ? disconnectWallet() : connectWallet(false)"
99
>
1010
{{ address ? "Disconnect" : "Connect" }}
1111
</button>
12+
<button
13+
v-if="!address"
14+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
15+
@click="address ? disconnectWallet() : connectWallet(true)"
16+
>
17+
Connect w/ Session
18+
</button>
1219
<div
1320
v-if="address"
1421
class="mt-4"
@@ -23,12 +30,20 @@
2330
</div>
2431
<button
2532
v-if="address"
26-
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-3"
33+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-3 mr-4 disabled:bg-slate-300"
2734
:disabled="isSendingEth"
28-
@click="sendTokens()"
35+
@click="sendTokens(false)"
2936
>
3037
Send 0.1 ETH
3138
</button>
39+
<button
40+
v-if="address"
41+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-3 disabled:bg-slate-300"
42+
:disabled="isSendingEth"
43+
@click="sendTokens(true)"
44+
>
45+
Send 0.1 ETH w/ Paymaster
46+
</button>
3247

3348
<div
3449
v-if="errorMessage"
@@ -45,11 +60,13 @@ import { zksyncSsoConnector } from "zksync-sso/connector";
4560
import { zksyncInMemoryNode } from "@wagmi/core/chains";
4661
import { createWalletClient, http, parseEther, type Address } from "viem";
4762
import { privateKeyToAccount } from "viem/accounts";
63+
import { getGeneralPaymasterInput } from "viem/zksync";
64+
import PaymasterContract from "../forge-output.json";
4865
4966
const chain = zksyncInMemoryNode;
5067
5168
const testTransferTarget = "0x55bE1B079b53962746B2e86d12f158a41DF294A6";
52-
const zksyncConnector = zksyncSsoConnector({
69+
const zksyncConnectorWithSession = zksyncSsoConnector({
5370
authServerUrl: "http://localhost:3002/confirm",
5471
session: {
5572
feeLimit: parseEther("0.1"),
@@ -61,6 +78,9 @@ const zksyncConnector = zksyncSsoConnector({
6178
],
6279
},
6380
});
81+
const zksyncConnector = zksyncSsoConnector({
82+
authServerUrl: "http://localhost:3002/confirm",
83+
});
6484
const wagmiConfig = createConfig({
6585
chains: [chain],
6686
connectors: [zksyncConnector],
@@ -84,10 +104,20 @@ const fundAccount = async () => {
84104
transport: http(),
85105
});
86106
87-
await richClient.sendTransaction({
107+
let transactionHash = await richClient.sendTransaction({
88108
to: address.value,
89109
value: parseEther("1"),
90110
});
111+
// FIXME: When not using sessions, sendTransaction returns a map and not a string
112+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
113+
if ((transactionHash as any).value !== undefined) {
114+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
115+
transactionHash = (transactionHash as any).value;
116+
}
117+
118+
await waitForTransactionReceipt(wagmiConfig, {
119+
hash: transactionHash,
120+
});
91121
};
92122
93123
watchAccount(wagmiConfig, {
@@ -118,11 +148,11 @@ watch(address, async () => {
118148
balance.value = currentBalance;
119149
}, { immediate: true });
120150
121-
const connectWallet = async () => {
151+
const connectWallet = async (useSession: boolean) => {
122152
try {
123153
errorMessage.value = "";
124154
connect(wagmiConfig, {
125-
connector: zksyncConnector,
155+
connector: useSession ? zksyncConnectorWithSession : zksyncConnector,
126156
chainId: chain.id,
127157
});
128158
} catch (error) {
@@ -133,25 +163,45 @@ const connectWallet = async () => {
133163
};
134164
135165
const disconnectWallet = async () => {
166+
errorMessage.value = "";
136167
await disconnect(wagmiConfig);
137168
};
138169
139-
const sendTokens = async () => {
170+
const sendTokens = async (usePaymaster: boolean) => {
140171
if (!address.value) return;
141172
142173
errorMessage.value = "";
143174
isSendingEth.value = true;
144175
try {
145-
const transactionHash = await sendTransaction(wagmiConfig, {
146-
to: testTransferTarget,
147-
value: parseEther("0.1"),
148-
});
176+
let transactionHash;
149177
178+
if (usePaymaster) {
179+
transactionHash = await sendTransaction(wagmiConfig, {
180+
to: testTransferTarget,
181+
value: parseEther("0.1"),
182+
paymaster: PaymasterContract.deployedTo as `0x${string}`,
183+
paymasterInput: getGeneralPaymasterInput({ innerInput: "0x" }),
184+
});
185+
} else {
186+
transactionHash = await sendTransaction(wagmiConfig, {
187+
to: testTransferTarget,
188+
value: parseEther("0.1"),
189+
});
190+
}
191+
192+
// FIXME: When not using sessions, sendTransaction returns a map and not a string
193+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
194+
if ((transactionHash as any).value !== undefined) {
195+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
196+
transactionHash = (transactionHash as any).value;
197+
}
198+
199+
const receipt = await waitForTransactionReceipt(wagmiConfig, {
200+
hash: transactionHash,
201+
});
150202
balance.value = await getBalance(wagmiConfig, {
151203
address: address.value,
152204
});
153-
154-
const receipt = await waitForTransactionReceipt(wagmiConfig, { hash: transactionHash });
155205
if (receipt.status === "reverted") throw new Error("Transaction reverted");
156206
} catch (error) {
157207
// eslint-disable-next-line no-console
@@ -162,6 +212,10 @@ const sendTokens = async () => {
162212
// eslint-disable-next-line @typescript-eslint/no-explicit-any
163213
transactionFailureDetails = (error as any).cause?.cause?.data?.originalError?.cause?.details;
164214
}
215+
if (!transactionFailureDetails) {
216+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
217+
transactionFailureDetails = (error as any).cause?.details;
218+
}
165219
166220
if (transactionFailureDetails) {
167221
errorMessage.value = transactionFailureDetails;

examples/demo-app/project.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"prefix": "Demo-App:"
1818
}
1919
]
20-
}
20+
},
21+
"dependsOn": ["deploy-contracts"]
2122
},
2223
"build": {
2324
"executor": "nx:run-commands",
@@ -28,8 +29,24 @@
2829
"command": "pnpm nuxt generate"
2930
}
3031
]
32+
},
33+
"dependsOn": ["deploy-contracts"]
34+
},
35+
"build-contracts": {
36+
"executor": "nx:run-commands",
37+
"options": {
38+
"cwd": "examples/demo-app",
39+
"command": "forge build smart-contracts/DemoPaymaster.sol --root . --zksync"
3140
}
3241
},
42+
"deploy-contracts": {
43+
"executor": "nx:run-commands",
44+
"options": {
45+
"cwd": "examples/demo-app",
46+
"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"
47+
},
48+
"dependsOn": ["build-contracts"]
49+
},
3350
"build:local": {
3451
"executor": "nx:run-commands",
3552
"options": {
@@ -60,7 +77,8 @@
6077
"options": {
6178
"cwd": "examples/demo-app",
6279
"command": "pnpm exec playwright install chromium"
63-
}
80+
},
81+
"dependsOn": ["deploy-contracts"]
6482
},
6583
"e2e": {
6684
"executor": "nx:run-commands",
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
5+
/// !!! !!!
6+
/// !!! THIS IS FOR DEMO PURPOSES ONLY !!!
7+
/// !!! !!!
8+
/// !!! DO NOT COPY THIS PAYMASTER !!!
9+
/// !!! FOR PRODUCTION APPLICATIONS !!!
10+
/// !!! !!!
11+
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
import { IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol";
14+
import { IPaymasterFlow } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol";
15+
import { TransactionHelper, Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol";
16+
17+
import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";
18+
19+
/// @author Matter Labs
20+
/// @notice DO NOT USE THIS FOR PRODUCTION. This contract does not include any validations other than using the paymaster general flow.
21+
contract DemoPaymaster is IPaymaster {
22+
modifier onlyBootloader() {
23+
require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Only bootloader can call this method");
24+
// Continue execution if called from the bootloader.
25+
_;
26+
}
27+
28+
function validateAndPayForPaymasterTransaction(
29+
bytes32,
30+
bytes32,
31+
Transaction calldata _transaction
32+
) external payable onlyBootloader returns (bytes4 magic, bytes memory context) {
33+
// By default we consider the transaction as accepted.
34+
magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC;
35+
require(_transaction.paymasterInput.length >= 4, "The standard paymaster input must be at least 4 bytes long");
36+
37+
bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]);
38+
require(paymasterInputSelector == IPaymasterFlow.general.selector, "Unsupported paymaster flow");
39+
40+
// Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit,
41+
// neither paymaster nor account are allowed to access this context variable.
42+
uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas;
43+
44+
// The bootloader never returns any data, so it can safely be ignored here.
45+
(bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ value: requiredETH }("");
46+
require(success, "Failed to transfer tx fee to the Bootloader. Paymaster balance might not be enough.");
47+
}
48+
49+
function postTransaction(
50+
bytes calldata _context,
51+
Transaction calldata _transaction,
52+
bytes32,
53+
bytes32,
54+
ExecutionResult _txResult,
55+
uint256 _maxRefundedGas
56+
) external payable override onlyBootloader {}
57+
58+
function withdraw(address payable _to) external {
59+
uint256 balance = address(this).balance;
60+
(bool success, ) = _to.call{ value: balance }("");
61+
require(success, "Failed to withdraw funds from paymaster.");
62+
}
63+
64+
receive() external payable {}
65+
}

0 commit comments

Comments
 (0)