mirror of
https://github.com/logos-messaging/lab.waku.org.git
synced 2026-01-04 06:43:11 +00:00
feat: toggle between zerokit and light implementaitons
This commit is contained in:
parent
21692759ce
commit
67b8723904
57
examples/keystore-management/package-lock.json
generated
57
examples/keystore-management/package-lock.json
generated
@ -8,7 +8,7 @@
|
|||||||
"name": "waku-keystore-management",
|
"name": "waku-keystore-management",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@waku/rln": "0.0.2-a3e7f15.0",
|
"@waku/rln": "0.0.2-5c50ed7.0",
|
||||||
"next": "15.1.7",
|
"next": "15.1.7",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
@ -2265,16 +2265,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@waku/core": {
|
"node_modules/@waku/core": {
|
||||||
"version": "0.0.34-a3e7f15.0",
|
"version": "0.0.34-5c50ed7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.34-a3e7f15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.34-5c50ed7.0.tgz",
|
||||||
"integrity": "sha512-KWx7Epz7yAZKt9SJdUPjbZYY90312omLMXzBV9C3I8H94Q7u8C3dQep4wZsTBB+VGnVsKLYEyVmdorGZIgcubg==",
|
"integrity": "sha512-GWYRzLZTfWgwXrK/KCWU3bbsYiE7pCyjGHKOfTDnUX9Z0LEx9fs5nDF7LyK41uefrZ0c6zrth3TH+Rkawm5aHw==",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libp2p/ping": "2.0.1",
|
"@libp2p/ping": "2.0.1",
|
||||||
"@waku/enr": "0.0.28-a3e7f15.0",
|
"@waku/enr": "0.0.28-5c50ed7.0",
|
||||||
"@waku/interfaces": "0.0.29-a3e7f15.0",
|
"@waku/interfaces": "0.0.29-5c50ed7.0",
|
||||||
"@waku/proto": "0.0.9-a3e7f15.0",
|
"@waku/proto": "0.0.9-5c50ed7.0",
|
||||||
"@waku/utils": "0.0.22-a3e7f15.0",
|
"@waku/utils": "0.0.22-5c50ed7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"it-all": "^3.0.4",
|
"it-all": "^3.0.4",
|
||||||
"it-length-prefixed": "^9.0.4",
|
"it-length-prefixed": "^9.0.4",
|
||||||
@ -2312,9 +2312,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@waku/enr": {
|
"node_modules/@waku/enr": {
|
||||||
"version": "0.0.28-a3e7f15.0",
|
"version": "0.0.28-5c50ed7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.28-a3e7f15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.28-5c50ed7.0.tgz",
|
||||||
"integrity": "sha512-IdVrw04Bi/DJd7caYtYbIGnb26hZJdGMsB0hBaqwto+kgAjp0gzOBB+NSutiBIPsVH0ED4fr1FHjW/pIRjrCfA==",
|
"integrity": "sha512-Ms4FSw/fvkC63yHlikXCSRNUbxcAph4Os4uveVEnevV96B9k5GIoy5VzkLQbimWEdplgNBTiPzwI47O2AhvDrw==",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersproject/rlp": "^5.7.0",
|
"@ethersproject/rlp": "^5.7.0",
|
||||||
@ -2322,7 +2322,7 @@
|
|||||||
"@libp2p/peer-id": "^5.0.1",
|
"@libp2p/peer-id": "^5.0.1",
|
||||||
"@multiformats/multiaddr": "^12.0.0",
|
"@multiformats/multiaddr": "^12.0.0",
|
||||||
"@noble/secp256k1": "^1.7.1",
|
"@noble/secp256k1": "^1.7.1",
|
||||||
"@waku/utils": "0.0.22-a3e7f15.0",
|
"@waku/utils": "0.0.22-5c50ed7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"js-sha3": "^0.9.2"
|
"js-sha3": "^0.9.2"
|
||||||
},
|
},
|
||||||
@ -2339,21 +2339,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@waku/interfaces": {
|
"node_modules/@waku/interfaces": {
|
||||||
"version": "0.0.29-a3e7f15.0",
|
"version": "0.0.29-5c50ed7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.29-a3e7f15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.29-5c50ed7.0.tgz",
|
||||||
"integrity": "sha512-RgtxDuLDmzrQBq17fcFRVSpQoeiZocbNTXIhW39+zh5HhggYRu0694W1D2c3toGMTODggEyCfPdFLfC24cMfuQ==",
|
"integrity": "sha512-yfu9+SYLHpA7z9U1VuYlbhQVb5hBfUnqvAT7cLSbBKW4S13+gHaalZ3SFpZy8hYTqiIA5MTvXysWlSfLv6zIyw==",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@waku/proto": "0.0.9-a3e7f15.0"
|
"@waku/proto": "0.0.9-5c50ed7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@waku/proto": {
|
"node_modules/@waku/proto": {
|
||||||
"version": "0.0.9-a3e7f15.0",
|
"version": "0.0.9-5c50ed7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.9-a3e7f15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.9-5c50ed7.0.tgz",
|
||||||
"integrity": "sha512-nHUDg6QQ1OG1lEcx5o4HawkKWZl8eUd0gU7amk7reAJ64Y0I2UkblQt+FzUM3v8RinZnFUCweRKy1j+/ygiPPw==",
|
"integrity": "sha512-3QfqbglgWjotrlzrNgtbiXDnxOOP68Bew2OZMhDW8bF7bUdBsq1Y8eih6eJPEcQ/8EP53OQ+gJ0FFv8BTaKLrw==",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protons-runtime": "^5.4.0"
|
"protons-runtime": "^5.4.0"
|
||||||
@ -2363,14 +2363,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@waku/rln": {
|
"node_modules/@waku/rln": {
|
||||||
"version": "0.0.2-a3e7f15.0",
|
"version": "0.0.2-5c50ed7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@waku/rln/-/rln-0.0.2-a3e7f15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@waku/rln/-/rln-0.0.2-5c50ed7.0.tgz",
|
||||||
"integrity": "sha512-IK0CBJ16XBnODSc1uIaVmCMejSqQbVLEIzP6HdvrGH+9bJuyUzBLL7hkceAlMNPsOwZXb/fj0VR3DvzW1x9rKQ==",
|
"integrity": "sha512-8C1OevAJMgZn7FWjqsQIMS5PCgAKlr9Dcs4nyXCvTaHBptkFWzVfhVa7hdz2EpcOtlYD1HqsvDeT17S15m1lmA==",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainsafe/bls-keystore": "3.0.0",
|
"@chainsafe/bls-keystore": "3.0.0",
|
||||||
"@waku/core": "0.0.34-a3e7f15.0",
|
"@noble/hashes": "^1.2.0",
|
||||||
"@waku/utils": "0.0.22-a3e7f15.0",
|
"@waku/core": "0.0.34-5c50ed7.0",
|
||||||
|
"@waku/utils": "0.0.22-5c50ed7.0",
|
||||||
"@waku/zerokit-rln-wasm": "^0.0.13",
|
"@waku/zerokit-rln-wasm": "^0.0.13",
|
||||||
"ethereum-cryptography": "^3.1.0",
|
"ethereum-cryptography": "^3.1.0",
|
||||||
"ethers": "^5.7.2",
|
"ethers": "^5.7.2",
|
||||||
@ -2382,13 +2383,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@waku/utils": {
|
"node_modules/@waku/utils": {
|
||||||
"version": "0.0.22-a3e7f15.0",
|
"version": "0.0.22-5c50ed7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.22-a3e7f15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.22-5c50ed7.0.tgz",
|
||||||
"integrity": "sha512-N3JiVD5mzJklheZAyVgMrE/RqrFQjUQ13b24c1XTLGgqrwXAVoLxPK47EQQK/hAt6OxX1ql2f4wHMYLP3mu1Tw==",
|
"integrity": "sha512-hD+XO7lZ86OL9zFEzZvBy6pmgZlmNw7g1xeoui3FOauv6+zL///tarjLrmkPWh+eLJHdalVvTF9D/zkUWbK9SA==",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "^1.3.2",
|
"@noble/hashes": "^1.3.2",
|
||||||
"@waku/interfaces": "0.0.29-a3e7f15.0",
|
"@waku/interfaces": "0.0.29-5c50ed7.0",
|
||||||
"chai": "^4.3.10",
|
"chai": "^4.3.10",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"uint8arrays": "^5.0.1"
|
"uint8arrays": "^5.0.1"
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@waku/rln": "0.0.2-a3e7f15.0",
|
"@waku/rln": "0.0.2-5c50ed7.0",
|
||||||
"next": "15.1.7",
|
"next": "15.1.7",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import type { Metadata } from "next";
|
|||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { WalletProvider } from "../contexts/WalletContext";
|
import { WalletProvider } from "../contexts/WalletContext";
|
||||||
import { RLNProvider } from "../contexts/RLNContext";
|
import { RLNUnifiedProvider } from "../contexts/RLNUnifiedContext2";
|
||||||
|
import { RLNImplementationProvider } from "../contexts/RLNImplementationContext";
|
||||||
import { Header } from "../components/Header";
|
import { Header } from "../components/Header";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
@ -31,14 +32,16 @@ export default function RootLayout({
|
|||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
<WalletProvider>
|
<WalletProvider>
|
||||||
<RLNProvider>
|
<RLNImplementationProvider>
|
||||||
<div className="flex flex-col min-h-screen">
|
<RLNUnifiedProvider>
|
||||||
<Header />
|
<div className="flex flex-col min-h-screen">
|
||||||
<main className="flex-grow">
|
<Header />
|
||||||
{children}
|
<main className="flex-grow">
|
||||||
</main>
|
{children}
|
||||||
</div>
|
</main>
|
||||||
</RLNProvider>
|
</div>
|
||||||
|
</RLNUnifiedProvider>
|
||||||
|
</RLNImplementationProvider>
|
||||||
</WalletProvider>
|
</WalletProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import RLNMembershipRegistration from '../components/RLNMembershipRegistration';
|
import RLNMembershipRegistration from '../components/RLNMembershipRegistration';
|
||||||
import { WalletInfo } from '../components/WalletInfo';
|
import { WalletInfo } from '../components/WalletInfo';
|
||||||
|
import { RLNImplementationToggle } from '../components/RLNImplementationToggle';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
@ -9,6 +10,12 @@ export default function Home() {
|
|||||||
<h2 className="text-2xl font-bold text-center text-gray-900 dark:text-white mb-6">Waku Keystore Management</h2>
|
<h2 className="text-2xl font-bold text-center text-gray-900 dark:text-white mb-6">Waku Keystore Management</h2>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
|
{/* RLN Implementation Toggle */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">RLN Implementation</h3>
|
||||||
|
<RLNImplementationToggle />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Wallet Information Section */}
|
{/* Wallet Information Section */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Wallet Connection</h3>
|
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Wallet Connection</h3>
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRLNImplementation, RLNImplementationType } from '../contexts/RLNImplementationContext';
|
||||||
|
|
||||||
|
export function RLNImplementationToggle() {
|
||||||
|
const { implementation, setImplementation } = useRLNImplementation();
|
||||||
|
|
||||||
|
const handleToggle = (newImplementation: RLNImplementationType) => {
|
||||||
|
setImplementation(newImplementation);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-4 p-3 bg-gray-100 dark:bg-gray-800 rounded-lg">
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
RLN Implementation:
|
||||||
|
</span>
|
||||||
|
<div className="flex rounded-md shadow-sm" role="group">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleToggle('standard')}
|
||||||
|
className={`px-4 py-2 text-sm font-medium rounded-l-lg ${
|
||||||
|
implementation === 'standard'
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Standard
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleToggle('light')}
|
||||||
|
className={`px-4 py-2 text-sm font-medium rounded-r-lg ${
|
||||||
|
implementation === 'light'
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Light
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{implementation === 'standard' ? (
|
||||||
|
<span>Using full RLN implementation</span>
|
||||||
|
) : (
|
||||||
|
<span>Using lightweight RLN implementation</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRLN } from '../contexts/RLNContext';
|
import { useRLN } from '../contexts/RLNUnifiedContext2';
|
||||||
import { useWallet } from '../contexts/WalletContext';
|
import { useWallet } from '../contexts/WalletContext';
|
||||||
import { DecryptedCredentials } from '@waku/rln';
|
import { DecryptedCredentials } from '@waku/rln';
|
||||||
|
|
||||||
|
|||||||
26
examples/keystore-management/src/contexts/RLNFactory.tsx
Normal file
26
examples/keystore-management/src/contexts/RLNFactory.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createRLN, RLNLightInstance } from '@waku/rln';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
|
// Define a unified interface that both implementations must support
|
||||||
|
export interface UnifiedRLNInstance {
|
||||||
|
contract: {
|
||||||
|
address: string;
|
||||||
|
membershipFee?: () => Promise<ethers.BigNumber>;
|
||||||
|
};
|
||||||
|
start: (options: { signer: ethers.Signer }) => Promise<void>;
|
||||||
|
// Both implementations use registerMembership but with different parameters
|
||||||
|
registerMembership: (options: { signature: string }) => Promise<Record<string, unknown>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a factory function that creates the appropriate RLN implementation
|
||||||
|
export async function createRLNImplementation(type: 'standard' | 'light'): Promise<UnifiedRLNInstance> {
|
||||||
|
if (type === 'standard') {
|
||||||
|
// Create and return the standard RLN implementation
|
||||||
|
return await createRLN() as unknown as UnifiedRLNInstance;
|
||||||
|
} else {
|
||||||
|
// Create and return the light RLN implementation
|
||||||
|
return new RLNLightInstance() as unknown as UnifiedRLNInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
|
// Define the implementation types
|
||||||
|
export type RLNImplementationType = 'standard' | 'light';
|
||||||
|
|
||||||
|
// Define the context type
|
||||||
|
interface RLNImplementationContextType {
|
||||||
|
implementation: RLNImplementationType;
|
||||||
|
setImplementation: (implementation: RLNImplementationType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the context
|
||||||
|
const RLNImplementationContext = createContext<RLNImplementationContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
// Create the provider component
|
||||||
|
export function RLNImplementationProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [implementation, setImplementation] = useState<RLNImplementationType>('standard');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RLNImplementationContext.Provider value={{ implementation, setImplementation }}>
|
||||||
|
{children}
|
||||||
|
</RLNImplementationContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a hook to use the context
|
||||||
|
export function useRLNImplementation() {
|
||||||
|
const context = useContext(RLNImplementationContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useRLNImplementation must be used within a RLNImplementationProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
297
examples/keystore-management/src/contexts/RLNLightContext.tsx
Normal file
297
examples/keystore-management/src/contexts/RLNLightContext.tsx
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||||
|
import { DecryptedCredentials, RLNInstance, RLNLightInstance } from '@waku/rln';
|
||||||
|
import { useWallet } from './WalletContext';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const SIGNATURE_MESSAGE = "Sign this message to generate your RLN credentials";
|
||||||
|
const ERC20_ABI = [
|
||||||
|
"function allowance(address owner, address spender) view returns (uint256)",
|
||||||
|
"function approve(address spender, uint256 amount) returns (bool)",
|
||||||
|
"function balanceOf(address account) view returns (uint256)"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Linea Sepolia configuration
|
||||||
|
const LINEA_SEPOLIA_CONFIG = {
|
||||||
|
chainId: 59141,
|
||||||
|
tokenAddress: '0x185A0015aC462a0aECb81beCc0497b649a64B9ea'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RLNContextType {
|
||||||
|
rln: RLNLightInstance | RLNInstance | null;
|
||||||
|
isInitialized: boolean;
|
||||||
|
isStarted: boolean;
|
||||||
|
error: string | null;
|
||||||
|
initializeRLN: () => Promise<void>;
|
||||||
|
registerMembership: (rateLimit: number) => Promise<{ success: boolean; error?: string; credentials?: DecryptedCredentials }>;
|
||||||
|
rateMinLimit: number;
|
||||||
|
rateMaxLimit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RLNContext = createContext<RLNContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function RLNProvider({ children }: { children: ReactNode }) {
|
||||||
|
const { isConnected, signer } = useWallet();
|
||||||
|
const [rln, setRln] = useState<RLNLightInstance | RLNInstance | null>(null);
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
|
const [isStarted, setIsStarted] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [rateMinLimit, setRateMinLimit] = useState(20);
|
||||||
|
const [rateMaxLimit, setRateMaxLimit] = useState(600);
|
||||||
|
|
||||||
|
const ensureLineaSepoliaNetwork = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
console.log("Current network: unknown", await signer?.getChainId());
|
||||||
|
|
||||||
|
// Check if already on Linea Sepolia
|
||||||
|
if (await signer?.getChainId() === LINEA_SEPOLIA_CONFIG.chainId) {
|
||||||
|
console.log("Already on Linea Sepolia network");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not on Linea Sepolia, try to switch
|
||||||
|
console.log("Not on Linea Sepolia, attempting to switch...");
|
||||||
|
|
||||||
|
interface EthereumProvider {
|
||||||
|
request: (args: {
|
||||||
|
method: string;
|
||||||
|
params?: unknown[]
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the provider from window.ethereum
|
||||||
|
const provider = window.ethereum as EthereumProvider | undefined;
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
console.warn("No Ethereum provider found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Request network switch
|
||||||
|
await provider.request({
|
||||||
|
method: 'wallet_switchEthereumChain',
|
||||||
|
params: [{ chainId: `0x${LINEA_SEPOLIA_CONFIG.chainId.toString(16)}` }],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Successfully switched to Linea Sepolia");
|
||||||
|
return true;
|
||||||
|
} catch (switchError: unknown) {
|
||||||
|
console.error("Error switching network:", switchError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error checking or switching network:", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeRLN = async () => {
|
||||||
|
console.log("InitializeRLN called. Connected:", isConnected, "Signer available:", !!signer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
if (!rln) {
|
||||||
|
console.log("Creating RLN instance...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rlnInstance = new RLNLightInstance();
|
||||||
|
|
||||||
|
console.log("RLN instance created successfully:", !!rlnInstance);
|
||||||
|
setRln(rlnInstance);
|
||||||
|
|
||||||
|
setIsInitialized(true);
|
||||||
|
console.log("isInitialized set to true");
|
||||||
|
|
||||||
|
// Update rate limits to match contract requirements
|
||||||
|
setRateMinLimit(20); // Contract minimum (RATE_LIMIT_PARAMS.MIN_RATE)
|
||||||
|
setRateMaxLimit(600); // Contract maximum (RATE_LIMIT_PARAMS.MAX_RATE)
|
||||||
|
} catch (createErr) {
|
||||||
|
console.error("Error creating RLN instance:", createErr);
|
||||||
|
throw createErr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("RLN instance already exists, skipping creation");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start RLN if wallet is connected
|
||||||
|
if (isConnected && signer && rln && !isStarted) {
|
||||||
|
console.log("Starting RLN with signer...");
|
||||||
|
try {
|
||||||
|
// Initialize with localKeystore if available (just for reference in localStorage)
|
||||||
|
const localKeystore = localStorage.getItem("rln-keystore") || "";
|
||||||
|
console.log("Local keystore available:", !!localKeystore);
|
||||||
|
|
||||||
|
// Start RLN with signer
|
||||||
|
await rln.start({ signer });
|
||||||
|
|
||||||
|
setIsStarted(true);
|
||||||
|
console.log("RLN started successfully, isStarted set to true");
|
||||||
|
} catch (startErr) {
|
||||||
|
console.error("Error starting RLN:", startErr);
|
||||||
|
throw startErr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Skipping RLN start because:", {
|
||||||
|
isConnected,
|
||||||
|
hasSigner: !!signer,
|
||||||
|
hasRln: !!rln,
|
||||||
|
isAlreadyStarted: isStarted
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in initializeRLN:', err);
|
||||||
|
setError(err instanceof Error ? err.message : 'Failed to initialize RLN');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerMembership = async (rateLimit: number) => {
|
||||||
|
console.log("registerMembership called with rate limit:", rateLimit);
|
||||||
|
|
||||||
|
if (!rln || !isStarted) {
|
||||||
|
return { success: false, error: 'RLN not initialized or not started' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signer) {
|
||||||
|
return { success: false, error: 'No signer available' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate rate limit
|
||||||
|
if (rateLimit < rateMinLimit || rateLimit > rateMaxLimit) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Rate limit must be between ${rateMinLimit} and ${rateMaxLimit}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we're on the correct network
|
||||||
|
const isOnLineaSepolia = await ensureLineaSepoliaNetwork();
|
||||||
|
if (!isOnLineaSepolia) {
|
||||||
|
console.warn("Could not switch to Linea Sepolia network. Registration may fail.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user address and contract address
|
||||||
|
const userAddress = await signer.getAddress();
|
||||||
|
|
||||||
|
if (!rln.contract || !rln.contract.address) {
|
||||||
|
return { success: false, error: "RLN contract address not available. Cannot proceed with registration." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const contractAddress = rln.contract.address;
|
||||||
|
const tokenAddress = LINEA_SEPOLIA_CONFIG.tokenAddress;
|
||||||
|
|
||||||
|
// Create token contract instance
|
||||||
|
const tokenContract = new ethers.Contract(
|
||||||
|
tokenAddress,
|
||||||
|
ERC20_ABI,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check token balance
|
||||||
|
const tokenBalance = await tokenContract.balanceOf(userAddress);
|
||||||
|
if (tokenBalance.isZero()) {
|
||||||
|
return { success: false, error: "You need tokens to register a membership. Your token balance is zero." };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and approve token allowance if needed
|
||||||
|
const currentAllowance = await tokenContract.allowance(userAddress, contractAddress);
|
||||||
|
if (currentAllowance.eq(0)) {
|
||||||
|
console.log("Requesting token approval...");
|
||||||
|
|
||||||
|
// Approve a large amount (max uint256)
|
||||||
|
const maxUint256 = ethers.constants.MaxUint256;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const approveTx = await tokenContract.approve(contractAddress, maxUint256);
|
||||||
|
console.log("Approval transaction submitted:", approveTx.hash);
|
||||||
|
|
||||||
|
// Wait for the transaction to be mined
|
||||||
|
await approveTx.wait(1);
|
||||||
|
console.log("Token approval confirmed");
|
||||||
|
} catch (approvalErr) {
|
||||||
|
console.error("Error during token approval:", approvalErr);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Failed to approve token: ${approvalErr instanceof Error ? approvalErr.message : String(approvalErr)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Token allowance already sufficient");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate signature for identity
|
||||||
|
const message = `${SIGNATURE_MESSAGE} ${Date.now()}`;
|
||||||
|
const signature = await signer.signMessage(message);
|
||||||
|
|
||||||
|
const _credentials = await rln.registerMembership({signature: signature});
|
||||||
|
if (!_credentials) {
|
||||||
|
throw new Error("Failed to register membership: No credentials returned");
|
||||||
|
}
|
||||||
|
if (!_credentials.identity) {
|
||||||
|
throw new Error("Failed to register membership: Missing identity information");
|
||||||
|
}
|
||||||
|
if (!_credentials.membership) {
|
||||||
|
throw new Error("Failed to register membership: Missing membership information");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, credentials: _credentials };
|
||||||
|
} catch (err) {
|
||||||
|
let errorMsg = "Failed to register membership";
|
||||||
|
if (err instanceof Error) {
|
||||||
|
errorMsg = err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, error: errorMsg };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize RLN when wallet connects
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Wallet connection state changed:", { isConnected, hasSigner: !!signer });
|
||||||
|
if (isConnected && signer) {
|
||||||
|
console.log("Wallet connected, attempting to initialize RLN");
|
||||||
|
initializeRLN();
|
||||||
|
} else {
|
||||||
|
console.log("Wallet not connected or no signer available, skipping RLN initialization");
|
||||||
|
}
|
||||||
|
}, [isConnected, signer]);
|
||||||
|
|
||||||
|
// Debug log for state changes
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("RLN Context state:", {
|
||||||
|
isInitialized,
|
||||||
|
isStarted,
|
||||||
|
hasRln: !!rln,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}, [isInitialized, isStarted, rln, error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RLNContext.Provider
|
||||||
|
value={{
|
||||||
|
rln,
|
||||||
|
isInitialized,
|
||||||
|
isStarted,
|
||||||
|
error,
|
||||||
|
initializeRLN,
|
||||||
|
registerMembership,
|
||||||
|
rateMinLimit,
|
||||||
|
rateMaxLimit
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RLNContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRLN() {
|
||||||
|
const context = useContext(RLNContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useRLN must be used within an RLNProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { RLNProvider as StandardRLNProvider } from './RLNContext';
|
||||||
|
import { RLNProvider as LightRLNProvider } from './RLNLightContext';
|
||||||
|
import { useRLNImplementation } from './RLNImplementationContext';
|
||||||
|
|
||||||
|
// Create a unified provider that conditionally renders the appropriate provider
|
||||||
|
export function RLNUnifiedProvider({ children }: { children: ReactNode }) {
|
||||||
|
const { implementation } = useRLNImplementation();
|
||||||
|
|
||||||
|
// Render the appropriate provider based on the implementation
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{implementation === 'standard' ? (
|
||||||
|
<StandardRLNProvider>{children}</StandardRLNProvider>
|
||||||
|
) : (
|
||||||
|
<LightRLNProvider>{children}</LightRLNProvider>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
358
examples/keystore-management/src/contexts/RLNUnifiedContext2.tsx
Normal file
358
examples/keystore-management/src/contexts/RLNUnifiedContext2.tsx
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||||
|
import { KeystoreEntity } from '@waku/rln';
|
||||||
|
import { UnifiedRLNInstance } from './RLNFactory';
|
||||||
|
import { useRLNImplementation } from './RLNImplementationContext';
|
||||||
|
import { createRLNImplementation } from './RLNFactory';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
|
// Constants for RLN membership registration
|
||||||
|
const ERC20_ABI = [
|
||||||
|
"function allowance(address owner, address spender) view returns (uint256)",
|
||||||
|
"function approve(address spender, uint256 amount) returns (bool)",
|
||||||
|
"function balanceOf(address account) view returns (uint256)"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Linea Sepolia configuration
|
||||||
|
const LINEA_SEPOLIA_CONFIG = {
|
||||||
|
chainId: 59141,
|
||||||
|
tokenAddress: '0x185A0015aC462a0aECb81beCc0497b649a64B9ea'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the context type
|
||||||
|
interface RLNContextType {
|
||||||
|
rln: UnifiedRLNInstance | null;
|
||||||
|
isInitialized: boolean;
|
||||||
|
isStarted: boolean;
|
||||||
|
error: string | null;
|
||||||
|
initializeRLN: () => Promise<void>;
|
||||||
|
registerMembership: (rateLimit: number) => Promise<{ success: boolean; error?: string; credentials?: KeystoreEntity }>;
|
||||||
|
rateMinLimit: number;
|
||||||
|
rateMaxLimit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the context
|
||||||
|
const RLNUnifiedContext = createContext<RLNContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
// Create the provider component
|
||||||
|
export function RLNUnifiedProvider({ children }: { children: ReactNode }) {
|
||||||
|
const { implementation } = useRLNImplementation();
|
||||||
|
const [rln, setRln] = useState<UnifiedRLNInstance | null>(null);
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
|
const [isStarted, setIsStarted] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [rateMinLimit, setRateMinLimit] = useState(20);
|
||||||
|
const [rateMaxLimit, setRateMaxLimit] = useState(600);
|
||||||
|
|
||||||
|
// Get the signer from window.ethereum
|
||||||
|
const [signer, setSigner] = useState<ethers.Signer | null>(null);
|
||||||
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
|
|
||||||
|
// Listen for wallet connection
|
||||||
|
useEffect(() => {
|
||||||
|
const checkWallet = async () => {
|
||||||
|
try {
|
||||||
|
if (window.ethereum) {
|
||||||
|
const provider = new ethers.providers.Web3Provider(window.ethereum);
|
||||||
|
const accounts = await provider.listAccounts();
|
||||||
|
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
const signer = provider.getSigner();
|
||||||
|
setSigner(signer);
|
||||||
|
setIsConnected(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSigner(null);
|
||||||
|
setIsConnected(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error checking wallet:", err);
|
||||||
|
setSigner(null);
|
||||||
|
setIsConnected(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkWallet();
|
||||||
|
|
||||||
|
// Listen for account changes
|
||||||
|
if (window.ethereum) {
|
||||||
|
window.ethereum.on('accountsChanged', checkWallet);
|
||||||
|
window.ethereum.on('chainChanged', checkWallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (window.ethereum) {
|
||||||
|
window.ethereum.removeListener('accountsChanged', checkWallet);
|
||||||
|
window.ethereum.removeListener('chainChanged', checkWallet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Reset RLN state when implementation changes
|
||||||
|
useEffect(() => {
|
||||||
|
setRln(null);
|
||||||
|
setIsInitialized(false);
|
||||||
|
setIsStarted(false);
|
||||||
|
setError(null);
|
||||||
|
}, [implementation]);
|
||||||
|
|
||||||
|
const ensureLineaSepoliaNetwork = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
console.log("Current network: unknown", await signer?.getChainId());
|
||||||
|
|
||||||
|
// Check if already on Linea Sepolia
|
||||||
|
if (await signer?.getChainId() === LINEA_SEPOLIA_CONFIG.chainId) {
|
||||||
|
console.log("Already on Linea Sepolia network");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not on Linea Sepolia, try to switch
|
||||||
|
console.log("Not on Linea Sepolia, attempting to switch...");
|
||||||
|
|
||||||
|
interface EthereumProvider {
|
||||||
|
request: (args: {
|
||||||
|
method: string;
|
||||||
|
params?: unknown[]
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the provider from window.ethereum
|
||||||
|
const provider = window.ethereum as EthereumProvider | undefined;
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
console.warn("No Ethereum provider found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Request network switch
|
||||||
|
await provider.request({
|
||||||
|
method: 'wallet_switchEthereumChain',
|
||||||
|
params: [{ chainId: `0x${LINEA_SEPOLIA_CONFIG.chainId.toString(16)}` }],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Successfully switched to Linea Sepolia");
|
||||||
|
return true;
|
||||||
|
} catch (switchError: unknown) {
|
||||||
|
console.error("Error switching network:", switchError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error checking or switching network:", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeRLN = async () => {
|
||||||
|
console.log("InitializeRLN called. Connected:", isConnected, "Signer available:", !!signer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
if (!rln) {
|
||||||
|
console.log(`Creating RLN ${implementation} instance...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use our factory to create the appropriate implementation
|
||||||
|
const rlnInstance = await createRLNImplementation(implementation);
|
||||||
|
|
||||||
|
console.log("RLN instance created successfully:", !!rlnInstance);
|
||||||
|
setRln(rlnInstance);
|
||||||
|
|
||||||
|
setIsInitialized(true);
|
||||||
|
console.log("isInitialized set to true");
|
||||||
|
|
||||||
|
// Update rate limits to match contract requirements
|
||||||
|
setRateMinLimit(20); // Contract minimum (RATE_LIMIT_PARAMS.MIN_RATE)
|
||||||
|
setRateMaxLimit(600); // Contract maximum (RATE_LIMIT_PARAMS.MAX_RATE)
|
||||||
|
} catch (createErr) {
|
||||||
|
console.error("Error creating RLN instance:", createErr);
|
||||||
|
throw createErr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("RLN instance already exists, skipping creation");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start RLN if wallet is connected
|
||||||
|
if (isConnected && signer && rln && !isStarted) {
|
||||||
|
console.log("Starting RLN with signer...");
|
||||||
|
try {
|
||||||
|
// Initialize with localKeystore if available (just for reference in localStorage)
|
||||||
|
const localKeystore = localStorage.getItem("rln-keystore") || "";
|
||||||
|
console.log("Local keystore available:", !!localKeystore);
|
||||||
|
|
||||||
|
// Start RLN with signer
|
||||||
|
await rln.start({ signer });
|
||||||
|
|
||||||
|
setIsStarted(true);
|
||||||
|
console.log("RLN started successfully, isStarted set to true");
|
||||||
|
} catch (startErr) {
|
||||||
|
console.error("Error starting RLN:", startErr);
|
||||||
|
throw startErr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Skipping RLN start because:", {
|
||||||
|
isConnected,
|
||||||
|
hasSigner: !!signer,
|
||||||
|
hasRln: !!rln,
|
||||||
|
isAlreadyStarted: isStarted
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in initializeRLN:', err);
|
||||||
|
setError(err instanceof Error ? err.message : 'Failed to initialize RLN');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerMembership = async (rateLimit: number) => {
|
||||||
|
console.log("registerMembership called with rate limit:", rateLimit);
|
||||||
|
|
||||||
|
if (!rln || !isStarted) {
|
||||||
|
return { success: false, error: 'RLN not initialized or not started' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signer) {
|
||||||
|
return { success: false, error: 'No signer available' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate rate limit
|
||||||
|
if (rateLimit < rateMinLimit || rateLimit > rateMaxLimit) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Rate limit must be between ${rateMinLimit} and ${rateMaxLimit}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we're on the correct network
|
||||||
|
const isOnLineaSepolia = await ensureLineaSepoliaNetwork();
|
||||||
|
if (!isOnLineaSepolia) {
|
||||||
|
console.warn("Could not switch to Linea Sepolia network. Registration may fail.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user address and contract address
|
||||||
|
const userAddress = await signer.getAddress();
|
||||||
|
|
||||||
|
if (!rln.contract || !rln.contract.address) {
|
||||||
|
return { success: false, error: "RLN contract address not available. Cannot proceed with registration." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const contractAddress = rln.contract.address;
|
||||||
|
const tokenAddress = LINEA_SEPOLIA_CONFIG.tokenAddress;
|
||||||
|
|
||||||
|
// Create token contract instance
|
||||||
|
const tokenContract = new ethers.Contract(
|
||||||
|
tokenAddress,
|
||||||
|
ERC20_ABI,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check token balance
|
||||||
|
const tokenBalance = await tokenContract.balanceOf(userAddress);
|
||||||
|
if (tokenBalance.isZero()) {
|
||||||
|
return { success: false, error: "You need tokens to register a membership. Your token balance is zero." };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and approve token allowance if needed
|
||||||
|
const currentAllowance = await tokenContract.allowance(userAddress, contractAddress);
|
||||||
|
// Get membership fee - implementation may differ between standard and light
|
||||||
|
const membershipFee = await rln.contract.membershipFee?.() || ethers.utils.parseEther("0.01");
|
||||||
|
|
||||||
|
if (currentAllowance.lt(membershipFee)) {
|
||||||
|
console.log("Approving token allowance...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const approveTx = await tokenContract.approve(contractAddress, membershipFee);
|
||||||
|
await approveTx.wait();
|
||||||
|
console.log("Token allowance approved");
|
||||||
|
} catch (approveErr) {
|
||||||
|
console.error("Error approving token allowance:", approveErr);
|
||||||
|
return { success: false, error: "Failed to approve token allowance for membership registration." };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Token allowance already sufficient");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register membership
|
||||||
|
console.log("Registering membership with rate limit:", rateLimit);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Both implementations use registerMembership with a signature
|
||||||
|
// Generate signature for identity
|
||||||
|
const message = `Sign this message to generate your RLN credentials ${Date.now()}`;
|
||||||
|
const signature = await signer.signMessage(message);
|
||||||
|
|
||||||
|
// Call registerMembership with the signature
|
||||||
|
const credentials = await rln.registerMembership({
|
||||||
|
signature: signature
|
||||||
|
}) as unknown as KeystoreEntity;
|
||||||
|
|
||||||
|
// Validate credentials
|
||||||
|
if (!credentials) {
|
||||||
|
throw new Error("Failed to register membership: No credentials returned");
|
||||||
|
}
|
||||||
|
if (!credentials.identity) {
|
||||||
|
throw new Error("Failed to register membership: Missing identity information");
|
||||||
|
}
|
||||||
|
if (!credentials.membership) {
|
||||||
|
throw new Error("Failed to register membership: Missing membership information");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Membership registered successfully");
|
||||||
|
|
||||||
|
// Store credentials in localStorage for reference
|
||||||
|
try {
|
||||||
|
localStorage.setItem("rln-keystore", JSON.stringify(credentials));
|
||||||
|
} catch (storageErr) {
|
||||||
|
console.warn("Could not store credentials in localStorage:", storageErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
credentials: credentials
|
||||||
|
};
|
||||||
|
} catch (registerErr) {
|
||||||
|
console.error("Error registering membership:", registerErr);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: registerErr instanceof Error ? registerErr.message : "Failed to register membership"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error in registerMembership:", err);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: err instanceof Error ? err.message : "An unknown error occurred during registration"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the context value
|
||||||
|
const contextValue: RLNContextType = {
|
||||||
|
rln,
|
||||||
|
isInitialized,
|
||||||
|
isStarted,
|
||||||
|
error,
|
||||||
|
initializeRLN,
|
||||||
|
registerMembership,
|
||||||
|
rateMinLimit,
|
||||||
|
rateMaxLimit
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RLNUnifiedContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
</RLNUnifiedContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a hook to use the context
|
||||||
|
export function useRLN() {
|
||||||
|
const context = useContext(RLNUnifiedContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useRLN must be used within a RLNUnifiedProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
91
examples/keystore-management/src/contexts/RLNUnifiedHook.tsx
Normal file
91
examples/keystore-management/src/contexts/RLNUnifiedHook.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||||
|
import { DecryptedCredentials, RLNInstance, RLNLightInstance } from '@waku/rln';
|
||||||
|
import { useRLNImplementation } from './RLNImplementationContext';
|
||||||
|
|
||||||
|
// Define a dummy context for when neither implementation is available
|
||||||
|
interface RLNContextType {
|
||||||
|
rln: RLNInstance | RLNLightInstance | null;
|
||||||
|
isInitialized: boolean;
|
||||||
|
isStarted: boolean;
|
||||||
|
error: string | null;
|
||||||
|
initializeRLN: () => Promise<void>;
|
||||||
|
registerMembership: (rateLimit: number) => Promise<{ success: boolean; error?: string; credentials?: DecryptedCredentials }>;
|
||||||
|
rateMinLimit: number;
|
||||||
|
rateMaxLimit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a dummy context with default values
|
||||||
|
const dummyRLNContext: RLNContextType = {
|
||||||
|
rln: null,
|
||||||
|
isInitialized: false,
|
||||||
|
isStarted: false,
|
||||||
|
error: 'RLN context not initialized',
|
||||||
|
initializeRLN: async () => { throw new Error('RLN context not initialized'); },
|
||||||
|
registerMembership: async () => ({ success: false, error: 'RLN context not initialized' }),
|
||||||
|
rateMinLimit: 20,
|
||||||
|
rateMaxLimit: 600
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a context to store the selected RLN implementation
|
||||||
|
const UnifiedRLNContext = createContext<RLNContextType>(dummyRLNContext);
|
||||||
|
|
||||||
|
// Create a provider component that will fetch the appropriate implementation
|
||||||
|
export function UnifiedRLNProvider({ children }: { children: ReactNode }) {
|
||||||
|
const { implementation } = useRLNImplementation();
|
||||||
|
const [contextValue, setContextValue] = useState<RLNContextType>(dummyRLNContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// This effect will run when the implementation changes
|
||||||
|
// We'll dynamically import the appropriate context module
|
||||||
|
const fetchContext = async () => {
|
||||||
|
try {
|
||||||
|
if (implementation === 'standard') {
|
||||||
|
// Import the standard RLN context
|
||||||
|
const standardModule = await import('./RLNContext');
|
||||||
|
const standardRLNContext = standardModule.RLNContext;
|
||||||
|
|
||||||
|
if (standardRLNContext) {
|
||||||
|
// Access the context value
|
||||||
|
const contextConsumer = standardRLNContext.Consumer;
|
||||||
|
contextConsumer(value => {
|
||||||
|
if (value) {
|
||||||
|
setContextValue(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Import the light RLN context
|
||||||
|
const lightModule = await import('./RLNLightContext');
|
||||||
|
const lightRLNContext = lightModule.RLNContext;
|
||||||
|
|
||||||
|
if (lightRLNContext) {
|
||||||
|
// Access the context value
|
||||||
|
const contextConsumer = lightRLNContext.Consumer;
|
||||||
|
contextConsumer(value => {
|
||||||
|
if (value) {
|
||||||
|
setContextValue(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading RLN context:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchContext();
|
||||||
|
}, [implementation]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UnifiedRLNContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
</UnifiedRLNContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a hook to use the unified RLN context
|
||||||
|
export function useRLN() {
|
||||||
|
return useContext(UnifiedRLNContext);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user