diff --git a/examples/demo-app/scripts/deploy-msa-anvil.sh b/examples/demo-app/scripts/deploy-msa-anvil.sh index f8710e7d..6837e496 100755 --- a/examples/demo-app/scripts/deploy-msa-anvil.sh +++ b/examples/demo-app/scripts/deploy-msa-anvil.sh @@ -44,7 +44,8 @@ FACTORY=$(echo "$DEPLOY_OUTPUT" | grep "MSAFactory:" | awk '{print $2}') echo "" echo "📦 Deploying MockPaymaster..." cd "$CONTRACTS_DIR" -PAYMASTER_OUTPUT=$(forge create test/mocks/MockPaymaster.sol:MockPaymaster --rpc-url "$RPC_URL" --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --broadcast 2>&1) +ANVIL_ACCOUNT_0_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +PAYMASTER_OUTPUT=$(forge create test/mocks/MockPaymaster.sol:MockPaymaster --rpc-url "$RPC_URL" --private-key "$ANVIL_ACCOUNT_0_KEY" --broadcast 2>&1) echo "$PAYMASTER_OUTPUT" PAYMASTER=$(echo "$PAYMASTER_OUTPUT" | grep "Deployed to:" | awk '{print $3}') @@ -53,7 +54,6 @@ echo "MockPaymaster deployed to: $PAYMASTER" # Fund the paymaster with ETH from Anvil account #0 (has plenty of ETH) echo "" echo "💰 Funding paymaster with 10 ETH..." -ANVIL_ACCOUNT_0_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" cast send "$PAYMASTER" --value 10ether --private-key "$ANVIL_ACCOUNT_0_KEY" --rpc-url "$RPC_URL" 2>&1 || echo "Fund transfer initiated" # Deposit the paymaster's ETH into the EntryPoint diff --git a/packages/auth-server/components/PrividiumLogin.vue b/packages/auth-server/components/PrividiumLogin.vue index 42282821..ee07ff40 100644 --- a/packages/auth-server/components/PrividiumLogin.vue +++ b/packages/auth-server/components/PrividiumLogin.vue @@ -1,7 +1,7 @@ diff --git a/packages/auth-server/project.json b/packages/auth-server/project.json index 73f31301..c8027b2d 100644 --- a/packages/auth-server/project.json +++ b/packages/auth-server/project.json @@ -11,14 +11,6 @@ }, "dependsOn": ["^build", "demo-app:deploy-contracts-erc4337"] }, - "dev:with-api": { - "executor": "nx:run-commands", - "options": { - "cwd": "packages/auth-server", - "commands": ["pnpm run copy:snarkjs && PORT=3002 nuxt dev", "pnpm nx run auth-server-api:dev"] - }, - "dependsOn": ["^build"] - }, "dev:no-deploy": { "executor": "nx:run-commands", "options": { diff --git a/packages/auth-server/stores/client.ts b/packages/auth-server/stores/client.ts index c0e0894c..ff4e9862 100644 --- a/packages/auth-server/stores/client.ts +++ b/packages/auth-server/stores/client.ts @@ -134,10 +134,22 @@ export const useClientStore = defineStore("client", () => { const contracts = contractsByChain[chainId]; const publicClient = getPublicClient({ chainId }); + // In prividium mode, use prividium transport for bundler as well + // Get transport from existing prividium instance - it auto-routes bundler methods + const bundlerTransport = runtimeConfig.public.prividiumMode + ? (() => { + const prividiumTransport = prividiumAuthStore.getTransport(); + if (!prividiumTransport) { + throw new Error("Prividium transport not available. User may need to authenticate."); + } + return prividiumTransport; + })() + : http(contracts.bundlerUrl || "http://localhost:4337"); + return createBundlerClient({ client: publicClient, chain, - transport: http(contracts.bundlerUrl || "http://localhost:4337"), + transport: bundlerTransport, userOperation: { async estimateFeesPerGas() { const feesPerGas = await publicClient.estimateFeesPerGas(); @@ -160,7 +172,7 @@ export const useClientStore = defineStore("client", () => { const contracts = contractsByChain[chainId]; const bundlerClient = getBundlerClient({ chainId }); - const finalPaymasterAddress = paymasterAddress ?? (usePaymaster ? contracts.testPaymaster : undefined); + const finalPaymasterAddress = paymasterAddress as Address | undefined ?? (usePaymaster ? contracts.testPaymaster : undefined); const client = createPasskeyClient({ account: { diff --git a/packages/auth-server/utils/constructReturn.ts b/packages/auth-server/utils/constructReturn.ts index 7da42c6e..7def76f2 100644 --- a/packages/auth-server/utils/constructReturn.ts +++ b/packages/auth-server/utils/constructReturn.ts @@ -1,7 +1,21 @@ import type { SessionSpec } from "zksync-sso-4337/client"; import type { AuthServerRpcSchema, ExtractReturnType } from "zksync-sso-4337/client-auth-server"; -export const constructReturn = (address: `0x${string}`, chainId: number, session?: { sessionKey: `0x${string}`; sessionConfig: SessionSpec }): ExtractReturnType<"eth_requestAccounts", AuthServerRpcSchema> => { +type ConstructReturnOptions = { + address: `0x${string}`; + chainId: number; + session?: { sessionKey: `0x${string}`; sessionConfig: SessionSpec }; + prividiumMode: boolean; + prividiumProxyUrl: string; +}; + +export const constructReturn = ({ + address, + chainId, + session, + prividiumMode, + prividiumProxyUrl, +}: ConstructReturnOptions): ExtractReturnType<"eth_requestAccounts", AuthServerRpcSchema> => { return { account: { address, @@ -22,7 +36,10 @@ export const constructReturn = (address: `0x${string}`, chainId: number, session }, }, contracts: contractsByChain[chain.id], - bundlerUrl: contractsByChain[chain.id].bundlerUrl, + bundlerUrl: prividiumMode + ? prividiumProxyUrl + : contractsByChain[chain.id].bundlerUrl || "", + prividiumMode, })), }; }; diff --git a/packages/sdk-4337/src/client-auth-server/Signer.ts b/packages/sdk-4337/src/client-auth-server/Signer.ts index 705867c0..e75dc667 100644 --- a/packages/sdk-4337/src/client-auth-server/Signer.ts +++ b/packages/sdk-4337/src/client-auth-server/Signer.ts @@ -1,4 +1,4 @@ -import { type Address, type Chain, createPublicClient, createWalletClient, custom, type Hash, http, type RpcSchema as RpcSchemaGeneric, type SendTransactionParameters, type Transport, type WalletClient } from "viem"; +import { type Address, type Chain, createPublicClient, createWalletClient, custom, type Hash, http, type RpcSchema as RpcSchemaGeneric, type SendTransactionParameters, toHex, type Transport, type WalletClient } from "viem"; import { type BundlerClient, createBundlerClient } from "viem/account-abstraction"; import type { PaymasterConfig } from "../actions/sendUserOperation.js"; @@ -143,9 +143,8 @@ export class Signer implements SignerInterface { return this.bundlerClients[chainId]; } - // Try to create bundler client from chainsInfo if bundlerUrl is available const chainInfo = this.chainsInfo.find((c) => c.id === chainId); - if (!chainInfo?.bundlerUrl) { + if (!chainInfo) { return undefined; } @@ -154,6 +153,16 @@ export class Signer implements SignerInterface { return undefined; } + // In prividium mode, use transport from constructor; otherwise use bundlerUrl + const bundlerTransport = chainInfo.prividiumMode + ? this.transports[chainId] + : http(chainInfo.bundlerUrl); + + if (!bundlerTransport) { + console.error(`Prividium mode requires a transport for chain ${chainId}`); + return undefined; + } + const publicClient = createPublicClient({ chain, transport: this.transports[chain.id] || http(), @@ -162,7 +171,7 @@ export class Signer implements SignerInterface { this.bundlerClients[chain.id] = createBundlerClient({ client: publicClient, chain, - transport: http(chainInfo.bundlerUrl), + transport: bundlerTransport, userOperation: { // Use fixed gas values matching old Rust SDK implementation // (old SDK used: 2M callGas, 2M verificationGas, 1M preVerificationGas) @@ -333,6 +342,9 @@ export class Signer implements SignerInterface { case "eth_accounts": { return this.accounts as ExtractReturnType; } + case "eth_chainId": { + return toHex(this.chain.id) as ExtractReturnType; + } default: return undefined; } diff --git a/packages/sdk-4337/src/client-auth-server/rpc.ts b/packages/sdk-4337/src/client-auth-server/rpc.ts index 91a9a816..09f090de 100644 --- a/packages/sdk-4337/src/client-auth-server/rpc.ts +++ b/packages/sdk-4337/src/client-auth-server/rpc.ts @@ -28,7 +28,8 @@ export type AuthServerRpcSchema = [ id: Chain["id"]; capabilities: Record; contracts: SessionRequiredContracts; - bundlerUrl?: string; + bundlerUrl: string; + prividiumMode: boolean; }[]; }; }, diff --git a/packages/sdk-4337/src/connector/index.ts b/packages/sdk-4337/src/connector/index.ts index 3ac8906b..cffd771e 100644 --- a/packages/sdk-4337/src/connector/index.ts +++ b/packages/sdk-4337/src/connector/index.ts @@ -8,6 +8,9 @@ import { } from "@wagmi/core"; import type { Compute } from "@wagmi/core/internal"; import { + type Abi, + type AbiFunction, + type AbiStateMutability, type Address, type Client, getAddress, @@ -18,7 +21,7 @@ import { import type { BundlerClient } from "viem/account-abstraction"; import type { PaymasterConfig } from "../actions/sendUserOperation.js"; -import type { SessionClient, SessionPreferences } from "../client/index.js"; +import type { PartialCallPolicy, SessionClient, SessionPreferences } from "../client/index.js"; import type { ProviderInterface } from "../client-auth-server/index.js"; import { WalletProvider } from "../client-auth-server/WalletProvider.js"; import type { AppMetadata, Communicator } from "../communicator/interface.js"; @@ -42,6 +45,30 @@ export type ZksyncSsoConnectorOptions = { paymaster?: Address | PaymasterConfig; }; +function filterContractCallsAbi(contractCalls: PartialCallPolicy[]): PartialCallPolicy[] { + const allowedStateMutability: AbiStateMutability[] = ["nonpayable", "payable"]; + + return contractCalls.map((call) => { + const matchingFunction = (call.abi as Abi).find( + (item): item is AbiFunction => + item.type === "function" + && item.name === call.functionName + && allowedStateMutability.includes(item.stateMutability), + ); + + if (!matchingFunction) { + throw new Error( + `Function "${call.functionName}" not found in ABI for contract ${call.address}. Only nonpayable/payable functions are allowed.`, + ); + } + + return { + ...call, + abi: [matchingFunction], + }; + }); +} + export const zksyncSsoConnector = (parameters: ZksyncSsoConnectorOptions) => { let walletProvider: ProviderInterface | undefined; @@ -160,6 +187,29 @@ export const zksyncSsoConnector = (parameters: ZksyncSsoConnectorOptions) => { } } + // Process session to filter ABI to only the specified function + let processedSession = parameters.session; + if (parameters.session) { + if (typeof parameters.session === "function") { + const originalSessionFn = parameters.session; + processedSession = async () => { + const sessionConfig = await originalSessionFn(); + if (sessionConfig.contractCalls) { + return { + ...sessionConfig, + contractCalls: filterContractCallsAbi(sessionConfig.contractCalls), + }; + } + return sessionConfig; + }; + } else if (parameters.session.contractCalls) { + processedSession = { + ...parameters.session, + contractCalls: filterContractCallsAbi(parameters.session.contractCalls), + }; + } + } + walletProvider = parameters.provider ?? new WalletProvider({ metadata: { name: parameters.metadata?.name, @@ -167,7 +217,7 @@ export const zksyncSsoConnector = (parameters: ZksyncSsoConnectorOptions) => { configData: parameters.metadata?.configData, }, authServerUrl: parameters.authServerUrl, - session: parameters.session, + session: processedSession, transports: config.transports, bundlerClients: parameters.bundlerClients, chains: config.chains, @@ -257,11 +307,13 @@ export const isSsoSessionClientConnected = async< try { // Check if this is a ZKsync SSO connector with our custom _getClient method + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (typeof (connection.connector as any)._getClient !== "function") { return false; } // Use the custom _getClient method to get our custom client + // eslint-disable-next-line @typescript-eslint/no-explicit-any const client = await (connection.connector as any)._getClient(parameters); return isSsoSessionClient(client); } catch { @@ -284,10 +336,12 @@ export const getConnectedSsoSessionClient = async< } // Check if this is a ZKsync SSO connector with our custom _getClient method + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (typeof (connection.connector as any)._getClient !== "function") { throw new Error("Connector does not support getClient method. Make sure you're using the ZKsync SSO connector."); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const client = await (connection.connector as any)._getClient(parameters); if (!isSsoSessionClient(client)) { @@ -297,3 +351,6 @@ export const getConnectedSsoSessionClient = async< const sessionClient = client as unknown as GetConnectedSsoClientReturnType; return sessionClient; }; + +// Re-export callPolicy utility for convenient access +export { callPolicy } from "../client/index.js";