zksync-sso
Version:
ZKsync Smart Sign On SDK
123 lines (110 loc) • 4.14 kB
text/typescript
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);
},
};
}