diff --git a/examples/rln-js/src/app/home/components/Blockchain.tsx b/examples/rln-js/src/app/home/components/Blockchain.tsx index 979bb44..83c7d21 100644 --- a/examples/rln-js/src/app/home/components/Blockchain.tsx +++ b/examples/rln-js/src/app/home/components/Blockchain.tsx @@ -21,9 +21,7 @@ export const Blockchain: React.FunctionComponent<{}> = () => {

Latest membership ID on contract

- - {lastMembershipID === -1 ? "Not loaded yet" : lastMembershipID} - + {lastMembershipID || "Not loaded yet"}
); diff --git a/examples/rln-js/src/app/home/components/Keystore.tsx b/examples/rln-js/src/app/home/components/Keystore.tsx index 5463c3f..a82dc65 100644 --- a/examples/rln-js/src/app/home/components/Keystore.tsx +++ b/examples/rln-js/src/app/home/components/Keystore.tsx @@ -1,13 +1,13 @@ import React from "react"; import { Block, BlockTypes } from "@/components/Block"; import { Button } from "@/components/Button"; -import { Status } from "@/components/Status"; import { Subtitle } from "@/components/Subtitle"; import { useStore, useWallet } from "@/hooks"; export const Keystore: React.FunctionComponent<{}> = () => { - const { keystoreStatus, keystoreCredentials } = useStore(); - const { onGenerateCredentials } = useWallet(); + const { keystorePassword, setKeystorePassword, keystoreCredentials } = + useStore(); + const { onGenerateCredentials, onRegisterCredentials } = useWallet(); const credentialsNodes = React.useMemo( () => @@ -19,6 +19,13 @@ export const Keystore: React.FunctionComponent<{}> = () => { [keystoreCredentials] ); + const onPasswordChanged = React.useCallback( + (event: React.FormEvent) => { + setKeystorePassword(event.currentTarget.value); + }, + [setKeystorePassword] + ); + return ( @@ -29,8 +36,6 @@ export const Keystore: React.FunctionComponent<{}> = () => { - - @@ -50,7 +57,9 @@ export const Keystore: React.FunctionComponent<{}> = () => { - + diff --git a/examples/rln-js/src/app/home/components/KeystoreDetails.tsx b/examples/rln-js/src/app/home/components/KeystoreDetails.tsx index 5e478c6..2c340e8 100644 --- a/examples/rln-js/src/app/home/components/KeystoreDetails.tsx +++ b/examples/rln-js/src/app/home/components/KeystoreDetails.tsx @@ -3,18 +3,18 @@ import { useStore } from "@/hooks"; import { bytesToHex } from "@waku/utils/bytes"; export const KeystoreDetails: React.FunctionComponent<{}> = () => { - const { credentials } = useStore(); + const { credentials, activeCredential, activeMembershipID } = useStore(); return (

Keystore hash

- none + {activeCredential || "none"}

Membership ID

- none + {activeMembershipID || "none"}
diff --git a/examples/rln-js/src/app/home/page.tsx b/examples/rln-js/src/app/home/page.tsx index 1d15f14..88ac030 100644 --- a/examples/rln-js/src/app/home/page.tsx +++ b/examples/rln-js/src/app/home/page.tsx @@ -7,9 +7,6 @@ import { KeystoreDetails } from "./components/KeystoreDetails"; import { useRLN } from "@/hooks"; export default function Home() { - const { rln } = useRLN(); - console.log(rln); - return (
diff --git a/examples/rln-js/src/hooks/useRLN.ts b/examples/rln-js/src/hooks/useRLN.ts index a928c86..98322b3 100644 --- a/examples/rln-js/src/hooks/useRLN.ts +++ b/examples/rln-js/src/hooks/useRLN.ts @@ -1,3 +1,4 @@ +"use client"; import React from "react"; import { rln, RLN, RLNEventsNames } from "@/services/rln"; import { useStore } from "./useStore"; @@ -7,38 +8,39 @@ type RLNResult = { }; export const useRLN = (): RLNResult => { - const { setAppStatus, setKeystoreStatus } = useStore(); + const { setAppStatus, setKeystoreCredentials } = useStore(); const rlnRef = React.useRef(undefined); React.useEffect(() => { - if (rlnRef.current) { + if (rlnRef.current || !rln) { return; } let terminate = false; + const statusListener = (event: CustomEvent) => { setAppStatus(event?.detail); }; - const keystoreListener = (event: CustomEvent) => { - setKeystoreStatus(event?.detail); - }; - rln.addEventListener(RLNEventsNames.Status, statusListener); + + const keystoreListener = (event: CustomEvent) => { + setKeystoreCredentials(event?.detail || []); + }; rln.addEventListener(RLNEventsNames.Keystore, keystoreListener); const run = async () => { if (terminate) { return; } - await rln.init(); + await rln?.init(); rlnRef.current = rln; }; run(); return () => { terminate = true; - rln.removeEventListener(RLNEventsNames.Status, statusListener); - rln.removeEventListener(RLNEventsNames.Keystore, keystoreListener); + rln?.removeEventListener(RLNEventsNames.Status, statusListener); + rln?.removeEventListener(RLNEventsNames.Keystore, keystoreListener); }; }, [rlnRef, setAppStatus]); diff --git a/examples/rln-js/src/hooks/useStore.ts b/examples/rln-js/src/hooks/useStore.ts index d43cd55..98e59bd 100644 --- a/examples/rln-js/src/hooks/useStore.ts +++ b/examples/rln-js/src/hooks/useStore.ts @@ -6,19 +6,21 @@ type StoreResult = { setAppStatus: (v: string) => void; ethAccount: string; setEthAccount: (v: string) => void; - chainID: number; + chainID: undefined | number; setChainID: (v: number) => void; - lastMembershipID: number; + lastMembershipID: undefined | number; setLastMembershipID: (v: number) => void; credentials: undefined | IdentityCredential; setCredentials: (v: undefined | IdentityCredential) => void; - keystoreStatus: string; - setKeystoreStatus: (v: string) => void; + keystorePassword: undefined | string; + setKeystorePassword: (v: string) => void; activeCredential: string; keystoreCredentials: string[]; setKeystoreCredentials: (v: string[]) => void; setActiveCredential: (v: string) => void; + activeMembershipID: undefined | number; + setActiveMembershipID: (v: number) => void; wakuStatus: string; setWakuStatus: (v: string) => void; @@ -33,9 +35,9 @@ export const useStore = create((set) => { ethAccount: "", setEthAccount: (v: string) => set((state) => ({ ...state, ethAccount: v })), - chainID: -1, + chainID: undefined, setChainID: (v: number) => set((state) => ({ ...state, chainID: v })), - lastMembershipID: -1, + lastMembershipID: undefined, setLastMembershipID: (v: number) => set((state) => ({ ...state, lastMembershipID: v })), credentials: undefined, @@ -50,15 +52,18 @@ export const useStore = create((set) => { }; const keystoreModule = { - keystoreStatus: DEFAULT_VALUE, - setKeystoreStatus: (v: string) => - set((state) => ({ ...state, keystoreStatus: v })), + keystorePassword: undefined, + setKeystorePassword: (v: string) => + set((state) => ({ ...state, keystorePassword: v })), activeCredential: DEFAULT_VALUE, - keystoreCredentials: [], // ["277026D55D6F3988FB4E4695F1DCA2F59B012581A854FEE6035EE1566F898908", "59FDF2A610545099326E736269EA2E297BCA0B2BA4D68D245130BF10F9FFAC43", "FC98D3EDD1CCB2AA4C25CCDDD18ADADC8C4BBA9BA11B9F652B2E5E9732D531D3"], setActiveCredential: (v: string) => set((state) => ({ ...state, activeCredential: v })), + keystoreCredentials: [], setKeystoreCredentials: (v: string[]) => set((state) => ({ ...state, keystoreCredentials: v })), + activeMembershipID: undefined, + setActiveMembershipID: (v: number) => + set((state) => ({ ...state, activeMembershipID: v })), }; return { diff --git a/examples/rln-js/src/hooks/useWallet.ts b/examples/rln-js/src/hooks/useWallet.ts index c90aa59..656b9cb 100644 --- a/examples/rln-js/src/hooks/useWallet.ts +++ b/examples/rln-js/src/hooks/useWallet.ts @@ -3,15 +3,27 @@ import { useStore } from "./useStore"; import { isEthereumEvenEmitterValid } from "@/utils/ethereum"; import { useRLN } from "./useRLN"; import { SIGNATURE_MESSAGE } from "@/constants"; +import { SEPOLIA_CONTRACT } from "@waku/rln"; +import { StatusEventPayload } from "@/services/rln"; type UseWalletResult = { onConnectWallet: () => void; onGenerateCredentials: () => void; + onRegisterCredentials: () => void; }; export const useWallet = (): UseWalletResult => { const { rln } = useRLN(); - const { setEthAccount, setChainID, setCredentials } = useStore(); + const { + keystorePassword, + credentials, + setEthAccount, + setChainID, + setCredentials, + setActiveCredential, + setActiveMembershipID, + setAppStatus, + } = useStore(); React.useEffect(() => { const ethereum = window.ethereum; @@ -69,9 +81,48 @@ export const useWallet = (): UseWalletResult => { setCredentials(credentials); }, [rln, setCredentials]); + const onRegisterCredentials = React.useCallback(async () => { + if (!credentials || !rln?.rlnContract || !keystorePassword) { + return; + } + + try { + setAppStatus(StatusEventPayload.CREDENTIALS_REGISTERING); + const membershipInfo = await rln.rlnContract.registerWithKey(credentials); + const membershipID = membershipInfo!.index.toNumber(); + const keystoreHash = await rln.keystore.addCredential( + { + membership: { + treeIndex: membershipID, + chainId: SEPOLIA_CONTRACT.chainId, + address: SEPOLIA_CONTRACT.address, + }, + identity: credentials, + }, + keystorePassword + ); + setActiveCredential(keystoreHash); + setActiveMembershipID(membershipID); + rln.saveKeystore(); + setAppStatus(StatusEventPayload.CREDENTIALS_REGISTERED); + } catch (error) { + setAppStatus(StatusEventPayload.CREDENTIALS_FAILURE); + console.error("Failed to register to RLN Contract: ", error); + return; + } + }, [ + credentials, + rln, + keystorePassword, + setActiveCredential, + setActiveMembershipID, + setAppStatus, + ]); + return { onConnectWallet, onGenerateCredentials, + onRegisterCredentials, }; }; diff --git a/examples/rln-js/src/services/rln.ts b/examples/rln-js/src/services/rln.ts index 628a957..79aa66b 100644 --- a/examples/rln-js/src/services/rln.ts +++ b/examples/rln-js/src/services/rln.ts @@ -12,10 +12,10 @@ import { isBrowserProviderValid } from "@/utils/ethereum"; export enum RLNEventsNames { Status = "status", - Keystore = "keystore", + Keystore = "keystore-changed", } -enum StatusEventPayload { +export enum StatusEventPayload { WASM_LOADING = "WASM Blob download in progress...", WASM_FAILED = "Failed to download WASM, check console", CONTRACT_LOADING = "Connecting to RLN contract", @@ -23,11 +23,15 @@ enum StatusEventPayload { RLN_INITIALIZED = "RLN dependencies initialized", KEYSTORE_LOCAL = "Keystore initialized from localStore", KEYSTORE_NEW = "New Keystore was initialized", + CREDENTIALS_REGISTERING = "Registering credentials...", + CREDENTIALS_REGISTERED = "Registered credentials", + CREDENTIALS_FAILURE = "Failed to register credentials, check console", } type EventListener = (event: CustomEvent) => void; type IRLN = { + saveKeystore: () => void; addEventListener: (name: RLNEventsNames, fn: EventListener) => void; removeEventListener: (name: RLNEventsNames, fn: EventListener) => void; }; @@ -38,7 +42,7 @@ export class RLN implements IRLN { public rlnInstance: undefined | RLNInstance; public rlnContract: undefined | RLNContract; - public keystore: undefined | Keystore; + public readonly keystore: Keystore; private initialized = false; private initializing = false; @@ -52,6 +56,7 @@ export class RLN implements IRLN { ); } this.ethProvider = new ethers.providers.Web3Provider(ethereum, "any"); + this.keystore = this.initKeystore(); } public async init(): Promise { @@ -65,7 +70,8 @@ export class RLN implements IRLN { this.emitStatusEvent(StatusEventPayload.RLN_INITIALIZED); - this.initKeystore(); + // emit keystore keys once app is ready + this.emitKeystoreKeys(); this.initialized = true; this.initializing = false; @@ -100,17 +106,11 @@ export class RLN implements IRLN { } } - private initKeystore(): void { + private initKeystore(): Keystore { const localKeystoreString = localStorage.getItem("keystore"); const _keystore = Keystore.fromString(localKeystoreString || ""); - if (localKeystoreString) { - this.emitKeystoreStatusEvent(StatusEventPayload.KEYSTORE_LOCAL); - } else { - this.emitKeystoreStatusEvent(StatusEventPayload.KEYSTORE_NEW); - } - - this.keystore = _keystore || Keystore.create(); + return _keystore || Keystore.create(); } public addEventListener(name: RLNEventsNames, fn: EventListener) { @@ -127,11 +127,17 @@ export class RLN implements IRLN { ); } - private emitKeystoreStatusEvent(payload: StatusEventPayload) { + private emitKeystoreKeys() { + const credentials = Object.keys(this.keystore.toObject().credentials || {}); this.emitter.dispatchEvent( - new CustomEvent(RLNEventsNames.Keystore, { detail: payload }) + new CustomEvent(RLNEventsNames.Keystore, { detail: credentials }) ); } + + public async saveKeystore() { + localStorage.setItem("keystore", this.keystore.toString()); + this.emitKeystoreKeys(); + } } -export const rln = new RLN(); +export const rln = typeof window === "undefined" ? undefined : new RLN();