diff --git a/packages/rln/package.json b/packages/rln/package.json index 393475635a..9eb5beaecd 100644 --- a/packages/rln/package.json +++ b/packages/rln/package.json @@ -44,8 +44,7 @@ "watch:test": "mocha --watch", "prepublish": "npm run build", "reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build", - "setup:contract-abi": "./generate_contract_abi.sh", - "generate:contract": "npx wagmi generate" + "setup:contract-abi": "./generate_contract_abi.sh" }, "engines": { "node": ">=22" diff --git a/packages/rln/src/contract/rln_base_contract.ts b/packages/rln/src/contract/rln_base_contract.ts index 760aa903b4..78bbbfd64b 100644 --- a/packages/rln/src/contract/rln_base_contract.ts +++ b/packages/rln/src/contract/rln_base_contract.ts @@ -3,15 +3,15 @@ import { type Address, decodeEventLog, getContract, - GetContractReturnType, + type GetContractReturnType, type Hash, - publicActions, - PublicClient, - WalletClient + type PublicClient, + type WalletClient } from "viem"; import { IdentityCredential } from "../identity.js"; -import { DecryptedCredentials } from "../keystore/types.js"; +import type { DecryptedCredentials } from "../keystore/types.js"; +import type { RpcClient } from "../utils/index.js"; import { DEFAULT_RATE_LIMIT, @@ -32,7 +32,7 @@ export class RLNBaseContract { typeof wakuRlnV2Abi, PublicClient | WalletClient >; - public rpcClient: WalletClient & PublicClient; + public rpcClient: RpcClient; private rateLimit: number; private minRateLimit?: number; private maxRateLimit?: number; @@ -45,8 +45,7 @@ export class RLNBaseContract { log.info("Initializing RLNBaseContract", { address, rateLimit }); - this.rpcClient = rpcClient.extend(publicActions) as WalletClient & - PublicClient; + this.rpcClient = rpcClient; this.contract = getContract({ address, abi: wakuRlnV2Abi, @@ -223,7 +222,7 @@ export class RLNBaseContract { try { await this.contract.simulate.extendMemberships([[idCommitmentBigInt]], { chain: this.rpcClient.chain, - account: (this.rpcClient as WalletClient).account!.address + account: this.rpcClient.account.address }); } catch (err) { throw new Error("Simulating extending membership failed: " + err); @@ -231,7 +230,7 @@ export class RLNBaseContract { const hash = await this.contract.write.extendMemberships( [[idCommitmentBigInt]], { - account: this.rpcClient.account!, + account: this.rpcClient.account, chain: this.rpcClient.chain } ); @@ -261,7 +260,7 @@ export class RLNBaseContract { [[idCommitmentBigInt], eraseFromMembershipSet], { chain: this.rpcClient.chain, - account: (this.rpcClient as WalletClient).account!.address + account: this.rpcClient.account.address } ); } catch (err) { @@ -272,7 +271,7 @@ export class RLNBaseContract { [[idCommitmentBigInt], eraseFromMembershipSet], { chain: this.rpcClient.chain, - account: this.rpcClient.account! + account: this.rpcClient.account } ); await this.rpcClient.waitForTransactionReceipt({ hash }); @@ -301,7 +300,7 @@ export class RLNBaseContract { [idCommitmentBigInt, rateLimit, []], { chain: this.rpcClient.chain, - account: (this.rpcClient as WalletClient).account!.address + account: this.rpcClient.account.address } ); } catch (err) { @@ -312,7 +311,7 @@ export class RLNBaseContract { [idCommitmentBigInt, rateLimit, []], { chain: this.rpcClient.chain, - account: this.rpcClient.account! + account: this.rpcClient.account } ); await this.rpcClient.waitForTransactionReceipt({ hash }); @@ -333,7 +332,7 @@ export class RLNBaseContract { try { await this.contract.simulate.withdraw([token as Address], { chain: this.rpcClient.chain, - account: (this.rpcClient as WalletClient).account!.address + account: this.rpcClient.account.address }); } catch (err) { throw new Error("Error simulating withdraw: " + err); @@ -341,7 +340,7 @@ export class RLNBaseContract { const hash = await this.contract.write.withdraw([token as Address], { chain: this.rpcClient.chain, - account: this.rpcClient.account! + account: this.rpcClient.account }); await this.rpcClient.waitForTransactionReceipt({ hash }); @@ -351,6 +350,12 @@ export class RLNBaseContract { identity: IdentityCredential ): Promise { try { + if (!this.rpcClient.account) { + throw new Error( + "Failed to registerWithIdentity: no account set in wallet client" + ); + } + log.info( `Registering identity with rate limit: ${this.rateLimit} messages/epoch` ); @@ -377,7 +382,7 @@ export class RLNBaseContract { [identity.IDCommitmentBigInt, this.rateLimit, []], { chain: this.rpcClient.chain, - account: (this.rpcClient as WalletClient).account!.address + account: this.rpcClient.account.address } ); @@ -385,7 +390,7 @@ export class RLNBaseContract { [identity.IDCommitmentBigInt, this.rateLimit, []], { chain: this.rpcClient.chain, - account: this.rpcClient.account! + account: this.rpcClient.account } ); diff --git a/packages/rln/src/contract/types.ts b/packages/rln/src/contract/types.ts index e07b09bb40..479ed412bb 100644 --- a/packages/rln/src/contract/types.ts +++ b/packages/rln/src/contract/types.ts @@ -1,4 +1,6 @@ -import { Address, WalletClient } from "viem"; +import { Address } from "viem"; + +import { RpcClient } from "../utils/index.js"; export type Member = { idCommitment: string; @@ -6,7 +8,7 @@ export type Member = { }; export interface RLNContractOptions { - rpcClient: WalletClient; + rpcClient: RpcClient; address: Address; rateLimit?: number; } diff --git a/packages/rln/src/credentials_manager.ts b/packages/rln/src/credentials_manager.ts index 993f4f89dc..25a61898f2 100644 --- a/packages/rln/src/credentials_manager.ts +++ b/packages/rln/src/credentials_manager.ts @@ -1,5 +1,5 @@ import { Logger } from "@waku/utils"; -import { publicActions, PublicClient, WalletClient } from "viem"; +import { publicActions } from "viem"; import { RLN_CONTRACT } from "./contract/constants.js"; import { RLNBaseContract } from "./contract/rln_base_contract.js"; @@ -10,7 +10,7 @@ import type { } from "./keystore/index.js"; import { KeystoreEntity, Password } from "./keystore/types.js"; import { RegisterMembershipOptions, StartRLNOptions } from "./types.js"; -import { createViemClientFromWindow } from "./utils/index.js"; +import { createViemClientFromWindow, RpcClient } from "./utils/index.js"; import { Zerokit } from "./zerokit.js"; const log = new Logger("rln:credentials"); @@ -24,7 +24,7 @@ export class RLNCredentialsManager { protected starting = false; public contract: undefined | RLNBaseContract; - public rpcClient: undefined | (WalletClient & PublicClient); + public rpcClient: undefined | RpcClient; protected keystore = Keystore.create(); public credentials: undefined | DecryptedCredentials; @@ -128,7 +128,7 @@ export class RLNCredentialsManager { protected async determineStartOptions( options: StartRLNOptions, credentials: KeystoreEntity | undefined - ): Promise { + ): Promise { let chainId = credentials?.membership.chainId; const address = credentials?.membership.address || @@ -140,11 +140,9 @@ export class RLNCredentialsManager { log.info(`Using Linea contract with chainId: ${chainId}`); } - let rpcClient: (WalletClient & PublicClient) | undefined = - options.rpcClient?.extend(publicActions) as WalletClient & PublicClient; - if (!rpcClient) { - rpcClient = await createViemClientFromWindow(); - } + const rpcClient: RpcClient = options.walletClient + ? options.walletClient.extend(publicActions) + : await createViemClientFromWindow(); const currentChainId = rpcClient.chain?.id; log.info(`Current chain ID: ${currentChainId}`); diff --git a/packages/rln/src/types.ts b/packages/rln/src/types.ts index 0e4f4f0980..55497de0a4 100644 --- a/packages/rln/src/types.ts +++ b/packages/rln/src/types.ts @@ -10,7 +10,7 @@ export type StartRLNOptions = { /** * If not set - will attempt to create from provider injected in window. */ - rpcClient?: WalletClient; + walletClient?: WalletClient; /** * If not set - will use default SEPOLIA_CONTRACT address. */ diff --git a/packages/rln/src/utils/index.ts b/packages/rln/src/utils/index.ts index aa6a17a0d2..a5cf32937e 100644 --- a/packages/rln/src/utils/index.ts +++ b/packages/rln/src/utils/index.ts @@ -1,4 +1,4 @@ -export { createViemClientFromWindow } from "./walletClient.js"; +export { createViemClientFromWindow, RpcClient } from "./rpcClient.js"; export { BytesUtils } from "./bytes.js"; export { sha256, poseidonHash } from "./hash.js"; export { dateToEpoch, epochIntToBytes, epochBytesToInt } from "./epoch.js"; diff --git a/packages/rln/src/utils/walletClient.ts b/packages/rln/src/utils/rpcClient.ts similarity index 52% rename from packages/rln/src/utils/walletClient.ts rename to packages/rln/src/utils/rpcClient.ts index bde294c276..585adf1a9d 100644 --- a/packages/rln/src/utils/walletClient.ts +++ b/packages/rln/src/utils/rpcClient.ts @@ -1,17 +1,25 @@ +import "viem/window"; import { + type Address, createWalletClient, custom, + PublicActions, publicActions, - PublicClient, WalletClient } from "viem"; import { lineaSepolia } from "viem/chains"; -export const createViemClientFromWindow = async (): Promise< - WalletClient & PublicClient -> => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const ethereum = (window as any).ethereum; +export type RpcClient = WalletClient & PublicActions; + +/** + * Checks window for injected Ethereum provider, requests user to connect, and creates an RPC client object + * capable of performing both read and write operations on the blockchain. + * + * If the wallet is not connected to the Linea Sepolia network, it will attempt to switch to it. + * If the wallet does not have the Linea Sepolia network added, it will attempt to add it. + */ +export const createViemClientFromWindow = async (): Promise => { + const ethereum = window.ethereum; if (!ethereum) { throw Error( @@ -21,10 +29,10 @@ export const createViemClientFromWindow = async (): Promise< const [account] = await ethereum.request({ method: "eth_requestAccounts" }); - const rpcClient = createWalletClient({ - account, + const rpcClient: RpcClient = createWalletClient({ + account: account as Address, chain: lineaSepolia, - transport: custom(ethereum) + transport: custom(window.ethereum!) }).extend(publicActions); // Ensure wallet is connected to Linea Sepolia @@ -39,6 +47,7 @@ export const createViemClientFromWindow = async (): Promise< error.code === 4902 ) { await rpcClient.addChain({ chain: lineaSepolia }); + await rpcClient.switchChain({ id: lineaSepolia.id }); } else { throw error; }