mirror of
https://github.com/logos-messaging/examples.waku.org.git
synced 2026-01-02 12:53:08 +00:00
add contract hooks, add store fields, fix multiple downloads issue
This commit is contained in:
parent
0be9df014f
commit
690cb1ae59
16
examples/rln-js/package-lock.json
generated
16
examples/rln-js/package-lock.json
generated
@ -17,6 +17,7 @@
|
||||
"zustand": "^4.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@metamask/types": "^1.1.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
@ -958,6 +959,15 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@metamask/types": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@metamask/types/-/types-1.1.0.tgz",
|
||||
"integrity": "sha512-EEV/GjlYkOSfSPnYXfOosxa3TqYtIW3fhg6jdw+cok/OhMgNn4wCfbENFqjytrHMU2f7ZKtBAvtiP5V8H44sSw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "13.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz",
|
||||
@ -6742,6 +6752,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"@metamask/types": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@metamask/types/-/types-1.1.0.tgz",
|
||||
"integrity": "sha512-EEV/GjlYkOSfSPnYXfOosxa3TqYtIW3fhg6jdw+cok/OhMgNn4wCfbENFqjytrHMU2f7ZKtBAvtiP5V8H44sSw==",
|
||||
"dev": true
|
||||
},
|
||||
"@next/env": {
|
||||
"version": "13.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz",
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"zustand": "^4.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@metamask/types": "^1.1.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
|
||||
@ -1,23 +1,29 @@
|
||||
import { Block, BlockTypes } from "@/components/Block";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Subtitle } from "@/components/Subtitle";
|
||||
import { useContract, useStore } from "@/hooks";
|
||||
|
||||
export const Blockchain: React.FunctionComponent<{}> = () => {
|
||||
const { ethAccount, lastMembershipID } = useStore();
|
||||
const { onFetchContract } = useContract();
|
||||
|
||||
return (
|
||||
<Block className="mt-10">
|
||||
<Block className="mb-3" type={BlockTypes.FlexHorizontal}>
|
||||
<Subtitle>Contract</Subtitle>
|
||||
<Button>Fetch state</Button>
|
||||
<Button onClick={onFetchContract}>Fetch state</Button>
|
||||
</Block>
|
||||
|
||||
<Block type={BlockTypes.FlexHorizontal}>
|
||||
<p>Your address</p>
|
||||
<code>Not loaded yet</code>
|
||||
<code>{ethAccount || "Not loaded yet"}</code>
|
||||
</Block>
|
||||
|
||||
<Block type={BlockTypes.FlexHorizontal}>
|
||||
<p>Latest membership ID on contract</p>
|
||||
<code>Not loaded yet</code>
|
||||
<code>
|
||||
{lastMembershipID === -1 ? "Not loaded yet" : lastMembershipID}
|
||||
</code>
|
||||
</Block>
|
||||
</Block>
|
||||
);
|
||||
|
||||
@ -2,16 +2,17 @@ import { Block, BlockTypes } from "@/components/Block";
|
||||
import { Title } from "@/components/Title";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Status } from "@/components/Status";
|
||||
import { useStore } from "@/hooks";
|
||||
import { useStore, useWallet } from "@/hooks";
|
||||
|
||||
export const Header: React.FunctionComponent<{}> = () => {
|
||||
const { appStatus } = useStore();
|
||||
const { onConnectWallet } = useWallet();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Block className="mb-5" type={BlockTypes.FlexHorizontal}>
|
||||
<Title>Waku RLN</Title>
|
||||
<Button>Connect Wallet</Button>
|
||||
<Button onClick={onConnectWallet}>Connect Wallet</Button>
|
||||
</Block>
|
||||
<Status text="Application status" mark={appStatus} />
|
||||
</>
|
||||
|
||||
@ -3,10 +3,11 @@ import { Block, BlockTypes } from "@/components/Block";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Status } from "@/components/Status";
|
||||
import { Subtitle } from "@/components/Subtitle";
|
||||
import { useStore } from "@/hooks";
|
||||
import { useStore, useWallet } from "@/hooks";
|
||||
|
||||
export const Keystore: React.FunctionComponent<{}> = () => {
|
||||
const { keystoreStatus, keystoreCredentials } = useStore();
|
||||
const { onGenerateCredentials } = useWallet();
|
||||
|
||||
const credentialsNodes = React.useMemo(
|
||||
() =>
|
||||
@ -46,7 +47,9 @@ export const Keystore: React.FunctionComponent<{}> = () => {
|
||||
|
||||
<Block className="mt-4">
|
||||
<p className="text-s mb-2">Generate new credentials from wallet</p>
|
||||
<Button>Generate new credentials</Button>
|
||||
<Button onClick={onGenerateCredentials}>
|
||||
Generate new credentials
|
||||
</Button>
|
||||
<Button className="ml-5">Register credentials</Button>
|
||||
</Block>
|
||||
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { Block, BlockTypes } from "@/components/Block";
|
||||
import { useStore } from "@/hooks";
|
||||
import { bytesToHex } from "@waku/utils/bytes";
|
||||
|
||||
export const KeystoreDetails: React.FunctionComponent<{}> = () => {
|
||||
const { credentials } = useStore();
|
||||
|
||||
return (
|
||||
<Block className="mt-5">
|
||||
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>
|
||||
<p>Keystore</p>
|
||||
<p>Keystore hash</p>
|
||||
<code>none</code>
|
||||
</Block>
|
||||
|
||||
@ -15,23 +19,27 @@ export const KeystoreDetails: React.FunctionComponent<{}> = () => {
|
||||
|
||||
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>
|
||||
<p>Secret Hash</p>
|
||||
<code>none</code>
|
||||
<code>{renderBytes(credentials?.IDSecretHash)}</code>
|
||||
</Block>
|
||||
|
||||
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>
|
||||
<p>Commitment</p>
|
||||
<code>none</code>
|
||||
<code>{renderBytes(credentials?.IDCommitment)}</code>
|
||||
</Block>
|
||||
|
||||
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>
|
||||
<p>Nullifier</p>
|
||||
<code>none</code>
|
||||
<code>{renderBytes(credentials?.IDNullifier)}</code>
|
||||
</Block>
|
||||
|
||||
<Block className="mt-3" type={BlockTypes.FlexHorizontal}>
|
||||
<p>Trapdoor</p>
|
||||
<code>none</code>
|
||||
<code>{renderBytes(credentials?.IDTrapdoor)}</code>
|
||||
</Block>
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
function renderBytes(bytes: undefined | Uint8Array): string {
|
||||
return bytes ? bytesToHex(bytes) : "none";
|
||||
}
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export { useStore } from "./useStore";
|
||||
export { useRLN } from "./useRLN";
|
||||
export { useWallet } from "./useWallet";
|
||||
export { useContract } from "./useContract";
|
||||
|
||||
34
examples/rln-js/src/hooks/useContract.ts
Normal file
34
examples/rln-js/src/hooks/useContract.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { useStore } from "./useStore";
|
||||
import { useRLN } from "./useRLN";
|
||||
|
||||
type UseContractResult = {
|
||||
onFetchContract: () => void;
|
||||
};
|
||||
|
||||
export const useContract = (): UseContractResult => {
|
||||
const { rln } = useRLN();
|
||||
const { setLastMembershipID } = useStore();
|
||||
|
||||
const onFetchContract = React.useCallback(async () => {
|
||||
if (!rln?.rlnContract || !rln?.rlnInstance) {
|
||||
console.log("Cannot fetch contract info, no contract found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// disable button
|
||||
await rln.rlnContract.fetchMembers(rln.rlnInstance);
|
||||
// enable button
|
||||
rln.rlnContract.subscribeToMembers(rln.rlnInstance);
|
||||
|
||||
const last = rln.rlnContract.members.at(-1);
|
||||
|
||||
if (last) {
|
||||
setLastMembershipID(last.index.toNumber());
|
||||
}
|
||||
}, [rln, setLastMembershipID]);
|
||||
|
||||
return {
|
||||
onFetchContract,
|
||||
};
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { RLN, RLNEventsNames } from "@/services/rln";
|
||||
import { rln, RLN, RLNEventsNames } from "@/services/rln";
|
||||
import { useStore } from "./useStore";
|
||||
|
||||
type RLNResult = {
|
||||
@ -23,8 +23,6 @@ export const useRLN = (): RLNResult => {
|
||||
setKeystoreStatus(event?.detail);
|
||||
};
|
||||
|
||||
const rln = new RLN();
|
||||
|
||||
rln.addEventListener(RLNEventsNames.Status, statusListener);
|
||||
rln.addEventListener(RLNEventsNames.Keystore, keystoreListener);
|
||||
|
||||
|
||||
@ -1,8 +1,17 @@
|
||||
import { create } from "zustand";
|
||||
import { IdentityCredential } from "@waku/rln";
|
||||
|
||||
type StoreResult = {
|
||||
appStatus: string;
|
||||
setAppStatus: (v: string) => void;
|
||||
ethAccount: string;
|
||||
setEthAccount: (v: string) => void;
|
||||
chainID: number;
|
||||
setChainID: (v: number) => void;
|
||||
lastMembershipID: number;
|
||||
setLastMembershipID: (v: number) => void;
|
||||
credentials: undefined | IdentityCredential;
|
||||
setCredentials: (v: undefined | IdentityCredential) => void;
|
||||
|
||||
keystoreStatus: string;
|
||||
setKeystoreStatus: (v: string) => void;
|
||||
@ -21,6 +30,17 @@ export const useStore = create<StoreResult>((set) => {
|
||||
const generalModule = {
|
||||
appStatus: DEFAULT_VALUE,
|
||||
setAppStatus: (v: string) => set((state) => ({ ...state, appStatus: v })),
|
||||
|
||||
ethAccount: "",
|
||||
setEthAccount: (v: string) => set((state) => ({ ...state, ethAccount: v })),
|
||||
chainID: -1,
|
||||
setChainID: (v: number) => set((state) => ({ ...state, chainID: v })),
|
||||
lastMembershipID: -1,
|
||||
setLastMembershipID: (v: number) =>
|
||||
set((state) => ({ ...state, lastMembershipID: v })),
|
||||
credentials: undefined,
|
||||
setCredentials: (v: undefined | IdentityCredential) =>
|
||||
set((state) => ({ ...state, credentials: v })),
|
||||
};
|
||||
|
||||
const wakuModule = {
|
||||
|
||||
80
examples/rln-js/src/hooks/useWallet.ts
Normal file
80
examples/rln-js/src/hooks/useWallet.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import React from "react";
|
||||
import { useStore } from "./useStore";
|
||||
import { isEthereumEvenEmitterValid } from "@/utils/ethereum";
|
||||
import { useRLN } from "./useRLN";
|
||||
import { SIGNATURE_MESSAGE } from "@/constants";
|
||||
|
||||
type UseWalletResult = {
|
||||
onConnectWallet: () => void;
|
||||
onGenerateCredentials: () => void;
|
||||
};
|
||||
|
||||
export const useWallet = (): UseWalletResult => {
|
||||
const { rln } = useRLN();
|
||||
const { setEthAccount, setChainID, setCredentials } = useStore();
|
||||
|
||||
React.useEffect(() => {
|
||||
const ethereum = window.ethereum;
|
||||
if (!isEthereumEvenEmitterValid(ethereum)) {
|
||||
console.log("Cannot subscribe to ethereum events.");
|
||||
return;
|
||||
}
|
||||
|
||||
const onAccountsChanged = (accounts: string[]) => {
|
||||
setEthAccount(accounts[0] || "");
|
||||
};
|
||||
ethereum.on("accountsChanged", onAccountsChanged);
|
||||
|
||||
const onChainChanged = (chainID: string) => {
|
||||
const ID = parseInt(chainID, 16);
|
||||
setChainID(ID);
|
||||
};
|
||||
ethereum.on("chainChanged", onChainChanged);
|
||||
|
||||
return () => {
|
||||
ethereum.removeListener("chainChanged", onChainChanged);
|
||||
ethereum.removeListener("accountsChanged", onAccountsChanged);
|
||||
};
|
||||
}, [setEthAccount, setChainID]);
|
||||
|
||||
const onConnectWallet = React.useCallback(async () => {
|
||||
if (!rln?.ethProvider) {
|
||||
console.log("Cannot connect wallet, no provider found.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const accounts = await rln.ethProvider.send("eth_requestAccounts", []);
|
||||
setEthAccount(accounts[0] || "");
|
||||
const network = await rln.ethProvider.getNetwork();
|
||||
setChainID(network.chainId);
|
||||
} catch (error) {
|
||||
console.error("Failed to connect to account: ", error);
|
||||
}
|
||||
}, [rln, setEthAccount, setChainID]);
|
||||
|
||||
const onGenerateCredentials = React.useCallback(async () => {
|
||||
if (!rln?.ethProvider) {
|
||||
console.log("Cannot generate credentials, no provider found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const signer = rln.ethProvider.getSigner();
|
||||
const signature = await signer.signMessage(
|
||||
`${SIGNATURE_MESSAGE}. Nonce: ${randomNumber()}`
|
||||
);
|
||||
const credentials = await rln.rlnInstance?.generateSeededIdentityCredential(
|
||||
signature
|
||||
);
|
||||
setCredentials(credentials);
|
||||
}, [rln, setCredentials]);
|
||||
|
||||
return {
|
||||
onConnectWallet,
|
||||
onGenerateCredentials,
|
||||
};
|
||||
};
|
||||
|
||||
function randomNumber(): number {
|
||||
return Math.ceil(Math.random() * 1000);
|
||||
}
|
||||
14
examples/rln-js/src/react-app-env.d.ts
vendored
Normal file
14
examples/rln-js/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
/// <reference types="react-scripts" />
|
||||
|
||||
type EthereumEvents = "accountsChanged" | "chainChanged";
|
||||
type EthereumEventListener = (v: any) => void;
|
||||
|
||||
type Ethereum = {
|
||||
request: () => void;
|
||||
on: (name: EthereumEvents, fn: EthereumEventListener) => void;
|
||||
removeListener: (name: EthereumEvents, fn: EthereumEventListener) => void;
|
||||
};
|
||||
|
||||
interface Window {
|
||||
ethereum: Ethereum;
|
||||
}
|
||||
@ -34,17 +34,18 @@ type IRLN = {
|
||||
|
||||
export class RLN implements IRLN {
|
||||
private readonly emitter = new EventTarget();
|
||||
private readonly ethProvider: ethers.providers.Web3Provider;
|
||||
public readonly ethProvider: ethers.providers.Web3Provider;
|
||||
|
||||
private rlnInstance: undefined | RLNInstance;
|
||||
private rlnContract: undefined | RLNContract;
|
||||
private keystore: undefined | Keystore;
|
||||
public rlnInstance: undefined | RLNInstance;
|
||||
public rlnContract: undefined | RLNContract;
|
||||
public keystore: undefined | Keystore;
|
||||
|
||||
private initialized = false;
|
||||
private initializing = false;
|
||||
|
||||
public constructor() {
|
||||
const ethereum = (<any>window)
|
||||
.ethereum as ethers.providers.ExternalProvider;
|
||||
const ethereum =
|
||||
window.ethereum as unknown as ethers.providers.ExternalProvider;
|
||||
if (!isBrowserProviderValid(ethereum)) {
|
||||
throw Error(
|
||||
"Invalid Ethereum provider present on the page. Check if MetaMask is connected."
|
||||
@ -54,19 +55,20 @@ export class RLN implements IRLN {
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
console.info("RLN is initialized.");
|
||||
if (this.initialized || this.initializing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const rlnInstance = await this.initRLNWasm();
|
||||
// await this.initRLNContract(rlnInstance);
|
||||
this.initializing = true;
|
||||
const rlnInstance = await this.initRLNWasm();
|
||||
await this.initRLNContract(rlnInstance);
|
||||
|
||||
this.emitStatusEvent(StatusEventPayload.RLN_INITIALIZED);
|
||||
|
||||
this.initKeystore();
|
||||
|
||||
this.initialized = true;
|
||||
this.initializing = false;
|
||||
}
|
||||
|
||||
private async initRLNWasm(): Promise<RLNInstance> {
|
||||
@ -131,3 +133,5 @@ export class RLN implements IRLN {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const rln = new RLN();
|
||||
|
||||
@ -1,6 +1,17 @@
|
||||
export const isBrowserProviderValid = (obj: any) => {
|
||||
export const isBrowserProviderValid = (obj: any): boolean => {
|
||||
if (obj && typeof obj.request === "function") {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isEthereumEvenEmitterValid = (obj: any): boolean => {
|
||||
if (
|
||||
obj &&
|
||||
typeof obj.on === "function" &&
|
||||
typeof obj.removeListener === "function"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user