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 +33 -0
- package/dist/add.js +401 -0
- package/dist/cli.js +23 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
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 <ConnectButton /> 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 <ConnectButton /> 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", "
|
|
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
|
-
?
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
//
|
|
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>
|
|
515
|
-
console.log(`
|
|
516
|
-
console.log(`
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|