UNPKG

zksync-sso

Version:
123 lines (110 loc) 4.14 kB
import { EventEmitter } from "eventemitter3"; import type { Address, Chain, Transport } from "viem"; import { toHex } from "viem"; import { PopupCommunicator } from "../communicator/PopupCommunicator.js"; import { serializeError, standardErrors } from "../errors/index.js"; import type { CustomPaymasterHandler } from "../paymaster/index.js"; import { getFavicon, getWebsiteName } from "../utils/helpers.js"; import type { AppMetadata, ProviderInterface, RequestArguments, } from "./interface.js"; import { type ExtractReturnType, type Method } from "./rpc.js"; import type { SessionPreferences } from "./session/index.js"; import { Signer } from "./Signer.js"; const DEFAULT_AUTH_SERVER_URL = "https://auth-test.zksync.dev/confirm"; export type WalletProviderConstructorOptions = { metadata: Partial<AppMetadata> | undefined; chains: readonly Chain[]; transports?: Record<number, Transport>; session?: SessionPreferences | (() => SessionPreferences | Promise<SessionPreferences>); authServerUrl?: string; paymasterHandler?: CustomPaymasterHandler; }; export class WalletProvider extends EventEmitter implements ProviderInterface { readonly isZksyncSso = true; private signer: Signer; constructor({ metadata, chains, transports, session, authServerUrl, paymasterHandler }: WalletProviderConstructorOptions) { super(); const communicator = new PopupCommunicator(authServerUrl || DEFAULT_AUTH_SERVER_URL); this.signer = new Signer({ metadata: () => ({ name: metadata?.name || getWebsiteName() || "Unknown DApp", icon: metadata?.icon || getFavicon(), configData: metadata?.configData || {}, }), updateListener: this.updateListener, communicator: communicator, chains, transports, session: typeof session === "object" ? () => session : session, paymasterHandler, }); } protected get chain() { return this.signer.chain; } public get connected() { return this.signer.accounts.length > 0; } public getClient(parameters?: { chainId?: number }) { return this.signer.getClient(parameters); } public async request<M extends Method>(request: RequestArguments<M>): Promise<ExtractReturnType<M>> { try { switch (request.method) { case "eth_requestAccounts": { return await this.handshake() as ExtractReturnType<M>; } case "personal_sign": case "eth_accounts": case "eth_estimateGas": case "eth_signTransaction": case "eth_sendTransaction": case "eth_signTypedData_v4": case "wallet_addEthereumChain": case "wallet_switchEthereumChain": case "wallet_watchAsset": case "wallet_getCapabilities": case "wallet_sendCalls": case "wallet_showCallsStatus": { if (!this.connected) { throw standardErrors.provider.unauthorized( "Must call 'eth_requestAccounts' before other methods", ); } return await this.signer.request(request) as ExtractReturnType<M>; } case "eth_chainId": case "net_version": { return toHex(this.chain.id) as ExtractReturnType<M>; } } throw standardErrors.rpc.methodNotSupported(`Method ${request.method} is not supported.`); } catch (error) { return Promise.reject(serializeError(error, request.method)); } } public async handshake(): Promise<Address[]> { if (this.connected) { this.emit("connect", { chainId: this.chain.id }); return this.signer.accounts; } const accounts = await this.signer.handshake(); this.emit("connect", { chainId: this.chain.id }); return accounts; } async disconnect(): Promise<void> { this.signer.disconnect(); this.emit("disconnect", standardErrors.provider.disconnected("User initiated disconnection")); } protected readonly updateListener = { onAccountsUpdate: (accounts: Address[]) => { this.emit("accountsChanged", accounts); }, onChainUpdate: (chainId: number) => { this.emit("chainChanged", chainId); }, }; }