From d527d06536a260e059a43d8198b5fe6e7b7b17f4 Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Tue, 26 Nov 2024 17:09:24 +0200 Subject: [PATCH 1/7] fix: typed call policy, with improved constraint index logic --- README.md | 38 +- examples/nft-quest/abi/ZeekNFTQuest.ts | 427 ++++++++++++++++ examples/nft-quest/stores/connector.ts | 19 +- .../views/confirmation/RequestSession.vue | 5 +- packages/sdk/README.md | 40 +- packages/sdk/src/abi/SessionKeyModule.ts | 480 ------------------ packages/sdk/src/client-auth-server/Signer.ts | 2 +- .../src/client-auth-server/WalletProvider.ts | 2 +- packages/sdk/src/client-auth-server/index.ts | 2 +- packages/sdk/src/client-auth-server/rpc.ts | 2 +- .../sdk/src/client-auth-server/session.ts | 139 ----- .../src/client-auth-server/session/index.ts | 222 ++++++++ .../client-auth-server/session/type-utils.ts | 14 + .../src/client-auth-server/session/utils.ts | 115 +++++ packages/sdk/src/client/clients/passkey.ts | 2 +- packages/sdk/src/client/clients/session.ts | 2 +- packages/sdk/src/connector/index.ts | 50 ++ packages/sdk/src/index.ts | 2 +- 18 files changed, 879 insertions(+), 684 deletions(-) create mode 100644 examples/nft-quest/abi/ZeekNFTQuest.ts delete mode 100644 packages/sdk/src/client-auth-server/session.ts create mode 100644 packages/sdk/src/client-auth-server/session/index.ts create mode 100644 packages/sdk/src/client-auth-server/session/type-utils.ts create mode 100644 packages/sdk/src/client-auth-server/session/utils.ts diff --git a/README.md b/README.md index 6cbded1f..2619bdf0 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,10 @@ npm i zksync-sso Add ZKsync SSO connector to your app (using `wagmi`): ```ts +import { zksyncSsoConnector, callPolicy } from "zksync-sso/connector"; import { zksyncSepoliaTestnet } from "viem/chains"; import { createConfig, connect } from "@wagmi/core"; -import { zksyncSsoConnector } from "zksync-sso/connector"; +import { erc20Abi } from "viem"; const ssoConnector = zksyncSsoConnector({ // Optional session configuration, if omitted user will have to sign every transaction via Auth Server @@ -51,41 +52,32 @@ const ssoConnector = zksyncSsoConnector({ to: "0x188bd99cd7D4d78d4E605Aeea12C17B32CC3135A", valueLimit: parseEther("0.1"), }, - - // Allow ETH transfers to specific address with a limit of 0.1 ETH per hour - // until the session expires - { - to: "0x188bd99cd7D4d78d4E605Aeea12C17B32CC3135A", - valueLimit: { - limit: parseEther("0.1"), - period: BigInt(60 * 60), // 1 hour in seconds - }, - }, ], // Allow calling specific smart contracts (e.g. ERC20 transfer): contractCalls: [ - { + callPolicy({ address: "0xa1cf087DB965Ab02Fb3CFaCe1f5c63935815f044", - function: "transfer(address,uint256)", - - // Optional call constraints (unconstrained otherwise): + abi: erc20Abi, + functionName: "transfer", constraints: [ - - // Only allow transfers to this address + // Only allow transfers to this address, any address if omitted { - index: 0, - condition: "Equal", - refValue: pad("0x6cC8cf7f6b488C58AA909B77E6e65c631c204784", { size: 32 }), + index: 0, // First argument of erc20 transfer function, recipient address + value: "0x6cC8cf7f6b488C58AA909B77E6e65c631c204784", }, - // Transfer up to 0.2 tokens + // Allow transfering up to 0.2 tokens per hour + // until the session expires { index: 1, - limit: parseUnits("0.2", TOKEN.decimals), // Unlimited if omitted + limit: { + limit: parseUnits("0.2", TOKEN.decimals), + period: BigInt(60 * 60), // 1 hour in seconds + }, }, ], - }, + }), ], }, }); diff --git a/examples/nft-quest/abi/ZeekNFTQuest.ts b/examples/nft-quest/abi/ZeekNFTQuest.ts new file mode 100644 index 00000000..162d1d24 --- /dev/null +++ b/examples/nft-quest/abi/ZeekNFTQuest.ts @@ -0,0 +1,427 @@ +export const ZeekNftQuestAbi = [ + { + inputs: [ + { + internalType: "string", + name: "baseTokenURI", + type: "string", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "approved", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "getApproved", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "operator", + type: "address", + }, + ], + name: "isApprovedForAll", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + ], + name: "mint", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "ownerOf", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "operator", + type: "address", + }, + { + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "tokenURI", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "tokenURI", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/nft-quest/stores/connector.ts b/examples/nft-quest/stores/connector.ts index ef67151c..0c2b8649 100644 --- a/examples/nft-quest/stores/connector.ts +++ b/examples/nft-quest/stores/connector.ts @@ -1,7 +1,9 @@ import { connect, createConfig, type CreateConnectorFn, disconnect, getAccount, http, reconnect, watchAccount } from "@wagmi/core"; import { zksyncInMemoryNode, zksyncLocalNode, zksyncSepoliaTestnet } from "@wagmi/core/chains"; import { type Address, type Hash, parseEther } from "viem"; -import { zksyncSsoConnector } from "zksync-sso/connector"; +import { callPolicy, zksyncSsoConnector } from "zksync-sso/connector"; + +import { ZeekNftQuestAbi } from "@/abi/ZeekNftQuest"; export const useConnectorStore = defineStore("connector", () => { const runtimeConfig = useRuntimeConfig(); @@ -15,17 +17,16 @@ export const useConnectorStore = defineStore("connector", () => { if (!chain) throw new Error(`Chain with id ${runtimeConfig.public.chain.id} was not found in supported chains list`); const connector = zksyncSsoConnector({ - metadata: { - name: "ZK NFT Quest", - icon: `${runtimeConfig.public.baseUrl}/favicon.svg`, - }, authServerUrl: runtimeConfig.public.authServerUrl, session: { feeLimit: parseEther("0.001"), - contractCalls: [{ - address: runtimeConfig.public.contracts.nft as Hash, - function: "mint(address)", - }], + contractCalls: [ + callPolicy({ + address: runtimeConfig.public.contracts.nft as Hash, + abi: ZeekNftQuestAbi, + functionName: "mint", + }), + ], }, }); const wagmiConfig = createConfig({ diff --git a/packages/auth-server/components/views/confirmation/RequestSession.vue b/packages/auth-server/components/views/confirmation/RequestSession.vue index a7cb3829..ee0c7c20 100644 --- a/packages/auth-server/components/views/confirmation/RequestSession.vue +++ b/packages/auth-server/components/views/confirmation/RequestSession.vue @@ -32,7 +32,10 @@ - + diff --git a/packages/sdk/README.md b/packages/sdk/README.md index a9b8b174..fcf9121f 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -1,7 +1,6 @@ # zksync-sso SDK [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE-MIT) -[![CI](https://github.com/matter-labs/zksync-account-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/matter-labs/zksync-account-sdk/actions/workflows/ci.yml) A user & developer friendly modular smart account implementation on ZKsync; simplifying user authentication, session management, and transaction processing. @@ -9,7 +8,6 @@ simplifying user authentication, session management, and transaction processing. ## Features and Goals -> [!CAUTION] > ZKsync SSO is under active development and is not yet feature > complete. Use it to improve your development applications and tooling. Please > do not use it in production environments. @@ -35,9 +33,10 @@ npm i zksync-sso Add ZKsync SSO connector to your app (using `wagmi`): ```ts +import { zksyncSsoConnector, callPolicy } from "zksync-sso/connector"; import { zksyncSepoliaTestnet } from "viem/chains"; import { createConfig, connect } from "@wagmi/core"; -import { zksyncSsoConnector } from "zksync-sso/connector"; +import { erc20Abi } from "viem"; const ssoConnector = zksyncSsoConnector({ // Optional session configuration, @@ -52,41 +51,32 @@ const ssoConnector = zksyncSsoConnector({ to: "0x188bd99cd7D4d78d4E605Aeea12C17B32CC3135A", valueLimit: parseEther("0.1"), }, - - // Allow ETH transfers to specific address with a limit of 0.1 ETH per hour - // until the session expires - { - to: "0x188bd99cd7D4d78d4E605Aeea12C17B32CC3135A", - valueLimit: { - limit: parseEther("0.1"), - period: BigInt(60 * 60), // 1 hour in seconds - }, - }, ], // Allow calling specific smart contracts (e.g. ERC20 transfer): contractCalls: [ - { + callPolicy({ address: "0xa1cf087DB965Ab02Fb3CFaCe1f5c63935815f044", - function: "transfer(address,uint256)", - - // Optional call constraints (unconstrained otherwise): + abi: erc20Abi, + functionName: "transfer", constraints: [ - - // Only allow transfers to this address + // Only allow transfers to this address, any address if omitted { - index: 0, - condition: "Equal", - refValue: pad("0x6cC8cf7f6b488C58AA909B77E6e65c631c204784"), + index: 0, // First argument of erc20 transfer function, recipient address + value: "0x6cC8cf7f6b488C58AA909B77E6e65c631c204784", }, - // Transfer up to 0.2 tokens + // Allow transfering up to 0.2 tokens per hour + // until the session expires { index: 1, - limit: parseUnits("0.2", TOKEN.decimals), // Unlimited if omitted + limit: { + limit: parseUnits("0.2", TOKEN.decimals), + period: BigInt(60 * 60), // 1 hour in seconds + }, }, ], - }, + }), ], }, }); diff --git a/packages/sdk/src/abi/SessionKeyModule.ts b/packages/sdk/src/abi/SessionKeyModule.ts index c21f3b2b..aafae56e 100644 --- a/packages/sdk/src/abi/SessionKeyModule.ts +++ b/packages/sdk/src/abi/SessionKeyModule.ts @@ -96,133 +96,6 @@ export const SessionKeyModuleAbi = [ name: "feeLimit", type: "tuple", }, - { - components: [ - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "bytes4", - name: "selector", - type: "bytes4", - }, - { - internalType: "uint256", - name: "maxValuePerUse", - type: "uint256", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "valueLimit", - type: "tuple", - }, - { - components: [ - { - internalType: "enum SessionLib.Condition", - name: "condition", - type: "uint8", - }, - { - internalType: "uint64", - name: "index", - type: "uint64", - }, - { - internalType: "bytes32", - name: "refValue", - type: "bytes32", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "limit", - type: "tuple", - }, - ], - internalType: "struct SessionLib.Constraint[]", - name: "constraints", - type: "tuple[]", - }, - ], - internalType: "struct SessionLib.CallSpec[]", - name: "callPolicies", - type: "tuple[]", - }, - { - components: [ - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "uint256", - name: "maxValuePerUse", - type: "uint256", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "valueLimit", - type: "tuple", - }, - ], - internalType: "struct SessionLib.TransferSpec[]", - name: "transferPolicies", - type: "tuple[]", - }, ], indexed: false, internalType: "struct SessionLib.SessionSpec", @@ -307,133 +180,6 @@ export const SessionKeyModuleAbi = [ name: "feeLimit", type: "tuple", }, - { - components: [ - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "bytes4", - name: "selector", - type: "bytes4", - }, - { - internalType: "uint256", - name: "maxValuePerUse", - type: "uint256", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "valueLimit", - type: "tuple", - }, - { - components: [ - { - internalType: "enum SessionLib.Condition", - name: "condition", - type: "uint8", - }, - { - internalType: "uint64", - name: "index", - type: "uint64", - }, - { - internalType: "bytes32", - name: "refValue", - type: "bytes32", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "limit", - type: "tuple", - }, - ], - internalType: "struct SessionLib.Constraint[]", - name: "constraints", - type: "tuple[]", - }, - ], - internalType: "struct SessionLib.CallSpec[]", - name: "callPolicies", - type: "tuple[]", - }, - { - components: [ - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "uint256", - name: "maxValuePerUse", - type: "uint256", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "valueLimit", - type: "tuple", - }, - ], - internalType: "struct SessionLib.TransferSpec[]", - name: "transferPolicies", - type: "tuple[]", - }, ], internalType: "struct SessionLib.SessionSpec", name: "sessionSpec", @@ -645,19 +391,6 @@ export const SessionKeyModuleAbi = [ stateMutability: "nonpayable", type: "function", }, - { - inputs: [ - { - internalType: "bytes32[]", - name: "sessionHashes", - type: "bytes32[]", - }, - ], - name: "revokeKeys", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, { inputs: [ { @@ -699,133 +432,6 @@ export const SessionKeyModuleAbi = [ name: "feeLimit", type: "tuple", }, - { - components: [ - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "bytes4", - name: "selector", - type: "bytes4", - }, - { - internalType: "uint256", - name: "maxValuePerUse", - type: "uint256", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "valueLimit", - type: "tuple", - }, - { - components: [ - { - internalType: "enum SessionLib.Condition", - name: "condition", - type: "uint8", - }, - { - internalType: "uint64", - name: "index", - type: "uint64", - }, - { - internalType: "bytes32", - name: "refValue", - type: "bytes32", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "limit", - type: "tuple", - }, - ], - internalType: "struct SessionLib.Constraint[]", - name: "constraints", - type: "tuple[]", - }, - ], - internalType: "struct SessionLib.CallSpec[]", - name: "callPolicies", - type: "tuple[]", - }, - { - components: [ - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "uint256", - name: "maxValuePerUse", - type: "uint256", - }, - { - components: [ - { - internalType: "enum SessionLib.LimitType", - name: "limitType", - type: "uint8", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "period", - type: "uint256", - }, - ], - internalType: "struct SessionLib.UsageLimit", - name: "valueLimit", - type: "tuple", - }, - ], - internalType: "struct SessionLib.TransferSpec[]", - name: "transferPolicies", - type: "tuple[]", - }, ], internalType: "struct SessionLib.SessionSpec", name: "spec", @@ -846,87 +452,6 @@ export const SessionKeyModuleAbi = [ name: "feesRemaining", type: "uint256", }, - { - components: [ - { - internalType: "uint256", - name: "remaining", - type: "uint256", - }, - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "bytes4", - name: "selector", - type: "bytes4", - }, - { - internalType: "uint256", - name: "index", - type: "uint256", - }, - ], - internalType: "struct SessionLib.LimitState[]", - name: "transferValue", - type: "tuple[]", - }, - { - components: [ - { - internalType: "uint256", - name: "remaining", - type: "uint256", - }, - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "bytes4", - name: "selector", - type: "bytes4", - }, - { - internalType: "uint256", - name: "index", - type: "uint256", - }, - ], - internalType: "struct SessionLib.LimitState[]", - name: "callValue", - type: "tuple[]", - }, - { - components: [ - { - internalType: "uint256", - name: "remaining", - type: "uint256", - }, - { - internalType: "address", - name: "target", - type: "address", - }, - { - internalType: "bytes4", - name: "selector", - type: "bytes4", - }, - { - internalType: "uint256", - name: "index", - type: "uint256", - }, - ], - internalType: "struct SessionLib.LimitState[]", - name: "callParams", - type: "tuple[]", - }, ], internalType: "struct SessionLib.SessionState", name: "", @@ -1053,11 +578,6 @@ export const SessionKeyModuleAbi = [ name: "signature", type: "bytes", }, - { - internalType: "bytes32[]", - name: "factoryDeps", - type: "bytes32[]", - }, { internalType: "bytes", name: "paymasterInput", diff --git a/packages/sdk/src/client-auth-server/Signer.ts b/packages/sdk/src/client-auth-server/Signer.ts index d0cd9e23..abde444f 100644 --- a/packages/sdk/src/client-auth-server/Signer.ts +++ b/packages/sdk/src/client-auth-server/Signer.ts @@ -6,7 +6,7 @@ import { type SessionConfig } from "../utils/session.js"; import { StorageItem } from "../utils/storage.js"; import type { AppMetadata, RequestArguments } from "./interface.js"; import type { AuthServerRpcSchema, ExtractParams, ExtractReturnType, Method, RPCRequestMessage, RPCResponseMessage, RpcSchema } from "./rpc.js"; -import type { SessionPreferences } from "./session.js"; +import type { SessionPreferences } from "./session/index.js"; type Account = { address: Address; diff --git a/packages/sdk/src/client-auth-server/WalletProvider.ts b/packages/sdk/src/client-auth-server/WalletProvider.ts index 3af560ee..693bf6d7 100644 --- a/packages/sdk/src/client-auth-server/WalletProvider.ts +++ b/packages/sdk/src/client-auth-server/WalletProvider.ts @@ -11,7 +11,7 @@ import type { RequestArguments, } from "./interface.js"; import { type ExtractReturnType, type Method } from "./rpc.js"; -import type { SessionPreferences } from "./session.js"; +import type { SessionPreferences } from "./session/index.js"; import { Signer } from "./Signer.js"; const DEFAULT_AUTH_SERVER_URL = "https://auth-test.zksync.dev/confirm"; diff --git a/packages/sdk/src/client-auth-server/index.ts b/packages/sdk/src/client-auth-server/index.ts index 28e8b552..ba3ee47c 100644 --- a/packages/sdk/src/client-auth-server/index.ts +++ b/packages/sdk/src/client-auth-server/index.ts @@ -1,3 +1,3 @@ export * from "./interface.js"; export * from "./rpc.js"; -export * from "./session.js"; +export * from "./session/index.js"; diff --git a/packages/sdk/src/client-auth-server/rpc.ts b/packages/sdk/src/client-auth-server/rpc.ts index 55c49dc0..46662bf9 100644 --- a/packages/sdk/src/client-auth-server/rpc.ts +++ b/packages/sdk/src/client-auth-server/rpc.ts @@ -5,7 +5,7 @@ import type { Message } from "../communicator/index.js"; import type { SerializedEthereumRpcError } from "../errors/index.js"; import type { SessionConfig } from "../utils/session.js"; import type { AppMetadata, RequestArguments } from "./interface.js"; -import type { SessionPreferences } from "./session.js"; +import type { SessionPreferences } from "./session/index.js"; export type AuthServerRpcSchema = [ { diff --git a/packages/sdk/src/client-auth-server/session.ts b/packages/sdk/src/client-auth-server/session.ts deleted file mode 100644 index 56ddb745..00000000 --- a/packages/sdk/src/client-auth-server/session.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { type AbiFunction, type Address, getAddress, type Hash, toFunctionSelector, toHex } from "viem"; - -import { ConstraintCondition, type Limit, LimitType, LimitUnlimited, LimitZero, type SessionConfig } from "../utils/session.js"; - -type PartialLimit = bigint | { - limit: bigint; - period?: bigint; -} | { - limitType: "lifetime" | LimitType.Lifetime; - limit: bigint; -} | { - limitType: "unlimited" | LimitType.Unlimited; -} | { - limitType: "allowance" | LimitType.Allowance; - limit: bigint; - period: bigint; -}; - -type PartialCallPolicy = { - address: Address; - function?: string | AbiFunction; - selector?: Hash; // if function is not provided - maxValuePerUse?: bigint; - valueLimit?: PartialLimit; - constraints?: { - index: number; - condition?: ConstraintCondition | keyof typeof ConstraintCondition; - refValue?: Hash; - limit?: PartialLimit; - }[]; -}; - -type PartialTransferPolicy = { - to: Address; - maxValuePerUse?: bigint; - valueLimit?: PartialLimit; -}; - -export interface SessionPreferences { - expiresAt?: bigint | Date; - feeLimit?: PartialLimit; - contractCalls?: PartialCallPolicy[]; - transfers?: PartialTransferPolicy[]; -}; - -const formatLimitPreferences = (limit: PartialLimit): Limit => { - /* Just bigint was passed */ - if (typeof limit === "bigint") { - return { - limitType: LimitType.Lifetime, - limit, - period: 0n, - }; - } - - /* LimitType was specified */ - if ("limitType" in limit) { - if (limit.limitType === "lifetime" || limit.limitType === LimitType.Lifetime) { - return { - limitType: LimitType.Lifetime, - limit: limit.limit, - period: 0n, - }; - } else if (limit.limitType === "unlimited" || limit.limitType === LimitType.Unlimited) { - return { - limitType: LimitType.Unlimited, - limit: 0n, - period: 0n, - }; - } else if (limit.limitType === "allowance" || limit.limitType === LimitType.Allowance) { - return { - limitType: LimitType.Allowance, - limit: limit.limit, - period: limit.period, - }; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - throw new Error(`Invalid limit type: ${(limit as any).limitType}`); - } - - /* LimitType not selected */ - if (!limit.period) { - return { - limitType: LimitType.Lifetime, - limit: limit.limit, - period: 0n, - }; - } - return { - limitType: LimitType.Allowance, - limit: limit.limit, - period: limit.period, - }; -}; - -const formatDatePreferences = (date: bigint | Date): bigint => { - if (date instanceof Date) { - return BigInt(Math.floor(date.getTime() / 1000)); - } - return date; -}; - -export function formatSessionPreferences( - preferences: SessionPreferences, - defaults: { - expiresAt: bigint; - feeLimit: Limit; - }, -): Omit { - return { - expiresAt: preferences.expiresAt ? formatDatePreferences(preferences.expiresAt) : defaults.expiresAt, - feeLimit: preferences.feeLimit ? formatLimitPreferences(preferences.feeLimit) : defaults.feeLimit, - callPolicies: preferences.contractCalls?.map((policy) => { - const valueLimit = policy.valueLimit ? formatLimitPreferences(policy.valueLimit) : LimitZero; - const selector = policy.function ? toFunctionSelector(policy.function) : policy.selector; - if (!selector) throw new Error("Missing function or selector in contract call policy"); - return { - target: getAddress(policy.address.toLowerCase()), - maxValuePerUse: policy.maxValuePerUse ?? valueLimit.limit, - valueLimit, - selector: selector, - constraints: policy.constraints?.map((constraint) => ({ - index: BigInt(constraint.index), - condition: typeof constraint.condition == "string" ? ConstraintCondition[constraint.condition] : (constraint.condition ?? ConstraintCondition.Unconstrained), - refValue: constraint.refValue ?? toHex("", { size: 32 }), - limit: constraint.limit ? formatLimitPreferences(constraint.limit) : LimitUnlimited, - })) ?? [], - }; - }) ?? [], - transferPolicies: preferences.transfers?.map((policy) => { - const valueLimit = policy.valueLimit ? formatLimitPreferences(policy.valueLimit) : LimitZero; - return { - target: getAddress(policy.to.toLowerCase()), - maxValuePerUse: policy.maxValuePerUse ?? valueLimit.limit, - valueLimit, - }; - }) ?? [], - }; -} diff --git a/packages/sdk/src/client-auth-server/session/index.ts b/packages/sdk/src/client-auth-server/session/index.ts new file mode 100644 index 00000000..d48ffeb7 --- /dev/null +++ b/packages/sdk/src/client-auth-server/session/index.ts @@ -0,0 +1,222 @@ +import { type Abi, type AbiFunction, type AbiStateMutability, type Address, type ContractFunctionArgs, type ContractFunctionName, encodeAbiParameters, getAddress, toFunctionSelector, toHex } from "viem"; + +import { ConstraintCondition, type Limit, LimitType, LimitUnlimited, LimitZero, type SessionConfig } from "../../utils/session.js"; +import type { ContractWriteMutability, IndexedValues } from "./type-utils.js"; +import { encodedInputToAbiChunks, getParameterChunkIndex, isDynamicInputType, isFollowedByDynamicInputType } from "./utils.js"; + +export type PartialLimit = bigint | { + limit: bigint; + period?: bigint; +} | { + limitType: "lifetime" | LimitType.Lifetime; + limit: bigint; +} | { + limitType: "unlimited" | LimitType.Unlimited; +} | { + limitType: "allowance" | LimitType.Allowance; + limit: bigint; + period: bigint; +}; + +export type PartialCallPolicy = { + address: Address; + abi: Abi; + functionName: string; + maxValuePerUse?: bigint; + valueLimit?: PartialLimit; + constraints?: { + index: number; + value?: unknown; + condition?: keyof typeof ConstraintCondition; + limit?: PartialLimit; + }[]; +}; + +export interface TypesafePartialCallPolicy< + abi extends Abi, + functionName extends ContractFunctionName, +> extends PartialCallPolicy { + abi: abi; + functionName: functionName; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + constraints?: (IndexedValues>[number] & { + condition?: keyof typeof ConstraintCondition; + limit?: PartialLimit; + })[]; +}; + +// Typesafety helper function +export const callPolicy = < + abi extends Abi, + functionName extends ContractFunctionName, +>( + policy: TypesafePartialCallPolicy, +): PartialCallPolicy => policy as PartialCallPolicy; + +export type PartialTransferPolicy = { + to: Address; + maxValuePerUse?: bigint; + valueLimit?: PartialLimit; +}; + +export interface SessionPreferences { + expiresAt?: bigint | Date; + feeLimit?: PartialLimit; + contractCalls?: PartialCallPolicy[]; + transfers?: PartialTransferPolicy[]; +}; + +export const formatLimitPreferences = (limit: PartialLimit): Limit => { + /* Just bigint was passed */ + if (typeof limit === "bigint") { + return { + limitType: LimitType.Lifetime, + limit, + period: 0n, + }; + } + + /* LimitType was specified */ + if ("limitType" in limit) { + if (limit.limitType === "lifetime" || limit.limitType === LimitType.Lifetime) { + return { + limitType: LimitType.Lifetime, + limit: limit.limit, + period: 0n, + }; + } else if (limit.limitType === "unlimited" || limit.limitType === LimitType.Unlimited) { + return { + limitType: LimitType.Unlimited, + limit: 0n, + period: 0n, + }; + } else if (limit.limitType === "allowance" || limit.limitType === LimitType.Allowance) { + return { + limitType: LimitType.Allowance, + limit: limit.limit, + period: limit.period, + }; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + throw new Error(`Invalid limit type: ${(limit as any).limitType}`); + } + + /* LimitType not selected */ + if (!limit.period) { + return { + limitType: LimitType.Lifetime, + limit: limit.limit, + period: 0n, + }; + } + return { + limitType: LimitType.Allowance, + limit: limit.limit, + period: limit.period, + }; +}; + +export const formatDatePreferences = (date: bigint | Date): bigint => { + if (date instanceof Date) { + return BigInt(Math.floor(date.getTime() / 1000)); + } + return date; +}; + +export function formatSessionPreferences( + preferences: SessionPreferences, + defaults: { + expiresAt: bigint; + feeLimit: Limit; + }, +): Omit { + return { + expiresAt: preferences.expiresAt ? formatDatePreferences(preferences.expiresAt) : defaults.expiresAt, + feeLimit: preferences.feeLimit ? formatLimitPreferences(preferences.feeLimit) : defaults.feeLimit, + callPolicies: preferences.contractCalls?.map((policy) => { + const allowedStateMutability: ContractWriteMutability[] = ["nonpayable", "payable"]; + const abiFunction = (policy.abi as Abi).find((fn) => fn.type === "function" && (allowedStateMutability as AbiStateMutability[]).includes(fn.stateMutability)) as AbiFunction; + if (!abiFunction) throw new Error(`Function not found in the provided ABI: ${policy.functionName}`); + + const selector = toFunctionSelector(abiFunction); + const valueLimit = policy.valueLimit ? formatLimitPreferences(policy.valueLimit) : LimitZero; + + return { + target: getAddress(policy.address.toLowerCase()), + maxValuePerUse: policy.maxValuePerUse ?? valueLimit.limit, + valueLimit, + selector: selector, + constraints: policy.constraints?.map((constraint) => { + const limit = constraint.limit ? formatLimitPreferences(constraint.limit) : LimitUnlimited; + const condition = constraint.condition ? ConstraintCondition[constraint.condition] : ConstraintCondition.Unconstrained; + /* index: BigInt(constraint.index), + condition, + refValue: encodedInput ?? toHex("", { size: 32 }), + limit: constraint.limit ? formatLimitPreferences(constraint.limit) : LimitUnlimited, */ + + const input = abiFunction.inputs[constraint.index]; + if (!input) { + throw new Error(`Target function parameter not found in the provided ABI function. Provided at function ${policy.functionName}, index ${constraint.index}`); + } + + const isDynamicType = isDynamicInputType(input.type); + if (isDynamicType) { + throw new Error(`Function parameters with dynamic types are not supported for constraint validation. Provided at function ${policy.functionName}, index ${constraint.index}`); + } + + const isFollowedByDynamicType = isFollowedByDynamicInputType(abiFunction, constraint.index); + if (isFollowedByDynamicType) { + throw new Error(`Target function parameter is followed by a dynamic type parameter. Provided at function ${policy.functionName}, index ${constraint.index}`); + } + + const startingAbiChunkIndex = getParameterChunkIndex(abiFunction, constraint.index); + console.log("startingAbiChunkIndex", startingAbiChunkIndex); + if (constraint.value === undefined || constraint.value === null) { + return { + index: BigInt(startingAbiChunkIndex), + condition: ConstraintCondition.Unconstrained, + refValue: toHex("", { size: 32 }), + limit, + }; + } + + const encodedInput = encodeAbiParameters([input], [constraint.value]); + const abiBytesChunks = encodedInputToAbiChunks(encodedInput); + const ALLOWED_OVERFLOW_CONDITIONS: ConstraintCondition[] = [ + ConstraintCondition.Unconstrained, + ConstraintCondition.Equal, + ConstraintCondition.NotEqual, + ]; + const ALLOWED_OVERFLOW_LIMIT_TYPES: LimitType[] = [ + LimitType.Unlimited, + ]; + + if ( + abiBytesChunks.length > 1 + && !ALLOWED_OVERFLOW_CONDITIONS.includes(condition) + && !ALLOWED_OVERFLOW_LIMIT_TYPES.includes(limit.limitType) + ) { + /* Can't make validation for < > when value is split across multiple chunks */ + throw new Error(`Encoded input size of parameter at index ${constraint.index} of ${policy.functionName} exceeds the maximum size of 32 bytes: ${abiBytesChunks.length * 32} bytes`); + }; + + return abiBytesChunks.map((abiChunk, index) => ({ + index: BigInt(startingAbiChunkIndex + index), + condition, + refValue: abiChunk, + limit, + })); + }).flat() ?? [], + }; + }) ?? [], + transferPolicies: preferences.transfers?.map((policy) => { + const valueLimit = policy.valueLimit ? formatLimitPreferences(policy.valueLimit) : LimitZero; + return { + target: getAddress(policy.to.toLowerCase()), + maxValuePerUse: policy.maxValuePerUse ?? valueLimit.limit, + valueLimit, + }; + }) ?? [], + }; +} diff --git a/packages/sdk/src/client-auth-server/session/type-utils.ts b/packages/sdk/src/client-auth-server/session/type-utils.ts new file mode 100644 index 00000000..74cc0b82 --- /dev/null +++ b/packages/sdk/src/client-auth-server/session/type-utils.ts @@ -0,0 +1,14 @@ +import { type AbiStateMutability } from "viem"; + +// Helper type to convert string indices to numeric indices +type ToNumber = S extends `${infer N extends number}` ? N : never; + +// Extract numeric keys as strings +type NumericKeys = Extract; + +// Updated IndexedValues type with numeric indices +export type IndexedValues = Array<{ + [K in NumericKeys]: { index: ToNumber; value?: T[K] }; +}[NumericKeys]>; + +export type ContractWriteMutability = Extract; diff --git a/packages/sdk/src/client-auth-server/session/utils.ts b/packages/sdk/src/client-auth-server/session/utils.ts new file mode 100644 index 00000000..fb366d5c --- /dev/null +++ b/packages/sdk/src/client-auth-server/session/utils.ts @@ -0,0 +1,115 @@ +import { type AbiFunction, type AbiParameter, type Address, encodeAbiParameters, type Hash, toHex } from "viem"; + +const DYNAMIC_ABI_INPUT_TYPES = ["bytes", "string"]; + +export const isDynamicInputType = (inputType: string) => { + return inputType.endsWith("[]") || DYNAMIC_ABI_INPUT_TYPES.includes(inputType); +}; + +const includesDynamicInputType = (abiParameters: readonly AbiParameter[]): boolean => { + return abiParameters.some((input) => { + const isDynamicType = isDynamicInputType(input.type); + if (isDynamicType) return true; + + if (input.type.startsWith("tuple")) { + const components = (input as TupleAbiParameter).components; + if (!components) throw new Error("Tuple without components is unsupported"); + return includesDynamicInputType(components); + } + return false; + }); +}; + +export const isFollowedByDynamicInputType = (abiFunction: AbiFunction, targetInputIndex: number) => { + if (targetInputIndex >= abiFunction.inputs.length) { + throw new Error(`Input index ${targetInputIndex} is out of bounds`); + } + + return includesDynamicInputType(abiFunction.inputs.slice(0, targetInputIndex)); +}; + +export const encodedInputToAbiChunks = (encodedInput: string) => { + if (!encodedInput.startsWith("0x")) { + throw new Error("Input is not a valid hex string"); + } + return (encodedInput.slice(2).match(/.{1,64}/g) || []) as Hash[]; // 32 bytes abi chunks +}; + +const getDummyBytesValue = (type: string) => { + const size = parseInt(type.slice(5)) || 32; + return toHex("", { size }); +}; + +// Function to generate dummy values for ABI types +const getDummyValue = (type: string) => { + if (type === "address") return "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" as Address; + if (type.startsWith("uint") || type.startsWith("int")) return 0n; // BigInt for numbers + if (type.startsWith("bytes")) return getDummyBytesValue(type); // Empty bytes + if (type === "bool") return false; + if (type === "string") return ""; // Empty string + throw new Error(`Unsupported ABI type: ${type}`); +}; + +function getArrayComponents( + type: string, +): [length: number | null, innerType: string] | undefined { + const matches = type.match(/^(.*)\[(\d+)?\]$/); + return matches + ? [matches[2] ? Number(matches[2]) : null, matches[1]] + : undefined; +} + +type TupleAbiParameter = AbiParameter & { components: readonly AbiParameter[] }; + +// Recursive function to fill dummy values for complex types like tuples +const getDummyValues = (inputs: readonly AbiParameter[]): unknown[] => { + return inputs.map((input) => { + const arrayComponents = getArrayComponents(input.type); + if (arrayComponents) { + // Recursively fill array components + const [length, innerType] = arrayComponents; + if (!length) throw new Error("Dynamic array length is unsupported"); + const arrayValue = Array.from({ length }, () => getDummyValues([{ + ...input, + type: innerType, + }])); + return arrayValue; + } + if (input.type.startsWith("tuple")) { + // Recursively fill tuple components + const components = (input as TupleAbiParameter).components; + if (!components) throw new Error("Tuple without components is unsupported"); + return getDummyValues(components); + } + return getDummyValue(input.type); + }); +}; + +export const getParameterChunkIndex = ( + abiFunction: AbiFunction, + targetInputIndex: number, +): number => { + if (targetInputIndex >= abiFunction.inputs.length) { + throw new Error(`Input index ${targetInputIndex} is out of bounds`); + } + + const inputs = abiFunction.inputs.slice(0, targetInputIndex); + const dummyValues = getDummyValues(inputs); + const encoded = encodeAbiParameters(inputs, dummyValues); + const chunks = encodedInputToAbiChunks(encoded); + const chunkIndex = chunks.length; + + return chunkIndex; +}; + +/* SessionKeyModuleAbi.forEach((abi) => { + if (abi.type !== "function") return; + const dummyValues = getDummyValues(abi.inputs); + + console.log(abi.name, abi.inputs, dummyValues); + try { + console.log("Encoded", encodeAbiParameters(abi.inputs, dummyValues as any)); + } catch (error) { + console.error("Error", error); + } +}); */ diff --git a/packages/sdk/src/client/clients/passkey.ts b/packages/sdk/src/client/clients/passkey.ts index 04b76788..17bf198a 100644 --- a/packages/sdk/src/client/clients/passkey.ts +++ b/packages/sdk/src/client/clients/passkey.ts @@ -18,7 +18,7 @@ export function createZksyncPasskeyClient< } = { ..._parameters, address: getAddress(_parameters.address), - key: _parameters.key || "wallet", + key: _parameters.key || "zksync-sso-passkey-wallet", name: _parameters.name || "ZKsync SSO Passkey Client", }; diff --git a/packages/sdk/src/client/clients/session.ts b/packages/sdk/src/client/clients/session.ts index f44bb973..300206ed 100644 --- a/packages/sdk/src/client/clients/session.ts +++ b/packages/sdk/src/client/clients/session.ts @@ -39,7 +39,7 @@ export function createZksyncSessionClient< } = { ..._parameters, address: getAddress(_parameters.address), - key: _parameters.key || "wallet", + key: _parameters.key || "zksync-sso-session-wallet", name: _parameters.name || "ZKsync SSO Session Client", }; diff --git a/packages/sdk/src/connector/index.ts b/packages/sdk/src/connector/index.ts index 450d618d..ca47b0be 100644 --- a/packages/sdk/src/connector/index.ts +++ b/packages/sdk/src/connector/index.ts @@ -1,17 +1,25 @@ import { ChainNotConfiguredError, + type Config, type Connector, createConnector, + getConnectorClient as wagmiGetConnectorClient, + type GetConnectorClientParameters, } from "@wagmi/core"; +import type { Compute } from "@wagmi/core/internal"; import { + type Account, + type Client, getAddress, SwitchChainError, toHex, UserRejectedRequestError, } from "viem"; +import type { ZksyncSsoSessionClient } from "../client/index.js"; import { EthereumProviderError } from "../errors/errors.js"; import { type AppMetadata, type ProviderInterface, type SessionPreferences, WalletProvider } from "../index.js"; +export { callPolicy } from "../client-auth-server/index.js"; export type ZksyncSsoConnectorOptions = { metadata?: Partial; @@ -175,3 +183,45 @@ export const zksyncSsoConnector = (parameters: ZksyncSsoConnectorOptions) => { }, })); }; + +export type GetConnectedSsoClientReturnType< + config extends Config = Config, + chainId extends config["chains"][number]["id"] = config["chains"][number]["id"], +> = Compute< + ZksyncSsoSessionClient< + config["_internal"]["transports"][chainId], + Extract, + undefined, + Account + > +>; + +export const isSsoSessionClient = (client: Client): boolean => { + return client.key === "zksync-sso-session-wallet"; +}; + +export const isSsoSessionClientConnected = async< + config extends Config, + chainId extends config["chains"][number]["id"], +>( + config: config, + parameters: GetConnectorClientParameters = {}, +): Promise => { + const connectorClient = await wagmiGetConnectorClient(config, parameters); + return isSsoSessionClient(connectorClient); +}; + +export const getConnectedSsoClient = async< + config extends Config, + chainId extends config["chains"][number]["id"], +>( + config: config, + parameters: GetConnectorClientParameters = {}, +): Promise> => { + const connectorClient = await wagmiGetConnectorClient(config, parameters); + if (!isSsoSessionClient(connectorClient)) { + throw new Error("ZKsync SSO Session Client not connected"); + } + const sessionClient = connectorClient as unknown as GetConnectedSsoClientReturnType; + return sessionClient; +}; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 4d888cff..d00a013d 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,3 +1,3 @@ export type { AppMetadata, ProviderInterface } from "./client-auth-server/interface.js"; -export type { SessionPreferences } from "./client-auth-server/session.js"; +export type { SessionPreferences } from "./client-auth-server/session/index.js"; export { WalletProvider, type WalletProviderConstructorOptions } from "./client-auth-server/WalletProvider.js"; From ae7eccb38c54fd65fa10c2103d493c7352fe50fd Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Tue, 26 Nov 2024 17:39:50 +0200 Subject: [PATCH 2/7] fix: add support for human readable expiry --- README.md | 6 ++- docs/sdk/client-auth-server/README.md | 40 +++++++++++++++++-- examples/nft-quest/stores/connector.ts | 2 +- packages/sdk/README.md | 25 ++++++------ packages/sdk/package.json | 4 +- .../src/client-auth-server/session/index.ts | 37 ++++++++--------- .../src/client-auth-server/session/utils.ts | 18 +++++---- packages/sdk/tsconfig.base.json | 2 +- pnpm-lock.yaml | 25 +++++++----- 9 files changed, 102 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 2619bdf0..f18da204 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ import { erc20Abi } from "viem"; const ssoConnector = zksyncSsoConnector({ // Optional session configuration, if omitted user will have to sign every transaction via Auth Server session: { + expiry: "1 day", + // Allow up to 0.1 ETH to be spend in gas fees feeLimit: parseEther("0.1"), @@ -61,7 +63,7 @@ const ssoConnector = zksyncSsoConnector({ abi: erc20Abi, functionName: "transfer", constraints: [ - // Only allow transfers to this address, any address if omitted + // Only allow transfers to this address. Or any address if omitted { index: 0, // First argument of erc20 transfer function, recipient address value: "0x6cC8cf7f6b488C58AA909B77E6e65c631c204784", @@ -73,7 +75,7 @@ const ssoConnector = zksyncSsoConnector({ index: 1, limit: { limit: parseUnits("0.2", TOKEN.decimals), - period: BigInt(60 * 60), // 1 hour in seconds + period: "1 hour", }, }, ], diff --git a/docs/sdk/client-auth-server/README.md b/docs/sdk/client-auth-server/README.md index 1dcbf2fe..d9503d3c 100644 --- a/docs/sdk/client-auth-server/README.md +++ b/docs/sdk/client-auth-server/README.md @@ -7,21 +7,53 @@ your application. It's built on top of [client SDK](../client/README.md) and ## Basic usage ```ts -import { zksync } from "viem/chains"; +import { zksyncSsoConnector, callPolicy } from "zksync-sso/connector"; +import { zksyncSepoliaTestnet } from "viem/chains"; import { createConfig, connect } from "@wagmi/core"; -import { zksyncSsoConnector } from "zksync-sso/connector"; +import { erc20Abi } from "viem"; const ssoConnector = zksyncSsoConnector({ // Optional session configuration + // if omitted user will have to sign every transaction via Auth Server session: { + expiry: "1 day", + + // Allow up to 0.1 ETH to be spend in gas fees feeLimit: parseEther("0.1"), - // Allow transfers to a specific address with a limit of 0.1 ETH + transfers: [ + // Allow ETH transfers of up to 0.1 ETH to specific address { to: "0x188bd99cd7D4d78d4E605Aeea12C17B32CC3135A", valueLimit: parseEther("0.1"), }, ], + + // Allow calling specific smart contracts (e.g. ERC20 transfer): + contractCalls: [ + callPolicy({ + address: "0xa1cf087DB965Ab02Fb3CFaCe1f5c63935815f044", + abi: erc20Abi, + functionName: "transfer", + constraints: [ + // Only allow transfers to this address. Or any address if omitted + { + index: 0, // First argument of erc20 transfer function, recipient address + value: "0x6cC8cf7f6b488C58AA909B77E6e65c631c204784", + }, + + // Allow transfering up to 0.2 tokens per hour + // until the session expires + { + index: 1, + limit: { + limit: parseUnits("0.2", TOKEN.decimals), + period: "1 hour", + }, + }, + ], + }), + ], }, }); @@ -33,7 +65,7 @@ const wagmiConfig = createConfig({ const connectWithSSO = () => { connect(wagmiConfig, { connector: ssoConnector, - chainId: zksync.id, // or another chain id that has SSO support + chainId: zksyncSepoliaTestnet.id, // or another chain id that has SSO support }); }; ``` diff --git a/examples/nft-quest/stores/connector.ts b/examples/nft-quest/stores/connector.ts index 0c2b8649..2e18db76 100644 --- a/examples/nft-quest/stores/connector.ts +++ b/examples/nft-quest/stores/connector.ts @@ -3,7 +3,7 @@ import { zksyncInMemoryNode, zksyncLocalNode, zksyncSepoliaTestnet } from "@wagm import { type Address, type Hash, parseEther } from "viem"; import { callPolicy, zksyncSsoConnector } from "zksync-sso/connector"; -import { ZeekNftQuestAbi } from "@/abi/ZeekNftQuest"; +import { ZeekNftQuestAbi } from "@/abi/ZeekNFTQuest"; export const useConnectorStore = defineStore("connector", () => { const runtimeConfig = useRuntimeConfig(); diff --git a/packages/sdk/README.md b/packages/sdk/README.md index fcf9121f..9dcea58b 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -39,9 +39,10 @@ import { createConfig, connect } from "@wagmi/core"; import { erc20Abi } from "viem"; const ssoConnector = zksyncSsoConnector({ - // Optional session configuration, - // if omitted user will have to sign every transaction via Auth Server + // Optional session configuration, if omitted user will have to sign every transaction via Auth Server session: { + expiry: "1 day", + // Allow up to 0.1 ETH to be spend in gas fees feeLimit: parseEther("0.1"), @@ -60,7 +61,7 @@ const ssoConnector = zksyncSsoConnector({ abi: erc20Abi, functionName: "transfer", constraints: [ - // Only allow transfers to this address, any address if omitted + // Only allow transfers to this address. Or any address if omitted { index: 0, // First argument of erc20 transfer function, recipient address value: "0x6cC8cf7f6b488C58AA909B77E6e65c631c204784", @@ -71,26 +72,26 @@ const ssoConnector = zksyncSsoConnector({ { index: 1, limit: { - limit: parseUnits("0.2", TOKEN.decimals), - period: BigInt(60 * 60), // 1 hour in seconds + limit: parseUnits("0.2", TOKEN.decimals), + period: "1 hour", }, }, ], }), ], - }, + }, }); const wagmiConfig = createConfig({ - connectors: [ssoConnector], - ..., // your wagmi config https://wagmi.sh/core/api/createConfig + connectors: [ssoConnector], + ..., // your wagmi config https://wagmi.sh/core/api/createConfig }); const connectWithSSO = () => { - connect(wagmiConfig, { - connector: ssoConnector, - chainId: zksyncSepoliaTestnet.id, // or another chain id that has SSO support - }); + connect(wagmiConfig, { + connector: ssoConnector, + chainId: zksyncSepoliaTestnet.id, // or another chain id that has SSO support + }); }; ``` diff --git a/packages/sdk/package.json b/packages/sdk/package.json index b8256b78..245a7e44 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -34,6 +34,7 @@ }, "devDependencies": { "@simplewebauthn/types": "^10.0.0", + "@types/ms": "^0.7.34", "@types/node": "^22.1.0", "eventemitter3": "^5.0.1", "viem": "2.21.14" @@ -144,6 +145,7 @@ "@peculiar/asn1-schema": "^2.3.13", "abitype": "^1.0.6", "bigint-conversion": "^2.4.3", - "buffer": "^6.0.3" + "buffer": "^6.0.3", + "ms": "^2.1.3" } } diff --git a/packages/sdk/src/client-auth-server/session/index.ts b/packages/sdk/src/client-auth-server/session/index.ts index d48ffeb7..7470d16f 100644 --- a/packages/sdk/src/client-auth-server/session/index.ts +++ b/packages/sdk/src/client-auth-server/session/index.ts @@ -2,11 +2,11 @@ import { type Abi, type AbiFunction, type AbiStateMutability, type Address, type import { ConstraintCondition, type Limit, LimitType, LimitUnlimited, LimitZero, type SessionConfig } from "../../utils/session.js"; import type { ContractWriteMutability, IndexedValues } from "./type-utils.js"; -import { encodedInputToAbiChunks, getParameterChunkIndex, isDynamicInputType, isFollowedByDynamicInputType } from "./utils.js"; +import { encodedInputToAbiChunks, getParameterChunkIndex, isDynamicInputType, isFollowedByDynamicInputType, msStringToSeconds } from "./utils.js"; export type PartialLimit = bigint | { limit: bigint; - period?: bigint; + period?: string | bigint; } | { limitType: "lifetime" | LimitType.Lifetime; limit: bigint; @@ -15,7 +15,7 @@ export type PartialLimit = bigint | { } | { limitType: "allowance" | LimitType.Allowance; limit: bigint; - period: bigint; + period: string | bigint; }; export type PartialCallPolicy = { @@ -61,7 +61,7 @@ export type PartialTransferPolicy = { }; export interface SessionPreferences { - expiresAt?: bigint | Date; + expiry?: string | bigint | Date; feeLimit?: PartialLimit; contractCalls?: PartialCallPolicy[]; transfers?: PartialTransferPolicy[]; @@ -95,7 +95,7 @@ export const formatLimitPreferences = (limit: PartialLimit): Limit => { return { limitType: LimitType.Allowance, limit: limit.limit, - period: limit.period, + period: typeof limit.period === "string" ? msStringToSeconds(limit.period) : limit.period, }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -103,23 +103,29 @@ export const formatLimitPreferences = (limit: PartialLimit): Limit => { } /* LimitType not selected */ - if (!limit.period) { + if (limit.period) { return { - limitType: LimitType.Lifetime, + limitType: LimitType.Allowance, limit: limit.limit, - period: 0n, + period: typeof limit.period === "string" ? msStringToSeconds(limit.period) : limit.period, }; } return { - limitType: LimitType.Allowance, + limitType: LimitType.Lifetime, limit: limit.limit, - period: limit.period, + period: 0n, }; }; -export const formatDatePreferences = (date: bigint | Date): bigint => { +export const formatDatePreferences = (date: string | bigint | Date): bigint => { + if (typeof date === "string") { + const now = new Date().getTime(); + const seconds = msStringToSeconds(date); + return BigInt(now) + seconds; + } if (date instanceof Date) { - return BigInt(Math.floor(date.getTime() / 1000)); + const seconds = Math.floor(date.getTime() / 1000); + return BigInt(seconds); } return date; }; @@ -132,7 +138,7 @@ export function formatSessionPreferences( }, ): Omit { return { - expiresAt: preferences.expiresAt ? formatDatePreferences(preferences.expiresAt) : defaults.expiresAt, + expiresAt: preferences.expiry ? formatDatePreferences(preferences.expiry) : defaults.expiresAt, feeLimit: preferences.feeLimit ? formatLimitPreferences(preferences.feeLimit) : defaults.feeLimit, callPolicies: preferences.contractCalls?.map((policy) => { const allowedStateMutability: ContractWriteMutability[] = ["nonpayable", "payable"]; @@ -150,10 +156,6 @@ export function formatSessionPreferences( constraints: policy.constraints?.map((constraint) => { const limit = constraint.limit ? formatLimitPreferences(constraint.limit) : LimitUnlimited; const condition = constraint.condition ? ConstraintCondition[constraint.condition] : ConstraintCondition.Unconstrained; - /* index: BigInt(constraint.index), - condition, - refValue: encodedInput ?? toHex("", { size: 32 }), - limit: constraint.limit ? formatLimitPreferences(constraint.limit) : LimitUnlimited, */ const input = abiFunction.inputs[constraint.index]; if (!input) { @@ -171,7 +173,6 @@ export function formatSessionPreferences( } const startingAbiChunkIndex = getParameterChunkIndex(abiFunction, constraint.index); - console.log("startingAbiChunkIndex", startingAbiChunkIndex); if (constraint.value === undefined || constraint.value === null) { return { index: BigInt(startingAbiChunkIndex), diff --git a/packages/sdk/src/client-auth-server/session/utils.ts b/packages/sdk/src/client-auth-server/session/utils.ts index fb366d5c..36fe2552 100644 --- a/packages/sdk/src/client-auth-server/session/utils.ts +++ b/packages/sdk/src/client-auth-server/session/utils.ts @@ -1,3 +1,4 @@ +import ms from "ms"; import { type AbiFunction, type AbiParameter, type Address, encodeAbiParameters, type Hash, toHex } from "viem"; const DYNAMIC_ABI_INPUT_TYPES = ["bytes", "string"]; @@ -102,14 +103,15 @@ export const getParameterChunkIndex = ( return chunkIndex; }; -/* SessionKeyModuleAbi.forEach((abi) => { - if (abi.type !== "function") return; - const dummyValues = getDummyValues(abi.inputs); - - console.log(abi.name, abi.inputs, dummyValues); +export const msStringToSeconds = (value: string): bigint => { + let millis: number; try { - console.log("Encoded", encodeAbiParameters(abi.inputs, dummyValues as any)); + millis = ms(value); } catch (error) { - console.error("Error", error); + throw new Error(`Invalid date format: ${value}: ${error}`); } -}); */ + if (millis < 0) throw new Error(`Date can't be in the past: ${value}`); + if (millis === 0) throw new Error(`Date can't be zero: ${value}`); + const seconds = Math.floor(millis / 1000); + return BigInt(seconds); +}; diff --git a/packages/sdk/tsconfig.base.json b/packages/sdk/tsconfig.base.json index 6f842535..549cb533 100644 --- a/packages/sdk/tsconfig.base.json +++ b/packages/sdk/tsconfig.base.json @@ -25,7 +25,7 @@ // Interop constraints "esModuleInterop": false, - "allowSyntheticDefaultImports": false, + "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "verbatimModuleSyntax": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b642aa9b..32bc354e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,7 +149,7 @@ importers: version: 3.5.13(typescript@5.6.2) vue-router: specifier: latest - version: 4.4.5(vue@3.5.13(typescript@5.6.2)) + version: 4.5.0(vue@3.5.13(typescript@5.6.2)) zksync-sso: specifier: workspace:* version: link:../../packages/sdk @@ -297,7 +297,7 @@ importers: version: 1.1.0(vue@3.5.13(typescript@5.6.2)) vue-router: specifier: latest - version: 4.4.5(vue@3.5.13(typescript@5.6.2)) + version: 4.5.0(vue@3.5.13(typescript@5.6.2)) zksync-ethers: specifier: ^6.15.0 version: 6.15.0(ethers@6.13.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)) @@ -567,10 +567,16 @@ importers: buffer: specifier: ^6.0.3 version: 6.0.3 + ms: + specifier: ^2.1.3 + version: 2.1.3 devDependencies: '@simplewebauthn/types': specifier: ^10.0.0 version: 10.0.0 + '@types/ms': + specifier: ^0.7.34 + version: 0.7.34 '@types/node': specifier: ^22.1.0 version: 22.8.0 @@ -10400,6 +10406,11 @@ packages: peerDependencies: vue: ^3.2.0 + vue-router@4.5.0: + resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==} + peerDependencies: + vue: ^3.2.0 + vue@3.5.12: resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==} peerDependencies: @@ -13690,12 +13701,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@nrwl/devkit@19.8.0(nx@19.8.0(@swc-node/register@1.10.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13)))': - dependencies: - '@nx/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.10.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13))) - transitivePeerDependencies: - - nx - '@nrwl/devkit@19.8.0(nx@19.8.6(@swc-node/register@1.10.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13)))': dependencies: '@nx/devkit': 19.8.0(nx@19.8.6(@swc-node/register@1.10.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13))) @@ -14298,7 +14303,7 @@ snapshots: '@nx/devkit@19.8.0(nx@19.8.0(@swc-node/register@1.10.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13)))': dependencies: - '@nrwl/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.10.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13))) + '@nrwl/devkit': 19.8.0(nx@19.8.6(@swc-node/register@1.10.9(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13))) ejs: 3.1.10 enquirer: 2.3.6 ignore: 5.3.2 @@ -24098,7 +24103,7 @@ 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)): + vue-router@4.5.0(vue@3.5.13(typescript@5.6.2)): dependencies: '@vue/devtools-api': 6.6.4 vue: 3.5.13(typescript@5.6.2) From 8fe6234239577d40fc59c55d920ecb9799a898b4 Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Tue, 26 Nov 2024 18:29:49 +0200 Subject: [PATCH 3/7] fix: undo accidental session key module abi changes --- packages/sdk/src/abi/SessionKeyModule.ts | 480 +++++++++++++++++++++++ 1 file changed, 480 insertions(+) diff --git a/packages/sdk/src/abi/SessionKeyModule.ts b/packages/sdk/src/abi/SessionKeyModule.ts index aafae56e..c21f3b2b 100644 --- a/packages/sdk/src/abi/SessionKeyModule.ts +++ b/packages/sdk/src/abi/SessionKeyModule.ts @@ -96,6 +96,133 @@ export const SessionKeyModuleAbi = [ name: "feeLimit", type: "tuple", }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "uint256", + name: "maxValuePerUse", + type: "uint256", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "valueLimit", + type: "tuple", + }, + { + components: [ + { + internalType: "enum SessionLib.Condition", + name: "condition", + type: "uint8", + }, + { + internalType: "uint64", + name: "index", + type: "uint64", + }, + { + internalType: "bytes32", + name: "refValue", + type: "bytes32", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "limit", + type: "tuple", + }, + ], + internalType: "struct SessionLib.Constraint[]", + name: "constraints", + type: "tuple[]", + }, + ], + internalType: "struct SessionLib.CallSpec[]", + name: "callPolicies", + type: "tuple[]", + }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "uint256", + name: "maxValuePerUse", + type: "uint256", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "valueLimit", + type: "tuple", + }, + ], + internalType: "struct SessionLib.TransferSpec[]", + name: "transferPolicies", + type: "tuple[]", + }, ], indexed: false, internalType: "struct SessionLib.SessionSpec", @@ -180,6 +307,133 @@ export const SessionKeyModuleAbi = [ name: "feeLimit", type: "tuple", }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "uint256", + name: "maxValuePerUse", + type: "uint256", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "valueLimit", + type: "tuple", + }, + { + components: [ + { + internalType: "enum SessionLib.Condition", + name: "condition", + type: "uint8", + }, + { + internalType: "uint64", + name: "index", + type: "uint64", + }, + { + internalType: "bytes32", + name: "refValue", + type: "bytes32", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "limit", + type: "tuple", + }, + ], + internalType: "struct SessionLib.Constraint[]", + name: "constraints", + type: "tuple[]", + }, + ], + internalType: "struct SessionLib.CallSpec[]", + name: "callPolicies", + type: "tuple[]", + }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "uint256", + name: "maxValuePerUse", + type: "uint256", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "valueLimit", + type: "tuple", + }, + ], + internalType: "struct SessionLib.TransferSpec[]", + name: "transferPolicies", + type: "tuple[]", + }, ], internalType: "struct SessionLib.SessionSpec", name: "sessionSpec", @@ -391,6 +645,19 @@ export const SessionKeyModuleAbi = [ stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { + internalType: "bytes32[]", + name: "sessionHashes", + type: "bytes32[]", + }, + ], + name: "revokeKeys", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { @@ -432,6 +699,133 @@ export const SessionKeyModuleAbi = [ name: "feeLimit", type: "tuple", }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "uint256", + name: "maxValuePerUse", + type: "uint256", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "valueLimit", + type: "tuple", + }, + { + components: [ + { + internalType: "enum SessionLib.Condition", + name: "condition", + type: "uint8", + }, + { + internalType: "uint64", + name: "index", + type: "uint64", + }, + { + internalType: "bytes32", + name: "refValue", + type: "bytes32", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "limit", + type: "tuple", + }, + ], + internalType: "struct SessionLib.Constraint[]", + name: "constraints", + type: "tuple[]", + }, + ], + internalType: "struct SessionLib.CallSpec[]", + name: "callPolicies", + type: "tuple[]", + }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "uint256", + name: "maxValuePerUse", + type: "uint256", + }, + { + components: [ + { + internalType: "enum SessionLib.LimitType", + name: "limitType", + type: "uint8", + }, + { + internalType: "uint256", + name: "limit", + type: "uint256", + }, + { + internalType: "uint256", + name: "period", + type: "uint256", + }, + ], + internalType: "struct SessionLib.UsageLimit", + name: "valueLimit", + type: "tuple", + }, + ], + internalType: "struct SessionLib.TransferSpec[]", + name: "transferPolicies", + type: "tuple[]", + }, ], internalType: "struct SessionLib.SessionSpec", name: "spec", @@ -452,6 +846,87 @@ export const SessionKeyModuleAbi = [ name: "feesRemaining", type: "uint256", }, + { + components: [ + { + internalType: "uint256", + name: "remaining", + type: "uint256", + }, + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "uint256", + name: "index", + type: "uint256", + }, + ], + internalType: "struct SessionLib.LimitState[]", + name: "transferValue", + type: "tuple[]", + }, + { + components: [ + { + internalType: "uint256", + name: "remaining", + type: "uint256", + }, + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "uint256", + name: "index", + type: "uint256", + }, + ], + internalType: "struct SessionLib.LimitState[]", + name: "callValue", + type: "tuple[]", + }, + { + components: [ + { + internalType: "uint256", + name: "remaining", + type: "uint256", + }, + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "uint256", + name: "index", + type: "uint256", + }, + ], + internalType: "struct SessionLib.LimitState[]", + name: "callParams", + type: "tuple[]", + }, ], internalType: "struct SessionLib.SessionState", name: "", @@ -578,6 +1053,11 @@ export const SessionKeyModuleAbi = [ name: "signature", type: "bytes", }, + { + internalType: "bytes32[]", + name: "factoryDeps", + type: "bytes32[]", + }, { internalType: "bytes", name: "paymasterInput", From cee7267c7c3e41f9894093625e8d3e8cdd071e17 Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Tue, 26 Nov 2024 18:46:33 +0200 Subject: [PATCH 4/7] fix: update rich pk --- examples/nft-quest-contracts/deploy/utils.ts | 4 ++-- examples/nft-quest-contracts/project.json | 2 +- packages/contracts/test/utils.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/nft-quest-contracts/deploy/utils.ts b/examples/nft-quest-contracts/deploy/utils.ts index 2741bf6b..47362416 100644 --- a/examples/nft-quest-contracts/deploy/utils.ts +++ b/examples/nft-quest-contracts/deploy/utils.ts @@ -135,8 +135,8 @@ export const deployContract = async (contractArtifactName: string, constructorAr */ export const LOCAL_RICH_WALLETS = [ { - address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", + address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", }, { address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", diff --git a/examples/nft-quest-contracts/project.json b/examples/nft-quest-contracts/project.json index 2c853b63..08a8e36c 100644 --- a/examples/nft-quest-contracts/project.json +++ b/examples/nft-quest-contracts/project.json @@ -21,7 +21,7 @@ "executor": "nx:run-commands", "options": { "cwd": "examples/nft-quest-contracts", - "command": "WALLET_PRIVATE_KEY=0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 hardhat deploy-zksync --script deploy.ts --network inMemoryNode" + "command": "WALLET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 hardhat deploy-zksync --script deploy.ts --network inMemoryNode" }, "dependsOn": ["build"] }, diff --git a/packages/contracts/test/utils.ts b/packages/contracts/test/utils.ts index e1994bca..919fea30 100644 --- a/packages/contracts/test/utils.ts +++ b/packages/contracts/test/utils.ts @@ -286,7 +286,7 @@ export const LOCAL_RICH_WALLETS = [ }, { address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", + privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", }, { address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", From 4dfbdff4c0e48352503f1e6b87871a87f470bbee Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Tue, 26 Nov 2024 19:57:00 +0200 Subject: [PATCH 5/7] Revert "fix: update rich pk" This reverts commit cee7267c7c3e41f9894093625e8d3e8cdd071e17. --- examples/nft-quest-contracts/deploy/utils.ts | 4 ++-- examples/nft-quest-contracts/project.json | 2 +- packages/contracts/test/utils.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/nft-quest-contracts/deploy/utils.ts b/examples/nft-quest-contracts/deploy/utils.ts index 47362416..2741bf6b 100644 --- a/examples/nft-quest-contracts/deploy/utils.ts +++ b/examples/nft-quest-contracts/deploy/utils.ts @@ -135,8 +135,8 @@ export const deployContract = async (contractArtifactName: string, constructorAr */ export const LOCAL_RICH_WALLETS = [ { - address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", }, { address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", diff --git a/examples/nft-quest-contracts/project.json b/examples/nft-quest-contracts/project.json index 08a8e36c..2c853b63 100644 --- a/examples/nft-quest-contracts/project.json +++ b/examples/nft-quest-contracts/project.json @@ -21,7 +21,7 @@ "executor": "nx:run-commands", "options": { "cwd": "examples/nft-quest-contracts", - "command": "WALLET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 hardhat deploy-zksync --script deploy.ts --network inMemoryNode" + "command": "WALLET_PRIVATE_KEY=0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 hardhat deploy-zksync --script deploy.ts --network inMemoryNode" }, "dependsOn": ["build"] }, diff --git a/packages/contracts/test/utils.ts b/packages/contracts/test/utils.ts index 919fea30..e1994bca 100644 --- a/packages/contracts/test/utils.ts +++ b/packages/contracts/test/utils.ts @@ -286,7 +286,7 @@ export const LOCAL_RICH_WALLETS = [ }, { address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", }, { address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", From 1b3f8415c05ca84509b408f5bc83c4414dee5908 Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Tue, 26 Nov 2024 20:50:35 +0200 Subject: [PATCH 6/7] fix: title --- examples/nft-quest/nuxt.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nft-quest/nuxt.config.ts b/examples/nft-quest/nuxt.config.ts index 17c970c2..fba50e7e 100644 --- a/examples/nft-quest/nuxt.config.ts +++ b/examples/nft-quest/nuxt.config.ts @@ -54,7 +54,7 @@ export default defineNuxtConfig({ css: ["@/assets/style.scss"], site: { url: "https://nft-quest.zksync.io", - name: "NFT Quest", + name: "ZK NFT Quest", description: "Mint your own ZKsync NFT gas-free", defaultLocale: "en", }, From 8f6ec49728f57eb1c5a4e0e1fcb188f641ddee35 Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Wed, 27 Nov 2024 11:45:14 +0200 Subject: [PATCH 7/7] fix: selector --- examples/nft-quest/stores/connector.ts | 3 +++ .../components/views/confirmation/RequestSession.vue | 2 +- packages/sdk/src/client-auth-server/session/index.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/nft-quest/stores/connector.ts b/examples/nft-quest/stores/connector.ts index 2e18db76..50a1a06c 100644 --- a/examples/nft-quest/stores/connector.ts +++ b/examples/nft-quest/stores/connector.ts @@ -17,6 +17,9 @@ export const useConnectorStore = defineStore("connector", () => { if (!chain) throw new Error(`Chain with id ${runtimeConfig.public.chain.id} was not found in supported chains list`); const connector = zksyncSsoConnector({ + metadata: { + icon: `${runtimeConfig.public.baseUrl}/icon-192.png`, + }, authServerUrl: runtimeConfig.public.authServerUrl, session: { feeLimit: parseEther("0.001"), diff --git a/packages/auth-server/components/views/confirmation/RequestSession.vue b/packages/auth-server/components/views/confirmation/RequestSession.vue index ee0c7c20..0990d3c4 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()" > - {{ isLoggedIn ? "Connect" : "Create" }} + Connect diff --git a/packages/sdk/src/client-auth-server/session/index.ts b/packages/sdk/src/client-auth-server/session/index.ts index 7470d16f..309d4dbf 100644 --- a/packages/sdk/src/client-auth-server/session/index.ts +++ b/packages/sdk/src/client-auth-server/session/index.ts @@ -142,7 +142,7 @@ export function formatSessionPreferences( feeLimit: preferences.feeLimit ? formatLimitPreferences(preferences.feeLimit) : defaults.feeLimit, callPolicies: preferences.contractCalls?.map((policy) => { const allowedStateMutability: ContractWriteMutability[] = ["nonpayable", "payable"]; - const abiFunction = (policy.abi as Abi).find((fn) => fn.type === "function" && (allowedStateMutability as AbiStateMutability[]).includes(fn.stateMutability)) as AbiFunction; + const abiFunction = (policy.abi as Abi).find((fn) => fn.type === "function" && fn.name === policy.functionName && (allowedStateMutability as AbiStateMutability[]).includes(fn.stateMutability)) as AbiFunction; if (!abiFunction) throw new Error(`Function not found in the provided ABI: ${policy.functionName}`); const selector = toFunctionSelector(abiFunction);