lightnode-sdk 0.4.8 → 0.5.0

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.
@@ -0,0 +1,380 @@
1
+ /**
2
+ * On-chain Model Registry reader (AIVMModelRegistry + BenchmarkRegistry).
3
+ *
4
+ * These contracts exist in `lightchain-protocol/lcai-smart-contract` and
5
+ * describe the full base-model + variant + benchmark + access-policy
6
+ * surface. As of the time of writing, no public mainnet/testnet deployment
7
+ * address has been published by the LightChain team - so this module
8
+ * exposes the typed ABI wrapper and asks the caller to supply the
9
+ * deployment address when constructing the reader. When LightChain
10
+ * publishes addresses, we will bake them into a `KNOWN_DEPLOYMENTS` map.
11
+ *
12
+ * Usage today (custom deployment):
13
+ *
14
+ * const reader = new OnchainModelRegistry({
15
+ * publicClient,
16
+ * registry: "0x...",
17
+ * benchmarks: "0x...",
18
+ * });
19
+ * const baseIds = await reader.getBaseModelIds();
20
+ *
21
+ * Once LightChain ships the official deployment, this becomes:
22
+ *
23
+ * const reader = new OnchainModelRegistry({ publicClient, network: "mainnet" });
24
+ */
25
+ export declare const AIVM_MODEL_REGISTRY_ABI: readonly [{
26
+ readonly name: "getBaseModelIds";
27
+ readonly type: "function";
28
+ readonly stateMutability: "view";
29
+ readonly inputs: readonly [];
30
+ readonly outputs: readonly [{
31
+ readonly type: "string[]";
32
+ }];
33
+ }, {
34
+ readonly name: "getBaseModel";
35
+ readonly type: "function";
36
+ readonly stateMutability: "view";
37
+ readonly inputs: readonly [{
38
+ readonly type: "string";
39
+ readonly name: "modelId";
40
+ }];
41
+ readonly outputs: readonly [{
42
+ readonly type: "tuple";
43
+ readonly components: readonly [{
44
+ readonly type: "string";
45
+ readonly name: "modelId";
46
+ }, {
47
+ readonly type: "string";
48
+ readonly name: "baseModelCID";
49
+ }, {
50
+ readonly type: "string";
51
+ readonly name: "metadataHash";
52
+ }, {
53
+ readonly type: "string";
54
+ readonly name: "policyVersion";
55
+ }, {
56
+ readonly type: "string";
57
+ readonly name: "benchmarkCID";
58
+ }, {
59
+ readonly type: "uint256";
60
+ readonly name: "createdAt";
61
+ }, {
62
+ readonly type: "bool";
63
+ readonly name: "isActive";
64
+ }];
65
+ }];
66
+ }, {
67
+ readonly name: "getAllVariants";
68
+ readonly type: "function";
69
+ readonly stateMutability: "view";
70
+ readonly inputs: readonly [];
71
+ readonly outputs: readonly [{
72
+ readonly type: "string[]";
73
+ }];
74
+ }, {
75
+ readonly name: "getTrainerVariants";
76
+ readonly type: "function";
77
+ readonly stateMutability: "view";
78
+ readonly inputs: readonly [{
79
+ readonly type: "address";
80
+ readonly name: "trainer";
81
+ }];
82
+ readonly outputs: readonly [{
83
+ readonly type: "string[]";
84
+ }];
85
+ }, {
86
+ readonly name: "getVariant";
87
+ readonly type: "function";
88
+ readonly stateMutability: "view";
89
+ readonly inputs: readonly [{
90
+ readonly type: "string";
91
+ readonly name: "variantId";
92
+ }];
93
+ readonly outputs: readonly [{
94
+ readonly type: "tuple";
95
+ readonly components: readonly [{
96
+ readonly type: "string";
97
+ readonly name: "variantId";
98
+ }, {
99
+ readonly type: "string";
100
+ readonly name: "variantCID";
101
+ }, {
102
+ readonly type: "string";
103
+ readonly name: "metadataHash";
104
+ }, {
105
+ readonly type: "string";
106
+ readonly name: "parentModelId";
107
+ }, {
108
+ readonly type: "address";
109
+ readonly name: "trainer";
110
+ }, {
111
+ readonly type: "uint256";
112
+ readonly name: "trainerStake";
113
+ }, {
114
+ readonly type: "uint8";
115
+ readonly name: "status";
116
+ }, {
117
+ readonly type: "uint256";
118
+ readonly name: "avgScore";
119
+ }, {
120
+ readonly type: "string";
121
+ readonly name: "reportCID";
122
+ }, {
123
+ readonly type: "uint256";
124
+ readonly name: "submittedAt";
125
+ }, {
126
+ readonly type: "uint256";
127
+ readonly name: "validatedAt";
128
+ }, {
129
+ readonly type: "uint256";
130
+ readonly name: "finalizedAt";
131
+ }, {
132
+ readonly type: "uint256";
133
+ readonly name: "validatorCount";
134
+ }, {
135
+ readonly type: "bool";
136
+ readonly name: "challengeWindowOpen";
137
+ }, {
138
+ readonly type: "uint256";
139
+ readonly name: "challengeDeadline";
140
+ }];
141
+ }];
142
+ }, {
143
+ readonly name: "isVariantAvailable";
144
+ readonly type: "function";
145
+ readonly stateMutability: "view";
146
+ readonly inputs: readonly [{
147
+ readonly type: "string";
148
+ readonly name: "variantId";
149
+ }];
150
+ readonly outputs: readonly [{
151
+ readonly type: "bool";
152
+ }];
153
+ }, {
154
+ readonly name: "getAccessPolicy";
155
+ readonly type: "function";
156
+ readonly stateMutability: "view";
157
+ readonly inputs: readonly [{
158
+ readonly type: "string";
159
+ readonly name: "variantId";
160
+ }];
161
+ readonly outputs: readonly [{
162
+ readonly type: "tuple";
163
+ readonly components: readonly [{
164
+ readonly type: "bool";
165
+ readonly name: "requireTicket";
166
+ }, {
167
+ readonly type: "uint256";
168
+ readonly name: "minStakeRequired";
169
+ }, {
170
+ readonly type: "address";
171
+ readonly name: "ticketManager";
172
+ }, {
173
+ readonly type: "uint256";
174
+ readonly name: "ticketTTL";
175
+ }];
176
+ }];
177
+ }];
178
+ export declare const BENCHMARK_REGISTRY_ABI: readonly [{
179
+ readonly name: "listBenchmarks";
180
+ readonly type: "function";
181
+ readonly stateMutability: "view";
182
+ readonly inputs: readonly [];
183
+ readonly outputs: readonly [{
184
+ readonly type: "string[]";
185
+ }];
186
+ }, {
187
+ readonly name: "listBenchmarksByDomain";
188
+ readonly type: "function";
189
+ readonly stateMutability: "view";
190
+ readonly inputs: readonly [{
191
+ readonly type: "string";
192
+ readonly name: "domain";
193
+ }];
194
+ readonly outputs: readonly [{
195
+ readonly type: "string[]";
196
+ }];
197
+ }, {
198
+ readonly name: "listBenchmarksByTask";
199
+ readonly type: "function";
200
+ readonly stateMutability: "view";
201
+ readonly inputs: readonly [{
202
+ readonly type: "string";
203
+ readonly name: "taskType";
204
+ }];
205
+ readonly outputs: readonly [{
206
+ readonly type: "string[]";
207
+ }];
208
+ }, {
209
+ readonly name: "getBenchmark";
210
+ readonly type: "function";
211
+ readonly stateMutability: "view";
212
+ readonly inputs: readonly [{
213
+ readonly type: "string";
214
+ readonly name: "benchmarkId";
215
+ }];
216
+ readonly outputs: readonly [{
217
+ readonly type: "tuple";
218
+ readonly components: readonly [{
219
+ readonly type: "string";
220
+ readonly name: "benchmarkId";
221
+ }, {
222
+ readonly type: "string";
223
+ readonly name: "domain";
224
+ }, {
225
+ readonly type: "string";
226
+ readonly name: "taskType";
227
+ }, {
228
+ readonly type: "string";
229
+ readonly name: "benchmarkCID";
230
+ }, {
231
+ readonly type: "string";
232
+ readonly name: "metadataCID";
233
+ }, {
234
+ readonly type: "string";
235
+ readonly name: "manifestHash";
236
+ }, {
237
+ readonly type: "string";
238
+ readonly name: "wrappedDEK";
239
+ }, {
240
+ readonly type: "string";
241
+ readonly name: "version";
242
+ }, {
243
+ readonly type: "address";
244
+ readonly name: "curator";
245
+ }, {
246
+ readonly type: "uint256";
247
+ readonly name: "registeredAt";
248
+ }, {
249
+ readonly type: "bool";
250
+ readonly name: "encrypted";
251
+ }, {
252
+ readonly type: "bool";
253
+ readonly name: "active";
254
+ }];
255
+ }];
256
+ }, {
257
+ readonly name: "getBenchmarkForVariant";
258
+ readonly type: "function";
259
+ readonly stateMutability: "view";
260
+ readonly inputs: readonly [{
261
+ readonly type: "string";
262
+ readonly name: "domain";
263
+ }, {
264
+ readonly type: "string";
265
+ readonly name: "taskType";
266
+ }];
267
+ readonly outputs: readonly [{
268
+ readonly type: "string";
269
+ readonly name: "benchmarkId";
270
+ }];
271
+ }];
272
+ /** Model status enum from AIVMModelRegistry. */
273
+ export declare enum ModelStatus {
274
+ Submitted = 0,
275
+ Validating = 1,
276
+ Approved = 2,
277
+ Rejected = 3,
278
+ Finalized = 4,
279
+ Deprecated = 5
280
+ }
281
+ export declare const MODEL_STATUS_LABEL: Record<ModelStatus, string>;
282
+ export interface BaseModel {
283
+ modelId: string;
284
+ baseModelCID: string;
285
+ metadataHash: string;
286
+ policyVersion: string;
287
+ benchmarkCID: string;
288
+ createdAt: bigint;
289
+ isActive: boolean;
290
+ }
291
+ export interface ModelVariant {
292
+ variantId: string;
293
+ variantCID: string;
294
+ metadataHash: string;
295
+ parentModelId: string;
296
+ trainer: `0x${string}`;
297
+ trainerStake: bigint;
298
+ status: ModelStatus;
299
+ avgScore: bigint;
300
+ reportCID: string;
301
+ submittedAt: bigint;
302
+ validatedAt: bigint;
303
+ finalizedAt: bigint;
304
+ validatorCount: bigint;
305
+ challengeWindowOpen: boolean;
306
+ challengeDeadline: bigint;
307
+ }
308
+ /**
309
+ * Builder-friendly access tier inferred from the raw AccessPolicyConfig
310
+ * fields. The contract's policy is more granular (ticket manager, TTL, etc.)
311
+ * but most consumers want to know "is this model free / paywalled / gated".
312
+ */
313
+ export type AccessTier = "free" | "paywalled" | "ticket-gated";
314
+ export interface AccessPolicy {
315
+ requireTicket: boolean;
316
+ minStakeRequiredWei: bigint;
317
+ ticketManager: `0x${string}`;
318
+ ticketTtlSecs: bigint;
319
+ tier: AccessTier;
320
+ }
321
+ export interface Benchmark {
322
+ benchmarkId: string;
323
+ domain: string;
324
+ taskType: string;
325
+ benchmarkCID: string;
326
+ metadataCID: string;
327
+ manifestHash: string;
328
+ wrappedDEK: string;
329
+ version: string;
330
+ curator: `0x${string}`;
331
+ registeredAt: bigint;
332
+ encrypted: boolean;
333
+ active: boolean;
334
+ }
335
+ interface MinimalPublicClient {
336
+ readContract: (args: {
337
+ address: `0x${string}`;
338
+ abi: readonly unknown[];
339
+ functionName: string;
340
+ args?: readonly unknown[];
341
+ }) => Promise<unknown>;
342
+ }
343
+ export interface OnchainModelRegistryOptions {
344
+ publicClient: MinimalPublicClient;
345
+ /** Deployed AIVMModelRegistry address. */
346
+ registry: `0x${string}`;
347
+ /** Optional: deployed BenchmarkRegistry address for the benchmark methods. */
348
+ benchmarks?: `0x${string}`;
349
+ }
350
+ /**
351
+ * Typed reader for AIVMModelRegistry + BenchmarkRegistry. Pass deployed
352
+ * contract addresses; the SDK does not bake in defaults until LightChain
353
+ * publishes them.
354
+ */
355
+ export declare class OnchainModelRegistry {
356
+ private readonly opts;
357
+ readonly registry: `0x${string}`;
358
+ readonly benchmarks: `0x${string}` | null;
359
+ constructor(opts: OnchainModelRegistryOptions);
360
+ getBaseModelIds(): Promise<string[]>;
361
+ getBaseModel(modelId: string): Promise<BaseModel>;
362
+ getAllVariants(): Promise<string[]>;
363
+ getTrainerVariants(trainer: `0x${string}`): Promise<string[]>;
364
+ getVariant(variantId: string): Promise<ModelVariant>;
365
+ isVariantAvailable(variantId: string): Promise<boolean>;
366
+ /**
367
+ * Variant access policy. The raw struct has four fields; this also adds
368
+ * a `tier` heuristic ("free" / "paywalled" / "ticket-gated") for builders
369
+ * who do not want to interpret the fields themselves.
370
+ */
371
+ getAccessPolicy(variantId: string): Promise<AccessPolicy>;
372
+ /** Return only the variants whose parentModelId matches `baseModelId`. */
373
+ getVariantsForBaseModel(baseModelId: string): Promise<ModelVariant[]>;
374
+ private requireBenchmarks;
375
+ listBenchmarks(): Promise<string[]>;
376
+ listBenchmarksByDomain(domain: string): Promise<string[]>;
377
+ getBenchmark(benchmarkId: string): Promise<Benchmark>;
378
+ getBenchmarkForVariant(domain: string, taskType: string): Promise<string>;
379
+ }
380
+ export {};
@@ -0,0 +1,187 @@
1
+ /**
2
+ * On-chain Model Registry reader (AIVMModelRegistry + BenchmarkRegistry).
3
+ *
4
+ * These contracts exist in `lightchain-protocol/lcai-smart-contract` and
5
+ * describe the full base-model + variant + benchmark + access-policy
6
+ * surface. As of the time of writing, no public mainnet/testnet deployment
7
+ * address has been published by the LightChain team - so this module
8
+ * exposes the typed ABI wrapper and asks the caller to supply the
9
+ * deployment address when constructing the reader. When LightChain
10
+ * publishes addresses, we will bake them into a `KNOWN_DEPLOYMENTS` map.
11
+ *
12
+ * Usage today (custom deployment):
13
+ *
14
+ * const reader = new OnchainModelRegistry({
15
+ * publicClient,
16
+ * registry: "0x...",
17
+ * benchmarks: "0x...",
18
+ * });
19
+ * const baseIds = await reader.getBaseModelIds();
20
+ *
21
+ * Once LightChain ships the official deployment, this becomes:
22
+ *
23
+ * const reader = new OnchainModelRegistry({ publicClient, network: "mainnet" });
24
+ */
25
+ import { parseAbi } from "viem";
26
+ export const AIVM_MODEL_REGISTRY_ABI = parseAbi([
27
+ "function getBaseModelIds() external view returns (string[])",
28
+ "function getBaseModel(string modelId) external view returns ((string modelId, string baseModelCID, string metadataHash, string policyVersion, string benchmarkCID, uint256 createdAt, bool isActive))",
29
+ "function getAllVariants() external view returns (string[])",
30
+ "function getTrainerVariants(address trainer) external view returns (string[])",
31
+ "function getVariant(string variantId) external view returns ((string variantId, string variantCID, string metadataHash, string parentModelId, address trainer, uint256 trainerStake, uint8 status, uint256 avgScore, string reportCID, uint256 submittedAt, uint256 validatedAt, uint256 finalizedAt, uint256 validatorCount, bool challengeWindowOpen, uint256 challengeDeadline))",
32
+ "function isVariantAvailable(string variantId) external view returns (bool)",
33
+ "function getAccessPolicy(string variantId) external view returns ((bool requireTicket, uint256 minStakeRequired, address ticketManager, uint256 ticketTTL))",
34
+ ]);
35
+ export const BENCHMARK_REGISTRY_ABI = parseAbi([
36
+ "function listBenchmarks() external view returns (string[])",
37
+ "function listBenchmarksByDomain(string domain) external view returns (string[])",
38
+ "function listBenchmarksByTask(string taskType) external view returns (string[])",
39
+ "function getBenchmark(string benchmarkId) external view returns ((string benchmarkId, string domain, string taskType, string benchmarkCID, string metadataCID, string manifestHash, string wrappedDEK, string version, address curator, uint256 registeredAt, bool encrypted, bool active))",
40
+ "function getBenchmarkForVariant(string domain, string taskType) external view returns (string benchmarkId)",
41
+ ]);
42
+ /** Model status enum from AIVMModelRegistry. */
43
+ export var ModelStatus;
44
+ (function (ModelStatus) {
45
+ ModelStatus[ModelStatus["Submitted"] = 0] = "Submitted";
46
+ ModelStatus[ModelStatus["Validating"] = 1] = "Validating";
47
+ ModelStatus[ModelStatus["Approved"] = 2] = "Approved";
48
+ ModelStatus[ModelStatus["Rejected"] = 3] = "Rejected";
49
+ ModelStatus[ModelStatus["Finalized"] = 4] = "Finalized";
50
+ ModelStatus[ModelStatus["Deprecated"] = 5] = "Deprecated";
51
+ })(ModelStatus || (ModelStatus = {}));
52
+ export const MODEL_STATUS_LABEL = {
53
+ [ModelStatus.Submitted]: "submitted",
54
+ [ModelStatus.Validating]: "validating",
55
+ [ModelStatus.Approved]: "approved",
56
+ [ModelStatus.Rejected]: "rejected",
57
+ [ModelStatus.Finalized]: "finalized",
58
+ [ModelStatus.Deprecated]: "deprecated",
59
+ };
60
+ /**
61
+ * Typed reader for AIVMModelRegistry + BenchmarkRegistry. Pass deployed
62
+ * contract addresses; the SDK does not bake in defaults until LightChain
63
+ * publishes them.
64
+ */
65
+ export class OnchainModelRegistry {
66
+ constructor(opts) {
67
+ this.opts = opts;
68
+ this.registry = opts.registry;
69
+ this.benchmarks = opts.benchmarks ?? null;
70
+ }
71
+ // -------- AIVMModelRegistry reads --------
72
+ getBaseModelIds() {
73
+ return this.opts.publicClient.readContract({
74
+ address: this.registry,
75
+ abi: AIVM_MODEL_REGISTRY_ABI,
76
+ functionName: "getBaseModelIds",
77
+ });
78
+ }
79
+ getBaseModel(modelId) {
80
+ return this.opts.publicClient.readContract({
81
+ address: this.registry,
82
+ abi: AIVM_MODEL_REGISTRY_ABI,
83
+ functionName: "getBaseModel",
84
+ args: [modelId],
85
+ });
86
+ }
87
+ getAllVariants() {
88
+ return this.opts.publicClient.readContract({
89
+ address: this.registry,
90
+ abi: AIVM_MODEL_REGISTRY_ABI,
91
+ functionName: "getAllVariants",
92
+ });
93
+ }
94
+ getTrainerVariants(trainer) {
95
+ return this.opts.publicClient.readContract({
96
+ address: this.registry,
97
+ abi: AIVM_MODEL_REGISTRY_ABI,
98
+ functionName: "getTrainerVariants",
99
+ args: [trainer],
100
+ });
101
+ }
102
+ async getVariant(variantId) {
103
+ const raw = (await this.opts.publicClient.readContract({
104
+ address: this.registry,
105
+ abi: AIVM_MODEL_REGISTRY_ABI,
106
+ functionName: "getVariant",
107
+ args: [variantId],
108
+ }));
109
+ return { ...raw, status: raw.status };
110
+ }
111
+ isVariantAvailable(variantId) {
112
+ return this.opts.publicClient.readContract({
113
+ address: this.registry,
114
+ abi: AIVM_MODEL_REGISTRY_ABI,
115
+ functionName: "isVariantAvailable",
116
+ args: [variantId],
117
+ });
118
+ }
119
+ /**
120
+ * Variant access policy. The raw struct has four fields; this also adds
121
+ * a `tier` heuristic ("free" / "paywalled" / "ticket-gated") for builders
122
+ * who do not want to interpret the fields themselves.
123
+ */
124
+ async getAccessPolicy(variantId) {
125
+ const raw = (await this.opts.publicClient.readContract({
126
+ address: this.registry,
127
+ abi: AIVM_MODEL_REGISTRY_ABI,
128
+ functionName: "getAccessPolicy",
129
+ args: [variantId],
130
+ }));
131
+ let tier = "free";
132
+ if (raw.requireTicket)
133
+ tier = "ticket-gated";
134
+ else if (raw.minStakeRequired > 0n)
135
+ tier = "paywalled";
136
+ return {
137
+ requireTicket: raw.requireTicket,
138
+ minStakeRequiredWei: raw.minStakeRequired,
139
+ ticketManager: raw.ticketManager,
140
+ ticketTtlSecs: raw.ticketTTL,
141
+ tier,
142
+ };
143
+ }
144
+ /** Return only the variants whose parentModelId matches `baseModelId`. */
145
+ async getVariantsForBaseModel(baseModelId) {
146
+ const allIds = await this.getAllVariants();
147
+ const variants = await Promise.all(allIds.map((id) => this.getVariant(id)));
148
+ return variants.filter((v) => v.parentModelId === baseModelId);
149
+ }
150
+ // -------- BenchmarkRegistry reads --------
151
+ requireBenchmarks() {
152
+ if (!this.benchmarks)
153
+ throw new Error("OnchainModelRegistry: no BenchmarkRegistry address; pass `benchmarks` in the constructor");
154
+ return this.benchmarks;
155
+ }
156
+ listBenchmarks() {
157
+ return this.opts.publicClient.readContract({
158
+ address: this.requireBenchmarks(),
159
+ abi: BENCHMARK_REGISTRY_ABI,
160
+ functionName: "listBenchmarks",
161
+ });
162
+ }
163
+ listBenchmarksByDomain(domain) {
164
+ return this.opts.publicClient.readContract({
165
+ address: this.requireBenchmarks(),
166
+ abi: BENCHMARK_REGISTRY_ABI,
167
+ functionName: "listBenchmarksByDomain",
168
+ args: [domain],
169
+ });
170
+ }
171
+ getBenchmark(benchmarkId) {
172
+ return this.opts.publicClient.readContract({
173
+ address: this.requireBenchmarks(),
174
+ abi: BENCHMARK_REGISTRY_ABI,
175
+ functionName: "getBenchmark",
176
+ args: [benchmarkId],
177
+ });
178
+ }
179
+ getBenchmarkForVariant(domain, taskType) {
180
+ return this.opts.publicClient.readContract({
181
+ address: this.requireBenchmarks(),
182
+ abi: BENCHMARK_REGISTRY_ABI,
183
+ functionName: "getBenchmarkForVariant",
184
+ args: [domain, taskType],
185
+ });
186
+ }
187
+ }
@@ -2,6 +2,8 @@ import type { NetworkConfig, Worker, Job, ModelInfo, NetworkStats } from "./type
2
2
  /** Convert a wei string to a number of whole tokens (18 decimals). */
3
3
  export declare function fromWei(wei?: string): number;
4
4
  export declare function fetchWorker(cfg: NetworkConfig, address: string): Promise<Worker | null>;
5
+ /** Fetch one job by its on-chain id. Null when the indexer has never seen it. */
6
+ export declare function fetchJob(cfg: NetworkConfig, jobId: string | bigint): Promise<Job | null>;
5
7
  export declare function fetchWorkerJobs(cfg: NetworkConfig, address: string, first?: number): Promise<Job[]>;
6
8
  /** Recent jobs across the whole network (not one worker), for analytics. */
7
9
  export declare function fetchRecentJobs(cfg: NetworkConfig, first?: number): Promise<Job[]>;
package/dist/subgraph.js CHANGED
@@ -56,6 +56,12 @@ export async function fetchWorker(cfg, address) {
56
56
  throw e;
57
57
  }
58
58
  }
59
+ /** Fetch one job by its on-chain id. Null when the indexer has never seen it. */
60
+ export async function fetchJob(cfg, jobId) {
61
+ const id = typeof jobId === "bigint" ? jobId.toString() : jobId;
62
+ const data = await gql(cfg.subgraph, `{ job(id:"${id}") { id state model_id worker submitted_at ack_at completed_at worker_share } }`);
63
+ return data.job ?? null;
64
+ }
59
65
  export async function fetchWorkerJobs(cfg, address, first = 20) {
60
66
  const data = await gql(cfg.subgraph, `{ jobs(first:${first}, orderBy:submitted_at, orderDirection:desc, where:{worker:"${checksum(address)}"}) { id state model_id submitted_at ack_at completed_at worker_share } }`);
61
67
  return data.jobs ?? [];
@@ -0,0 +1,104 @@
1
+ /**
2
+ * SDK-level worker preflight + watch.
3
+ *
4
+ * The desktop app already has local worker-health parsers (parseWorkerHealth,
5
+ * parseSpeedTest) that need shell access to the running container. Those are
6
+ * great for operators ON the worker box, but useless for a builder watching
7
+ * the network from anywhere else.
8
+ *
9
+ * This module fills the remote-only gap:
10
+ * - `preflight()` runs ONE real test inference against the live network
11
+ * and returns a verdict (works / over-deadline / failed). This is the
12
+ * "test job before joining a worker pool" the community has been asking
13
+ * for; works from any machine with a funded wallet.
14
+ * - `watch()` polls a worker's on-chain + indexer status on a fixed
15
+ * interval and yields an event each time the status meaningfully
16
+ * changes (registered <-> deregistered, last-seen went stale, completion
17
+ * rate dropped). Suitable for a CI gate or a `cron` job.
18
+ *
19
+ * Both are pure SDK calls. No Docker, no SSH, no privileged access.
20
+ */
21
+ import { type RunInferenceWithKeyArgs } from "./inference.js";
22
+ import { LightNode } from "./index.js";
23
+ export interface WorkerPreflightArgs extends Omit<RunInferenceWithKeyArgs, "prompt"> {
24
+ /**
25
+ * Prompt to send. Defaults to a tiny, deterministic prompt so the test is
26
+ * cheap (one short answer is enough to verify the round trip).
27
+ */
28
+ prompt?: string;
29
+ /**
30
+ * Verdict deadline. If the inference takes longer than this many ms but
31
+ * still completes, the verdict is "over-deadline" instead of "ok". Default
32
+ * 60s, matching the protocol's typical good-worker p95.
33
+ */
34
+ deadlineMs?: number;
35
+ }
36
+ export interface WorkerPreflightResult {
37
+ verdict: "ok" | "over-deadline" | "stalled" | "failed";
38
+ /** Total elapsed milliseconds from the first SDK call to the decrypted answer. */
39
+ elapsedMs: number;
40
+ /** Plain-English summary suitable for printing to a CLI or alerting. */
41
+ summary: string;
42
+ /** The actual decrypted answer (may be empty on failure). */
43
+ answer: string;
44
+ /** Address of the dispatcher-assigned worker that produced (or failed) the response. */
45
+ worker: `0x${string}` | null;
46
+ /** On-chain receipts. `jobCompleted` may be null when the WS delivered the answer but the on-chain event is still propagating. */
47
+ txs: {
48
+ createSession: `0x${string}` | null;
49
+ submitJob: `0x${string}` | null;
50
+ jobCompleted: `0x${string}` | null;
51
+ };
52
+ /** Underlying error if the test did not complete cleanly. */
53
+ error: string | null;
54
+ }
55
+ /**
56
+ * Run one real encrypted inference against the live network and classify
57
+ * the result. Useful as a CI gate ("did the wallet survive a real call this
58
+ * deploy?") or as a pre-join check for a worker operator who wants to test
59
+ * the protocol path before staking.
60
+ */
61
+ export declare function preflight(args: WorkerPreflightArgs): Promise<WorkerPreflightResult>;
62
+ export interface WorkerWatchOptions {
63
+ /** Polling interval in milliseconds. Default 30s. Indexer / RPC usage scales linearly. */
64
+ intervalMs?: number;
65
+ /**
66
+ * Mark the worker as "stale" when its last_seen_at is older than this many
67
+ * seconds. Default 90 (matches the worker daemon's heartbeat cadence + grace).
68
+ */
69
+ staleSecs?: number;
70
+ /**
71
+ * Stop polling automatically after this many events. Default Infinity (the
72
+ * caller controls lifetime via the returned `stop()` function).
73
+ */
74
+ maxEvents?: number;
75
+ }
76
+ export type WorkerEventKind = "snapshot" | "registered" | "deregistered" | "went-stale" | "back-online" | "jobs-completed" | "earnings-up";
77
+ export interface WorkerEvent {
78
+ kind: WorkerEventKind;
79
+ at: number;
80
+ worker: string;
81
+ network: "mainnet" | "testnet";
82
+ /** Snapshot of the worker at the moment the event was raised. */
83
+ state: {
84
+ registered: boolean | null;
85
+ lastSeenSecsAgo: number | null;
86
+ jobsCompleted: number | null;
87
+ earningsLcai: number;
88
+ activeJobs: number | null;
89
+ isStale: boolean;
90
+ };
91
+ }
92
+ export interface WorkerWatchHandle {
93
+ /** AsyncIterable of events; consume with `for await (const e of handle.events) ...`. */
94
+ events: AsyncIterable<WorkerEvent>;
95
+ /** Stop polling and end the iterator gracefully. */
96
+ stop: () => void;
97
+ }
98
+ /**
99
+ * Poll one worker's on-chain + indexer status and yield an event each time
100
+ * something meaningful changes (registration, staleness, completed-jobs
101
+ * counter, earnings). Runs until `handle.stop()` is called or `maxEvents`
102
+ * is reached.
103
+ */
104
+ export declare function watch(ln: LightNode, address: string, opts?: WorkerWatchOptions): WorkerWatchHandle;