add features

This commit is contained in:
Sasha 2023-10-27 14:41:53 +02:00
parent 690cb1ae59
commit eecd6a29b9
No known key found for this signature in database
8 changed files with 118 additions and 50 deletions

View File

@ -21,9 +21,7 @@ export const Blockchain: React.FunctionComponent<{}> = () => {
<Block type={BlockTypes.FlexHorizontal}>
<p>Latest membership ID on contract</p>
<code>
{lastMembershipID === -1 ? "Not loaded yet" : lastMembershipID}
</code>
<code>{lastMembershipID || "Not loaded yet"}</code>
</Block>
</Block>
);

View File

@ -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<HTMLInputElement>) => {
setKeystorePassword(event.currentTarget.value);
},
[setKeystorePassword]
);
return (
<Block className="mt-10">
<Block type={BlockTypes.FlexHorizontal}>
@ -29,8 +36,6 @@ export const Keystore: React.FunctionComponent<{}> = () => {
</div>
</Block>
<Status text="Keystore status" mark={keystoreStatus} />
<Block className="mt-4">
<label
htmlFor="keystore-input"
@ -41,6 +46,8 @@ export const Keystore: React.FunctionComponent<{}> = () => {
<input
type="text"
id="keystore-input"
value={keystorePassword || ""}
onChange={onPasswordChanged}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm w-full rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
</Block>
@ -50,7 +57,9 @@ export const Keystore: React.FunctionComponent<{}> = () => {
<Button onClick={onGenerateCredentials}>
Generate new credentials
</Button>
<Button className="ml-5">Register credentials</Button>
<Button className="ml-5" onClick={onRegisterCredentials}>
Register credentials
</Button>
</Block>
<Block className="mt-4">

View File

@ -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 (
<Block className="mt-5">
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>
<p>Keystore hash</p>
<code>none</code>
<code>{activeCredential || "none"}</code>
</Block>
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>
<p>Membership ID</p>
<code>none</code>
<code>{activeMembershipID || "none"}</code>
</Block>
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>

View File

@ -7,9 +7,6 @@ import { KeystoreDetails } from "./components/KeystoreDetails";
import { useRLN } from "@/hooks";
export default function Home() {
const { rln } = useRLN();
console.log(rln);
return (
<main className="flex min-h-screen flex-col p-24 font-mono max-w-screen-lg m-auto">
<Header />

View File

@ -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 | RLN>(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]);

View File

@ -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<StoreResult>((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<StoreResult>((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 {

View File

@ -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,
};
};

View File

@ -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<void> {
@ -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();