lightnode-sdk 0.7.1 → 0.7.3
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 +2 -0
- package/dist/analytics.js +1 -1
- package/dist/dao.d.ts +139 -0
- package/dist/dao.js +166 -1
- package/dist/index.d.ts +29 -7
- package/dist/index.js +31 -4
- package/dist/inference.d.ts +6 -6
- package/dist/subgraph.d.ts +22 -1
- package/dist/subgraph.js +75 -4
- package/dist/types.d.ts +11 -0
- package/dist/worker-operator.d.ts +3 -3
- package/dist/worker-operator.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -426,6 +426,8 @@ npx lightnode dao config # live voting delay / period / threshol
|
|
|
426
426
|
|
|
427
427
|
```bash
|
|
428
428
|
PRIVATE_KEY=0x... npx lightnode chat "Write me a haiku about LightChain"
|
|
429
|
+
PRIVATE_KEY=0x... npx lightnode batch prompts.json --concurrency 4 # N prompts in parallel
|
|
430
|
+
PRIVATE_KEY=0x... npx lightnode agent "research X and summarize" # ReAct agent, built-in tools
|
|
429
431
|
PRIVATE_KEY=0x... npx lightnode wallet address
|
|
430
432
|
PRIVATE_KEY=0x... npx lightnode wallet balance --net testnet
|
|
431
433
|
npx lightnode wallet new # generates a fresh key
|
package/dist/analytics.js
CHANGED
|
@@ -76,7 +76,7 @@ function groupBy(jobs, key) {
|
|
|
76
76
|
export function aggregateModelStats(jobs, models, nowSec = Math.floor(Date.now() / 1000)) {
|
|
77
77
|
const nameById = new Map(models.map((m) => [m.id.toLowerCase(), m.name]));
|
|
78
78
|
return [...groupBy(jobs, (j) => j.model_id?.toLowerCase()).entries()]
|
|
79
|
-
.map(([id, js]) => ({ modelId: id, name: nameById.get(id) ?? `${id.slice(0, 10)}
|
|
79
|
+
.map(([id, js]) => ({ modelId: id, name: nameById.get(id) ?? `${id.slice(0, 10)}...`, ...classifyJobs(js, nowSec) }))
|
|
80
80
|
.sort((a, b) => b.total - a.total);
|
|
81
81
|
}
|
|
82
82
|
/** Per-worker reliability, busiest first (top `limit`). */
|
package/dist/dao.d.ts
CHANGED
|
@@ -329,6 +329,57 @@ export declare const GOVERNOR_ABI: readonly [{
|
|
|
329
329
|
readonly outputs: readonly [{
|
|
330
330
|
readonly type: "bool";
|
|
331
331
|
}];
|
|
332
|
+
}, {
|
|
333
|
+
readonly name: "cancel";
|
|
334
|
+
readonly type: "function";
|
|
335
|
+
readonly stateMutability: "nonpayable";
|
|
336
|
+
readonly inputs: readonly [{
|
|
337
|
+
readonly type: "address[]";
|
|
338
|
+
readonly name: "targets";
|
|
339
|
+
}, {
|
|
340
|
+
readonly type: "uint256[]";
|
|
341
|
+
readonly name: "values";
|
|
342
|
+
}, {
|
|
343
|
+
readonly type: "bytes[]";
|
|
344
|
+
readonly name: "calldatas";
|
|
345
|
+
}, {
|
|
346
|
+
readonly type: "bytes32";
|
|
347
|
+
readonly name: "descriptionHash";
|
|
348
|
+
}];
|
|
349
|
+
readonly outputs: readonly [{
|
|
350
|
+
readonly type: "uint256";
|
|
351
|
+
}];
|
|
352
|
+
}, {
|
|
353
|
+
readonly name: "ProposalCreated";
|
|
354
|
+
readonly type: "event";
|
|
355
|
+
readonly inputs: readonly [{
|
|
356
|
+
readonly type: "uint256";
|
|
357
|
+
readonly name: "proposalId";
|
|
358
|
+
}, {
|
|
359
|
+
readonly type: "address";
|
|
360
|
+
readonly name: "proposer";
|
|
361
|
+
}, {
|
|
362
|
+
readonly type: "address[]";
|
|
363
|
+
readonly name: "targets";
|
|
364
|
+
}, {
|
|
365
|
+
readonly type: "uint256[]";
|
|
366
|
+
readonly name: "values";
|
|
367
|
+
}, {
|
|
368
|
+
readonly type: "string[]";
|
|
369
|
+
readonly name: "signatures";
|
|
370
|
+
}, {
|
|
371
|
+
readonly type: "bytes[]";
|
|
372
|
+
readonly name: "calldatas";
|
|
373
|
+
}, {
|
|
374
|
+
readonly type: "uint256";
|
|
375
|
+
readonly name: "voteStart";
|
|
376
|
+
}, {
|
|
377
|
+
readonly type: "uint256";
|
|
378
|
+
readonly name: "voteEnd";
|
|
379
|
+
}, {
|
|
380
|
+
readonly type: "string";
|
|
381
|
+
readonly name: "description";
|
|
382
|
+
}];
|
|
332
383
|
}];
|
|
333
384
|
/** Minimal IVotes ABI for delegate + balance reads (LCAIBallots). */
|
|
334
385
|
export declare const VOTES_ABI: readonly [{
|
|
@@ -380,6 +431,18 @@ interface MinimalPublicClient {
|
|
|
380
431
|
functionName: string;
|
|
381
432
|
args?: readonly unknown[];
|
|
382
433
|
}) => Promise<unknown>;
|
|
434
|
+
getBlockNumber?: () => Promise<bigint>;
|
|
435
|
+
getLogs?: (args: {
|
|
436
|
+
address?: `0x${string}`;
|
|
437
|
+
event?: unknown;
|
|
438
|
+
args?: Record<string, unknown>;
|
|
439
|
+
fromBlock?: bigint | "earliest" | "latest";
|
|
440
|
+
toBlock?: bigint | "latest";
|
|
441
|
+
}) => Promise<ReadonlyArray<{
|
|
442
|
+
args?: Record<string, unknown>;
|
|
443
|
+
blockNumber?: bigint;
|
|
444
|
+
transactionHash?: `0x${string}`;
|
|
445
|
+
}>>;
|
|
383
446
|
}
|
|
384
447
|
interface MinimalWalletClient {
|
|
385
448
|
writeContract: (args: {
|
|
@@ -412,6 +475,20 @@ export interface DaoConfig {
|
|
|
412
475
|
/** Approx voting period in seconds, assuming 12s/block on Ethereum. */
|
|
413
476
|
votingPeriodSecs: number;
|
|
414
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* One row returned by `DAO.recentProposals`. Lightweight summary you can
|
|
480
|
+
* map straight into a list UI. Use `dao.proposal(id)` for the full vote
|
|
481
|
+
* counts and exact deadlines.
|
|
482
|
+
*/
|
|
483
|
+
export interface ProposalRow {
|
|
484
|
+
id: bigint;
|
|
485
|
+
proposer: `0x${string}`;
|
|
486
|
+
/** First non-empty line of the proposal `description`. */
|
|
487
|
+
title: string;
|
|
488
|
+
state: ProposalState;
|
|
489
|
+
stateLabel: string;
|
|
490
|
+
blockNumber: bigint;
|
|
491
|
+
}
|
|
415
492
|
/**
|
|
416
493
|
* DAO client. Wraps reads (proposal state, config) + writes (propose, vote,
|
|
417
494
|
* queue, execute) on the Ethereum LCAIGovernor.
|
|
@@ -434,6 +511,19 @@ export declare class DAO {
|
|
|
434
511
|
config(): Promise<DaoConfig>;
|
|
435
512
|
/** Quorum required at a given timepoint (in wei of voting weight). */
|
|
436
513
|
quorum(timepoint: bigint): Promise<bigint>;
|
|
514
|
+
/**
|
|
515
|
+
* List recent proposals on the governor by scanning `ProposalCreated`
|
|
516
|
+
* events. Scans back `lookbackBlocks` from the current head and returns
|
|
517
|
+
* up to `limit` proposals (newest first) with their current state.
|
|
518
|
+
*
|
|
519
|
+
* Ethereum mainnet RPCs cap a single `getLogs` to 10k blocks; the SDK
|
|
520
|
+
* chunks the range automatically. Default lookback is 300k blocks
|
|
521
|
+
* (~40 days) and default limit is 10.
|
|
522
|
+
*/
|
|
523
|
+
recentProposals(opts?: {
|
|
524
|
+
lookbackBlocks?: number;
|
|
525
|
+
limit?: number;
|
|
526
|
+
}): Promise<ProposalRow[]>;
|
|
437
527
|
/** Cast a For / Against / Abstain vote. Wallet must be the voter and have delegated their LCAI. */
|
|
438
528
|
castVote(proposalId: bigint, support: VoteSupport, reason?: string): Promise<`0x${string}`>;
|
|
439
529
|
/** Submit a new proposal. Wallet must hold at least `proposalThreshold` delegated votes. */
|
|
@@ -460,5 +550,54 @@ export declare class DAO {
|
|
|
460
550
|
calldatas: `0x${string}`[];
|
|
461
551
|
descriptionHash: `0x${string}`;
|
|
462
552
|
}): Promise<`0x${string}`>;
|
|
553
|
+
/**
|
|
554
|
+
* Cancel a proposal. OZ Governor permits the proposer (and sometimes the
|
|
555
|
+
* Guardian / Timelock role) to cancel a Pending or Active proposal. Same
|
|
556
|
+
* `descriptionHash` you'd pass to `queue` / `execute`.
|
|
557
|
+
*/
|
|
558
|
+
cancel(args: {
|
|
559
|
+
targets: `0x${string}`[];
|
|
560
|
+
values: bigint[];
|
|
561
|
+
calldatas: `0x${string}`[];
|
|
562
|
+
descriptionHash: `0x${string}`;
|
|
563
|
+
}): Promise<`0x${string}`>;
|
|
564
|
+
/**
|
|
565
|
+
* keccak256 of the raw description string. The Governor stores proposals
|
|
566
|
+
* keyed by `(targets, values, calldatas, descriptionHash)` rather than
|
|
567
|
+
* the description itself - this is the same hash the OZ Governor computes
|
|
568
|
+
* internally. Pass it to `queue`, `execute`, `cancel`, `hashProposal`.
|
|
569
|
+
*/
|
|
570
|
+
descriptionHash(description: string): `0x${string}`;
|
|
571
|
+
/**
|
|
572
|
+
* Predict a proposal id BEFORE submitting. Mirrors `Governor.hashProposal`
|
|
573
|
+
* on chain so you can compute the id deterministically (the proposer can
|
|
574
|
+
* persist it ahead of time, drop it into a UI, or sanity-check that a
|
|
575
|
+
* proposal hasn't already been submitted).
|
|
576
|
+
*/
|
|
577
|
+
hashProposal(args: {
|
|
578
|
+
targets: `0x${string}`[];
|
|
579
|
+
values: bigint[];
|
|
580
|
+
calldatas: `0x${string}`[];
|
|
581
|
+
/** Either the description string or the precomputed hash; both accepted. */
|
|
582
|
+
description: string | `0x${string}`;
|
|
583
|
+
}): Promise<bigint>;
|
|
584
|
+
/**
|
|
585
|
+
* IVotes.balanceOf - wrapped voting-token balance (LCAIBallots on Ethereum
|
|
586
|
+
* mainnet, native LCAI via the NativeVotes precompile on LightChain
|
|
587
|
+
* mainnet). Returns wei.
|
|
588
|
+
*/
|
|
589
|
+
getBallotsBalance(voter: `0x${string}`): Promise<bigint>;
|
|
590
|
+
/**
|
|
591
|
+
* Address the voter has delegated to. Wraps `IVotes.delegates`. The zero
|
|
592
|
+
* address means the voter has not yet delegated, which is the OZ default
|
|
593
|
+
* (no voting power for self until you self-delegate).
|
|
594
|
+
*/
|
|
595
|
+
getDelegate(voter: `0x${string}`): Promise<`0x${string}`>;
|
|
596
|
+
/**
|
|
597
|
+
* Delegate voting weight to `delegatee` (use the voter's own address to
|
|
598
|
+
* self-delegate, which is the common first step before voting). Wallet
|
|
599
|
+
* required.
|
|
600
|
+
*/
|
|
601
|
+
delegate(delegatee: `0x${string}`): Promise<`0x${string}`>;
|
|
463
602
|
}
|
|
464
603
|
export {};
|
package/dist/dao.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
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
|
-
import { parseAbi } from "viem";
|
|
20
|
+
import { keccak256, parseAbi, toBytes } from "viem";
|
|
21
21
|
/**
|
|
22
22
|
* Confirmed deployment addresses. Each entry is what an SDK user passes to
|
|
23
23
|
* `new DAO(client, chainKey, walletClient?)`.
|
|
@@ -98,6 +98,9 @@ export const GOVERNOR_ABI = parseAbi([
|
|
|
98
98
|
"function proposalEta(uint256 proposalId) external view returns (uint256)",
|
|
99
99
|
"function getVotes(address account, uint256 timepoint) external view returns (uint256)",
|
|
100
100
|
"function hasVoted(uint256 proposalId, address account) external view returns (bool)",
|
|
101
|
+
"function cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) external returns (uint256)",
|
|
102
|
+
// Events - needed for recentProposals() event scan.
|
|
103
|
+
"event ProposalCreated(uint256 proposalId, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 voteStart, uint256 voteEnd, string description)",
|
|
101
104
|
]);
|
|
102
105
|
/** Minimal IVotes ABI for delegate + balance reads (LCAIBallots). */
|
|
103
106
|
export const VOTES_ABI = parseAbi([
|
|
@@ -190,6 +193,75 @@ export class DAO {
|
|
|
190
193
|
args: [timepoint],
|
|
191
194
|
});
|
|
192
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* List recent proposals on the governor by scanning `ProposalCreated`
|
|
198
|
+
* events. Scans back `lookbackBlocks` from the current head and returns
|
|
199
|
+
* up to `limit` proposals (newest first) with their current state.
|
|
200
|
+
*
|
|
201
|
+
* Ethereum mainnet RPCs cap a single `getLogs` to 10k blocks; the SDK
|
|
202
|
+
* chunks the range automatically. Default lookback is 300k blocks
|
|
203
|
+
* (~40 days) and default limit is 10.
|
|
204
|
+
*/
|
|
205
|
+
async recentProposals(opts = {}) {
|
|
206
|
+
if (!this.publicClient.getLogs || !this.publicClient.getBlockNumber) {
|
|
207
|
+
throw new Error("DAO.recentProposals: publicClient does not expose getLogs / getBlockNumber. Use a viem PublicClient.");
|
|
208
|
+
}
|
|
209
|
+
const lookback = BigInt(opts.lookbackBlocks ?? 300000);
|
|
210
|
+
const limit = Math.max(1, opts.limit ?? 10);
|
|
211
|
+
const head = await this.publicClient.getBlockNumber();
|
|
212
|
+
const start = head > lookback ? head - lookback : 0n;
|
|
213
|
+
const CHUNK = 10000n;
|
|
214
|
+
// ProposalCreated is item index that matches the parsed ABI entry.
|
|
215
|
+
const event = GOVERNOR_ABI.find((x) => x.type === "event" && x.name === "ProposalCreated");
|
|
216
|
+
if (!event)
|
|
217
|
+
throw new Error("DAO.recentProposals: ProposalCreated event missing from GOVERNOR_ABI");
|
|
218
|
+
// Fan out chunked log reads in parallel - same shape the public proxy
|
|
219
|
+
// endpoint uses, but driven entirely by the caller's RPC.
|
|
220
|
+
const ranges = [];
|
|
221
|
+
for (let from = start; from <= head; from += CHUNK) {
|
|
222
|
+
const to = from + CHUNK - 1n > head ? head : from + CHUNK - 1n;
|
|
223
|
+
ranges.push({ from, to });
|
|
224
|
+
}
|
|
225
|
+
const rows = [];
|
|
226
|
+
const settled = await Promise.allSettled(ranges.map((r) => this.publicClient.getLogs({
|
|
227
|
+
address: this.addresses.governor,
|
|
228
|
+
event,
|
|
229
|
+
fromBlock: r.from,
|
|
230
|
+
toBlock: r.to,
|
|
231
|
+
})));
|
|
232
|
+
for (const res of settled) {
|
|
233
|
+
if (res.status !== "fulfilled")
|
|
234
|
+
continue;
|
|
235
|
+
for (const log of res.value) {
|
|
236
|
+
const args = log.args ?? {};
|
|
237
|
+
const id = args.proposalId;
|
|
238
|
+
const proposer = args.proposer;
|
|
239
|
+
const description = args.description ?? "";
|
|
240
|
+
if (id == null || !proposer)
|
|
241
|
+
continue;
|
|
242
|
+
const title = description.split(/\r?\n/).map((s) => s.trim()).find(Boolean) ?? `Proposal #${id.toString()}`;
|
|
243
|
+
rows.push({
|
|
244
|
+
id,
|
|
245
|
+
proposer,
|
|
246
|
+
title: title.length > 140 ? title.slice(0, 137) + "..." : title,
|
|
247
|
+
// state filled in below in one batched call
|
|
248
|
+
state: 0,
|
|
249
|
+
stateLabel: "",
|
|
250
|
+
blockNumber: log.blockNumber ?? 0n,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Sort newest-first by id (uint256 monotonic in OZ Governor) and trim.
|
|
255
|
+
rows.sort((a, b) => (b.id > a.id ? 1 : b.id < a.id ? -1 : 0));
|
|
256
|
+
const trimmed = rows.slice(0, limit);
|
|
257
|
+
// Fetch the live state for each in parallel so the row is immediately usable.
|
|
258
|
+
const states = await Promise.all(trimmed.map((r) => this.state(r.id).catch(() => 0)));
|
|
259
|
+
return trimmed.map((r, i) => ({
|
|
260
|
+
...r,
|
|
261
|
+
state: states[i],
|
|
262
|
+
stateLabel: PROPOSAL_STATE_LABEL[states[i]] ?? "unknown",
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
193
265
|
// -------- Writes --------
|
|
194
266
|
/** Cast a For / Against / Abstain vote. Wallet must be the voter and have delegated their LCAI. */
|
|
195
267
|
castVote(proposalId, support, reason) {
|
|
@@ -247,4 +319,97 @@ export class DAO {
|
|
|
247
319
|
value: totalValue,
|
|
248
320
|
});
|
|
249
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Cancel a proposal. OZ Governor permits the proposer (and sometimes the
|
|
324
|
+
* Guardian / Timelock role) to cancel a Pending or Active proposal. Same
|
|
325
|
+
* `descriptionHash` you'd pass to `queue` / `execute`.
|
|
326
|
+
*/
|
|
327
|
+
cancel(args) {
|
|
328
|
+
if (!this.walletClient)
|
|
329
|
+
throw new Error("DAO: no wallet client; pass one to the DAO constructor for writes");
|
|
330
|
+
return this.walletClient.writeContract({
|
|
331
|
+
address: this.addresses.governor,
|
|
332
|
+
abi: GOVERNOR_ABI,
|
|
333
|
+
functionName: "cancel",
|
|
334
|
+
args: [args.targets, args.values, args.calldatas, args.descriptionHash],
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
// -------- Helpers (pure, no chain reads) --------
|
|
338
|
+
/**
|
|
339
|
+
* keccak256 of the raw description string. The Governor stores proposals
|
|
340
|
+
* keyed by `(targets, values, calldatas, descriptionHash)` rather than
|
|
341
|
+
* the description itself - this is the same hash the OZ Governor computes
|
|
342
|
+
* internally. Pass it to `queue`, `execute`, `cancel`, `hashProposal`.
|
|
343
|
+
*/
|
|
344
|
+
descriptionHash(description) {
|
|
345
|
+
return keccak256(toBytes(description));
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Predict a proposal id BEFORE submitting. Mirrors `Governor.hashProposal`
|
|
349
|
+
* on chain so you can compute the id deterministically (the proposer can
|
|
350
|
+
* persist it ahead of time, drop it into a UI, or sanity-check that a
|
|
351
|
+
* proposal hasn't already been submitted).
|
|
352
|
+
*/
|
|
353
|
+
async hashProposal(args) {
|
|
354
|
+
const descHash = typeof args.description === "string" && args.description.startsWith("0x") && args.description.length === 66
|
|
355
|
+
? args.description
|
|
356
|
+
: keccak256(toBytes(args.description));
|
|
357
|
+
return this.publicClient.readContract({
|
|
358
|
+
address: this.addresses.governor,
|
|
359
|
+
abi: GOVERNOR_ABI,
|
|
360
|
+
functionName: "hashProposal",
|
|
361
|
+
args: [args.targets, args.values, args.calldatas, descHash],
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
// -------- IVotes helpers (LCAIBallots wrapped token) --------
|
|
365
|
+
/**
|
|
366
|
+
* IVotes.balanceOf - wrapped voting-token balance (LCAIBallots on Ethereum
|
|
367
|
+
* mainnet, native LCAI via the NativeVotes precompile on LightChain
|
|
368
|
+
* mainnet). Returns wei.
|
|
369
|
+
*/
|
|
370
|
+
getBallotsBalance(voter) {
|
|
371
|
+
const ballots = this.addresses.ballots;
|
|
372
|
+
if (!ballots)
|
|
373
|
+
throw new Error("DAO.getBallotsBalance: this chain has no Ballots address");
|
|
374
|
+
return this.publicClient.readContract({
|
|
375
|
+
address: ballots,
|
|
376
|
+
abi: VOTES_ABI,
|
|
377
|
+
functionName: "balanceOf",
|
|
378
|
+
args: [voter],
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Address the voter has delegated to. Wraps `IVotes.delegates`. The zero
|
|
383
|
+
* address means the voter has not yet delegated, which is the OZ default
|
|
384
|
+
* (no voting power for self until you self-delegate).
|
|
385
|
+
*/
|
|
386
|
+
getDelegate(voter) {
|
|
387
|
+
const ballots = this.addresses.ballots;
|
|
388
|
+
if (!ballots)
|
|
389
|
+
throw new Error("DAO.getDelegate: this chain has no Ballots address");
|
|
390
|
+
return this.publicClient.readContract({
|
|
391
|
+
address: ballots,
|
|
392
|
+
abi: VOTES_ABI,
|
|
393
|
+
functionName: "delegates",
|
|
394
|
+
args: [voter],
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Delegate voting weight to `delegatee` (use the voter's own address to
|
|
399
|
+
* self-delegate, which is the common first step before voting). Wallet
|
|
400
|
+
* required.
|
|
401
|
+
*/
|
|
402
|
+
delegate(delegatee) {
|
|
403
|
+
if (!this.walletClient)
|
|
404
|
+
throw new Error("DAO: no wallet client; pass one to the DAO constructor for writes");
|
|
405
|
+
const ballots = this.addresses.ballots;
|
|
406
|
+
if (!ballots)
|
|
407
|
+
throw new Error("DAO.delegate: this chain has no Ballots address");
|
|
408
|
+
return this.walletClient.writeContract({
|
|
409
|
+
address: ballots,
|
|
410
|
+
abi: VOTES_ABI,
|
|
411
|
+
functionName: "delegate",
|
|
412
|
+
args: [delegatee],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
250
415
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS } from "./networks.js";
|
|
2
|
-
import { fromWei } from "./subgraph.js";
|
|
2
|
+
import { fromWei, resolveJobTransactions } 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";
|
|
@@ -13,7 +13,7 @@ import { OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI,
|
|
|
13
13
|
import { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker } from "./errors.js";
|
|
14
14
|
import { GatewayClient, GatewayHttpError } from "./gateway.js";
|
|
15
15
|
import * as crypto from "./crypto.js";
|
|
16
|
-
import type { NetworkId, NetworkConfig, Worker, Job, ModelInfo, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics } from "./types.js";
|
|
16
|
+
import type { NetworkId, NetworkConfig, Worker, Job, JobTransactions, ModelInfo, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics } from "./types.js";
|
|
17
17
|
/**
|
|
18
18
|
* Read-only client for a LightChain AI network. Pure reads from the public indexer
|
|
19
19
|
* and the chain; no keys, no writes. Independent, community-built.
|
|
@@ -59,7 +59,9 @@ export declare class LightNode {
|
|
|
59
59
|
* builder-friendly label; `raw` is the indexer's literal state string.
|
|
60
60
|
* Null when the indexer has never seen the job (still pending propagation).
|
|
61
61
|
*/
|
|
62
|
-
getJobStatus(jobId: string | bigint
|
|
62
|
+
getJobStatus(jobId: string | bigint, opts?: {
|
|
63
|
+
withTransactions?: boolean;
|
|
64
|
+
}): Promise<{
|
|
63
65
|
id: string;
|
|
64
66
|
raw: string;
|
|
65
67
|
category: "submitted" | "in-flight" | "completed" | "stalled" | "disputed" | "resolved" | "unknown";
|
|
@@ -69,7 +71,27 @@ export declare class LightNode {
|
|
|
69
71
|
completedAt: number | null;
|
|
70
72
|
workerShareLcai: number;
|
|
71
73
|
refundable: boolean;
|
|
74
|
+
/** Block numbers as the indexer recorded them. Null until indexer sees the event. */
|
|
75
|
+
submitBlock: number | null;
|
|
76
|
+
completionBlock: number | null;
|
|
77
|
+
/**
|
|
78
|
+
* Tx hashes for submitJob + jobCompleted, only resolved when
|
|
79
|
+
* `withTransactions: true`. Each hash deep-links to Lightscan via
|
|
80
|
+
* {@link Network.explorerTxUrl}. Costs one eth_getLogs RPC call per
|
|
81
|
+
* transaction (max two per job); skip the flag if you don't need them.
|
|
82
|
+
*/
|
|
83
|
+
submitTx: `0x${string}` | null;
|
|
84
|
+
completionTx: `0x${string}` | null;
|
|
72
85
|
} | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Build a Lightscan URL for an arbitrary address or tx hash on this
|
|
88
|
+
* network. Useful for surfacing deep-links in builder UIs without
|
|
89
|
+
* each consumer needing to know which explorer corresponds to which
|
|
90
|
+
* chain.
|
|
91
|
+
*/
|
|
92
|
+
explorerAddressUrl(address: string): string;
|
|
93
|
+
explorerTxUrl(hash: string): string;
|
|
94
|
+
explorerBlockUrl(block: number | bigint): string;
|
|
73
95
|
/** keccak256 of a model tag (its on-chain + indexer id). */
|
|
74
96
|
modelId(tag: string): `0x${string}`;
|
|
75
97
|
/** On-chain inference fee for a model, in whole LCAI (what submitJob must be paid). */
|
|
@@ -91,8 +113,8 @@ export declare class LightNode {
|
|
|
91
113
|
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
92
114
|
* may pin an older minor than the local install command suggests).
|
|
93
115
|
*/
|
|
94
|
-
export declare const SDK_VERSION = "0.7.
|
|
95
|
-
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, WorkerOperator, WORKER_REGISTRY_ABI, JOB_REGISTRY_OPERATOR_ABI, AI_CONFIG_ABI, JOB_STATE, decodeWorkerError, WorkerOpError, isWorkerOpError, };
|
|
116
|
+
export declare const SDK_VERSION = "0.7.3";
|
|
117
|
+
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, resolveJobTransactions, 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, };
|
|
96
118
|
export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
|
|
97
119
|
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
|
|
98
120
|
export type { ChatRole, ChatMessage, ConversationOptions, ConversationSendResult } from "./chat.js";
|
|
@@ -100,7 +122,7 @@ export type { BatchPrompt, BatchResult, RunInferenceBatchArgs } from "./batch.js
|
|
|
100
122
|
export type { AgentTool, AgentStep, AgentOptions, AgentRunResult } from "./agent.js";
|
|
101
123
|
export type { WorkerPreflightArgs, WorkerPreflightResult, WorkerWatchOptions, WorkerEventKind, WorkerEvent, WorkerWatchHandle } from "./worker.js";
|
|
102
124
|
export type { BridgeChain, BridgeEndpoints, BridgeTransferArgs } from "./bridge.js";
|
|
103
|
-
export type { DaoChain, DaoAddresses, ProposalSummary, DaoConfig } from "./dao.js";
|
|
125
|
+
export type { DaoChain, DaoAddresses, ProposalSummary, ProposalRow, DaoConfig } from "./dao.js";
|
|
104
126
|
export type { BaseModel, ModelVariant, AccessTier, AccessPolicy, Benchmark, OnchainModelRegistryOptions } from "./onchain-models.js";
|
|
105
127
|
export type { MinimalWalletClient, MinimalPublicClient, WorkerOperatorOpts, WorkerProtocolConfig, WorkerStatus, DeregisterReadiness, StuckJob, EarningsBreakdown, OnchainJob, JobState, DecodedWorkerError, } from "./worker-operator.js";
|
|
106
|
-
export type { NetworkId, NetworkConfig, Worker, Job, ModelInfo, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics };
|
|
128
|
+
export type { NetworkId, NetworkConfig, Worker, Job, JobTransactions, ModelInfo, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS } from "./networks.js";
|
|
2
|
-
import { fetchWorker, fetchWorkerJobs, fetchRecentJobs, fetchJob, fetchModels, fetchWorkers, summarize, fromWei, } from "./subgraph.js";
|
|
2
|
+
import { fetchWorker, fetchWorkerJobs, fetchRecentJobs, fetchJob, fetchModels, fetchWorkers, summarize, fromWei, resolveJobTransactions, } from "./subgraph.js";
|
|
3
3
|
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";
|
|
@@ -86,7 +86,7 @@ export class LightNode {
|
|
|
86
86
|
* builder-friendly label; `raw` is the indexer's literal state string.
|
|
87
87
|
* Null when the indexer has never seen the job (still pending propagation).
|
|
88
88
|
*/
|
|
89
|
-
async getJobStatus(jobId) {
|
|
89
|
+
async getJobStatus(jobId, opts = {}) {
|
|
90
90
|
const j = await fetchJob(this.network, jobId);
|
|
91
91
|
if (!j)
|
|
92
92
|
return null;
|
|
@@ -110,6 +110,11 @@ export class LightNode {
|
|
|
110
110
|
// own timeout/dispute pipeline reclaims the fee; this flag is the SDK's
|
|
111
111
|
// builder-facing hint that the on-chain refund call is the right path.
|
|
112
112
|
const refundable = category === "stalled" || category === "disputed";
|
|
113
|
+
// Tx hashes need a second RPC roundtrip. Opt in only - the historical
|
|
114
|
+
// shape of getJobStatus stays pure-subgraph for callers who don't ask.
|
|
115
|
+
const txs = opts.withTransactions
|
|
116
|
+
? await resolveJobTransactions(this.network, j.id, { job: j })
|
|
117
|
+
: { submit: null, completion: null };
|
|
113
118
|
return {
|
|
114
119
|
id: j.id,
|
|
115
120
|
raw: state,
|
|
@@ -120,8 +125,27 @@ export class LightNode {
|
|
|
120
125
|
completedAt: j.completed_at ?? null,
|
|
121
126
|
workerShareLcai: fromWei(j.worker_share),
|
|
122
127
|
refundable,
|
|
128
|
+
submitBlock: j.submit_block_number ?? null,
|
|
129
|
+
completionBlock: j.completion_block_number && j.completion_block_number > 0 ? j.completion_block_number : null,
|
|
130
|
+
submitTx: txs.submit,
|
|
131
|
+
completionTx: txs.completion,
|
|
123
132
|
};
|
|
124
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Build a Lightscan URL for an arbitrary address or tx hash on this
|
|
136
|
+
* network. Useful for surfacing deep-links in builder UIs without
|
|
137
|
+
* each consumer needing to know which explorer corresponds to which
|
|
138
|
+
* chain.
|
|
139
|
+
*/
|
|
140
|
+
explorerAddressUrl(address) {
|
|
141
|
+
return `${this.network.explorer}/address/${address}`;
|
|
142
|
+
}
|
|
143
|
+
explorerTxUrl(hash) {
|
|
144
|
+
return `${this.network.explorer}/tx/${hash}`;
|
|
145
|
+
}
|
|
146
|
+
explorerBlockUrl(block) {
|
|
147
|
+
return `${this.network.explorer}/block/${block.toString()}`;
|
|
148
|
+
}
|
|
125
149
|
/** keccak256 of a model tag (its on-chain + indexer id). */
|
|
126
150
|
modelId(tag) {
|
|
127
151
|
return computeModelId(tag);
|
|
@@ -146,8 +170,11 @@ export class LightNode {
|
|
|
146
170
|
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
147
171
|
* may pin an older minor than the local install command suggests).
|
|
148
172
|
*/
|
|
149
|
-
export const SDK_VERSION = "0.7.
|
|
150
|
-
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei,
|
|
173
|
+
export const SDK_VERSION = "0.7.3";
|
|
174
|
+
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei,
|
|
175
|
+
// v0.7.3 per-job transaction-hash resolver (lifts the upstream
|
|
176
|
+
// subgraph's "block-only" Job entity to a deep-linkable Job + tx pair).
|
|
177
|
+
resolveJobTransactions, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost,
|
|
151
178
|
// v0.3 inference-submit surface (BETA - see README "Submitting inference").
|
|
152
179
|
GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto,
|
|
153
180
|
// v0.4 high-level orchestrator: one call, full flow.
|
package/dist/inference.d.ts
CHANGED
|
@@ -57,12 +57,12 @@ export interface SessionPreparation {
|
|
|
57
57
|
* Parameter names match the canonical on-chain ABI (paramsHash,
|
|
58
58
|
* ephemeralPubKey, initState) verified live in the LightChain inference
|
|
59
59
|
* integration guide. The slot mapping is:
|
|
60
|
-
* - paramsHash
|
|
61
|
-
* - worker
|
|
62
|
-
* - encWorkerKey
|
|
63
|
-
* - ephemeralPubKey
|
|
64
|
-
* - initState
|
|
65
|
-
* - expiry
|
|
60
|
+
* - paramsHash from keccak256(model tag)
|
|
61
|
+
* - worker from prepared.worker
|
|
62
|
+
* - encWorkerKey from hex(encWorker) // ECDH-wrap for the worker
|
|
63
|
+
* - ephemeralPubKey from hex(encDisputer) // ECDH-wrap for the disputer
|
|
64
|
+
* - initState from prepared.signature // dispatcher EIP-712 signature
|
|
65
|
+
* - expiry from prepared.expiry
|
|
66
66
|
*/
|
|
67
67
|
createSessionArgs: {
|
|
68
68
|
paramsHash: `0x${string}`;
|
package/dist/subgraph.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicClient } from "viem";
|
|
2
|
+
import type { NetworkConfig, Worker, Job, JobTransactions, ModelInfo, NetworkStats } from "./types.js";
|
|
2
3
|
/** Convert a wei string to a number of whole tokens (18 decimals). */
|
|
3
4
|
export declare function fromWei(wei?: string): number;
|
|
4
5
|
export declare function fetchWorker(cfg: NetworkConfig, address: string): Promise<Worker | null>;
|
|
@@ -7,6 +8,26 @@ export declare function fetchJob(cfg: NetworkConfig, jobId: string | bigint): Pr
|
|
|
7
8
|
export declare function fetchWorkerJobs(cfg: NetworkConfig, address: string, first?: number): Promise<Job[]>;
|
|
8
9
|
/** Recent jobs across the whole network (not one worker), for analytics. */
|
|
9
10
|
export declare function fetchRecentJobs(cfg: NetworkConfig, first?: number): Promise<Job[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a job's tx hashes (submitJob + jobCompleted) by re-scanning the
|
|
13
|
+
* exact blocks the indexer recorded. The upstream subgraph entity stores
|
|
14
|
+
* `submit_block_number` + `completion_block_number` but NOT the
|
|
15
|
+
* transactionHash. Re-deriving them needs one RPC eth_getLogs call per
|
|
16
|
+
* block: at most two calls per job, and each is tightly bounded
|
|
17
|
+
* (single-block range, single contract, single topic, single jobId match).
|
|
18
|
+
*
|
|
19
|
+
* `submit` is null only if the indexer hasn't seen the job yet (we couldn't
|
|
20
|
+
* resolve the block). `completion` is null until the worker emits
|
|
21
|
+
* JobCompleted (still-in-flight or stalled jobs).
|
|
22
|
+
*
|
|
23
|
+
* Pass a `publicClient` to reuse an existing RPC connection. Without one
|
|
24
|
+
* the function builds a transient client from `cfg.rpc` - simple but spends
|
|
25
|
+
* a TCP handshake per call.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveJobTransactions(cfg: NetworkConfig, jobId: string | bigint, opts?: {
|
|
28
|
+
publicClient?: PublicClient;
|
|
29
|
+
job?: Job | null;
|
|
30
|
+
}): Promise<JobTransactions>;
|
|
10
31
|
export declare function fetchModels(cfg: NetworkConfig): Promise<ModelInfo[]>;
|
|
11
32
|
export declare function fetchWorkers(cfg: NetworkConfig, first?: number): Promise<Worker[]>;
|
|
12
33
|
export declare function summarize(workers: Worker[], models: ModelInfo[]): NetworkStats;
|
package/dist/subgraph.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { getAddress } from "viem";
|
|
1
|
+
import { createPublicClient, getAddress, http, toHex, pad } from "viem";
|
|
2
|
+
// keccak256("JobSubmitted(uint256,uint256,address)")
|
|
3
|
+
const JOB_SUBMITTED_TOPIC = "0xfb47370368875d7490803c5653d9496d0a3c5e1b49a17f013ec37abd9d86d356";
|
|
4
|
+
// keccak256("JobCompleted(uint256,address,bytes32,bytes32)")
|
|
5
|
+
const JOB_COMPLETED_TOPIC = "0xdb545db74bae046337ed01971cf61569fd1a1460ff8ed511ab19ceaac1326377";
|
|
2
6
|
const TIMEOUT_MS = 12000;
|
|
3
7
|
async function gql(url, query) {
|
|
4
8
|
const ctrl = new AbortController();
|
|
@@ -59,18 +63,85 @@ export async function fetchWorker(cfg, address) {
|
|
|
59
63
|
/** Fetch one job by its on-chain id. Null when the indexer has never seen it. */
|
|
60
64
|
export async function fetchJob(cfg, jobId) {
|
|
61
65
|
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 } }`);
|
|
66
|
+
const data = await gql(cfg.subgraph, `{ job(id:"${id}") { id state model_id worker submitted_at ack_at completed_at worker_share submit_block_number completion_block_number } }`);
|
|
63
67
|
return data.job ?? null;
|
|
64
68
|
}
|
|
65
69
|
export async function fetchWorkerJobs(cfg, address, first = 20) {
|
|
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 } }`);
|
|
70
|
+
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 submit_block_number completion_block_number } }`);
|
|
67
71
|
return data.jobs ?? [];
|
|
68
72
|
}
|
|
69
73
|
/** Recent jobs across the whole network (not one worker), for analytics. */
|
|
70
74
|
export async function fetchRecentJobs(cfg, first = 1000) {
|
|
71
|
-
const data = await gql(cfg.subgraph, `{ jobs(first:${first}, orderBy:submitted_at, orderDirection:desc) { id state model_id worker ack_at completed_at worker_share } }`);
|
|
75
|
+
const data = await gql(cfg.subgraph, `{ jobs(first:${first}, orderBy:submitted_at, orderDirection:desc) { id state model_id worker ack_at completed_at worker_share submit_block_number completion_block_number } }`);
|
|
72
76
|
return data.jobs ?? [];
|
|
73
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve a job's tx hashes (submitJob + jobCompleted) by re-scanning the
|
|
80
|
+
* exact blocks the indexer recorded. The upstream subgraph entity stores
|
|
81
|
+
* `submit_block_number` + `completion_block_number` but NOT the
|
|
82
|
+
* transactionHash. Re-deriving them needs one RPC eth_getLogs call per
|
|
83
|
+
* block: at most two calls per job, and each is tightly bounded
|
|
84
|
+
* (single-block range, single contract, single topic, single jobId match).
|
|
85
|
+
*
|
|
86
|
+
* `submit` is null only if the indexer hasn't seen the job yet (we couldn't
|
|
87
|
+
* resolve the block). `completion` is null until the worker emits
|
|
88
|
+
* JobCompleted (still-in-flight or stalled jobs).
|
|
89
|
+
*
|
|
90
|
+
* Pass a `publicClient` to reuse an existing RPC connection. Without one
|
|
91
|
+
* the function builds a transient client from `cfg.rpc` - simple but spends
|
|
92
|
+
* a TCP handshake per call.
|
|
93
|
+
*/
|
|
94
|
+
export async function resolveJobTransactions(cfg, jobId, opts = {}) {
|
|
95
|
+
const id = typeof jobId === "bigint" ? jobId : BigInt(jobId);
|
|
96
|
+
const job = opts.job !== undefined ? opts.job : await fetchJob(cfg, id);
|
|
97
|
+
if (!job)
|
|
98
|
+
return { submit: null, completion: null };
|
|
99
|
+
const client = opts.publicClient ??
|
|
100
|
+
createPublicClient({ transport: http(cfg.rpc) });
|
|
101
|
+
// Topic 1 is the indexed jobId, padded to 32 bytes. The subgraph keys
|
|
102
|
+
// entries by exact on-chain jobId so this match is unambiguous.
|
|
103
|
+
const jobTopic = pad(toHex(id), { size: 32 });
|
|
104
|
+
const submitBlock = job.submit_block_number ? BigInt(job.submit_block_number) : null;
|
|
105
|
+
const completionBlock = job.completion_block_number ? BigInt(job.completion_block_number) : null;
|
|
106
|
+
// Address the contract that emits Job* events. JobRegistry handles both.
|
|
107
|
+
const address = cfg.jobRegistry;
|
|
108
|
+
if (!address)
|
|
109
|
+
return { submit: null, completion: null };
|
|
110
|
+
const [submitTx, completionTx] = await Promise.all([
|
|
111
|
+
submitBlock !== null
|
|
112
|
+
? fetchTxHashForJobEvent(client, address, JOB_SUBMITTED_TOPIC, jobTopic, submitBlock)
|
|
113
|
+
: Promise.resolve(null),
|
|
114
|
+
completionBlock !== null && completionBlock > 0n
|
|
115
|
+
? fetchTxHashForJobEvent(client, address, JOB_COMPLETED_TOPIC, jobTopic, completionBlock)
|
|
116
|
+
: Promise.resolve(null),
|
|
117
|
+
]);
|
|
118
|
+
return { submit: submitTx, completion: completionTx };
|
|
119
|
+
}
|
|
120
|
+
async function fetchTxHashForJobEvent(client, address, eventTopic, jobTopic, block) {
|
|
121
|
+
// Bypass viem's typed getLogs - it rejects the [topic0, topic1] tuple
|
|
122
|
+
// without a parsed `event` ABI, and we don't want to ship the full ABI
|
|
123
|
+
// through this path. The raw eth_getLogs at the transport layer is what
|
|
124
|
+
// we need: single block, single contract, two-topic match (event sig +
|
|
125
|
+
// indexed jobId), so the response is at most one log.
|
|
126
|
+
try {
|
|
127
|
+
const blockHex = `0x${block.toString(16)}`;
|
|
128
|
+
const logs = (await client.request({
|
|
129
|
+
method: "eth_getLogs",
|
|
130
|
+
params: [
|
|
131
|
+
{
|
|
132
|
+
address,
|
|
133
|
+
fromBlock: blockHex,
|
|
134
|
+
toBlock: blockHex,
|
|
135
|
+
topics: [eventTopic, jobTopic],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}));
|
|
139
|
+
return logs[0]?.transactionHash ?? null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
74
145
|
export async function fetchModels(cfg) {
|
|
75
146
|
const data = await gql(cfg.subgraph, `{ modelinfos { id name fee max_output_tokens is_whitelisted is_enabled } }`);
|
|
76
147
|
return data.modelinfos ?? [];
|
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,17 @@ export interface Job {
|
|
|
43
43
|
ack_at?: number;
|
|
44
44
|
completed_at?: number;
|
|
45
45
|
worker_share?: string;
|
|
46
|
+
submit_block_number?: number;
|
|
47
|
+
completion_block_number?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Per-job transaction hashes. Populated lazily by the SDK via
|
|
51
|
+
* resolveJobTransactions(); not present on the raw subgraph Job entity.
|
|
52
|
+
* `completion` is null until the worker emits JobCompleted.
|
|
53
|
+
*/
|
|
54
|
+
export interface JobTransactions {
|
|
55
|
+
submit: `0x${string}` | null;
|
|
56
|
+
completion: `0x${string}` | null;
|
|
46
57
|
}
|
|
47
58
|
export interface ModelInfo {
|
|
48
59
|
id: string;
|
|
@@ -236,7 +236,7 @@ export declare class WorkerOperator {
|
|
|
236
236
|
* Clear one stuck (acknowledged-but-never-completed, past-deadline) job.
|
|
237
237
|
* Permissionless on-chain - the worker itself may call it.
|
|
238
238
|
*
|
|
239
|
-
*
|
|
239
|
+
* MAINNET SLASH: this finalizes the job as TimedOut, which realizes the
|
|
240
240
|
* completion-timeout slash on the worker's stake (mainnet:
|
|
241
241
|
* `config().slashBps.completionTimeout` = 5% of stake per job at the time of
|
|
242
242
|
* writing; TESTNET has slashing disabled, so it's free there). This is the
|
|
@@ -283,8 +283,8 @@ export declare class WorkerOperator {
|
|
|
283
283
|
/** Deregister - releases stake to the wallet. Reverts (ActiveJobsExist) if any in-flight job remains. */
|
|
284
284
|
deregister(): Promise<`0x${string}`>;
|
|
285
285
|
/**
|
|
286
|
-
* The flagship rescue: clear stuck jobs
|
|
287
|
-
* withdraw earnings
|
|
286
|
+
* The flagship rescue: clear stuck jobs then release any settled completed jobs +
|
|
287
|
+
* withdraw earnings then deregister. The one flow no official tool provides.
|
|
288
288
|
* Pass the worker's known job IDs (from the subgraph). Returns every tx done.
|
|
289
289
|
*/
|
|
290
290
|
unstickAndDeregister(candidateJobIds: Array<bigint | number>): Promise<{
|
package/dist/worker-operator.js
CHANGED
|
@@ -431,7 +431,7 @@ export class WorkerOperator {
|
|
|
431
431
|
* Clear one stuck (acknowledged-but-never-completed, past-deadline) job.
|
|
432
432
|
* Permissionless on-chain - the worker itself may call it.
|
|
433
433
|
*
|
|
434
|
-
*
|
|
434
|
+
* MAINNET SLASH: this finalizes the job as TimedOut, which realizes the
|
|
435
435
|
* completion-timeout slash on the worker's stake (mainnet:
|
|
436
436
|
* `config().slashBps.completionTimeout` = 5% of stake per job at the time of
|
|
437
437
|
* writing; TESTNET has slashing disabled, so it's free there). This is the
|
|
@@ -526,8 +526,8 @@ export class WorkerOperator {
|
|
|
526
526
|
return this.send("deregister", this.workerReg, WORKER_REGISTRY_ABI_PARSED, "deregisterWorker", []);
|
|
527
527
|
}
|
|
528
528
|
/**
|
|
529
|
-
* The flagship rescue: clear stuck jobs
|
|
530
|
-
* withdraw earnings
|
|
529
|
+
* The flagship rescue: clear stuck jobs then release any settled completed jobs +
|
|
530
|
+
* withdraw earnings then deregister. The one flow no official tool provides.
|
|
531
531
|
* Pass the worker's known job IDs (from the subgraph). Returns every tx done.
|
|
532
532
|
*/
|
|
533
533
|
async unstickAndDeregister(candidateJobIds) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
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",
|