mirror of
https://github.com/logos-messaging/rln.waku.org.git
synced 2026-01-07 16:43:05 +00:00
chore: merge
This commit is contained in:
commit
58bc81b6d1
@ -58,14 +58,6 @@ If you encounter an "ERC20: insufficient allowance" error, it means the token ap
|
|||||||
1. Waku Testnet Tokens: 0x185A0015aC462a0aECb81beCc0497b649a64B9ea
|
1. Waku Testnet Tokens: 0x185A0015aC462a0aECb81beCc0497b649a64B9ea
|
||||||
2. RLN Registration Contract: 0xB9cd878C90E49F797B4431fBF4fb333108CB90e6
|
2. RLN Registration Contract: 0xB9cd878C90E49F797B4431fBF4fb333108CB90e6
|
||||||
|
|
||||||
## TODO
|
|
||||||
- [ ] add info about using with nwaku/nwaku-compose/waku-simulator
|
|
||||||
- [x] fix rate limit fetch
|
|
||||||
- [ ] fix membership management methods
|
|
||||||
- [ ] define epoch / quanity epoch
|
|
||||||
- [x] alias for individual credentials
|
|
||||||
- [x] remove export keystore method (if >1 credentials in keystore)
|
|
||||||
|
|
||||||
## CI/CD
|
## CI/CD
|
||||||
|
|
||||||
PRs should be made for `develop` branch and `master` should be [rebased](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) on `develop` once changes are verified.
|
PRs should be made for `develop` branch and `master` should be [rebased](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) on `develop` once changes are verified.
|
||||||
|
|||||||
1445
package-lock.json
generated
1445
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.2",
|
"@radix-ui/react-toggle": "^1.1.2",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.2",
|
"@radix-ui/react-toggle-group": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@waku/rln": "0.1.6-0cec760.0",
|
"@waku/rln": "0.1.7-987c6cd.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^12.6.3",
|
"framer-motion": "^12.6.3",
|
||||||
@ -30,6 +30,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"sonner": "^2.0.3",
|
"sonner": "^2.0.3",
|
||||||
"tailwind-merge": "^3.1.0"
|
"tailwind-merge": "^3.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,12 +4,14 @@ import React from 'react';
|
|||||||
import { Layout } from '../components/Layout';
|
import { Layout } from '../components/Layout';
|
||||||
import { MembershipRegistration } from '../components/Tabs/MembershipTab/MembershipRegistration';
|
import { MembershipRegistration } from '../components/Tabs/MembershipTab/MembershipRegistration';
|
||||||
import { KeystoreManagement } from '../components/Tabs/KeystoreTab/KeystoreManagement';
|
import { KeystoreManagement } from '../components/Tabs/KeystoreTab/KeystoreManagement';
|
||||||
|
import RunNodeTab from '../components/Tabs/RunNodeTab/RunNodeTab';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<MembershipRegistration tabId="membership" />
|
<MembershipRegistration tabId="membership" />
|
||||||
<KeystoreManagement tabId="keystore" />
|
<KeystoreManagement tabId="keystore" />
|
||||||
|
<RunNodeTab tabId="runNode" />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -13,6 +13,10 @@ const tabs = [
|
|||||||
id: 'keystore',
|
id: 'keystore',
|
||||||
label: 'Keystore Management',
|
label: 'Keystore Management',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'runNode',
|
||||||
|
label: 'Run a Node',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
export function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { ethers } from 'ethers';
|
|||||||
import { MembershipState } from '@waku/rln';
|
import { MembershipState } from '@waku/rln';
|
||||||
import { useRLN } from '../contexts/rln/RLNContext';
|
import { useRLN } from '../contexts/rln/RLNContext';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './ui/tooltip';
|
||||||
|
|
||||||
interface MembershipDetailsProps {
|
interface MembershipDetailsProps {
|
||||||
membershipInfo: {
|
membershipInfo: {
|
||||||
@ -177,7 +178,21 @@ export function MembershipDetails({ membershipInfo, copyToClipboard, hash }: Mem
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground text-xs">Rate Limit:</span>
|
<span className="text-muted-foreground text-xs">Rate Limit:</span>
|
||||||
<div className="text-accent">{membershipInfo.rateLimit} msg/epoch</div>
|
<div className="text-accent flex items-center gap-1">
|
||||||
|
{membershipInfo.rateLimit} msg/epoch
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="ml-1 cursor-pointer align-middle inline-flex items-center justify-center">
|
||||||
|
<span className="w-4 h-4 rounded-full border border-muted-foreground text-muted-foreground flex items-center justify-center text-xs font-bold" style={{ fontFamily: 'monospace' }}>i</span>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
1 epoch = 10 minutes
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Contract Info */}
|
{/* Contract Info */}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export function RLNStatusIndicator() {
|
|||||||
const getStatusText = () => {
|
const getStatusText = () => {
|
||||||
if (error) return 'Error';
|
if (error) return 'Error';
|
||||||
if (isLoading) return 'Initializing RLN...';
|
if (isLoading) return 'Initializing RLN...';
|
||||||
if (!isConnected) return 'Connect Wallet';
|
if (!isConnected) return 'Wallet Not Connected';
|
||||||
if (chainId !== 59141) return 'Switch to Linea Sepolia';
|
if (chainId !== 59141) return 'Switch to Linea Sepolia';
|
||||||
if (isInitialized && isStarted) return 'RLN Active';
|
if (isInitialized && isStarted) return 'RLN Active';
|
||||||
return 'RLN Inactive';
|
return 'RLN Inactive';
|
||||||
|
|||||||
@ -6,21 +6,22 @@ import { KeystoreEntity } from '@waku/rln';
|
|||||||
import { useRLN } from '../../../contexts/rln/RLNContext';
|
import { useRLN } from '../../../contexts/rln/RLNContext';
|
||||||
import { useWallet } from '../../../contexts/wallet';
|
import { useWallet } from '../../../contexts/wallet';
|
||||||
import { TerminalWindow } from '../../ui/terminal-window';
|
import { TerminalWindow } from '../../ui/terminal-window';
|
||||||
import { Slider } from '../../ui/slider';
|
import { ToggleGroup, ToggleGroupItem } from '../../ui/toggle-group';
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from '../../ui/button';
|
||||||
import { membershipRegistration, type ContentSegment } from '../../../content/index';
|
import { membershipRegistration, type ContentSegment } from '../../../content/index';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../../ui/tooltip';
|
||||||
interface MembershipRegistrationProps {
|
interface MembershipRegistrationProps {
|
||||||
tabId?: string;
|
tabId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export function MembershipRegistration({ tabId: _tabId }: MembershipRegistrationProps) {
|
export function MembershipRegistration({ tabId: _tabId }: MembershipRegistrationProps) {
|
||||||
const { registerMembership, isInitialized, isStarted, rateMinLimit, rateMaxLimit, error, isLoading } = useRLN();
|
const { registerMembership, isInitialized, isStarted, error, isLoading, getPriceForRateLimit } = useRLN();
|
||||||
const { isConnected, chainId } = useWallet();
|
const { isConnected, chainId } = useWallet();
|
||||||
|
|
||||||
const [rateLimit, setRateLimit] = useState<number>(rateMinLimit);
|
// Replace slider state with discrete options
|
||||||
|
const [rateLimit, setRateLimit] = useState<number>(300); // Default to Standard
|
||||||
const [isRegistering, setIsRegistering] = useState(false);
|
const [isRegistering, setIsRegistering] = useState(false);
|
||||||
const [saveToKeystore, setSaveToKeystore] = useState(true);
|
const [saveToKeystore, setSaveToKeystore] = useState(true);
|
||||||
const [keystorePassword, setKeystorePassword] = useState('');
|
const [keystorePassword, setKeystorePassword] = useState('');
|
||||||
@ -35,6 +36,33 @@ export function MembershipRegistration({ tabId: _tabId }: MembershipRegistration
|
|||||||
|
|
||||||
const isLineaSepolia = chainId === 59141;
|
const isLineaSepolia = chainId === 59141;
|
||||||
|
|
||||||
|
const [price, setPrice] = useState<string>('');
|
||||||
|
const [priceLoading, setPriceLoading] = useState(false);
|
||||||
|
const [priceError, setPriceError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoading || !isInitialized || !isStarted ) return;
|
||||||
|
let cancelled = false;
|
||||||
|
setPrice('');
|
||||||
|
setPriceError(null);
|
||||||
|
setPriceLoading(true);
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const result = await getPriceForRateLimit(rateLimit);
|
||||||
|
if (!cancelled) {
|
||||||
|
setPrice(result.price.toString());
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (!cancelled) {
|
||||||
|
setPriceError('Could not fetch price');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setPriceLoading(false);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}, [rateLimit, getPriceForRateLimit, isLoading, isInitialized, isStarted]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
toast.error(error);
|
toast.error(error);
|
||||||
@ -172,22 +200,51 @@ export function MembershipRegistration({ tabId: _tabId }: MembershipRegistration
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="rateLimit"
|
htmlFor="rateLimit"
|
||||||
className="block text-sm font-mono text-muted-foreground mb-2"
|
className="block text-sm font-mono text-muted-foreground mb-2 flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{membershipRegistration.form.rateLimitLabel}
|
Rate Limit (messages per epoch)
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="ml-1 cursor-pointer align-middle inline-flex items-center justify-center">
|
||||||
|
{/* Unicode info icon, styled */}
|
||||||
|
<span className="w-4 h-4 rounded-full border border-muted-foreground text-muted-foreground flex items-center justify-center text-xs font-bold" style={{ fontFamily: 'monospace' }}>i</span>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
1 epoch = 10 minutes
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center space-x-4 py-2">
|
<div className="flex items-center space-x-4 py-2">
|
||||||
<Slider
|
<ToggleGroup
|
||||||
id="rateLimit"
|
type="single"
|
||||||
min={rateMinLimit}
|
value={String(rateLimit)}
|
||||||
max={rateMaxLimit}
|
onValueChange={(value) => {
|
||||||
value={[rateLimit]}
|
if (value === '300' || value === '600') setRateLimit(Number(value));
|
||||||
onValueChange={(value) => setRateLimit(value[0])}
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
>
|
||||||
<span className="text-sm text-muted-foreground w-12 font-mono">
|
<ToggleGroupItem value="300" className="flex-1 flex flex-col items-center">
|
||||||
{rateLimit}
|
<span>Standard (300)</span>
|
||||||
</span>
|
<span className="text-xs text-muted-foreground">lower token spend.</span>
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="600" className="flex-1 flex flex-col items-center">
|
||||||
|
<span>Max (600)</span>
|
||||||
|
<span className="text-xs text-muted-foreground">higher token spend. more messages.</span>
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
{/* Show calculated token spend for selected rate limit */}
|
||||||
|
<div className="text-xs text-muted-foreground font-mono mt-1">
|
||||||
|
{priceLoading ? (
|
||||||
|
<>Token spend required: <span className="italic">Loading...</span></>
|
||||||
|
) : priceError ? (
|
||||||
|
<>Token spend required: <span className="text-destructive">{priceError}</span></>
|
||||||
|
) : (
|
||||||
|
<>Token spend required: <span>{price}</span> WTT</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
225
src/components/Tabs/RunNodeTab/RunNodeTab.tsx
Normal file
225
src/components/Tabs/RunNodeTab/RunNodeTab.tsx
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useKeystore } from '../../../contexts/keystore/KeystoreContext';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { TerminalWindow } from '@/components/ui/terminal-window';
|
||||||
|
import { toast } from '@/components/ui/toast';
|
||||||
|
import { Copy } from 'lucide-react';
|
||||||
|
|
||||||
|
interface RunNodeTabProps {
|
||||||
|
tabId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CodeBlock({ code }: { code: string }) {
|
||||||
|
const handleCopy = () => {
|
||||||
|
navigator.clipboard.writeText(code);
|
||||||
|
toast({ message: 'Copied to clipboard!', type: 'success' });
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="relative my-2 flex items-center group">
|
||||||
|
<pre className="bg-terminal-background border border-terminal-border rounded px-3 py-2 text-xs font-mono overflow-x-auto w-full pr-12">
|
||||||
|
{code}
|
||||||
|
</pre>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 opacity-60 group-hover:opacity-100"
|
||||||
|
onClick={handleCopy}
|
||||||
|
title="Copy to clipboard"
|
||||||
|
type="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RunNodeTab({ tabId: _tabId }: RunNodeTabProps) {
|
||||||
|
const {
|
||||||
|
hasStoredCredentials,
|
||||||
|
storedCredentialsHashes,
|
||||||
|
credentialAliases,
|
||||||
|
exportCredential,
|
||||||
|
} = useKeystore();
|
||||||
|
|
||||||
|
const [exportPassword, setExportPassword] = useState('');
|
||||||
|
const [selectedCredential, setSelectedCredential] = useState<string | null>(null);
|
||||||
|
const [exportError, setExportError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleExport = async (hash: string) => {
|
||||||
|
setExportError(null);
|
||||||
|
try {
|
||||||
|
if (!exportPassword) {
|
||||||
|
setExportError('Please enter your keystore password to export');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const keystore = await exportCredential(hash, exportPassword);
|
||||||
|
// Download as file
|
||||||
|
const blob = new Blob([JSON.stringify(keystore)], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'keystore.json';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
setExportPassword('');
|
||||||
|
setSelectedCredential(null);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
setExportError(err.message);
|
||||||
|
} else {
|
||||||
|
setExportError('Failed to export credential');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8 p-4">
|
||||||
|
<TerminalWindow title="Run a Node" className="mb-8">
|
||||||
|
<h2 className="text-lg font-mono font-medium mb-6">How to Run a Waku Node</h2>
|
||||||
|
<ol className="space-y-8 list-decimal ml-6">
|
||||||
|
{/* Step 1: Export Keystore with UI */}
|
||||||
|
<li className="space-y-2">
|
||||||
|
<div className="font-mono text-base font-semibold text-primary mb-1">Export Your Keystore</div>
|
||||||
|
<div className="text-sm text-foreground/90 mb-1">
|
||||||
|
Select your RLN keystore from the list below and click <b>Export Keystore</b>.<br />
|
||||||
|
This will download a file (e.g., <code>keystore.json</code>) to your computer.
|
||||||
|
</div>
|
||||||
|
{/* Keystore Export UI */}
|
||||||
|
{!hasStoredCredentials ? (
|
||||||
|
<div className="p-3 border border-warning-DEFAULT/20 bg-warning-DEFAULT/5 rounded text-warning-DEFAULT">
|
||||||
|
No keystores found. Please register a membership and generate a keystore first.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{storedCredentialsHashes.map((hash: string) => {
|
||||||
|
const alias = credentialAliases[hash] || `${hash.substring(0, 6)}...${hash.substring(hash.length - 4)}`;
|
||||||
|
return (
|
||||||
|
<div key={hash} className="p-4 border rounded bg-terminal-background/30 flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
||||||
|
<span className="font-mono text-sm text-foreground">{alias}</span>
|
||||||
|
{selectedCredential === hash ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={exportPassword}
|
||||||
|
onChange={(e) => setExportPassword(e.target.value)}
|
||||||
|
placeholder="Enter password to export"
|
||||||
|
className="h-8 text-sm"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="terminal"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleExport(hash)}
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => { setSelectedCredential(null); setExportPassword(''); }}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="terminal"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSelectedCredential(hash)}
|
||||||
|
>
|
||||||
|
Export Keystore
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{exportError && <div className="text-red-600 text-sm mt-2">{exportError}</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{/* Step 2: Set Up nwaku-compose */}
|
||||||
|
<li className="space-y-2">
|
||||||
|
<div className="font-mono text-base font-semibold text-primary mb-1">Set Up nwaku-compose</div>
|
||||||
|
<div className="text-sm text-foreground/90 mb-1">
|
||||||
|
If you haven’t already, clone the nwaku-compose repository:
|
||||||
|
</div>
|
||||||
|
<CodeBlock code={`git clone https://github.com/waku-org/nwaku-compose.git\ncd nwaku-compose`} />
|
||||||
|
<div className="mt-2 p-2 border-l-4 border-info-DEFAULT bg-info-DEFAULT/10 text-info-DEFAULT text-xs font-mono">
|
||||||
|
Make sure you have Docker and docker-compose installed.
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{/* Step 3: Place Your Keystore */}
|
||||||
|
<li className="space-y-2">
|
||||||
|
<div className="font-mono text-base font-semibold text-primary mb-1">Place Your Keystore</div>
|
||||||
|
<div className="text-sm text-foreground/90 mb-1">
|
||||||
|
Copy your exported <code>keystore.json</code> file into the <code>keystore/</code> directory inside your <code>nwaku-compose</code> folder.<br />
|
||||||
|
Replace any existing file if prompted.
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{/* Step 4: Configure Environment */}
|
||||||
|
<li className="space-y-2">
|
||||||
|
<div className="font-mono text-base font-semibold text-primary mb-1">Configure Environment</div>
|
||||||
|
<div className="text-sm text-foreground/90 mb-1">
|
||||||
|
Copy <code>.env.example</code> to <code>.env</code>:
|
||||||
|
</div>
|
||||||
|
<CodeBlock code={`cp .env.example .env`} />
|
||||||
|
<div className="text-sm text-foreground/90 mt-2">
|
||||||
|
Open <code>.env</code> in your editor and fill in the required values:
|
||||||
|
<ul className="list-disc ml-6 text-sm mt-1">
|
||||||
|
<li><b>Ethereum Sepolia HTTP endpoint</b> (e.g., from Infura)</li>
|
||||||
|
<li><b>Ethereum Sepolia account</b> (with a small balance)</li>
|
||||||
|
<li><b>Password</b> (the one you used to encrypt your keystore)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{/* Step 5: (Optional) Set Database Parameters */}
|
||||||
|
<li className="space-y-2">
|
||||||
|
<div className="font-mono text-base font-semibold text-primary mb-1">(Optional) Set Database Parameters</div>
|
||||||
|
<div className="text-sm text-foreground/90 mb-1">
|
||||||
|
To set storage size, run:
|
||||||
|
</div>
|
||||||
|
<CodeBlock code={`./set_storage_retention.sh`} />
|
||||||
|
<div className="text-sm text-foreground/90 mt-2">
|
||||||
|
or manually set <code>STORAGE_SIZE</code> in <code>.env</code>.<br />
|
||||||
|
To set Postgres memory, run:
|
||||||
|
</div>
|
||||||
|
<CodeBlock code={`./set_postgres_shm.sh`} />
|
||||||
|
<div className="text-sm text-foreground/90 mt-2">
|
||||||
|
or set <code>POSTGRES_SHM</code> in <code>.env</code>.
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{/* Step 6: Start Your Node */}
|
||||||
|
<li className="space-y-2">
|
||||||
|
<div className="font-mono text-base font-semibold text-primary mb-1">Start Your Node</div>
|
||||||
|
<div className="text-sm text-foreground/90 mb-1">
|
||||||
|
Start all services:
|
||||||
|
</div>
|
||||||
|
<CodeBlock code={`docker-compose up -d`} />
|
||||||
|
<div className="mt-2 p-2 border-l-4 border-info-DEFAULT bg-info-DEFAULT/10 text-info-DEFAULT text-xs font-mono">
|
||||||
|
Your node will load your RLN membership from the keystore.
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{/* Step 7: Interact with Your Node */}
|
||||||
|
<li className="space-y-2">
|
||||||
|
<div className="font-mono text-base font-semibold text-primary mb-1">Interact with Your Node</div>
|
||||||
|
<div className="text-sm text-foreground/90 mb-1">
|
||||||
|
<div>Visit <b>localhost:4000</b> for the frontend chat.</div>
|
||||||
|
<div>Visit <b>localhost:3000</b> for node metrics.</div>
|
||||||
|
<div>Use the REST API as described in the nwaku-compose README.</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<div className="mt-8 p-3 border border-warning-DEFAULT/40 bg-warning-DEFAULT/10 rounded text-warning-DEFAULT text-sm font-mono">
|
||||||
|
<b>Note:</b> You do <u>NOT</u> need to run the <code>register_rln.sh</code> script—your RLN membership is already registered and stored in your exported keystore.
|
||||||
|
</div>
|
||||||
|
</TerminalWindow>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -33,6 +33,7 @@ interface RLNContextType {
|
|||||||
getRateLimitsBounds: () => Promise<{ success: boolean; rateMinLimit: number; rateMaxLimit: number; error?: string }>;
|
getRateLimitsBounds: () => Promise<{ success: boolean; rateMinLimit: number; rateMaxLimit: number; error?: string }>;
|
||||||
saveCredentialsToKeystore: (credentials: KeystoreEntity, password: string) => Promise<string>;
|
saveCredentialsToKeystore: (credentials: KeystoreEntity, password: string) => Promise<string>;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
getPriceForRateLimit: (rateLimit: number) => Promise<{ price: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RLNContext = createContext<RLNContextType | undefined>(undefined);
|
const RLNContext = createContext<RLNContextType | undefined>(undefined);
|
||||||
@ -465,6 +466,20 @@ export function RLNProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPriceForRateLimit = async (rateLimit: number): Promise<{ price: string }> => {
|
||||||
|
try {
|
||||||
|
if (!rln || !rln.contract || !isStarted) {
|
||||||
|
throw new Error('RLN not initialized or contract not available');
|
||||||
|
}
|
||||||
|
const result = await rln.contract.getPriceForRateLimit(rateLimit);
|
||||||
|
const formatted = ethers.utils.formatUnits(result.price, 18);
|
||||||
|
return { price: formatted };
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error getting price for rate limit:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RLNContext.Provider
|
<RLNContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -483,7 +498,8 @@ export function RLNProvider({ children }: { children: ReactNode }) {
|
|||||||
getCurrentRateLimit,
|
getCurrentRateLimit,
|
||||||
getRateLimitsBounds,
|
getRateLimitsBounds,
|
||||||
saveCredentialsToKeystore: saveToKeystore,
|
saveCredentialsToKeystore: saveToKeystore,
|
||||||
isLoading
|
isLoading,
|
||||||
|
getPriceForRateLimit
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -14,4 +14,4 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
*/
|
*/
|
||||||
export function hslToVar(hsl: string): string {
|
export function hslToVar(hsl: string): string {
|
||||||
return `hsl(var(${hsl}))`;
|
return `hsl(var(${hsl}))`;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user