lightnode-sdk 0.7.17 → 0.7.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/add.d.ts CHANGED
@@ -70,6 +70,39 @@ export declare function addChatWeb3(opts?: AddOpts): {
70
70
  network: Network;
71
71
  needsWagmi: boolean;
72
72
  };
73
+ /**
74
+ * `lightnode add inference-web3` - the user-pays counterpart to addInference.
75
+ *
76
+ * Browser-only React component. Each visitor signs SIWE + createSession from
77
+ * their own wallet. No backend, no PRIVATE_KEY, no per-call cost for the dev.
78
+ *
79
+ * Fits any one-shot inference UI: classifier, NFT trait generator, content
80
+ * moderation, evaluator. For multi-turn chat use addChatWeb3; for the
81
+ * dedicated judge pattern use addJudgeWeb3.
82
+ */
83
+ export declare function addInferenceWeb3(opts?: AddOpts): {
84
+ written: WrittenFile[];
85
+ install: string;
86
+ template: Template;
87
+ network: Network;
88
+ needsWagmi: boolean;
89
+ };
90
+ /**
91
+ * `lightnode add judge-web3` - the user-pays counterpart to addJudge.
92
+ *
93
+ * The LightChallenge pattern with the user paying: visitor submits criteria
94
+ * + evidence, signs from their own wallet, the structured pass/fail/confidence
95
+ * verdict comes back with on-chain proof. Fit: challenge completion grading,
96
+ * NFT trait verification, content moderation, automated scoring - any flow
97
+ * where the END USER wants a verifiable AI verdict on their own submission.
98
+ */
99
+ export declare function addJudgeWeb3(opts?: AddOpts): {
100
+ written: WrittenFile[];
101
+ install: string;
102
+ template: Template;
103
+ network: Network;
104
+ needsWagmi: boolean;
105
+ };
73
106
  export declare function addWagmiSetup(opts?: AddOpts): {
74
107
  written: WrittenFile[];
75
108
  install: string;
package/dist/add.js CHANGED
@@ -1140,6 +1140,354 @@ export default function ChatWeb3() {
1140
1140
  );
1141
1141
  }
1142
1142
  `;
1143
+ const NEXTJS_INFERENCE_WEB3_PAGE = `// app/inference-web3/page.tsx
1144
+ // Generated by 'lightnode add inference-web3'. User-pays one-shot inference:
1145
+ // each visitor's wallet signs SIWE + createSession + submitJob. Your app
1146
+ // holds zero funds and the answer comes back with on-chain proof anyone
1147
+ // can verify.
1148
+ //
1149
+ // Prereqs:
1150
+ // - wagmi configured (npx lightnode add wagmi-setup if you don't have it)
1151
+ // - the connected wallet has LCAI on the chain it's on
1152
+ // (mainnet 9200 or testnet 8200). Mainnet llama3-8b is 0.02 LCAI per call.
1153
+ "use client";
1154
+
1155
+ import { useEffect, useState } from "react";
1156
+ import { useAccount, useWalletClient, usePublicClient } from "wagmi";
1157
+ import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
1158
+
1159
+ type Result = {
1160
+ answer: string;
1161
+ worker: \`0x\${string}\`;
1162
+ jobId: string;
1163
+ submitJob: \`0x\${string}\`;
1164
+ jobCompleted: \`0x\${string}\` | null;
1165
+ elapsedMs: number;
1166
+ };
1167
+
1168
+ const MODEL = "llama3-8b";
1169
+
1170
+ export default function InferenceWeb3() {
1171
+ const { address, chain } = useAccount();
1172
+ const network: "mainnet" | "testnet" | null =
1173
+ chain?.id === 9200 ? "mainnet" : chain?.id === 8200 ? "testnet" : null;
1174
+ const { data: walletClient } = useWalletClient({ chainId: chain?.id });
1175
+ const publicClient = usePublicClient({ chainId: chain?.id });
1176
+
1177
+ const [system, setSystem] = useState("You are a concise assistant. Reply in one or two short sentences.");
1178
+ const [prompt, setPrompt] = useState("Reply with the single word OK.");
1179
+ const [busy, setBusy] = useState(false);
1180
+ const [busyStage, setBusyStage] = useState("");
1181
+ const [result, setResult] = useState<Result | null>(null);
1182
+ const [err, setErr] = useState<string | null>(null);
1183
+ const [feeLcai, setFeeLcai] = useState<number | null>(null);
1184
+
1185
+ useEffect(() => {
1186
+ if (!network) { setFeeLcai(null); return; }
1187
+ let cancelled = false;
1188
+ estimateJobFee(NETWORKS[network], MODEL).then(
1189
+ (fee) => { if (!cancelled) setFeeLcai(fee); },
1190
+ () => { if (!cancelled) setFeeLcai(null); },
1191
+ );
1192
+ return () => { cancelled = true; };
1193
+ }, [network]);
1194
+
1195
+ async function run() {
1196
+ if (!walletClient || !publicClient || !address || !network) {
1197
+ setErr("Connect a wallet on LightChain mainnet (9200) or testnet (8200) first.");
1198
+ return;
1199
+ }
1200
+ if (!prompt.trim()) return;
1201
+ setBusy(true);
1202
+ setErr(null);
1203
+ setResult(null);
1204
+ const t0 = Date.now();
1205
+ try {
1206
+ setBusyStage("Sign in with your wallet (SIWE)...");
1207
+ const session = await siweSignIn(walletClient as unknown as Parameters<typeof siweSignIn>[0], network);
1208
+
1209
+ setBusyStage("Approve the createSession transaction in your wallet...");
1210
+ const gateway = new GatewayClient({ network, bearer: session.bearer });
1211
+ const composed = system.trim() ? \`\${system.trim()}\\n\\n\${prompt}\` : prompt;
1212
+ const r = await runInference({
1213
+ prompt: composed,
1214
+ gateway,
1215
+ wallet: walletClient as unknown as Parameters<typeof runInference>[0]["wallet"],
1216
+ publicClient: publicClient as unknown as Parameters<typeof runInference>[0]["publicClient"],
1217
+ network: NETWORKS[network],
1218
+ model: MODEL,
1219
+ jobCompletedTimeoutMs: 120_000,
1220
+ maxRetries: 1,
1221
+ });
1222
+ setResult({
1223
+ answer: r.answer,
1224
+ worker: r.worker,
1225
+ jobId: r.jobId?.toString() ?? "",
1226
+ submitJob: r.txs?.submitJob,
1227
+ jobCompleted: r.txs?.jobCompleted ?? null,
1228
+ elapsedMs: Date.now() - t0,
1229
+ });
1230
+ } catch (e) {
1231
+ const msg = (e as Error).message ?? "inference failed";
1232
+ setErr(
1233
+ /user rejected|user denied|reject/i.test(msg) ? "You rejected the wallet popup. Try again."
1234
+ : /insufficient funds|insufficient balance/i.test(msg)
1235
+ ? \`Your wallet has no \${network === "mainnet" ? "LCAI" : "testnet LCAI"}. Top it up before sending.\`
1236
+ : msg.split("\\n")[0]
1237
+ );
1238
+ } finally {
1239
+ setBusy(false);
1240
+ setBusyStage("");
1241
+ }
1242
+ }
1243
+
1244
+ return (
1245
+ <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1246
+ <h1>Inference (user-pays)</h1>
1247
+ <p style={{ color: "#666" }}>
1248
+ Signs one encrypted inference from your wallet on{" "}
1249
+ <code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
1250
+ <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per call plus a small gas amount.
1251
+ </p>
1252
+ {!address && (
1253
+ <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
1254
+ Connect a wallet to run inference. Drop &lt;ConnectButton /&gt; here or wherever your app exposes one.
1255
+ </div>
1256
+ )}
1257
+
1258
+ <label style={{ display: "block", margin: "12px 0" }}>
1259
+ <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>System prompt</div>
1260
+ <textarea value={system} onChange={(e) => setSystem(e.target.value)} rows={2}
1261
+ style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1262
+ </label>
1263
+ <label style={{ display: "block", margin: "12px 0" }}>
1264
+ <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>Prompt</div>
1265
+ <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} rows={5}
1266
+ style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1267
+ </label>
1268
+ <button type="button" onClick={() => run()} disabled={busy || !prompt.trim() || !address || !network}
1269
+ style={{ padding: "8px 16px", borderRadius: 8, cursor: busy ? "wait" : "pointer" }}>
1270
+ {busy ? (busyStage || "Running...") : "Run inference"}
1271
+ </button>
1272
+
1273
+ {err && (
1274
+ <p style={{ marginTop: 12, padding: "8px 12px", border: "1px solid #f5c2c7", background: "#f8d7da", color: "#842029", borderRadius: 6 }}>
1275
+ {err}
1276
+ </p>
1277
+ )}
1278
+
1279
+ {result && (
1280
+ <div style={{ marginTop: 16, padding: 16, border: "1px solid #ddd", borderRadius: 8 }}>
1281
+ <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 8 }}>Answer</div>
1282
+ <pre style={{ whiteSpace: "pre-wrap", margin: 0, fontFamily: "inherit" }}>{result.answer}</pre>
1283
+ <div style={{ marginTop: 12, fontSize: 12, color: "#666", display: "flex", gap: 12, flexWrap: "wrap" }}>
1284
+ <span>elapsed {Math.round(result.elapsedMs / 1000)}s</span>
1285
+ <span>job #{result.jobId}</span>
1286
+ <a href={\`https://\${network}.lightscan.app/address/\${result.worker}\`} target="_blank" rel="noopener noreferrer">worker</a>
1287
+ <a href={\`https://\${network}.lightscan.app/tx/\${result.submitJob}\`} target="_blank" rel="noopener noreferrer">submitJob</a>
1288
+ {result.jobCompleted && (
1289
+ <a href={\`https://\${network}.lightscan.app/tx/\${result.jobCompleted}\`} target="_blank" rel="noopener noreferrer">jobCompleted</a>
1290
+ )}
1291
+ </div>
1292
+ </div>
1293
+ )}
1294
+ </main>
1295
+ );
1296
+ }
1297
+ `;
1298
+ const NEXTJS_JUDGE_WEB3_PAGE = `// app/judge-web3/page.tsx
1299
+ // Generated by 'lightnode add judge-web3'. The LightChallenge pattern with
1300
+ // the user paying: visitor submits criteria + evidence, signs from their own
1301
+ // wallet, the verdict comes back with on-chain proof. Pattern fits any
1302
+ // platform where users want a verifiable AI verdict on their own submission
1303
+ // (challenge completion, NFT trait grading, content moderation, etc.).
1304
+ //
1305
+ // Prereqs:
1306
+ // - wagmi configured (npx lightnode add wagmi-setup if you don't have it)
1307
+ // - the connected wallet has LCAI on the chain it's on
1308
+ "use client";
1309
+
1310
+ import { useEffect, useState } from "react";
1311
+ import { useAccount, useWalletClient, usePublicClient } from "wagmi";
1312
+ import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
1313
+
1314
+ type Verdict = {
1315
+ passed: boolean;
1316
+ confidence: number;
1317
+ reason: string;
1318
+ };
1319
+
1320
+ type Result = {
1321
+ verdict: Verdict | null;
1322
+ raw: string;
1323
+ worker: \`0x\${string}\`;
1324
+ jobId: string;
1325
+ submitJob: \`0x\${string}\`;
1326
+ jobCompleted: \`0x\${string}\` | null;
1327
+ };
1328
+
1329
+ const MODEL = "llama3-8b";
1330
+
1331
+ export default function JudgeWeb3() {
1332
+ const { address, chain } = useAccount();
1333
+ const network: "mainnet" | "testnet" | null =
1334
+ chain?.id === 9200 ? "mainnet" : chain?.id === 8200 ? "testnet" : null;
1335
+ const { data: walletClient } = useWalletClient({ chainId: chain?.id });
1336
+ const publicClient = usePublicClient({ chainId: chain?.id });
1337
+
1338
+ const [criteria, setCriteria] = useState("Run a mile under 8 minutes");
1339
+ const [evidence, setEvidence] = useState('{"distance_km": 1.61, "time_minutes": 7.4}');
1340
+ const [busy, setBusy] = useState(false);
1341
+ const [busyStage, setBusyStage] = useState("");
1342
+ const [result, setResult] = useState<Result | null>(null);
1343
+ const [err, setErr] = useState<string | null>(null);
1344
+ const [feeLcai, setFeeLcai] = useState<number | null>(null);
1345
+
1346
+ useEffect(() => {
1347
+ if (!network) { setFeeLcai(null); return; }
1348
+ let cancelled = false;
1349
+ estimateJobFee(NETWORKS[network], MODEL).then(
1350
+ (fee) => { if (!cancelled) setFeeLcai(fee); },
1351
+ () => { if (!cancelled) setFeeLcai(null); },
1352
+ );
1353
+ return () => { cancelled = true; };
1354
+ }, [network]);
1355
+
1356
+ /** Parse the verdict defensively; fall back to the first {...} block. */
1357
+ function parseVerdict(answer: string): Verdict | null {
1358
+ try { return JSON.parse(answer) as Verdict; } catch { /* try regex */ }
1359
+ const m = answer.match(/\\{[\\s\\S]*\\}/);
1360
+ if (m) {
1361
+ try { return JSON.parse(m[0]) as Verdict; } catch { /* keep null */ }
1362
+ }
1363
+ return null;
1364
+ }
1365
+
1366
+ async function run() {
1367
+ if (!walletClient || !publicClient || !address || !network) {
1368
+ setErr("Connect a wallet on LightChain mainnet (9200) or testnet (8200) first.");
1369
+ return;
1370
+ }
1371
+ if (!criteria.trim() || !evidence.trim()) return;
1372
+ // Validate evidence JSON before paying.
1373
+ try { JSON.parse(evidence); } catch {
1374
+ setErr("Evidence is not valid JSON. Fix it and try again.");
1375
+ return;
1376
+ }
1377
+ setBusy(true);
1378
+ setErr(null);
1379
+ setResult(null);
1380
+ try {
1381
+ setBusyStage("Sign in with your wallet (SIWE)...");
1382
+ const session = await siweSignIn(walletClient as unknown as Parameters<typeof siweSignIn>[0], network);
1383
+
1384
+ setBusyStage("Approve the createSession transaction in your wallet...");
1385
+ const gateway = new GatewayClient({ network, bearer: session.bearer });
1386
+ const prompt = \`Criteria: \${criteria.trim()}
1387
+
1388
+ Evidence: \${evidence.trim()}
1389
+
1390
+ Reply with STRICT JSON only, matching: { "passed": boolean, "confidence": 0-1, "reason": string }\`;
1391
+
1392
+ const r = await runInference({
1393
+ prompt: \`You are a careful judge. Reply with STRICT JSON only, no prose.\\n\\n\${prompt}\`,
1394
+ gateway,
1395
+ wallet: walletClient as unknown as Parameters<typeof runInference>[0]["wallet"],
1396
+ publicClient: publicClient as unknown as Parameters<typeof runInference>[0]["publicClient"],
1397
+ network: NETWORKS[network],
1398
+ model: MODEL,
1399
+ jobCompletedTimeoutMs: 120_000,
1400
+ maxRetries: 1,
1401
+ });
1402
+ setResult({
1403
+ verdict: parseVerdict(r.answer),
1404
+ raw: r.answer,
1405
+ worker: r.worker,
1406
+ jobId: r.jobId?.toString() ?? "",
1407
+ submitJob: r.txs?.submitJob,
1408
+ jobCompleted: r.txs?.jobCompleted ?? null,
1409
+ });
1410
+ } catch (e) {
1411
+ const msg = (e as Error).message ?? "judge failed";
1412
+ setErr(
1413
+ /user rejected|user denied|reject/i.test(msg) ? "You rejected the wallet popup. Try again."
1414
+ : /insufficient funds|insufficient balance/i.test(msg)
1415
+ ? \`Your wallet has no \${network === "mainnet" ? "LCAI" : "testnet LCAI"}. Top it up before submitting.\`
1416
+ : msg.split("\\n")[0]
1417
+ );
1418
+ } finally {
1419
+ setBusy(false);
1420
+ setBusyStage("");
1421
+ }
1422
+ }
1423
+
1424
+ return (
1425
+ <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1426
+ <h1>AI Judge (user-pays)</h1>
1427
+ <p style={{ color: "#666" }}>
1428
+ Each submission signs one inference from your wallet on{" "}
1429
+ <code>{network ?? "(connect a wallet)"}</code>. Cost:{" "}
1430
+ <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> plus gas. Verdict comes back with on-chain proof.
1431
+ </p>
1432
+ {!address && (
1433
+ <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
1434
+ Connect a wallet to submit. Drop &lt;ConnectButton /&gt; here or wherever your app exposes one.
1435
+ </div>
1436
+ )}
1437
+
1438
+ <label style={{ display: "block", margin: "12px 0" }}>
1439
+ <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>Criteria</div>
1440
+ <textarea value={criteria} onChange={(e) => setCriteria(e.target.value)} rows={2}
1441
+ style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1442
+ </label>
1443
+ <label style={{ display: "block", margin: "12px 0" }}>
1444
+ <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>Evidence (JSON)</div>
1445
+ <textarea value={evidence} onChange={(e) => setEvidence(e.target.value)} rows={5}
1446
+ style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1447
+ </label>
1448
+ <button type="button" onClick={() => run()} disabled={busy || !criteria.trim() || !evidence.trim() || !address || !network}
1449
+ style={{ padding: "8px 16px", borderRadius: 8, cursor: busy ? "wait" : "pointer" }}>
1450
+ {busy ? (busyStage || "Judging...") : "Get AI verdict"}
1451
+ </button>
1452
+
1453
+ {err && (
1454
+ <p style={{ marginTop: 12, padding: "8px 12px", border: "1px solid #f5c2c7", background: "#f8d7da", color: "#842029", borderRadius: 6 }}>
1455
+ {err}
1456
+ </p>
1457
+ )}
1458
+
1459
+ {result && (
1460
+ <div style={{ marginTop: 16, padding: 16, border: "1px solid #ddd", borderRadius: 8 }}>
1461
+ <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 8 }}>Verdict</div>
1462
+ {result.verdict ? (
1463
+ <div>
1464
+ <div style={{ fontSize: 24, fontWeight: 600, color: result.verdict.passed ? "#2e7d32" : "#c62828" }}>
1465
+ {result.verdict.passed ? "PASSED" : "FAILED"}
1466
+ <span style={{ marginLeft: 12, fontSize: 14, color: "#666" }}>
1467
+ confidence {Math.round(result.verdict.confidence * 100)}%
1468
+ </span>
1469
+ </div>
1470
+ <p style={{ marginTop: 8, color: "#444" }}>{result.verdict.reason}</p>
1471
+ </div>
1472
+ ) : (
1473
+ <pre style={{ whiteSpace: "pre-wrap", margin: 0, fontFamily: "monospace", fontSize: 12, color: "#666" }}>
1474
+ {result.raw}
1475
+ </pre>
1476
+ )}
1477
+ <div style={{ marginTop: 12, fontSize: 12, color: "#666", display: "flex", gap: 12, flexWrap: "wrap" }}>
1478
+ <span>job #{result.jobId}</span>
1479
+ <a href={\`https://\${network}.lightscan.app/address/\${result.worker}\`} target="_blank" rel="noopener noreferrer">worker</a>
1480
+ <a href={\`https://\${network}.lightscan.app/tx/\${result.submitJob}\`} target="_blank" rel="noopener noreferrer">submitJob</a>
1481
+ {result.jobCompleted && (
1482
+ <a href={\`https://\${network}.lightscan.app/tx/\${result.jobCompleted}\`} target="_blank" rel="noopener noreferrer">jobCompleted</a>
1483
+ )}
1484
+ </div>
1485
+ </div>
1486
+ )}
1487
+ </main>
1488
+ );
1489
+ }
1490
+ `;
1143
1491
  const NODE_CHAT_REPL = `// chat-repl.ts
1144
1492
  // Generated by 'lightnode add chat'. Interactive chat REPL in your terminal.
1145
1493
  // npm install lightnode-sdk viem ws
@@ -1426,6 +1774,59 @@ export function addChatWeb3(opts = {}) {
1426
1774
  needsWagmi: !hasWagmi,
1427
1775
  };
1428
1776
  }
1777
+ /**
1778
+ * `lightnode add inference-web3` - the user-pays counterpart to addInference.
1779
+ *
1780
+ * Browser-only React component. Each visitor signs SIWE + createSession from
1781
+ * their own wallet. No backend, no PRIVATE_KEY, no per-call cost for the dev.
1782
+ *
1783
+ * Fits any one-shot inference UI: classifier, NFT trait generator, content
1784
+ * moderation, evaluator. For multi-turn chat use addChatWeb3; for the
1785
+ * dedicated judge pattern use addJudgeWeb3.
1786
+ */
1787
+ export function addInferenceWeb3(opts = {}) {
1788
+ const cwd = opts.cwd ?? process.cwd();
1789
+ const network = opts.network ?? "mainnet";
1790
+ const detected = detectTemplate(cwd);
1791
+ const template = opts.template && opts.template !== "auto" ? opts.template : detected;
1792
+ const force = !!opts.force;
1793
+ const written = [];
1794
+ const pkg = readPackageJson(cwd);
1795
+ const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) };
1796
+ const hasWagmi = Boolean(deps["wagmi"]);
1797
+ written.push(writeFile(path.join(cwd, "app/inference-web3/page.tsx"), NEXTJS_INFERENCE_WEB3_PAGE, force));
1798
+ return {
1799
+ written,
1800
+ install: `npm install lightnode-sdk viem` + (hasWagmi ? "" : " wagmi @tanstack/react-query"),
1801
+ template, network, needsWagmi: !hasWagmi,
1802
+ };
1803
+ }
1804
+ /**
1805
+ * `lightnode add judge-web3` - the user-pays counterpart to addJudge.
1806
+ *
1807
+ * The LightChallenge pattern with the user paying: visitor submits criteria
1808
+ * + evidence, signs from their own wallet, the structured pass/fail/confidence
1809
+ * verdict comes back with on-chain proof. Fit: challenge completion grading,
1810
+ * NFT trait verification, content moderation, automated scoring - any flow
1811
+ * where the END USER wants a verifiable AI verdict on their own submission.
1812
+ */
1813
+ export function addJudgeWeb3(opts = {}) {
1814
+ const cwd = opts.cwd ?? process.cwd();
1815
+ const network = opts.network ?? "mainnet";
1816
+ const detected = detectTemplate(cwd);
1817
+ const template = opts.template && opts.template !== "auto" ? opts.template : detected;
1818
+ const force = !!opts.force;
1819
+ const written = [];
1820
+ const pkg = readPackageJson(cwd);
1821
+ const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) };
1822
+ const hasWagmi = Boolean(deps["wagmi"]);
1823
+ written.push(writeFile(path.join(cwd, "app/judge-web3/page.tsx"), NEXTJS_JUDGE_WEB3_PAGE, force));
1824
+ return {
1825
+ written,
1826
+ install: `npm install lightnode-sdk viem` + (hasWagmi ? "" : " wagmi @tanstack/react-query"),
1827
+ template, network, needsWagmi: !hasWagmi,
1828
+ };
1829
+ }
1429
1830
  const WAGMI_CONFIG_FILE = `// lib/wagmi.ts
1430
1831
  // Generated by 'lightnode add wagmi-setup'. Minimal wagmi setup for
1431
1832
  // LightChain mainnet (9200) + testnet (8200). Use this as a starting
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { LightNode, modelStatsCsv, workerStatsCsv, workerJobsCsv, runInferenceWithKey, runInferenceBatch, Agent, isStalledWorker, workerPreflight, workerWatch, WorkerOperator, isWorkerOpError, BRIDGE_ROUTE, DAO, DAO_ADDRESSES } from "./index.js";
3
- import { addInference, addAnalyticsDashboard, addNftMint, addChat, addChatWeb3, addAgent, addJudge, addWagmiSetup } from "./add.js";
3
+ import { addInference, addInferenceWeb3, addJudgeWeb3, addAnalyticsDashboard, addNftMint, addChat, addChatWeb3, addAgent, addJudge, addWagmiSetup } from "./add.js";
4
4
  import { createPublicClient, createWalletClient, http, parseEther } from "viem";
5
5
  import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
6
6
  function flag(name) {
@@ -462,25 +462,20 @@ async function main() {
462
462
  const template = flag("--template") ?? "auto";
463
463
  const force = process.argv.includes("--force");
464
464
  const network = (net === "mainnet" ? "mainnet" : "testnet");
465
- const known = ["inference", "chat", "chat-web3", "wagmi-setup", "agent", "judge", "analytics-dashboard", "nft-mint-with-inference"];
465
+ const known = ["inference", "inference-web3", "chat", "chat-web3", "judge", "judge-web3", "wagmi-setup", "agent", "analytics-dashboard", "nft-mint-with-inference"];
466
466
  if (!known.includes(sub ?? "")) {
467
467
  die(`usage: lightnode add <${known.join("|")}> [--template auto|nextjs-api|hono|node] [--net testnet|mainnet] [--force]`);
468
468
  }
469
- const result = sub === "analytics-dashboard"
470
- ? addAnalyticsDashboard({ template, network, force })
471
- : sub === "nft-mint-with-inference"
472
- ? addNftMint({ template, network, force })
473
- : sub === "chat-web3"
474
- ? addChatWeb3({ template, network, force })
475
- : sub === "wagmi-setup"
476
- ? addWagmiSetup({ template, network, force })
477
- : sub === "chat"
478
- ? addChat({ template, network, force })
479
- : sub === "agent"
480
- ? addAgent({ template, network, force })
481
- : sub === "judge"
482
- ? addJudge({ template, network, force })
483
- : addInference({ template, network, force });
469
+ const result = sub === "analytics-dashboard" ? addAnalyticsDashboard({ template, network, force })
470
+ : sub === "nft-mint-with-inference" ? addNftMint({ template, network, force })
471
+ : sub === "chat-web3" ? addChatWeb3({ template, network, force })
472
+ : sub === "inference-web3" ? addInferenceWeb3({ template, network, force })
473
+ : sub === "judge-web3" ? addJudgeWeb3({ template, network, force })
474
+ : sub === "wagmi-setup" ? addWagmiSetup({ template, network, force })
475
+ : sub === "chat" ? addChat({ template, network, force })
476
+ : sub === "agent" ? addAgent({ template, network, force })
477
+ : sub === "judge" ? addJudge({ template, network, force })
478
+ : addInference({ template, network, force });
484
479
  console.log(`▶ add ${sub} (${result.template} template, default network ${result.network})`);
485
480
  for (const f of result.written) {
486
481
  if (f.skipped)
@@ -504,24 +499,26 @@ async function main() {
504
499
  console.log(` 4. You can now use any wagmi hook (useAccount, useWalletClient, ...).`);
505
500
  console.log(` Wallets on chains other than 9200/8200 will be prompted to switch.`);
506
501
  }
507
- else if (sub === "chat-web3") {
508
- // chat-web3 has no PRIVATE_KEY (each visitor pays their own way).
502
+ else if (sub === "chat-web3" || sub === "inference-web3" || sub === "judge-web3") {
503
+ // *-web3 variants have no PRIVATE_KEY (each visitor pays their own way).
509
504
  const needsWagmi = result.needsWagmi;
505
+ const route = sub === "chat-web3" ? "/chat-web3"
506
+ : sub === "inference-web3" ? "/inference-web3"
507
+ : "/judge-web3";
510
508
  if (needsWagmi) {
511
509
  console.log(` 2. Get wagmi wired up with one command:`);
512
510
  console.log(` npx lightnode add wagmi-setup`);
513
511
  console.log(` (drops lib/wagmi.ts + app/providers.tsx + components/connect-button.tsx)`);
514
- console.log(` 3. Wrap your layout with <Providers> (see step 2 output) and drop`);
515
- console.log(` <ConnectButton /> somewhere on the page.`);
516
- console.log(` 4. npm run dev, open /chat-web3, connect on chainId ${result.network === "mainnet" ? "9200" : "8200"}.`);
517
- console.log(` Mainnet llama3-8b costs 0.02 LCAI per turn; testnet is free from https://lightfaucet.ai`);
512
+ console.log(` 3. Wrap your layout with <Providers> and drop <ConnectButton /> on the page.`);
513
+ console.log(` 4. npm run dev, open ${route}, connect on chainId ${result.network === "mainnet" ? "9200" : "8200"}.`);
514
+ console.log(` Mainnet llama3-8b is 0.02 LCAI per call; testnet is free from https://lightfaucet.ai`);
518
515
  }
519
516
  else {
520
- console.log(` 2. npm run dev, open /chat-web3`);
517
+ console.log(` 2. npm run dev, open ${route}`);
521
518
  console.log(` 3. Connect a wallet on LightChain ${result.network === "mainnet" ? "mainnet (chainId 9200)" : "testnet (chainId 8200)"}.`);
522
- console.log(` Mainnet llama3-8b costs 0.02 LCAI per turn; testnet is free from https://lightfaucet.ai`);
519
+ console.log(` Mainnet llama3-8b is 0.02 LCAI per call; testnet is free from https://lightfaucet.ai`);
523
520
  }
524
- console.log(`\n Note: chat-web3 has NO server-side route, so it scales infinitely on`);
521
+ console.log(`\n Note: ${sub} has NO server-side route, so it scales infinitely on`);
525
522
  console.log(` static hosting (Vercel/Netlify/Cloudflare Pages free tier all work).`);
526
523
  }
527
524
  else if (sub === "nft-mint-with-inference" || sub === "inference" || sub === "chat" || sub === "agent" || sub === "judge") {
package/dist/index.d.ts CHANGED
@@ -134,7 +134,7 @@ export declare class LightNode {
134
134
  * (especially in registry-proxy environments like StackBlitz where lockfiles
135
135
  * may pin an older minor than the local install command suggests).
136
136
  */
137
- export declare const SDK_VERSION = "0.7.17";
137
+ export declare const SDK_VERSION = "0.7.18";
138
138
  export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, resolveJobTransactions, siweSignIn, siweChallenge, siweVerify, fetchWorkerModels, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, Conversation, chat, runInferenceBatch, Agent, parseAgentOutput, workerPreflight, workerWatch, Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, WorkerOperator, WORKER_REGISTRY_ABI, JOB_REGISTRY_OPERATOR_ABI, AI_CONFIG_ABI, JOB_STATE, decodeWorkerError, WorkerOpError, isWorkerOpError, };
139
139
  export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
140
140
  export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
package/dist/index.js CHANGED
@@ -213,7 +213,7 @@ export class LightNode {
213
213
  * (especially in registry-proxy environments like StackBlitz where lockfiles
214
214
  * may pin an older minor than the local install command suggests).
215
215
  */
216
- export const SDK_VERSION = "0.7.17";
216
+ export const SDK_VERSION = "0.7.18";
217
217
  export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei,
218
218
  // v0.7.3 per-job transaction-hash resolver (lifts the upstream
219
219
  // subgraph's "block-only" Job entity to a deep-linkable Job + tx pair).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightnode-sdk",
3
- "version": "0.7.17",
3
+ "version": "0.7.18",
4
4
  "description": "Read-only TypeScript client for LightChain AI: workers, jobs, models, on-chain registration, and per-model network analytics. Independent, community-built (not an official LightChain package).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",