UNPKG

zksync-sso

Version:
205 lines (184 loc) 6.86 kB
import { type Account, type Address, type Chain, type Client, encodeFunctionData, type Hash, type Hex, type Prettify, type TransactionReceipt, type Transport } from "viem"; import { readContract, waitForTransactionReceipt } from "viem/actions"; import { getGeneralPaymasterInput, sendTransaction } from "viem/zksync"; import { SessionKeyValidatorAbi } from "../../../abi/SessionKeyValidator.js"; import { type CustomPaymasterHandler, getTransactionWithPaymasterData } from "../../../paymaster/index.js"; import { noThrow } from "../../../utils/helpers.js"; import type { SessionConfig, SessionState, SessionStateEventCallback } from "../../../utils/session.js"; import { SessionEventType, SessionStatus } from "../../../utils/session.js"; export type CreateSessionArgs = { sessionConfig: SessionConfig; contracts: { session: Address; // session module }; paymaster?: { address: Address; paymasterInput?: Hex; }; onTransactionSent?: (hash: Hash) => void; paymasterHandler?: CustomPaymasterHandler; }; export type CreateSessionReturnType = { transactionReceipt: TransactionReceipt; }; export const createSession = async < transport extends Transport, chain extends Chain, account extends Account, >(client: Client<transport, chain, account>, args: Prettify<CreateSessionArgs>): Promise<Prettify<CreateSessionReturnType>> => { const callData = encodeFunctionData({ abi: SessionKeyValidatorAbi, functionName: "createSession", args: [args.sessionConfig], }); const sendTransactionArgs = { account: client.account, to: args.contracts.session, paymaster: args.paymaster?.address, paymasterInput: args.paymaster?.address ? (args.paymaster?.paymasterInput || getGeneralPaymasterInput({ innerInput: "0x" })) : undefined, data: callData, gas: 10_000_000n, // TODO: Remove when gas estimation is fixed // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const transactionWithPaymasterData: any = await getTransactionWithPaymasterData( client.chain.id, client.account.address, sendTransactionArgs, args.paymasterHandler, ); const transactionHash = await sendTransaction(client, transactionWithPaymasterData); if (args.onTransactionSent) { noThrow(() => args.onTransactionSent?.(transactionHash)); } const transactionReceipt = await waitForTransactionReceipt(client, { hash: transactionHash }); if (transactionReceipt.status !== "success") throw new Error("createSession transaction reverted"); return { transactionReceipt, }; }; export type RevokeSessionArgs = { sessionId: Hash; contracts: { session: Address; // session module }; paymaster?: { address: Address; paymasterInput?: Hex; }; onTransactionSent?: (hash: Hash) => void; paymasterHandler?: CustomPaymasterHandler; }; export type RevokeSessionReturnType = { transactionReceipt: TransactionReceipt; }; export const revokeSession = async < transport extends Transport, chain extends Chain, account extends Account, >(client: Client<transport, chain, account>, args: Prettify<RevokeSessionArgs>): Promise<Prettify<RevokeSessionReturnType>> => { const callData = encodeFunctionData({ abi: SessionKeyValidatorAbi, functionName: "revokeKey", args: [args.sessionId], }); const sendTransactionArgs = { account: client.account, to: args.contracts.session, paymaster: args.paymaster?.address, paymasterInput: args.paymaster?.address ? (args.paymaster?.paymasterInput || getGeneralPaymasterInput({ innerInput: "0x" })) : undefined, data: callData, gas: 10_000_000n, // TODO: Remove when gas estimation is fixed // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const transactionWithPaymasterData: any = await getTransactionWithPaymasterData( client.chain.id, client.account.address, sendTransactionArgs, args.paymasterHandler, ); const transactionHash = await sendTransaction(client, transactionWithPaymasterData); if (args.onTransactionSent) { noThrow(() => args.onTransactionSent?.(transactionHash)); } const transactionReceipt = await waitForTransactionReceipt(client, { hash: transactionHash }); if (transactionReceipt.status !== "success") throw new Error("createSession transaction reverted"); return { transactionReceipt, }; }; export type GetSessionStateArgs = { account: Address; sessionConfig: SessionConfig; contracts: { session: Address; // session module }; }; export type GetSessionStateReturnType = { sessionState: SessionState; }; export const getSessionState = async < transport extends Transport, chain extends Chain, >(client: Client<transport, chain>, args: Prettify<GetSessionStateArgs>): Promise<Prettify<GetSessionStateReturnType>> => { const sessionState = await readContract(client, { address: args.contracts.session, abi: SessionKeyValidatorAbi, functionName: "sessionState", args: [args.account, args.sessionConfig], }); return { sessionState: sessionState as SessionState, }; }; export type CheckSessionStateArgs = { sessionConfig: SessionConfig; sessionState: SessionState; onSessionStateChange: SessionStateEventCallback; sessionNotifyTimeout?: NodeJS.Timeout; }; export type CheckSessionStateReturnType = { newTimeout?: NodeJS.Timeout; }; /** * Checks the current session state and sets up expiry notification. * This function will trigger the callback with the session state. */ export const sessionStateNotify = (args: Prettify<CheckSessionStateArgs>): CheckSessionStateReturnType => { // Generate a session ID for tracking timeouts const { sessionState } = args; const now = BigInt(Math.floor(Date.now() / 1000)); // Check session status if (sessionState.status === SessionStatus.NotInitialized) { // Not initialized args.onSessionStateChange({ type: SessionEventType.Inactive, message: "Session is not initialized", }); } else if (sessionState.status === SessionStatus.Closed) { // Closed/Revoked args.onSessionStateChange({ type: SessionEventType.Revoked, message: "Session has been revoked", }); } else if (args.sessionConfig.expiresAt <= now) { // Session has expired args.onSessionStateChange({ type: SessionEventType.Expired, message: "Session has expired", }); } else { // Session is active, set up expiry notification const timeToExpiry = Number(args.sessionConfig.expiresAt - now) * 1000; if (args.sessionNotifyTimeout) { clearTimeout(args.sessionNotifyTimeout); } const newTimeout = setTimeout(() => { args.onSessionStateChange({ type: SessionEventType.Expired, message: "Session has expired", }); }, timeToExpiry); return { newTimeout, }; } return {}; };