Skip to content

Commit 770c8d2

Browse files
authored
Nillion x Monadic DNA Collab (#30)
* First draft of the Nillion x Monadic collab * nilDB integration * nilDB API usage fixes * nilDB API usage fixes * nilDB API usage fixes * nilDB API usage fixes * Horrible fix for a Nillion build issue. * Fixed a bunch of build errors. * Reverted some testing hacks. * Added a processing step to the modal. * Fixed RAG result processing. * Fixed a build error
1 parent 11f29be commit 770c8d2

9 files changed

Lines changed: 6672 additions & 3563 deletions

File tree

app/api/nildb-delegation/route.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import {Signer, NilauthClient, Builder as NucTokenBuilder, Did, Builder, Command} from '@nillion/nuc';
3+
import {NucCmd, SecretVaultBuilderClient } from '@nillion/secretvaults';
4+
import { validateOrigin } from '@/lib/origin-validator';
5+
import { NILDB_CONFIG } from '@/lib/nildb-config';
6+
7+
// Simple in-memory rate limiter
8+
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
9+
const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
10+
const MAX_REQUESTS_PER_WINDOW = 5; // 5 tokens per minute per IP
11+
12+
function getRateLimitKey(request: NextRequest): string {
13+
// Try to get real IP from common headers (for proxies/load balancers)
14+
const forwarded = request.headers.get('x-forwarded-for');
15+
const realIp = request.headers.get('x-real-ip');
16+
const ip = forwarded?.split(',')[0] || realIp || 'unknown';
17+
return ip;
18+
}
19+
20+
function isRateLimited(key: string): boolean {
21+
const now = Date.now();
22+
const record = rateLimitMap.get(key);
23+
24+
if (!record || now > record.resetTime) {
25+
// New window
26+
rateLimitMap.set(key, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
27+
return false;
28+
}
29+
30+
if (record.count >= MAX_REQUESTS_PER_WINDOW) {
31+
return true;
32+
}
33+
34+
record.count++;
35+
return false;
36+
}
37+
38+
export async function POST(request: NextRequest) {
39+
try {
40+
// Validate origin
41+
const originError = validateOrigin(request);
42+
if (originError) return originError;
43+
44+
// Rate limiting check
45+
const rateLimitKey = getRateLimitKey(request);
46+
if (isRateLimited(rateLimitKey)) {
47+
console.warn(`Rate limit exceeded for IP: ${rateLimitKey}`);
48+
return NextResponse.json(
49+
{ error: 'Too many requests. Please try again later.' },
50+
{ status: 429 }
51+
);
52+
}
53+
54+
const { userDid } = await request.json();
55+
56+
if (!userDid) {
57+
return NextResponse.json(
58+
{ error: 'User DID is required' },
59+
{ status: 400 }
60+
);
61+
}
62+
63+
// Check for Nillion API key
64+
const apiKey = process.env.NILLION_API_KEY;
65+
if (!apiKey) {
66+
return NextResponse.json(
67+
{ error: 'Nillion API key not configured. Please set NILLION_API_KEY in your environment variables.' },
68+
{ status: 503 }
69+
);
70+
}
71+
72+
console.log('Creating nilDB delegation token for user:', userDid);
73+
74+
// Create signer from API key
75+
const builderSigner = await Signer.fromPrivateKey(apiKey, "nil");
76+
const builderDid = await builderSigner.getDid();
77+
78+
// Create Nilauth client manually using the provided public key
79+
// (avoiding network call to /about endpoint)
80+
// @ts-expect-error - Constructor is private but works fine at runtime; waiting for Nillion API fix
81+
const nilauthClient = new NilauthClient({
82+
payer: builderSigner,
83+
nilauth: {
84+
baseUrl: NILDB_CONFIG.nilauthUrl,
85+
publicKey: NILDB_CONFIG.nilauthPublicKey,
86+
did: Did.fromPublicKey(NILDB_CONFIG.nilauthPublicKey, 'key')
87+
}
88+
});
89+
90+
// Create builder client
91+
console.log('Creating SecretVaultBuilderClient with', NILDB_CONFIG.nodes.length, 'nodes');
92+
const builderClient = await SecretVaultBuilderClient.from({
93+
signer: builderSigner,
94+
nilauthClient,
95+
dbs: [...NILDB_CONFIG.nodes]
96+
});
97+
console.log('SecretVaultBuilderClient created successfully');
98+
99+
// Refresh authentication to get root token
100+
console.log('Refreshing root token from nilauth...');
101+
try {
102+
await builderClient.refreshRootToken();
103+
console.log('Root token refreshed successfully');
104+
} catch (err) {
105+
console.error('Failed to refresh root token:', err);
106+
throw new Error(`Failed to refresh root token: ${err instanceof Error ? err.message : String(err)}`);
107+
}
108+
109+
// Get the root token
110+
const rootToken = builderClient.rootToken;
111+
if (!rootToken) {
112+
throw new Error('No root token available from builderClient');
113+
}
114+
115+
// Create delegation token from builderClient to user
116+
// Token expires in 10 minutes - enough time for user to submit data
117+
const expiresInSeconds = 10 * 60;
118+
const expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds;
119+
120+
/*// Create delegation token from builderClient to user
121+
// Don't specify command - let the user client add it
122+
// Note: Using signer.privateKey() since builderClient.keypair is not exposed in current API
123+
const delegationToken = new NucTokenBuilder()
124+
.extending(rootToken)
125+
.audience(userDid)
126+
.expiresAt(expiresAt)
127+
.build(signer.privateKey());*/
128+
129+
const delegationToken = await Builder.delegationFrom(builderClient.rootToken)
130+
.command(NucCmd.nil.db.data.create as Command)
131+
.audience(userDid)
132+
.expiresIn(3600)
133+
.signAndSerialize(builderSigner);
134+
135+
console.log('nilDB delegation token created successfully');
136+
137+
return NextResponse.json({
138+
success: true,
139+
delegationToken,
140+
collectionId: NILDB_CONFIG.collectionId,
141+
builderDid: builderDid.didString
142+
});
143+
144+
} catch (error) {
145+
console.error('Error creating nilDB delegation token:', error);
146+
return NextResponse.json(
147+
{ error: error instanceof Error ? error.message : 'Failed to create delegation token' },
148+
{ status: 500 }
149+
);
150+
}
151+
}

app/components/Icons.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,20 @@ export function UserIcon({ className = "", size = 20 }: IconProps) {
713713
</svg>
714714
);
715715
}
716+
717+
export function NillionIcon({ className = "", size = 20 }: IconProps) {
718+
return (
719+
<svg
720+
width={size}
721+
height={size}
722+
viewBox="0 0 24 24"
723+
fill="none"
724+
xmlns="http://www.w3.org/2000/svg"
725+
className={className}
726+
>
727+
{/* Nillion "N" logo inspired design */}
728+
<path d="M6 18V6L12 12L18 6V18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
729+
<path d="M6 6L12 12L18 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
730+
</svg>
731+
);
732+
}

app/components/MenuBar.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { useCustomization } from "./CustomizationContext";
77
import CustomizationModal from "./CustomizationModal";
88
import LLMConfigModal from "./LLMConfigModal";
99
import { MyDataDropdown, ResultsDropdown, CacheDropdown, HelpDropdown } from "./MenuDropdowns";
10-
import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon } from "./Icons";
10+
import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon, NillionIcon } from "./Icons";
1111
import { getLLMConfig, getProviderDisplayName } from "@/lib/llm-config";
12+
import NillionModal from "./NillionModal";
1213

1314
export default function MenuBar() {
1415
const { isUploaded, genotypeData, fileHash } = useGenotype();
@@ -21,6 +22,7 @@ export default function MenuBar() {
2122
const [showResultsDropdown, setShowResultsDropdown] = useState(false);
2223
const [showCacheDropdown, setShowCacheDropdown] = useState(false);
2324
const [showHelpDropdown, setShowHelpDropdown] = useState(false);
25+
const [showNillionModal, setShowNillionModal] = useState(false);
2426
const [mounted, setMounted] = useState(false);
2527
// Initialize theme from localStorage (lazy initialization to avoid hydration issues)
2628
const [theme, setTheme] = useState<"light" | "dark">(() => {
@@ -187,6 +189,10 @@ export default function MenuBar() {
187189
window.dispatchEvent(event);
188190
}}
189191
/>
192+
<NillionModal
193+
isOpen={showNillionModal}
194+
onClose={() => setShowNillionModal(false)}
195+
/>
190196
<div className="menu-bar">
191197
<div className="menu-left">
192198
<h1 className="app-title">
@@ -282,6 +288,17 @@ export default function MenuBar() {
282288
<span className="label">Help</span>
283289
</button>
284290

291+
<button
292+
className="menu-icon-button"
293+
onClick={() => setShowNillionModal(true)}
294+
title="x Nillion: Test your crypto degen score"
295+
>
296+
<span className="icon">
297+
<NillionIcon size={32} />
298+
</span>
299+
<span className="label">x Nillion</span>
300+
</button>
301+
285302
{mounted && (
286303
<button
287304
className="menu-icon-button"

0 commit comments

Comments
 (0)