lightnode-sdk 0.5.1 → 0.6.1

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/README.md CHANGED
@@ -205,6 +205,84 @@ for await (const event of handle.events) {
205
205
  }
206
206
  ```
207
207
 
208
+ ### Batch runner (new in 0.6.0)
209
+
210
+ Fan out many prompts as parallel encrypted inferences. Capped concurrency, stable result order, per-slot errors so one stalled worker does not kill the batch.
211
+
212
+ ```ts
213
+ import { runInferenceBatch } from "lightnode-sdk";
214
+
215
+ const results = await runInferenceBatch({
216
+ network: "testnet",
217
+ privateKey: process.env.PRIVATE_KEY!,
218
+ model: "llama3-8b",
219
+ system: "Reply in one short sentence.",
220
+ concurrency: 4,
221
+ prompts: [
222
+ "one-line fact about the ocean",
223
+ "one-line fact about the moon",
224
+ "one-line fact about coffee",
225
+ ],
226
+ onSlotComplete: ({ index, result, error }) => {
227
+ console.log(`#${index}`, error?.message ?? result?.answer);
228
+ },
229
+ });
230
+
231
+ for (const r of results) {
232
+ if (r.error) console.warn(`slot ${r.index} failed:`, r.error.message);
233
+ else console.log(r.result.answer);
234
+ }
235
+ ```
236
+
237
+ Fits: batch evals, content scoring, RAG re-ranking, parallel rewrites. Pass `{signal}` (`AbortSignal`) to cancel queued slots mid-run.
238
+
239
+ ### Agent class (new in 0.6.0)
240
+
241
+ ReAct-style tool calling on top of `runInferenceWithKey`. The model emits `<tool>name {"k":"v"}</tool>` or `<answer>...</answer>`; the SDK parses, runs the handler, threads the observation back. Works on small open models (llama3-8b) without native function calling.
242
+
243
+ ```ts
244
+ import { Agent } from "lightnode-sdk";
245
+
246
+ const agent = new Agent({
247
+ network: "testnet",
248
+ privateKey: process.env.PRIVATE_KEY!,
249
+ model: "llama3-8b",
250
+ system: "You are a careful research assistant.",
251
+ tools: [
252
+ {
253
+ name: "add",
254
+ description: "Add two integers and return the sum.",
255
+ args: { a: "first integer", b: "second integer" },
256
+ handler: ({ a, b }) => Number(a) + Number(b),
257
+ },
258
+ ],
259
+ maxIterations: 3,
260
+ onStep: (step) => console.log(step.kind, step),
261
+ });
262
+
263
+ const { answer, steps, hitLimit } = await agent.run("What is 17 + 25?");
264
+ console.log(answer); // "42"
265
+ console.log(steps); // [{ kind: "tool_call", ... }, { kind: "answer", text: "42" }]
266
+ ```
267
+
268
+ Each iteration is one inference (one on-chain `submitJob`); cap `maxIterations` to keep wall-clock + cost bounded. Tool handlers are plain functions that may be async; return JSON-serializable data so the model can read the observation.
269
+
270
+ ### Cancellation (new in 0.6.0)
271
+
272
+ `runInference` and `runInferenceWithKey` accept an `AbortSignal`. In-flight on-chain transactions still settle (the protocol is the source of truth); the SDK just stops awaiting and rejects with `Error("aborted")`.
273
+
274
+ ```ts
275
+ const controller = new AbortController();
276
+ setTimeout(() => controller.abort(), 15_000);
277
+
278
+ await runInferenceWithKey({
279
+ network: "testnet",
280
+ privateKey: process.env.PRIVATE_KEY!,
281
+ prompt: "short answer please",
282
+ signal: controller.signal,
283
+ });
284
+ ```
285
+
208
286
  ### Typed errors
209
287
 
210
288
  ```ts
@@ -0,0 +1,122 @@
1
+ import { type RunInferenceWithKeyArgs } from "./inference.js";
2
+ /**
3
+ * One tool the agent can call. The model decides when by emitting a tool
4
+ * call block; the handler returns plain JSON which is threaded back into
5
+ * the next turn as an observation.
6
+ *
7
+ * The handler MUST return JSON-serializable data so the model can read
8
+ * it back. Use `String()` to stringify primitives, `JSON.stringify`-able
9
+ * objects otherwise.
10
+ */
11
+ export interface AgentTool {
12
+ /** Short snake_case name. The model uses this exact string in tool calls. */
13
+ name: string;
14
+ /** One-line description shown to the model so it knows when to use the tool. */
15
+ description: string;
16
+ /**
17
+ * Argument schema, very informal: { argName: "string description" }. The
18
+ * model is told to fill in matching JSON values; the SDK does not enforce
19
+ * types beyond JSON parsing.
20
+ */
21
+ args: Record<string, string>;
22
+ /** Run the tool. Return JSON-serializable data (string, number, object, etc.). */
23
+ handler: (args: Record<string, unknown>) => Promise<unknown> | unknown;
24
+ }
25
+ /** One step in the agent's reasoning loop. */
26
+ export type AgentStep = {
27
+ kind: "thought";
28
+ text: string;
29
+ } | {
30
+ kind: "tool_call";
31
+ name: string;
32
+ args: Record<string, unknown>;
33
+ result: unknown;
34
+ } | {
35
+ kind: "tool_error";
36
+ name: string;
37
+ args: Record<string, unknown>;
38
+ error: string;
39
+ } | {
40
+ kind: "answer";
41
+ text: string;
42
+ };
43
+ export interface AgentRunResult {
44
+ /** The model's final answer. May be empty if maxIterations was hit. */
45
+ answer: string;
46
+ /** Every step taken (thoughts + tool calls + final answer). Useful for debugging + UI. */
47
+ steps: AgentStep[];
48
+ /** Total inferences fired. Each iteration is one inference, so this is also iteration count. */
49
+ iterations: number;
50
+ /** True if the loop bailed because it ran out of iterations without an `answer` block. */
51
+ hitLimit: boolean;
52
+ }
53
+ export interface AgentOptions extends Omit<RunInferenceWithKeyArgs, "prompt" | "system"> {
54
+ /** Human-readable goal / persona. Becomes the BASE system prompt; the tool harness is appended automatically. */
55
+ system?: string;
56
+ /** Tools the model is allowed to call. */
57
+ tools: ReadonlyArray<AgentTool>;
58
+ /** Hard cap on reasoning steps. Default 5. */
59
+ maxIterations?: number;
60
+ /** Called after every step (thought / tool call / answer). Useful for live UI. */
61
+ onStep?: (step: AgentStep) => void;
62
+ }
63
+ /**
64
+ * ReAct-style agent on top of `runInferenceWithKey`. Each iteration:
65
+ * 1. The SDK sends a system prompt that lists tools + the JSON tool-call
66
+ * format to the model, plus the running transcript.
67
+ * 2. The model emits either a tool call (`<tool>name {"k":"v"}</tool>`)
68
+ * or a final answer (`<answer>...</answer>`).
69
+ * 3. The SDK parses, runs the tool, threads the result back, repeats.
70
+ *
71
+ * Designed for smaller open models (llama3-8b). The protocol is simple
72
+ * string markers, not native function calling, so it works on any model
73
+ * the LightChain network exposes.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { Agent } from "lightnode-sdk";
78
+ *
79
+ * const agent = new Agent({
80
+ * network: "testnet",
81
+ * privateKey: process.env.PRIVATE_KEY!,
82
+ * model: "llama3-8b",
83
+ * system: "You are a careful research assistant.",
84
+ * tools: [
85
+ * {
86
+ * name: "add",
87
+ * description: "Add two integers and return the sum.",
88
+ * args: { a: "first integer", b: "second integer" },
89
+ * handler: ({ a, b }) => Number(a) + Number(b),
90
+ * },
91
+ * ],
92
+ * maxIterations: 3,
93
+ * });
94
+ *
95
+ * const { answer, steps } = await agent.run("What is 17 + 25?");
96
+ * console.log(answer); // "42"
97
+ * ```
98
+ */
99
+ export declare class Agent {
100
+ private readonly opts;
101
+ constructor(opts: AgentOptions);
102
+ run(userMessage: string): Promise<AgentRunResult>;
103
+ private buildSystemPrompt;
104
+ }
105
+ type ParsedOutput = {
106
+ kind: "tool_call";
107
+ name: string;
108
+ args: Record<string, unknown>;
109
+ } | {
110
+ kind: "answer";
111
+ text: string;
112
+ } | {
113
+ kind: "thought";
114
+ text: string;
115
+ };
116
+ /**
117
+ * Parse the model's raw output into either a tool call, a final answer, or
118
+ * a thought (everything else). Tolerant of whitespace and the model
119
+ * forgetting to close a tag. Visible for testing.
120
+ */
121
+ export declare function parseAgentOutput(raw: string): ParsedOutput;
122
+ export {};
package/dist/agent.js ADDED
@@ -0,0 +1,176 @@
1
+ import { runInferenceWithKey } from "./inference.js";
2
+ /**
3
+ * ReAct-style agent on top of `runInferenceWithKey`. Each iteration:
4
+ * 1. The SDK sends a system prompt that lists tools + the JSON tool-call
5
+ * format to the model, plus the running transcript.
6
+ * 2. The model emits either a tool call (`<tool>name {"k":"v"}</tool>`)
7
+ * or a final answer (`<answer>...</answer>`).
8
+ * 3. The SDK parses, runs the tool, threads the result back, repeats.
9
+ *
10
+ * Designed for smaller open models (llama3-8b). The protocol is simple
11
+ * string markers, not native function calling, so it works on any model
12
+ * the LightChain network exposes.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { Agent } from "lightnode-sdk";
17
+ *
18
+ * const agent = new Agent({
19
+ * network: "testnet",
20
+ * privateKey: process.env.PRIVATE_KEY!,
21
+ * model: "llama3-8b",
22
+ * system: "You are a careful research assistant.",
23
+ * tools: [
24
+ * {
25
+ * name: "add",
26
+ * description: "Add two integers and return the sum.",
27
+ * args: { a: "first integer", b: "second integer" },
28
+ * handler: ({ a, b }) => Number(a) + Number(b),
29
+ * },
30
+ * ],
31
+ * maxIterations: 3,
32
+ * });
33
+ *
34
+ * const { answer, steps } = await agent.run("What is 17 + 25?");
35
+ * console.log(answer); // "42"
36
+ * ```
37
+ */
38
+ export class Agent {
39
+ constructor(opts) {
40
+ if (!opts.tools || opts.tools.length === 0) {
41
+ throw new Error("Agent: at least one tool is required (use runInferenceWithKey for plain inference)");
42
+ }
43
+ // Tool names must be unique - the SDK matches model-emitted names by
44
+ // string and a dup would silently route to the wrong handler.
45
+ const seen = new Set();
46
+ for (const t of opts.tools) {
47
+ if (seen.has(t.name))
48
+ throw new Error(`Agent: duplicate tool name "${t.name}"`);
49
+ seen.add(t.name);
50
+ }
51
+ this.opts = opts;
52
+ }
53
+ async run(userMessage) {
54
+ const maxIter = Math.max(1, this.opts.maxIterations ?? 5);
55
+ const steps = [];
56
+ const transcript = [`User: ${userMessage}`];
57
+ const system = this.buildSystemPrompt();
58
+ let iterations = 0;
59
+ // Strip Agent-only fields before passing through to runInferenceWithKey.
60
+ const passthrough = { ...this.opts };
61
+ delete passthrough.tools;
62
+ delete passthrough.maxIterations;
63
+ delete passthrough.onStep;
64
+ delete passthrough.system;
65
+ while (iterations < maxIter) {
66
+ iterations++;
67
+ const prompt = `${system}\n\n${transcript.join("\n\n")}\n\nAssistant:`;
68
+ const { answer: raw } = await runInferenceWithKey({
69
+ ...passthrough,
70
+ prompt,
71
+ });
72
+ const parsed = parseAgentOutput(raw);
73
+ if (parsed.kind === "answer") {
74
+ const step = { kind: "answer", text: parsed.text };
75
+ steps.push(step);
76
+ this.opts.onStep?.(step);
77
+ return { answer: parsed.text, steps, iterations, hitLimit: false };
78
+ }
79
+ if (parsed.kind === "thought") {
80
+ const step = { kind: "thought", text: parsed.text };
81
+ steps.push(step);
82
+ this.opts.onStep?.(step);
83
+ transcript.push(`Assistant: <think>${parsed.text}</think>`);
84
+ continue;
85
+ }
86
+ // tool_call: look up + execute
87
+ const tool = this.opts.tools.find((t) => t.name === parsed.name);
88
+ if (!tool) {
89
+ const step = {
90
+ kind: "tool_error",
91
+ name: parsed.name,
92
+ args: parsed.args,
93
+ error: `unknown tool "${parsed.name}"`,
94
+ };
95
+ steps.push(step);
96
+ this.opts.onStep?.(step);
97
+ transcript.push(`Assistant: <tool>${parsed.name} ${JSON.stringify(parsed.args)}</tool>`);
98
+ transcript.push(`Observation: error: unknown tool "${parsed.name}"`);
99
+ continue;
100
+ }
101
+ try {
102
+ const result = await tool.handler(parsed.args);
103
+ const step = { kind: "tool_call", name: parsed.name, args: parsed.args, result };
104
+ steps.push(step);
105
+ this.opts.onStep?.(step);
106
+ transcript.push(`Assistant: <tool>${parsed.name} ${JSON.stringify(parsed.args)}</tool>`);
107
+ transcript.push(`Observation: ${JSON.stringify(result)}`);
108
+ }
109
+ catch (err) {
110
+ const msg = err.message ?? String(err);
111
+ const step = {
112
+ kind: "tool_error",
113
+ name: parsed.name,
114
+ args: parsed.args,
115
+ error: msg,
116
+ };
117
+ steps.push(step);
118
+ this.opts.onStep?.(step);
119
+ transcript.push(`Assistant: <tool>${parsed.name} ${JSON.stringify(parsed.args)}</tool>`);
120
+ transcript.push(`Observation: error: ${msg}`);
121
+ }
122
+ }
123
+ return { answer: "", steps, iterations, hitLimit: true };
124
+ }
125
+ buildSystemPrompt() {
126
+ const base = this.opts.system?.trim() ?? "You are a helpful assistant.";
127
+ const toolDocs = this.opts.tools
128
+ .map((t) => {
129
+ const argList = Object.entries(t.args)
130
+ .map(([k, v]) => ` "${k}": <${v}>`)
131
+ .join(",\n");
132
+ return `- ${t.name}: ${t.description}\n Call as: <tool>${t.name} {\n${argList}\n }</tool>`;
133
+ })
134
+ .join("\n");
135
+ return `${base}
136
+
137
+ You have access to these tools:
138
+ ${toolDocs}
139
+
140
+ To call a tool, write EXACTLY one line:
141
+ <tool>tool_name {"arg":"value"}</tool>
142
+
143
+ After the tool runs, the user will reply with: Observation: <json>
144
+
145
+ When you are done, respond with EXACTLY:
146
+ <answer>your final reply to the user</answer>
147
+
148
+ Use one tool at a time. Do not invent tools. Keep observations short.`;
149
+ }
150
+ }
151
+ /**
152
+ * Parse the model's raw output into either a tool call, a final answer, or
153
+ * a thought (everything else). Tolerant of whitespace and the model
154
+ * forgetting to close a tag. Visible for testing.
155
+ */
156
+ export function parseAgentOutput(raw) {
157
+ const answer = raw.match(/<answer>([\s\S]*?)(?:<\/answer>|$)/i);
158
+ if (answer)
159
+ return { kind: "answer", text: answer[1].trim() };
160
+ const tool = raw.match(/<tool>\s*([a-zA-Z_][\w-]*)\s*(\{[\s\S]*?\})\s*(?:<\/tool>|$)/i);
161
+ if (tool) {
162
+ const name = tool[1];
163
+ let args = {};
164
+ try {
165
+ const parsed = JSON.parse(tool[2]);
166
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
167
+ args = parsed;
168
+ }
169
+ }
170
+ catch {
171
+ // best-effort: leave args empty so the handler sees missing fields
172
+ }
173
+ return { kind: "tool_call", name, args };
174
+ }
175
+ return { kind: "thought", text: raw.trim() };
176
+ }
@@ -0,0 +1,88 @@
1
+ import { type RunInferenceWithKeyArgs, type RunInferenceResult } from "./inference.js";
2
+ /**
3
+ * One slot in a batch run. `prompt` is the only required field; everything
4
+ * else inherits from the shared base args passed to `runInferenceBatch`.
5
+ * A per-prompt `model` / `system` overrides the base when set.
6
+ */
7
+ export interface BatchPrompt {
8
+ prompt: string;
9
+ model?: string;
10
+ system?: string;
11
+ /** Opaque per-slot tag returned on the result so the caller can correlate. */
12
+ tag?: string;
13
+ }
14
+ /** One slot's outcome. `error` is set on failure; `result` on success. Always exactly one. */
15
+ export type BatchResult = {
16
+ index: number;
17
+ tag?: string;
18
+ prompt: string;
19
+ result: RunInferenceResult;
20
+ error: null;
21
+ } | {
22
+ index: number;
23
+ tag?: string;
24
+ prompt: string;
25
+ result: null;
26
+ error: {
27
+ name: string;
28
+ message: string;
29
+ jobId?: string;
30
+ };
31
+ };
32
+ export interface RunInferenceBatchArgs extends Omit<RunInferenceWithKeyArgs, "prompt"> {
33
+ /** Prompts to run. Each slot becomes one independent encrypted inference. */
34
+ prompts: ReadonlyArray<string | BatchPrompt>;
35
+ /**
36
+ * Shared system prompt prepended to every slot. A per-slot `system`
37
+ * overrides this. Implemented by prefixing the prompt at submit time
38
+ * (the underlying inference call has no native system role).
39
+ */
40
+ system?: string;
41
+ /**
42
+ * Max parallel inferences in flight (default 4). Each slot is one
43
+ * createSession + submitJob pair, so this caps the concurrent wallet
44
+ * nonce pressure as well as the gateway socket fan-out.
45
+ */
46
+ concurrency?: number;
47
+ /**
48
+ * Called when ANY slot resolves (success or failure), useful for live
49
+ * progress UI. Fires in order of completion, not submission.
50
+ */
51
+ onSlotComplete?: (result: BatchResult) => void;
52
+ /**
53
+ * When set, abort all in-flight + queued slots if this signal fires.
54
+ * Slots already submitted on chain still settle on chain; the SDK just
55
+ * stops awaiting their answer.
56
+ */
57
+ signal?: AbortSignal;
58
+ }
59
+ /**
60
+ * Run many prompts as parallel encrypted inferences against the same
61
+ * worker pool. Returns a stable array indexed by submission order.
62
+ *
63
+ * Slots fail independently - a stalled worker on one prompt does not
64
+ * cancel the others. The caller decides what to retry from the per-slot
65
+ * error.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * import { runInferenceBatch } from "lightnode-sdk";
70
+ *
71
+ * const results = await runInferenceBatch({
72
+ * network: "testnet",
73
+ * privateKey: process.env.PRIVATE_KEY!,
74
+ * model: "llama3-8b",
75
+ * prompts: ["one", "two", "three"],
76
+ * concurrency: 3,
77
+ * onSlotComplete: ({ index, result, error }) => {
78
+ * console.log(`#${index}:`, error?.message ?? result?.answer);
79
+ * },
80
+ * });
81
+ *
82
+ * for (const r of results) {
83
+ * if (r.error) console.warn(`slot ${r.index} failed:`, r.error.message);
84
+ * else console.log(r.result.answer);
85
+ * }
86
+ * ```
87
+ */
88
+ export declare function runInferenceBatch(args: RunInferenceBatchArgs): Promise<BatchResult[]>;
package/dist/batch.js ADDED
@@ -0,0 +1,102 @@
1
+ import { runInferenceWithKey } from "./inference.js";
2
+ import { StalledWorkerError } from "./errors.js";
3
+ /**
4
+ * Run many prompts as parallel encrypted inferences against the same
5
+ * worker pool. Returns a stable array indexed by submission order.
6
+ *
7
+ * Slots fail independently - a stalled worker on one prompt does not
8
+ * cancel the others. The caller decides what to retry from the per-slot
9
+ * error.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { runInferenceBatch } from "lightnode-sdk";
14
+ *
15
+ * const results = await runInferenceBatch({
16
+ * network: "testnet",
17
+ * privateKey: process.env.PRIVATE_KEY!,
18
+ * model: "llama3-8b",
19
+ * prompts: ["one", "two", "three"],
20
+ * concurrency: 3,
21
+ * onSlotComplete: ({ index, result, error }) => {
22
+ * console.log(`#${index}:`, error?.message ?? result?.answer);
23
+ * },
24
+ * });
25
+ *
26
+ * for (const r of results) {
27
+ * if (r.error) console.warn(`slot ${r.index} failed:`, r.error.message);
28
+ * else console.log(r.result.answer);
29
+ * }
30
+ * ```
31
+ */
32
+ export async function runInferenceBatch(args) {
33
+ const concurrency = Math.max(1, args.concurrency ?? 4);
34
+ const slots = args.prompts.map((p) => (typeof p === "string" ? { prompt: p } : p));
35
+ const out = new Array(slots.length);
36
+ let nextIndex = 0;
37
+ let aborted = false;
38
+ const onAbort = () => {
39
+ aborted = true;
40
+ };
41
+ if (args.signal) {
42
+ if (args.signal.aborted)
43
+ aborted = true;
44
+ else
45
+ args.signal.addEventListener("abort", onAbort, { once: true });
46
+ }
47
+ const runOne = async (index) => {
48
+ if (aborted) {
49
+ out[index] = {
50
+ index,
51
+ tag: slots[index].tag,
52
+ prompt: slots[index].prompt,
53
+ result: null,
54
+ error: { name: "AbortError", message: "batch aborted before this slot ran" },
55
+ };
56
+ return;
57
+ }
58
+ const slot = slots[index];
59
+ try {
60
+ const system = slot.system ?? args.system;
61
+ const finalPrompt = system ? `${system.trim()}\n\n${slot.prompt}` : slot.prompt;
62
+ // Strip batch-only fields before passing through to runInferenceWithKey.
63
+ const passthrough = { ...args };
64
+ delete passthrough.prompts;
65
+ delete passthrough.system;
66
+ delete passthrough.concurrency;
67
+ delete passthrough.onSlotComplete;
68
+ const result = await runInferenceWithKey({
69
+ ...passthrough,
70
+ prompt: finalPrompt,
71
+ model: slot.model ?? args.model,
72
+ });
73
+ out[index] = { index, tag: slot.tag, prompt: slot.prompt, result, error: null };
74
+ }
75
+ catch (err) {
76
+ const e = err;
77
+ const jobId = e instanceof StalledWorkerError ? e.jobId.toString() : undefined;
78
+ out[index] = {
79
+ index,
80
+ tag: slot.tag,
81
+ prompt: slot.prompt,
82
+ result: null,
83
+ error: { name: e.name ?? "Error", message: e.message, jobId },
84
+ };
85
+ }
86
+ args.onSlotComplete?.(out[index]);
87
+ };
88
+ // N parallel workers pulling off the queue. Order of completion is
89
+ // non-deterministic, order of OUTPUT is stable via `index`.
90
+ const workers = new Array(Math.min(concurrency, slots.length)).fill(0).map(async () => {
91
+ while (true) {
92
+ const i = nextIndex++;
93
+ if (i >= slots.length)
94
+ return;
95
+ await runOne(i);
96
+ }
97
+ });
98
+ await Promise.all(workers);
99
+ if (args.signal)
100
+ args.signal.removeEventListener("abort", onAbort);
101
+ return out;
102
+ }
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { LightNode, modelStatsCsv, workerStatsCsv, workerJobsCsv, runInferenceWithKey, isStalledWorker, workerPreflight, workerWatch, BRIDGE_ROUTE, DAO, DAO_ADDRESSES } from "./index.js";
2
+ import { LightNode, modelStatsCsv, workerStatsCsv, workerJobsCsv, runInferenceWithKey, runInferenceBatch, Agent, isStalledWorker, workerPreflight, workerWatch, BRIDGE_ROUTE, DAO, DAO_ADDRESSES } from "./index.js";
3
3
  import { addInference, addAnalyticsDashboard, addNftMint, addChat, addAgent } from "./add.js";
4
4
  import { createPublicClient, http, parseEther } from "viem";
5
5
  import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
@@ -22,6 +22,10 @@ const HELP = `lightnode <command> [--net mainnet|testnet]
22
22
  Run one inference (needs PRIVATE_KEY in env):
23
23
  chat <prompt> stream one encrypted inference answer to stdout
24
24
  ([--model llama3-8b] [--key 0x...])
25
+ batch <prompts.json> run N prompts in parallel, JSON line per result
26
+ ([--model] [--concurrency 4])
27
+ agent <task> ReAct-style agent with built-in add + now tools
28
+ ([--model] [--max-iter 4])
25
29
 
26
30
  Wallet helpers:
27
31
  wallet new generate a fresh testnet key, print it
@@ -392,8 +396,140 @@ async function main() {
392
396
  }
393
397
  break;
394
398
  }
399
+ case "batch": {
400
+ // `lightnode batch <prompts.json>` or `lightnode batch -` (stdin).
401
+ // Input shape: ["prompt one","prompt two"] OR
402
+ // { "model": "llama3-8b", "system": "...", "prompts": ["...", "..."] }
403
+ // Output: one JSON object per line (index, answer or error). Composes
404
+ // with `jq` for downstream processing.
405
+ const arg = positionals[1] ?? "";
406
+ if (!arg)
407
+ die("usage: lightnode batch <prompts.json> (or `lightnode batch -` to read stdin)");
408
+ const raw = await (arg === "-" ? readStdin() : readFile(arg));
409
+ let parsed;
410
+ try {
411
+ parsed = JSON.parse(raw);
412
+ }
413
+ catch (e) {
414
+ die("invalid JSON: " + e.message);
415
+ }
416
+ const cfg = normalizeBatchInput(parsed);
417
+ const model = flag("--model") ?? cfg.model ?? "llama3-8b";
418
+ const concurrency = Number(flag("--concurrency") ?? "4") || 4;
419
+ const privateKey = pickKey();
420
+ process.stderr.write(`Running ${cfg.prompts.length} prompts on ${net} via ${model} (concurrency=${concurrency})\n`);
421
+ const results = await runInferenceBatch({
422
+ network: net,
423
+ privateKey,
424
+ model,
425
+ system: cfg.system,
426
+ concurrency,
427
+ prompts: cfg.prompts,
428
+ onSlotComplete: ({ index, result, error }) => {
429
+ process.stdout.write(JSON.stringify({
430
+ index,
431
+ ok: error == null,
432
+ answer: result?.answer ?? null,
433
+ error: error?.message ?? null,
434
+ jobId: result?.jobId.toString() ?? error?.jobId ?? null,
435
+ }) + "\n");
436
+ },
437
+ });
438
+ const okCount = results.filter((r) => r.error == null).length;
439
+ process.stderr.write(`Done: ${okCount}/${results.length} succeeded\n`);
440
+ break;
441
+ }
442
+ case "agent": {
443
+ // Quick one-shot agent demo: built-in `add` + `now` tools. Bring the
444
+ // model your prompt as positional args (or pipe via stdin) and watch
445
+ // the step trace on stderr while the answer streams to stdout.
446
+ const inline = positionals.slice(1).join(" ").trim();
447
+ const task = inline || (await readStdin()).trim();
448
+ if (!task)
449
+ die("usage: lightnode agent <task> (or pipe the task to stdin)");
450
+ const model = flag("--model") ?? "llama3-8b";
451
+ const maxIter = Number(flag("--max-iter") ?? "4") || 4;
452
+ const privateKey = pickKey();
453
+ // A tiny built-in toolset so the command is runnable without writing
454
+ // a wrapper. For real tools, import Agent from the SDK and pass your own.
455
+ const tools = [
456
+ {
457
+ name: "add",
458
+ description: "Add two integers and return the sum.",
459
+ args: { a: "first integer", b: "second integer" },
460
+ handler: ({ a, b }) => Number(a) + Number(b),
461
+ },
462
+ {
463
+ name: "now",
464
+ description: "Return the current ISO timestamp.",
465
+ args: {},
466
+ handler: () => new Date().toISOString(),
467
+ },
468
+ ];
469
+ const agent = new Agent({
470
+ network: net,
471
+ privateKey,
472
+ model,
473
+ system: "You are a careful assistant. Use tools when they help; otherwise answer directly.",
474
+ tools,
475
+ maxIterations: maxIter,
476
+ onStep: (step) => {
477
+ if (step.kind === "tool_call") {
478
+ process.stderr.write(`[tool] ${step.name}(${JSON.stringify(step.args)}) -> ${JSON.stringify(step.result)}\n`);
479
+ }
480
+ else if (step.kind === "tool_error") {
481
+ process.stderr.write(`[tool-error] ${step.name}: ${step.error}\n`);
482
+ }
483
+ else if (step.kind === "thought") {
484
+ process.stderr.write(`[think] ${step.text.slice(0, 200)}\n`);
485
+ }
486
+ },
487
+ });
488
+ try {
489
+ const { answer, steps, iterations, hitLimit } = await agent.run(task);
490
+ process.stdout.write(answer + "\n");
491
+ process.stderr.write(JSON.stringify({ iterations, steps: steps.length, hitLimit }) + "\n");
492
+ }
493
+ catch (e) {
494
+ die("agent failed: " + e.message);
495
+ }
496
+ break;
497
+ }
395
498
  default:
396
499
  console.log(HELP);
397
500
  }
398
501
  }
502
+ async function readStdin() {
503
+ return new Promise((resolve) => {
504
+ let buf = "";
505
+ process.stdin.setEncoding("utf8");
506
+ process.stdin.on("data", (d) => (buf += d));
507
+ process.stdin.on("end", () => resolve(buf));
508
+ });
509
+ }
510
+ async function readFile(path) {
511
+ const fs = await import("node:fs/promises");
512
+ return fs.readFile(path, "utf8");
513
+ }
514
+ function normalizeBatchInput(parsed) {
515
+ if (Array.isArray(parsed)) {
516
+ const prompts = parsed.filter((p) => typeof p === "string");
517
+ if (!prompts.length)
518
+ die("batch input: array must contain at least one string prompt");
519
+ return { prompts };
520
+ }
521
+ if (parsed && typeof parsed === "object") {
522
+ const obj = parsed;
523
+ const prompts = Array.isArray(obj.prompts) ? obj.prompts.filter((p) => typeof p === "string") : [];
524
+ if (!prompts.length)
525
+ die("batch input: object must have a `prompts` array of strings");
526
+ return {
527
+ prompts,
528
+ system: typeof obj.system === "string" ? obj.system : undefined,
529
+ model: typeof obj.model === "string" ? obj.model : undefined,
530
+ };
531
+ }
532
+ die("batch input: expected JSON array of strings OR object { prompts, system?, model? }");
533
+ return { prompts: [] };
534
+ }
399
535
  main().catch((e) => die(String(e?.message ?? e)));
package/dist/dao.d.ts CHANGED
@@ -17,22 +17,47 @@
17
17
  * This module covers the OZ Governor v5 surface: state machine, propose,
18
18
  * castVote, queue, execute. Plus convenience reads of the constants.
19
19
  */
20
- export type DaoChain = "ethereum";
20
+ /**
21
+ * Both deployed DAOs we know about:
22
+ *
23
+ * - `ethereum` LCAIGovernor on Ethereum mainnet. Token holders
24
+ * wrap LCAI ERC-20 into LCAI-Ballots (IVotes) at
25
+ * https://ballots.lightchain.ai, then vote /
26
+ * propose / queue / execute through the Governor.
27
+ * Proposal threshold: 140,000 LCAI wrapped.
28
+ *
29
+ * - `lightchain` LightChainGovernor on LightChain mainnet (chain
30
+ * 9200). Uses the NativeVotes precompile at
31
+ * 0x...0001001 - native LCAI itself acts as the
32
+ * voting token, no wrapping needed. Governor is
33
+ * an upgradeable proxy controlled by the
34
+ * TimelockController.
35
+ *
36
+ * Addresses sourced from LightChain's official "Mainnet Contract Addresses"
37
+ * page.
38
+ */
39
+ export type DaoChain = "ethereum" | "lightchain";
21
40
  export interface DaoAddresses {
22
41
  chainId: number;
23
- /** OZ Governor contract. */
42
+ label: string;
43
+ /** OZ Governor contract (proxy when behind an upgradeable pattern). */
24
44
  governor: `0x${string}`;
25
45
  /** Timelock controller. queue/execute dispatch through this. */
26
46
  timelock: `0x${string}`;
27
- /** ERC-20 wrapped as IVotes; this is what users delegate / hold to vote. */
28
- ballots: `0x${string}`;
29
- /** LCAI ERC-20 (the underlying governance token). */
30
- token: `0x${string}`;
47
+ /** ERC-20 wrapped as IVotes (null when the chain uses a NativeVotes precompile). */
48
+ ballots: `0x${string}` | null;
49
+ /** Underlying governance token (LCAI ERC-20 on Ethereum, native LCAI on LightChain). */
50
+ token: `0x${string}` | null;
31
51
  /** Treasury contract holding DAO funds. */
32
52
  treasury: `0x${string}`;
53
+ /** Optional UI link (where regular users wrap / view proposals). */
54
+ wrapperUi?: string;
33
55
  explorer: string;
34
56
  }
35
- /** Confirmed Ethereum mainnet addresses (chain 1). */
57
+ /**
58
+ * Confirmed deployment addresses. Each entry is what an SDK user passes to
59
+ * `new DAO(client, chainKey, walletClient?)`.
60
+ */
36
61
  export declare const DAO_ADDRESSES: Record<DaoChain, DaoAddresses>;
37
62
  /**
38
63
  * The 8-state OZ Governor v5 enum. The string label is what most builders
package/dist/dao.js CHANGED
@@ -18,17 +18,33 @@
18
18
  * castVote, queue, execute. Plus convenience reads of the constants.
19
19
  */
20
20
  import { parseAbi } from "viem";
21
- /** Confirmed Ethereum mainnet addresses (chain 1). */
21
+ /**
22
+ * Confirmed deployment addresses. Each entry is what an SDK user passes to
23
+ * `new DAO(client, chainKey, walletClient?)`.
24
+ */
22
25
  export const DAO_ADDRESSES = {
23
26
  ethereum: {
24
27
  chainId: 1,
28
+ label: "Ethereum mainnet",
25
29
  governor: "0x6dfa413B5900a1a7947BC75E68AbBA093cB2492d",
26
30
  timelock: "0xbE1c37F8C4DA77dD06F4A8AC5098Ec70273093d7",
27
31
  ballots: "0x75F3D01c4D960FE986A598B7954A3b786B29cE49",
28
32
  token: "0x9cA8530CA349c966Fe9ef903Df17a75B8A778927",
29
33
  treasury: "0x07A716a551E5f4CA7D6C71Da9dF1cb1429Dba826",
34
+ wrapperUi: "https://ballots.lightchain.ai",
30
35
  explorer: "https://etherscan.io",
31
36
  },
37
+ lightchain: {
38
+ chainId: 9200,
39
+ label: "LightChain mainnet",
40
+ governor: "0x262E9f9232933E8565253918db703baD58DE93aB",
41
+ timelock: "0x79e571420c5473Ca9b0FCd599B1b0062D7793c97",
42
+ // Native voting via the genesis predeploy precompile; no separate wrapping token.
43
+ ballots: "0x0000000000000000000000000000000000001001",
44
+ token: null,
45
+ treasury: "0x786eDe8C42Ca54E54c9dCECa9b30052CF4743389",
46
+ explorer: "https://mainnet.lightscan.app",
47
+ },
32
48
  };
33
49
  /**
34
50
  * The 8-state OZ Governor v5 enum. The string label is what most builders
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ import { fromWei } from "./subgraph.js";
3
3
  import { aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv } from "./analytics.js";
4
4
  import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, runInferenceStream } from "./inference.js";
5
5
  import { Conversation, chat } from "./chat.js";
6
+ import { runInferenceBatch } from "./batch.js";
7
+ import { Agent, parseAgentOutput } from "./agent.js";
6
8
  import { preflight as workerPreflight, watch as workerWatch } from "./worker.js";
7
9
  import { Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer } from "./bridge.js";
8
10
  import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI } from "./dao.js";
@@ -88,11 +90,13 @@ export declare class LightNode {
88
90
  * (especially in registry-proxy environments like StackBlitz where lockfiles
89
91
  * may pin an older minor than the local install command suggests).
90
92
  */
91
- export declare const SDK_VERSION = "0.5.1";
92
- export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, Conversation, chat, 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, };
93
+ export declare const SDK_VERSION = "0.6.1";
94
+ export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, 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, };
93
95
  export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
94
96
  export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
95
97
  export type { ChatRole, ChatMessage, ConversationOptions, ConversationSendResult } from "./chat.js";
98
+ export type { BatchPrompt, BatchResult, RunInferenceBatchArgs } from "./batch.js";
99
+ export type { AgentTool, AgentStep, AgentOptions, AgentRunResult } from "./agent.js";
96
100
  export type { WorkerPreflightArgs, WorkerPreflightResult, WorkerWatchOptions, WorkerEventKind, WorkerEvent, WorkerWatchHandle } from "./worker.js";
97
101
  export type { BridgeChain, BridgeEndpoints, BridgeTransferArgs } from "./bridge.js";
98
102
  export type { DaoChain, DaoAddresses, ProposalSummary, DaoConfig } from "./dao.js";
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { isRegistered } from "./onchain.js";
4
4
  import { aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, } from "./analytics.js";
5
5
  import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, runInferenceStream, } from "./inference.js";
6
6
  import { Conversation, chat } from "./chat.js";
7
+ import { runInferenceBatch } from "./batch.js";
8
+ import { Agent, parseAgentOutput } from "./agent.js";
7
9
  import { preflight as workerPreflight, watch as workerWatch } from "./worker.js";
8
10
  import { Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, } from "./bridge.js";
9
11
  import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, } from "./dao.js";
@@ -143,7 +145,7 @@ export class LightNode {
143
145
  * (especially in registry-proxy environments like StackBlitz where lockfiles
144
146
  * may pin an older minor than the local install command suggests).
145
147
  */
146
- export const SDK_VERSION = "0.5.1";
148
+ export const SDK_VERSION = "0.6.1";
147
149
  export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost,
148
150
  // v0.3 inference-submit surface (BETA - see README "Submitting inference").
149
151
  GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto,
@@ -155,6 +157,10 @@ runInferenceWithKey,
155
157
  runInferenceStream,
156
158
  // v0.5.0 multi-turn conversation helper (history client-side; one inference per turn).
157
159
  Conversation, chat,
160
+ // v0.6.0 batch runner: many prompts, capped parallelism, stable result order.
161
+ runInferenceBatch,
162
+ // v0.6.0 ReAct-style agent: tool calling on any LightChain-hosted model.
163
+ Agent, parseAgentOutput,
158
164
  // v0.5.0 worker preflight + watch (one real test inference + status polling).
159
165
  workerPreflight, workerWatch,
160
166
  // v0.5.0 Bridge SDK (Hyperlane Warp Route wrapper for LCAI <-> Ethereum).
@@ -171,6 +171,11 @@ export interface RunInferenceArgs {
171
171
  * Useful for tests / mirrors.
172
172
  */
173
173
  relayUrl?: string;
174
+ /**
175
+ * Cancellation signal. Aborts pending awaits inside the run; in-flight
176
+ * submitJob transactions still settle on chain (the SDK stops listening).
177
+ */
178
+ signal?: AbortSignal;
174
179
  }
175
180
  export interface RunInferenceResult {
176
181
  /** The decrypted, fully-assembled model answer. */
@@ -264,6 +269,13 @@ export interface RunInferenceWithKeyArgs {
264
269
  * Useful for tests / mirrors / proxying through your own backend.
265
270
  */
266
271
  gatewayUrl?: string;
272
+ /**
273
+ * Cancellation signal. Aborts the SIWE handshake and stops awaiting the
274
+ * relay; in-flight submitJob transactions still settle on chain (the SDK
275
+ * just stops listening). Throws `Error("aborted")` synchronously if the
276
+ * signal is already fired when the call starts.
277
+ */
278
+ signal?: AbortSignal;
267
279
  }
268
280
  /**
269
281
  * One call, key-in / answer-out encrypted inference. Builds viem clients,
package/dist/inference.js CHANGED
@@ -428,12 +428,14 @@ export async function runInference(args) {
428
428
  const maxRetries = args.maxRetries ?? 2;
429
429
  const stalled = [];
430
430
  for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
431
+ if (args.signal?.aborted)
432
+ throw new Error("runInference: aborted");
431
433
  try {
432
434
  const result = await runOneAttempt(args, attempt);
433
435
  return { ...result, stalled };
434
436
  }
435
437
  catch (err) {
436
- if (err instanceof StalledWorkerError && attempt <= maxRetries) {
438
+ if (err instanceof StalledWorkerError && attempt <= maxRetries && !args.signal?.aborted) {
437
439
  stalled.push({ jobId: err.jobId, worker: err.worker, submitTx: err.submitTx });
438
440
  continue;
439
441
  }
@@ -482,6 +484,9 @@ export async function runInferenceWithKey(args) {
482
484
  if (!key || !key.startsWith("0x") || key.length !== 66) {
483
485
  throw new Error("runInferenceWithKey: privateKey must be a 0x-prefixed 32-byte hex string");
484
486
  }
487
+ if (args.signal?.aborted) {
488
+ throw new Error("runInferenceWithKey: aborted before start");
489
+ }
485
490
  const account = viemPrivateKeyToAccount(key);
486
491
  const chain = {
487
492
  id: network.chainId,
@@ -504,7 +509,7 @@ export async function runInferenceWithKey(args) {
504
509
  // looks like a CORS or undici-level reachability error.
505
510
  const fetchOrFail = async (url, init, label) => {
506
511
  try {
507
- return await fetch(url, init);
512
+ return await fetch(url, { ...init, signal: args.signal });
508
513
  }
509
514
  catch (err) {
510
515
  const cause = err.cause;
@@ -574,6 +579,7 @@ export async function runInferenceWithKey(args) {
574
579
  jobCompletedTimeoutMs: args.jobCompletedTimeoutMs,
575
580
  WebSocket: wsCtor,
576
581
  relayUrl: args.relayUrl,
582
+ signal: args.signal,
577
583
  });
578
584
  }
579
585
  /**
package/dist/networks.js CHANGED
@@ -17,6 +17,12 @@ export const NETWORKS = {
17
17
  aiConfig: "0x24D11533C354092ed6E18b964257819cE78Ce77D",
18
18
  jobRegistry: "0xfB15F90298e4CcD7106E76fFB5e520315cC42B0b",
19
19
  minStakeLcai: 50000,
20
+ // Sourced from LightChain's official "Mainnet Contract Addresses" page.
21
+ feePool: "0x0000000000000000000000000000000000001004",
22
+ nativeVotes: "0x0000000000000000000000000000000000001001",
23
+ governor: "0x262E9f9232933E8565253918db703baD58DE93aB",
24
+ timelock: "0x79e571420c5473Ca9b0FCd599B1b0062D7793c97",
25
+ treasury: "0x786eDe8C42Ca54E54c9dCECa9b30052CF4743389",
20
26
  },
21
27
  testnet: {
22
28
  id: "testnet",
@@ -30,6 +36,9 @@ export const NETWORKS = {
30
36
  aiConfig: "0xeCF4Ca5Ba6D97ae586993e170764a1E92231b67e",
31
37
  jobRegistry: "0x531b3a87c5d785441b9cf55b98169f20fd9056a7",
32
38
  minStakeLcai: 5000,
39
+ // Same genesis predeploys as mainnet (chain bytecode is identical).
40
+ feePool: "0x0000000000000000000000000000000000001004",
41
+ nativeVotes: "0x0000000000000000000000000000000000001001",
33
42
  },
34
43
  };
35
44
  /** WorkerRegistry genesis predeploy (same address on both networks). */
package/dist/types.d.ts CHANGED
@@ -12,6 +12,16 @@ export interface NetworkConfig {
12
12
  aiConfig: string;
13
13
  jobRegistry: string;
14
14
  minStakeLcai: number;
15
+ /** FeePool genesis predeploy. Where per-job fees accumulate before payout. */
16
+ feePool?: string;
17
+ /** NativeVotes precompile (mainnet only - backs LightChainGovernor). */
18
+ nativeVotes?: string;
19
+ /** On-chain DAO governor (mainnet only today). */
20
+ governor?: string;
21
+ /** Governor timelock controller (mainnet only). */
22
+ timelock?: string;
23
+ /** DAO-controlled treasury (mainnet only). */
24
+ treasury?: string;
15
25
  }
16
26
  export interface Worker {
17
27
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightnode-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
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",