lightnode-sdk 0.7.14 → 0.7.15

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
@@ -48,6 +48,28 @@ export declare function addChat(opts?: AddOpts): {
48
48
  template: Template;
49
49
  network: Network;
50
50
  };
51
+ /**
52
+ * `lightnode add chat-web3` - the user-pays counterpart to addChat.
53
+ *
54
+ * Architecture:
55
+ * - Each visitor's own wallet signs SIWE + createSession per turn.
56
+ * - The dev's app holds zero funds; cost is borne by each user (0.02 LCAI
57
+ * per llama3-8b turn on mainnet).
58
+ * - No backend, no PRIVATE_KEY, no server-side hot wallet.
59
+ *
60
+ * Fit:
61
+ * - Web3 dApps where users already have a wallet (NFT, meme coin, on-chain
62
+ * games, LightChallenge-style challenge platforms).
63
+ * - For SaaS chatbots where users do NOT have a wallet, use `add chat`
64
+ * instead (dev pays, server-side route).
65
+ */
66
+ export declare function addChatWeb3(opts?: AddOpts): {
67
+ written: WrittenFile[];
68
+ install: string;
69
+ template: Template;
70
+ network: Network;
71
+ needsWagmi: boolean;
72
+ };
51
73
  export declare function addJudge(opts?: AddOpts): {
52
74
  written: WrittenFile[];
53
75
  install: string;
package/dist/add.js CHANGED
@@ -671,6 +671,207 @@ export default function Chat() {
671
671
  );
672
672
  }
673
673
  `;
674
+ const NEXTJS_CHAT_WEB3_PAGE = `// app/chat-web3/page.tsx
675
+ // Generated by 'lightnode add chat-web3'. User-pays chat: each visitor's own
676
+ // wallet signs SIWE + createSession per turn. Your app holds zero funds.
677
+ //
678
+ // Prereqs:
679
+ // - wagmi configured in your app (https://wagmi.sh/react/getting-started)
680
+ // - the connected wallet has LCAI on the LightChain network it is on
681
+ // (mainnet 9200 or testnet 8200). Mainnet llama3-8b costs 0.02 LCAI per
682
+ // turn plus a small gas amount.
683
+ "use client";
684
+
685
+ import { useEffect, useState } from "react";
686
+ import { useAccount, useWalletClient, usePublicClient } from "wagmi";
687
+ import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
688
+
689
+ type Turn = {
690
+ role: "user" | "assistant";
691
+ text: string;
692
+ worker?: string | null;
693
+ jobId?: string | null;
694
+ submitTx?: \`0x\${string}\` | null;
695
+ jobCompletedTx?: \`0x\${string}\` | null;
696
+ };
697
+
698
+ const MODEL = "llama3-8b";
699
+
700
+ export default function ChatWeb3() {
701
+ const { address, chain } = useAccount();
702
+ const network: "mainnet" | "testnet" | null =
703
+ chain?.id === 9200 ? "mainnet" : chain?.id === 8200 ? "testnet" : null;
704
+ const { data: walletClient } = useWalletClient({ chainId: chain?.id });
705
+ const publicClient = usePublicClient({ chainId: chain?.id });
706
+
707
+ const [turns, setTurns] = useState<Turn[]>([]);
708
+ const [input, setInput] = useState("");
709
+ const [busy, setBusy] = useState(false);
710
+ const [busyStage, setBusyStage] = useState("");
711
+ const [err, setErr] = useState<string | null>(null);
712
+ const [feeLcai, setFeeLcai] = useState<number | null>(null);
713
+
714
+ // Read the on-chain fee for the connected network so we can show the
715
+ // visitor the real cost per turn before they click Send.
716
+ useEffect(() => {
717
+ if (!network) { setFeeLcai(null); return; }
718
+ let cancelled = false;
719
+ estimateJobFee(NETWORKS[network], MODEL).then(
720
+ (fee) => { if (!cancelled) setFeeLcai(fee); },
721
+ () => { if (!cancelled) setFeeLcai(null); },
722
+ );
723
+ return () => { cancelled = true; };
724
+ }, [network]);
725
+
726
+ /** Build a single prompt from history + new user input. */
727
+ function composePrompt(history: Turn[], next: string, system: string): string {
728
+ const lines: string[] = [];
729
+ for (const t of history) {
730
+ lines.push(\`\${t.role === "user" ? "User" : "Assistant"}: \${t.text}\`);
731
+ }
732
+ lines.push(\`User: \${next}\`);
733
+ lines.push("Assistant:");
734
+ return system ? \`\${system}\\n\\n\${lines.join("\\n\\n")}\` : lines.join("\\n\\n");
735
+ }
736
+
737
+ async function send() {
738
+ if (!walletClient || !publicClient || !address || !network) {
739
+ setErr("Connect a wallet on LightChain mainnet (9200) or testnet (8200) first.");
740
+ return;
741
+ }
742
+ const next = input.trim();
743
+ if (!next) return;
744
+ setBusy(true);
745
+ setErr(null);
746
+ const history = [...turns];
747
+ // Optimistic user bubble so it appears immediately.
748
+ setTurns([...history, { role: "user", text: next }]);
749
+ setInput("");
750
+ try {
751
+ const system = "You are a concise assistant. Reply in one or two short sentences.";
752
+ const prompt = composePrompt(history, next, system);
753
+
754
+ setBusyStage("Sign in with your wallet (SIWE)...");
755
+ const session = await siweSignIn(walletClient as unknown as Parameters<typeof siweSignIn>[0], network);
756
+
757
+ setBusyStage("Approve the createSession transaction in your wallet...");
758
+ const gateway = new GatewayClient({ network, bearer: session.bearer });
759
+ const result = await runInference({
760
+ prompt,
761
+ gateway,
762
+ wallet: walletClient as unknown as Parameters<typeof runInference>[0]["wallet"],
763
+ publicClient: publicClient as unknown as Parameters<typeof runInference>[0]["publicClient"],
764
+ network: NETWORKS[network],
765
+ model: MODEL,
766
+ jobCompletedTimeoutMs: 120_000,
767
+ maxRetries: 1,
768
+ });
769
+
770
+ setTurns([...history, { role: "user", text: next }, {
771
+ role: "assistant",
772
+ text: result.answer,
773
+ worker: result.worker,
774
+ jobId: result.jobId?.toString() ?? null,
775
+ submitTx: result.txs?.submitJob ?? null,
776
+ jobCompletedTx: result.txs?.jobCompleted ?? null,
777
+ }]);
778
+ } catch (e) {
779
+ // Roll back the optimistic user bubble so the visitor can retry.
780
+ setTurns(history);
781
+ setInput(next);
782
+ const msg = (e as Error).message ?? "chat failed";
783
+ setErr(
784
+ /user rejected|user denied|reject/i.test(msg)
785
+ ? "You rejected the wallet popup. Try again."
786
+ : /insufficient funds|insufficient balance/i.test(msg)
787
+ ? \`Your wallet has no \${network === "mainnet" ? "LCAI" : "testnet LCAI"}. Top it up before sending.\`
788
+ : msg.split("\\n")[0],
789
+ );
790
+ } finally {
791
+ setBusy(false);
792
+ setBusyStage("");
793
+ }
794
+ }
795
+
796
+ return (
797
+ <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
798
+ <h1>Chat (user-pays)</h1>
799
+ <p style={{ color: "#666" }}>
800
+ Each turn signs one createSession transaction from your wallet on{" "}
801
+ <code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
802
+ <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per turn plus a small gas amount.
803
+ </p>
804
+ {!address && (
805
+ <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
806
+ Connect a wallet to start chatting. (Use whichever connector your app exposes - e.g. RainbowKit,
807
+ ConnectKit, Reown AppKit, or wagmi&apos;s useConnect directly.)
808
+ </div>
809
+ )}
810
+
811
+ <div style={{ display: "flex", flexDirection: "column", gap: 8, margin: "16px 0" }}>
812
+ {turns.map((t, i) => (
813
+ <div
814
+ key={i}
815
+ style={{
816
+ alignSelf: t.role === "user" ? "flex-end" : "flex-start",
817
+ maxWidth: "85%",
818
+ borderRadius: 12,
819
+ padding: "8px 12px",
820
+ background: t.role === "user" ? "#e9e7ff" : "#f5f5f7",
821
+ }}
822
+ >
823
+ <div style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{t.text}</div>
824
+ {t.role === "assistant" && t.submitTx ? (
825
+ <div style={{ marginTop: 6, fontSize: 11, color: "#666", display: "flex", gap: 8, flexWrap: "wrap" }}>
826
+ {t.worker && (
827
+ <a href={\`https://\${network}.lightscan.app/address/\${t.worker}\`} target="_blank" rel="noopener noreferrer">
828
+ worker
829
+ </a>
830
+ )}
831
+ {t.jobId && <span>job #{t.jobId}</span>}
832
+ {t.submitTx && (
833
+ <a href={\`https://\${network}.lightscan.app/tx/\${t.submitTx}\`} target="_blank" rel="noopener noreferrer">
834
+ submitJob
835
+ </a>
836
+ )}
837
+ {t.jobCompletedTx && (
838
+ <a href={\`https://\${network}.lightscan.app/tx/\${t.jobCompletedTx}\`} target="_blank" rel="noopener noreferrer">
839
+ completed
840
+ </a>
841
+ )}
842
+ </div>
843
+ ) : null}
844
+ </div>
845
+ ))}
846
+ </div>
847
+
848
+ <textarea
849
+ value={input}
850
+ onChange={(e) => setInput(e.target.value)}
851
+ onKeyDown={(e) => {
852
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); if (!busy && input.trim()) send(); }
853
+ }}
854
+ rows={2}
855
+ placeholder={turns.length === 0 ? "Say hello (cmd+enter to send)" : "Reply..."}
856
+ style={{ width: "100%", padding: 8, fontFamily: "inherit" }}
857
+ />
858
+ <button
859
+ type="button"
860
+ onClick={() => send()}
861
+ disabled={busy || !input.trim() || !address || !network}
862
+ style={{ marginTop: 8, padding: "8px 16px" }}
863
+ >
864
+ {busy ? (busyStage || "Sending...") : (turns.length === 0 ? "Send first message" : "Send")}
865
+ </button>
866
+ {err && (
867
+ <p style={{ marginTop: 8, padding: "8px 12px", border: "1px solid #f5c2c7", background: "#f8d7da", color: "#842029", borderRadius: 6 }}>
868
+ {err}
869
+ </p>
870
+ )}
871
+ </main>
872
+ );
873
+ }
874
+ `;
674
875
  const NODE_CHAT_REPL = `// chat-repl.ts
675
876
  // Generated by 'lightnode add chat'. Interactive chat REPL in your terminal.
676
877
  // npm install lightnode-sdk viem ws
@@ -908,6 +1109,46 @@ export function addChat(opts = {}) {
908
1109
  written.push(writeFile(path.join(cwd, ".env.example"), ENV_EXAMPLE(network), force));
909
1110
  return { written, install: `npm install ${depsNeeded(template).join(" ")}`, template, network };
910
1111
  }
1112
+ /**
1113
+ * `lightnode add chat-web3` - the user-pays counterpart to addChat.
1114
+ *
1115
+ * Architecture:
1116
+ * - Each visitor's own wallet signs SIWE + createSession per turn.
1117
+ * - The dev's app holds zero funds; cost is borne by each user (0.02 LCAI
1118
+ * per llama3-8b turn on mainnet).
1119
+ * - No backend, no PRIVATE_KEY, no server-side hot wallet.
1120
+ *
1121
+ * Fit:
1122
+ * - Web3 dApps where users already have a wallet (NFT, meme coin, on-chain
1123
+ * games, LightChallenge-style challenge platforms).
1124
+ * - For SaaS chatbots where users do NOT have a wallet, use `add chat`
1125
+ * instead (dev pays, server-side route).
1126
+ */
1127
+ export function addChatWeb3(opts = {}) {
1128
+ const cwd = opts.cwd ?? process.cwd();
1129
+ const network = opts.network ?? "mainnet";
1130
+ // chat-web3 is browser-only. If the project is not Next.js (or another
1131
+ // React framework we'd detect), fall back to nextjs-api anyway and warn
1132
+ // the user in the CLI's next-steps that the file expects a Next.js
1133
+ // setup with wagmi.
1134
+ const detected = detectTemplate(cwd);
1135
+ const template = opts.template && opts.template !== "auto" ? opts.template : detected;
1136
+ const force = !!opts.force;
1137
+ const written = [];
1138
+ // Detect whether the project already has wagmi; if not, the next-steps
1139
+ // output prints the install line.
1140
+ const pkg = readPackageJson(cwd);
1141
+ const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) };
1142
+ const hasWagmi = Boolean(deps["wagmi"]);
1143
+ written.push(writeFile(path.join(cwd, "app/chat-web3/page.tsx"), NEXTJS_CHAT_WEB3_PAGE, force));
1144
+ return {
1145
+ written,
1146
+ install: `npm install lightnode-sdk viem` + (hasWagmi ? "" : " wagmi @tanstack/react-query"),
1147
+ template,
1148
+ network,
1149
+ needsWagmi: !hasWagmi,
1150
+ };
1151
+ }
911
1152
  const NEXTJS_JUDGE_ROUTE = `// app/api/judge/route.ts
912
1153
  // Generated by 'lightnode add judge'. See https://lightnode.app/build
913
1154
  // The LightChallenge-style evaluator: post evidence + criteria, get a
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, addAgent, addJudge } from "./add.js";
3
+ import { addInference, addAnalyticsDashboard, addNftMint, addChat, addChatWeb3, addAgent, addJudge } 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,7 +462,7 @@ 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", "agent", "judge", "analytics-dashboard", "nft-mint-with-inference"];
465
+ const known = ["inference", "chat", "chat-web3", "agent", "judge", "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
  }
@@ -470,13 +470,15 @@ async function main() {
470
470
  ? addAnalyticsDashboard({ template, network, force })
471
471
  : sub === "nft-mint-with-inference"
472
472
  ? addNftMint({ template, network, force })
473
- : sub === "chat"
474
- ? addChat({ template, network, force })
475
- : sub === "agent"
476
- ? addAgent({ template, network, force })
477
- : sub === "judge"
478
- ? addJudge({ template, network, force })
479
- : addInference({ template, network, force });
473
+ : sub === "chat-web3"
474
+ ? addChatWeb3({ template, network, force })
475
+ : sub === "chat"
476
+ ? addChat({ template, network, force })
477
+ : sub === "agent"
478
+ ? addAgent({ template, network, force })
479
+ : sub === "judge"
480
+ ? addJudge({ template, network, force })
481
+ : addInference({ template, network, force });
480
482
  console.log(`▶ add ${sub} (${result.template} template, default network ${result.network})`);
481
483
  for (const f of result.written) {
482
484
  if (f.skipped)
@@ -491,7 +493,25 @@ async function main() {
491
493
  else {
492
494
  console.log(`\nNext steps (these files were added to your CURRENT folder, not a new project):`);
493
495
  console.log(` 1. ${result.install}`);
494
- if (sub === "nft-mint-with-inference" || sub === "inference" || sub === "chat" || sub === "agent" || sub === "judge") {
496
+ if (sub === "chat-web3") {
497
+ // chat-web3 has no PRIVATE_KEY (each visitor pays their own way).
498
+ const needsWagmi = result.needsWagmi;
499
+ if (needsWagmi) {
500
+ console.log(` 2. Set up wagmi in your app if you have not already.`);
501
+ console.log(` See https://wagmi.sh/react/getting-started - wrap your root layout with`);
502
+ console.log(` <WagmiProvider config={wagmiConfig}> and add a Connect button using`);
503
+ console.log(` useConnect / RainbowKit / Reown AppKit / ConnectKit, whatever you prefer.`);
504
+ console.log(` 3. npm run dev, open /chat-web3`);
505
+ console.log(` 4. Connect a wallet on LightChain ${result.network === "mainnet" ? "mainnet (chainId 9200)" : "testnet (chainId 8200)"}.`);
506
+ console.log(` Mainnet llama3-8b costs 0.02 LCAI per turn; testnet is free from https://lightfaucet.ai`);
507
+ }
508
+ else {
509
+ console.log(` 2. npm run dev, open /chat-web3`);
510
+ console.log(` 3. Connect a wallet on LightChain ${result.network === "mainnet" ? "mainnet (chainId 9200)" : "testnet (chainId 8200)"}.`);
511
+ console.log(` Mainnet llama3-8b costs 0.02 LCAI per turn; testnet is free from https://lightfaucet.ai`);
512
+ }
513
+ }
514
+ else if (sub === "nft-mint-with-inference" || sub === "inference" || sub === "chat" || sub === "agent" || sub === "judge") {
495
515
  console.log(` 2. cp .env.example .env (and put a funded ${result.network} PRIVATE_KEY in it)`);
496
516
  if (sub === "agent" && result.template === "nextjs-api") {
497
517
  console.log(` 3. Set CRON_SECRET in your Vercel env vars + edit AGENT_TASK in .env`);
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.14";
137
+ export declare const SDK_VERSION = "0.7.15";
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.14";
216
+ export const SDK_VERSION = "0.7.15";
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.14",
3
+ "version": "0.7.15",
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",