Skip to content

Commit a514699

Browse files
committed
fix: update sdk to help with session usage
1 parent a7f17a2 commit a514699

File tree

16 files changed

+451
-206
lines changed

16 files changed

+451
-206
lines changed

examples/nft-quest/composables/useMintNft.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,35 @@ export const useMintNft = async (_address: MaybeRef<Address>) => {
99
const { wagmiConfig } = storeToRefs(useConnectorStore());
1010

1111
const mintingForAddress = address.value;
12-
// Paymaster is configured at connector level, no need to specify here
13-
const transactionHash = await writeContract(wagmiConfig.value, {
14-
address: runtimeConfig.public.contracts.nft as Address,
15-
abi: nftAbi,
16-
functionName: "mint",
17-
args: [mintingForAddress],
18-
});
12+
console.log("[useMintNft] Starting mint for address:", mintingForAddress);
13+
console.log("[useMintNft] NFT contract:", runtimeConfig.public.contracts.nft);
1914

20-
const transactionReceipt = await waitForTransactionReceipt(wagmiConfig.value, { hash: transactionHash });
21-
if (transactionReceipt.status === "reverted") {
22-
throw new Error("Transaction reverted");
23-
}
15+
try {
16+
// Paymaster is configured at connector level, no need to specify here
17+
console.log("[useMintNft] Calling writeContract...");
18+
const transactionHash = await writeContract(wagmiConfig.value, {
19+
address: runtimeConfig.public.contracts.nft as Address,
20+
abi: nftAbi,
21+
functionName: "mint",
22+
args: [mintingForAddress],
23+
});
24+
console.log("[useMintNft] Transaction hash:", transactionHash);
25+
26+
console.log("[useMintNft] Waiting for transaction receipt...");
27+
const transactionReceipt = await waitForTransactionReceipt(wagmiConfig.value, { hash: transactionHash });
28+
console.log("[useMintNft] Transaction status:", transactionReceipt.status);
2429

25-
return transactionReceipt;
30+
if (transactionReceipt.status === "reverted") {
31+
console.error("[useMintNft] Transaction reverted!");
32+
throw new Error("Transaction reverted");
33+
}
34+
35+
console.log("[useMintNft] Mint successful!");
36+
return transactionReceipt;
37+
} catch (error) {
38+
console.error("[useMintNft] Error during mint:", error);
39+
throw error;
40+
}
2641
}, {
2742
server: false,
2843
immediate: false,

examples/nft-quest/nuxt.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
import { readFileSync } from "node:fs";
2+
import { fileURLToPath } from "node:url";
3+
14
import { defineNuxtConfig } from "nuxt/config";
25
import { defineChain } from "viem";
36
import { localhost } from "viem/chains";
47
import wasm from "vite-plugin-wasm";
58

6-
import contracts from "./public/contracts.json";
9+
// Read contracts.json dynamically at config evaluation time to ensure
10+
// freshly deployed addresses are always picked up (avoids module cache)
11+
const contracts = JSON.parse(
12+
readFileSync(fileURLToPath(new URL("./public/contracts.json", import.meta.url)), "utf-8"),
13+
);
714

815
// TODO: Deploy NFT Quest to ZKsync OS Testnet and update contract addresses
916
const zksyncOsTestnet = defineChain({

examples/nft-quest/pages/mint/index.vue

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,42 @@
2626
:delay="50"
2727
class="inline"
2828
>
29-
<p class="mt-8 text-neutral-400 max-w-prose">
30-
Your authorized session for the NFT quest is now active! You can mint your NFT without any additional transaction approvals.
31-
</p>
32-
<p class="mt-8 text-neutral-400 max-w-prose">
33-
Also, it's free. ZKsync can leverage paymasters to enable app developers to choose a custom gas token or even entirely sponsor gas fees for their users.
34-
</p>
35-
<div class="sticky bottom-[-8px] pb-4 bg-gradient-to-t from-black to-transparent w-full flex justify-center sm:justify-start">
36-
<ZkButton
37-
type="primary"
38-
class="uppercase mt-8"
39-
:loading="status === 'pending'"
40-
@click="mintNFT"
41-
>
42-
Mint 100% free NFT
43-
</ZkButton>
44-
</div>
29+
<!-- Show session setup prompt if no session is active -->
30+
<template v-if="!hasSession">
31+
<p class="mt-8 text-neutral-400 max-w-prose">
32+
To mint NFTs without popup approvals, you need to authorize a session. This allows the app to perform transactions on your behalf within the specified limits.
33+
</p>
34+
<div class="sticky bottom-[-8px] pb-4 bg-gradient-to-t from-black to-transparent w-full flex justify-center sm:justify-start">
35+
<ZkButton
36+
type="primary"
37+
class="uppercase mt-8"
38+
data-testid="add-session"
39+
@click="addSession"
40+
>
41+
Add Session
42+
</ZkButton>
43+
</div>
44+
</template>
45+
46+
<!-- Show mint UI when session is active -->
47+
<template v-else>
48+
<p class="mt-8 text-neutral-400 max-w-prose">
49+
Your authorized session for the NFT quest is now active! You can mint your NFT without any additional transaction approvals.
50+
</p>
51+
<p class="mt-8 text-neutral-400 max-w-prose">
52+
Also, it's free. ZKsync can leverage paymasters to enable app developers to choose a custom gas token or even entirely sponsor gas fees for their users.
53+
</p>
54+
<div class="sticky bottom-[-8px] pb-4 bg-gradient-to-t from-black to-transparent w-full flex justify-center sm:justify-start">
55+
<ZkButton
56+
type="primary"
57+
class="uppercase mt-8"
58+
:loading="status === 'pending'"
59+
@click="mintNFT"
60+
>
61+
Mint 100% free NFT
62+
</ZkButton>
63+
</div>
64+
</template>
4565

4666
<p
4767
v-if="status === 'error'"
@@ -54,9 +74,18 @@
5474
</template>
5575

5676
<script setup lang="ts">
57-
const { account } = storeToRefs(useConnectorStore());
77+
const { account, hasSession } = storeToRefs(useConnectorStore());
78+
const { connectWithSession } = useConnectorStore();
5879
const { error: mintNFTError, execute: mintNFT, status, data } = await useMintNft(computed(() => account.value.address!));
5980
81+
const addSession = async () => {
82+
try {
83+
await connectWithSession();
84+
} catch (error) {
85+
console.error("Failed to add session:", error);
86+
}
87+
};
88+
6089
watch(status, (status) => {
6190
if (status === "success") {
6291
navigateTo({ path: "mint/share", query: { tx: data.value!.transactionHash } });

examples/nft-quest/playwright.config.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,14 @@ export default defineConfig({
4444
],
4545

4646
/* Run your local server before starting the tests */
47-
/* IMPORTANT: Order matters! Contracts must be deployed BEFORE auth-server-api starts,
48-
* because auth-server-api reads contract addresses from contracts.json at startup.
47+
/* IMPORTANT: Contracts must already be deployed BEFORE these servers start.
48+
* The nft-quest:e2e NX target depends on nft-quest-contracts:deploy:local,
49+
* which ensures contracts are deployed before Playwright launches.
4950
* Playwright starts webServers sequentially and waits for each URL before starting the next.
5051
*/
5152
webServer: [
5253
{
53-
// Step 1: Deploy all contracts first (creates/updates contracts.json)
54-
// This deploys MSA factory, validators, paymaster, and NFT contract
55-
// The "server" is just a simple echo to satisfy playwright's URL check
56-
command: "pnpm nx deploy:local nft-quest-contracts && echo 'Contracts deployed' && node -e \"require('http').createServer((req,res)=>{res.writeHead(200);res.end('ok')}).listen(3099)\"",
57-
url: "http://localhost:3099",
58-
reuseExistingServer: !process.env.CI,
59-
stdout: "pipe",
60-
stderr: "pipe",
61-
timeout: 180_000,
62-
},
63-
{
64-
// Step 2: Start auth-server-api (reads fresh contracts.json)
54+
// Step 1: Start auth-server-api (reads fresh contracts.json)
6555
// Run directly with node to ensure PORT env var is passed correctly
6656
command: "cd ../../packages/auth-server-api && PORT=3004 node --experimental-wasm-modules --import tsx src/index.ts",
6757
url: "http://localhost:3004/api/health",
@@ -71,19 +61,21 @@ export default defineConfig({
7161
timeout: 180_000,
7262
},
7363
{
74-
// Step 3: Start auth-server (UI for account creation)
75-
// Use dev:nuxt-only since we start auth-server-api separately in step 2
76-
command: "NUXT_PUBLIC_AUTH_SERVER_API_URL=http://localhost:3004 pnpm nx dev:nuxt-only auth-server",
64+
// Step 2: Start auth-server (UI for account creation)
65+
// Use dev:nuxt-only since we start auth-server-api separately in step 1
66+
// Clean .nuxt cache first to ensure fresh config from newly deployed contracts
67+
command: "rm -rf ../../packages/auth-server/.nuxt && NUXT_PUBLIC_AUTH_SERVER_API_URL=http://localhost:3004 pnpm nx dev:nuxt-only auth-server",
7768
url: "http://localhost:3002",
7869
reuseExistingServer: !process.env.CI,
7970
stdout: "pipe",
8071
stderr: "pipe",
8172
timeout: 180_000,
8273
},
8374
{
84-
// Step 4: Start nft-quest dev server (contracts already deployed in step 1)
85-
command: "PORT=3006 pnpm nuxt dev",
86-
cwd: "../../examples/nft-quest",
75+
// Step 3: Start nft-quest dev server (contracts already deployed via NX dependency)
76+
// Clean .nuxt and Vite cache to force fresh config resolution from
77+
// the newly written contracts.json and .env.local (avoids stale cached addresses)
78+
command: "rm -rf .nuxt node_modules/.cache/vite && PORT=3006 pnpm nuxt dev",
8779
url: "http://localhost:3006",
8880
reuseExistingServer: !process.env.CI,
8981
stdout: "pipe",

examples/nft-quest/public/contracts.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"chainId": 1337,
44
"entryPoint": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
55
"bundlerUrl": "http://localhost:4337",
6-
"factory": "0x7Fca2E690a627078804E4d39306a025abb49daf2",
7-
"eoaValidator": "0xEEb3c421c5EE1e6dDD5e58D6BBe3fA3Fc4E61c83",
8-
"sessionValidator": "0x599100081e5327389384217e5Dc5930a669E9Da2",
9-
"webauthnValidator": "0x2dfBD9Aa24784C4a984B940B28eb0e6CB43C4878",
10-
"guardianExecutor": "0x1122b500785804EA139a302395B6E7D1EebCC8e5",
11-
"testPaymaster": "0xF10685a7b083BA60D1467b15Cb83E8bFc28b5961",
12-
"mockPaymaster": "0xF10685a7b083BA60D1467b15Cb83E8bFc28b5961",
13-
"nftContract": "0x86F55c9967575F1AaE5Ce961058EB902C023171b"
6+
"factory": "0xb2E0e93B20442b80912DBA3b74De4A5718eBd359",
7+
"eoaValidator": "0x3a63639F3B216461Cbd39Cc7ccfE5d3bD0D34d90",
8+
"sessionValidator": "0xE9f92D2EB5d8Dcd38c96D94BCA1854c71c475383",
9+
"webauthnValidator": "0x3dF16B0d1a113f5E2B4810419a749a3d966a9dAc",
10+
"guardianExecutor": "0x46245F52Fc7037D219354BbBF5b4dF560119052c",
11+
"testPaymaster": "0x1483801ed608979f6c1640ECE216c2B9E394cbeA",
12+
"mockPaymaster": "0x1483801ed608979f6c1640ECE216c2B9E394cbeA",
13+
"nftContract": "0xc3Ee0A87e6526e2120698710bAb650B33504d950"
1414
}

examples/nft-quest/stores/connector.ts

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,59 @@
11
import { connect, createConfig, type CreateConnectorFn, disconnect, getAccount, http, reconnect, watchAccount } from "@wagmi/core";
22
import { localhost } from "@wagmi/core/chains";
3-
import type { Address } from "viem";
3+
import { type Address, parseEther } from "viem";
44
import { zksyncSsoConnector } from "zksync-sso-4337/connector";
55

6+
import { ZeekNftQuestAbi } from "@/abi/ZeekNFTQuest";
7+
68
export const useConnectorStore = defineStore("connector", () => {
79
const runtimeConfig = useRuntimeConfig();
10+
console.log("[ConnectorStore] runtimeConfig.public.contracts:", JSON.stringify(runtimeConfig.public.contracts));
11+
console.log("[ConnectorStore] paymaster from runtimeConfig:", runtimeConfig.public.contracts.paymaster);
812
const supportedChains = [localhost] as const;
913
const chain = supportedChains.filter((x) => x.id == runtimeConfig.public.chain.id)[0];
1014
type SupportedChainId = (typeof supportedChains)[number]["id"];
1115
if (!chain) throw new Error(`Chain with id ${runtimeConfig.public.chain.id} was not found in supported chains list`);
1216

13-
const connector = zksyncSsoConnector({
17+
// Session state - tracks whether a session has been added
18+
const hasSession = ref(false);
19+
20+
// Session configuration for NFT minting
21+
const sessionConfig = {
22+
// Set a reasonable fee limit for the session
23+
feeLimit: parseEther("0.1"),
24+
// Allow calls to the NFT contract
25+
contractCalls: [
26+
{
27+
address: runtimeConfig.public.contracts.nft as Address,
28+
abi: ZeekNftQuestAbi,
29+
// No functionName = allow all functions (mint, safeTransferFrom, etc.)
30+
},
31+
],
32+
};
33+
34+
// Connector WITHOUT session - for initial account creation
35+
const connectorWithoutSession = zksyncSsoConnector({
36+
metadata: {
37+
icon: `${runtimeConfig.public.baseUrl}/icon-192.png`,
38+
},
39+
authServerUrl: runtimeConfig.public.authServerUrl,
40+
// No session config - just create account
41+
paymaster: runtimeConfig.public.contracts.paymaster as Address,
42+
});
43+
44+
// Connector WITH session - for adding session to existing account
45+
const connectorWithSession = zksyncSsoConnector({
1446
metadata: {
1547
icon: `${runtimeConfig.public.baseUrl}/icon-192.png`,
1648
},
1749
authServerUrl: runtimeConfig.public.authServerUrl,
18-
// Session disabled for now - TODO: re-enable after getting basic flow working
19-
// Each transaction will open an approval popup, but paymaster still pays fees
20-
// session: {
21-
// feeLimit: {
22-
// limitType: "unlimited",
23-
// limit: 0n, // Unlimited fees
24-
// },
25-
// contractCalls: [
26-
// {
27-
// address: runtimeConfig.public.contracts.nft as Address,
28-
// abi: ZeekNftQuestAbi,
29-
// // Remove functionName to allow all functions
30-
// valueLimit: {
31-
// limitType: "unlimited",
32-
// limit: 0n, // Unlimited value
33-
// },
34-
// },
35-
// ],
36-
// },
50+
session: sessionConfig,
3751
paymaster: runtimeConfig.public.contracts.paymaster as Address,
3852
});
53+
3954
const wagmiConfig = createConfig({
4055
chains: [chain],
41-
connectors: [connector as CreateConnectorFn],
56+
connectors: [connectorWithoutSession as CreateConnectorFn, connectorWithSession as CreateConnectorFn],
4257
transports: (Object.fromEntries(supportedChains.map((chain) => [chain.id, http()]))) as Record<SupportedChainId, ReturnType<typeof http>>,
4358
});
4459

@@ -53,11 +68,27 @@ export const useConnectorStore = defineStore("connector", () => {
5368
},
5469
});
5570

71+
// Connect without session - for initial account creation
5672
const connectAccount = async () => {
57-
return await connect(wagmiConfig, {
58-
connector,
73+
const result = await connect(wagmiConfig, {
74+
connector: connectorWithoutSession,
75+
chainId: chain.id,
76+
});
77+
return result;
78+
};
79+
80+
// Connect with session - for adding session to existing account
81+
// Must disconnect first since wagmi won't allow a new connection when already connected
82+
const connectWithSession = async () => {
83+
// Disconnect from the current connector (without session)
84+
await disconnect(wagmiConfig);
85+
// Connect with the session-enabled connector
86+
const result = await connect(wagmiConfig, {
87+
connector: connectorWithSession,
5988
chainId: chain.id,
6089
});
90+
hasSession.value = true;
91+
return result;
6192
};
6293

6394
const disconnectAccount = () => {
@@ -78,7 +109,9 @@ export const useConnectorStore = defineStore("connector", () => {
78109
wagmiConfig: computed(() => wagmiConfig),
79110
account: computed(() => account.value),
80111
isConnected,
112+
hasSession: computed(() => hasSession.value),
81113
connectAccount,
114+
connectWithSession,
82115
disconnectAccount,
83116
address$,
84117
shortAddress,

0 commit comments

Comments
 (0)