Skip to content

Commit

Permalink
fix: add support for human readable expiry
Browse files Browse the repository at this point in the history
  • Loading branch information
JackHamer09 committed Nov 26, 2024
1 parent d527d06 commit ae7eccb
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 57 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"),

Expand All @@ -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",
Expand All @@ -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",
},
},
],
Expand Down
40 changes: 36 additions & 4 deletions docs/sdk/client-auth-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
],
}),
],
},
});

Expand All @@ -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
});
};
```
2 changes: 1 addition & 1 deletion examples/nft-quest/stores/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
25 changes: 13 additions & 12 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"),

Expand All @@ -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",
Expand All @@ -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
});
};
```

Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
}
}
37 changes: 19 additions & 18 deletions packages/sdk/src/client-auth-server/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,7 +15,7 @@ export type PartialLimit = bigint | {
} | {
limitType: "allowance" | LimitType.Allowance;
limit: bigint;
period: bigint;
period: string | bigint;
};

export type PartialCallPolicy = {
Expand Down Expand Up @@ -61,7 +61,7 @@ export type PartialTransferPolicy = {
};

export interface SessionPreferences {
expiresAt?: bigint | Date;
expiry?: string | bigint | Date;
feeLimit?: PartialLimit;
contractCalls?: PartialCallPolicy[];
transfers?: PartialTransferPolicy[];
Expand Down Expand Up @@ -95,31 +95,37 @@ 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
throw new Error(`Invalid limit type: ${(limit as any).limitType}`);
}

/* 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;
};
Expand All @@ -132,7 +138,7 @@ export function formatSessionPreferences(
},
): Omit<SessionConfig, "signer"> {
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"];
Expand All @@ -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) {
Expand All @@ -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),
Expand Down
18 changes: 10 additions & 8 deletions packages/sdk/src/client-auth-server/session/utils.ts
Original file line number Diff line number Diff line change
@@ -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"];
Expand Down Expand Up @@ -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);
};
2 changes: 1 addition & 1 deletion packages/sdk/tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

// Interop constraints
"esModuleInterop": false,
"allowSyntheticDefaultImports": false,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,

Expand Down
25 changes: 15 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ae7eccb

Please sign in to comment.