lightnode-sdk 0.7.9 → 0.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * SIWE sign-in against LightChain's consumer-api. The gateway accepts the
3
+ * JWT minted here as its `Authorization: Bearer <token>` header, so this
4
+ * unlocks every protected gateway endpoint (selectSession, prepareSession,
5
+ * uploadBlob, getSessionToken).
6
+ *
7
+ * Reverse-engineered from lightchain-protocol/lcai-chat-v2 (the official
8
+ * chat reference app). The same two endpoints work on mainnet and testnet
9
+ * - only the host differs (see {@link NetworkConfig.consumerApi}).
10
+ *
11
+ * 1. `GET /api/auth/challenge?address=0x...`
12
+ * -> { nonce: string, message: string }
13
+ * The server pre-builds the SIWE message; clients sign it verbatim.
14
+ *
15
+ * 2. `POST /api/auth/verify` body { message, signature }
16
+ * -> { success, address, token, user }
17
+ * `token` is the JWT bearer for the gateway.
18
+ *
19
+ * Returns a {@link SiweSession} that the SDK's GatewayClient consumes as
20
+ * its `bearer` source. The session also exposes `expiresAt` so consumers
21
+ * can refresh proactively.
22
+ */
23
+ import type { NetworkId, NetworkConfig } from "./types.js";
24
+ /**
25
+ * Minimal subset of viem's `WalletClient` we need to sign the SIWE
26
+ * message. Accepts viem's `WalletClient`, wagmi's `useWalletClient().data`,
27
+ * or any structurally compatible object with `account.address` and
28
+ * `signMessage`.
29
+ */
30
+ export interface SiweWalletClient {
31
+ account?: {
32
+ address?: `0x${string}`;
33
+ };
34
+ signMessage(args: {
35
+ account: `0x${string}` | {
36
+ address: `0x${string}`;
37
+ };
38
+ message: string;
39
+ }): Promise<`0x${string}`>;
40
+ }
41
+ export interface SiweChallenge {
42
+ nonce: string;
43
+ message: string;
44
+ }
45
+ export interface SiweVerifyResult {
46
+ success: boolean;
47
+ address: `0x${string}`;
48
+ token: string;
49
+ user?: {
50
+ id: string;
51
+ username?: string | null;
52
+ walletAddress: `0x${string}`;
53
+ type: string;
54
+ };
55
+ }
56
+ export interface SiweSession {
57
+ /** ES256K JWT bearer accepted by the worker-gateway. */
58
+ token: string;
59
+ /** Wallet that signed the message. */
60
+ address: `0x${string}`;
61
+ /** Network the session is bound to. */
62
+ network: NetworkId;
63
+ /** SIWE message expiry as a unix-ms timestamp (null when not parseable). */
64
+ expiresAt: number | null;
65
+ /**
66
+ * Drop-in `BearerSource` for `new GatewayClient({ bearer })`. Returns
67
+ * the same JWT for every call; refresh by calling {@link siweSignIn}
68
+ * again before {@link expiresAt} elapses.
69
+ */
70
+ bearer: () => string;
71
+ }
72
+ /**
73
+ * Fetch a SIWE challenge for `address` from the network's consumer-api.
74
+ * The returned `message` is the canonical SIWE string the wallet must
75
+ * sign verbatim - do NOT reformat or strip whitespace.
76
+ */
77
+ export declare function siweChallenge(network: NetworkId | NetworkConfig, address: `0x${string}`, opts?: {
78
+ signal?: AbortSignal;
79
+ baseUrl?: string;
80
+ }): Promise<SiweChallenge>;
81
+ /**
82
+ * Verify a signed SIWE message and mint a JWT bearer.
83
+ */
84
+ export declare function siweVerify(network: NetworkId | NetworkConfig, args: {
85
+ message: string;
86
+ signature: `0x${string}`;
87
+ }, opts?: {
88
+ signal?: AbortSignal;
89
+ baseUrl?: string;
90
+ }): Promise<SiweVerifyResult>;
91
+ /**
92
+ * End-to-end SIWE sign-in: challenge -> sign -> verify -> JWT.
93
+ *
94
+ * ```ts
95
+ * import { siweSignIn, GatewayClient } from "lightnode-sdk";
96
+ * // browser: walletClient from wagmi's useWalletClient().data
97
+ * const session = await siweSignIn(walletClient, "testnet");
98
+ * const gateway = new GatewayClient({ network: "testnet", bearer: session.bearer });
99
+ * // ... use the gateway normally (runInference, selectSession, etc.)
100
+ * ```
101
+ *
102
+ * Pass `address` explicitly when the wallet client cannot expose its own
103
+ * account (some viem clients are intentionally accountless).
104
+ */
105
+ export declare function siweSignIn(walletClient: SiweWalletClient, network: NetworkId | NetworkConfig, opts?: {
106
+ address?: `0x${string}`;
107
+ signal?: AbortSignal;
108
+ baseUrl?: string;
109
+ }): Promise<SiweSession>;
package/dist/auth.js ADDED
@@ -0,0 +1,170 @@
1
+ /**
2
+ * SIWE sign-in against LightChain's consumer-api. The gateway accepts the
3
+ * JWT minted here as its `Authorization: Bearer <token>` header, so this
4
+ * unlocks every protected gateway endpoint (selectSession, prepareSession,
5
+ * uploadBlob, getSessionToken).
6
+ *
7
+ * Reverse-engineered from lightchain-protocol/lcai-chat-v2 (the official
8
+ * chat reference app). The same two endpoints work on mainnet and testnet
9
+ * - only the host differs (see {@link NetworkConfig.consumerApi}).
10
+ *
11
+ * 1. `GET /api/auth/challenge?address=0x...`
12
+ * -> { nonce: string, message: string }
13
+ * The server pre-builds the SIWE message; clients sign it verbatim.
14
+ *
15
+ * 2. `POST /api/auth/verify` body { message, signature }
16
+ * -> { success, address, token, user }
17
+ * `token` is the JWT bearer for the gateway.
18
+ *
19
+ * Returns a {@link SiweSession} that the SDK's GatewayClient consumes as
20
+ * its `bearer` source. The session also exposes `expiresAt` so consumers
21
+ * can refresh proactively.
22
+ */
23
+ import { NETWORKS } from "./networks.js";
24
+ // Same logic as gateway.ts: in a browser-like runtime the consumer-api's
25
+ // CORS allowlist excludes most origins, so we route the SIWE handshake
26
+ // through lightnode.app's public pass-through proxy. The proxy preserves
27
+ // the upstream `/api/auth/{challenge,verify}` shape, so callers do not
28
+ // have to special-case it.
29
+ const PROXY_HOSTS = {
30
+ mainnet: "https://lightnode.app/api/gw/mainnet",
31
+ testnet: "https://lightnode.app/api/gw/testnet",
32
+ };
33
+ function looksLikeBrowserFetch() {
34
+ if (typeof window !== "undefined" && typeof document !== "undefined")
35
+ return true;
36
+ const wc = globalThis.process?.versions?.webcontainer;
37
+ return Boolean(wc);
38
+ }
39
+ function defaultSiweBase(cfg) {
40
+ if (looksLikeBrowserFetch())
41
+ return PROXY_HOSTS[cfg.id];
42
+ return cfg.consumerApi ?? "";
43
+ }
44
+ const REQUEST_TIMEOUT_MS = 15000;
45
+ function resolveNetwork(network) {
46
+ if (typeof network === "string") {
47
+ const cfg = NETWORKS[network];
48
+ if (!cfg)
49
+ throw new Error(`siweSignIn: unknown network "${network}"`);
50
+ return cfg;
51
+ }
52
+ return network;
53
+ }
54
+ async function httpJson(url, init) {
55
+ const ctrl = new AbortController();
56
+ const onAbort = () => ctrl.abort();
57
+ init.signal?.addEventListener("abort", onAbort);
58
+ const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
59
+ try {
60
+ const res = await fetch(url, {
61
+ method: init.method,
62
+ headers: { Accept: "application/json", ...(init.body ? { "content-type": "application/json" } : {}) },
63
+ body: init.body ? JSON.stringify(init.body) : undefined,
64
+ signal: ctrl.signal,
65
+ });
66
+ const text = await res.text();
67
+ if (!res.ok) {
68
+ // Surface the server-side validation message when present.
69
+ const detail = text.length < 400 ? text : `${text.slice(0, 380)}...`;
70
+ throw new Error(`siwe ${url}: ${res.status} ${detail}`);
71
+ }
72
+ try {
73
+ return JSON.parse(text);
74
+ }
75
+ catch {
76
+ throw new Error(`siwe ${url}: non-JSON response (${text.slice(0, 120)})`);
77
+ }
78
+ }
79
+ finally {
80
+ clearTimeout(timer);
81
+ init.signal?.removeEventListener("abort", onAbort);
82
+ }
83
+ }
84
+ /**
85
+ * Fetch a SIWE challenge for `address` from the network's consumer-api.
86
+ * The returned `message` is the canonical SIWE string the wallet must
87
+ * sign verbatim - do NOT reformat or strip whitespace.
88
+ */
89
+ export async function siweChallenge(network, address, opts = {}) {
90
+ const cfg = resolveNetwork(network);
91
+ // baseUrl override lets a browser caller route through a different
92
+ // proxy. By default the SDK auto-routes browser traffic through
93
+ // lightnode.app's pass-through proxy (the consumer-api's CORS allowlist
94
+ // excludes most origins). Server-side, we hit the consumer-api direct.
95
+ const base = (opts.baseUrl ?? defaultSiweBase(cfg)).replace(/\/+$/, "");
96
+ if (!base) {
97
+ throw new Error(`siweChallenge: network "${cfg.id}" has no consumerApi configured (and no baseUrl override)`);
98
+ }
99
+ const url = `${base}/api/auth/challenge?address=${address}`;
100
+ return httpJson(url, { method: "GET", signal: opts.signal });
101
+ }
102
+ /**
103
+ * Verify a signed SIWE message and mint a JWT bearer.
104
+ */
105
+ export async function siweVerify(network, args, opts = {}) {
106
+ const cfg = resolveNetwork(network);
107
+ const base = (opts.baseUrl ?? defaultSiweBase(cfg)).replace(/\/+$/, "");
108
+ if (!base) {
109
+ throw new Error(`siweVerify: network "${cfg.id}" has no consumerApi configured (and no baseUrl override)`);
110
+ }
111
+ const url = `${base}/api/auth/verify`;
112
+ const out = await httpJson(url, {
113
+ method: "POST",
114
+ body: { message: args.message, signature: args.signature },
115
+ signal: opts.signal,
116
+ });
117
+ if (!out.success || !out.token) {
118
+ throw new Error("siweVerify: gateway returned success=false or missing token");
119
+ }
120
+ return out;
121
+ }
122
+ /**
123
+ * Extract a SIWE message's `Expiration Time` (ISO-8601) and convert to a
124
+ * unix-ms timestamp. Best-effort: returns null if the field is absent or
125
+ * unparseable.
126
+ */
127
+ function parseExpiry(message) {
128
+ const m = message.match(/Expiration Time:\s*(\S+)/);
129
+ if (!m)
130
+ return null;
131
+ const t = Date.parse(m[1]);
132
+ return Number.isFinite(t) ? t : null;
133
+ }
134
+ /**
135
+ * End-to-end SIWE sign-in: challenge -> sign -> verify -> JWT.
136
+ *
137
+ * ```ts
138
+ * import { siweSignIn, GatewayClient } from "lightnode-sdk";
139
+ * // browser: walletClient from wagmi's useWalletClient().data
140
+ * const session = await siweSignIn(walletClient, "testnet");
141
+ * const gateway = new GatewayClient({ network: "testnet", bearer: session.bearer });
142
+ * // ... use the gateway normally (runInference, selectSession, etc.)
143
+ * ```
144
+ *
145
+ * Pass `address` explicitly when the wallet client cannot expose its own
146
+ * account (some viem clients are intentionally accountless).
147
+ */
148
+ export async function siweSignIn(walletClient, network, opts = {}) {
149
+ const cfg = resolveNetwork(network);
150
+ const address = opts.address ?? walletClient.account?.address;
151
+ if (!address) {
152
+ throw new Error("siweSignIn: walletClient has no account; pass `address` explicitly");
153
+ }
154
+ const { message } = await siweChallenge(cfg, address, { signal: opts.signal, baseUrl: opts.baseUrl });
155
+ // viem's signMessage requires `account` even when one is set on the
156
+ // client; passing it explicitly works with both wagmi and bare viem.
157
+ const signature = await walletClient.signMessage({
158
+ account: walletClient.account?.address ?? address,
159
+ message,
160
+ });
161
+ const verified = await siweVerify(cfg, { message, signature }, { signal: opts.signal, baseUrl: opts.baseUrl });
162
+ const token = verified.token;
163
+ return {
164
+ token,
165
+ address: verified.address ?? address,
166
+ network: cfg.id,
167
+ expiresAt: parseExpiry(message),
168
+ bearer: () => token,
169
+ };
170
+ }
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, G
12
12
  import { OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL } from "./onchain-models.js";
13
13
  import { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker } from "./errors.js";
14
14
  import { GatewayClient, GatewayHttpError } from "./gateway.js";
15
+ import { siweSignIn, siweChallenge, siweVerify } from "./auth.js";
15
16
  import * as crypto from "./crypto.js";
16
17
  import type { NetworkId, NetworkConfig, Worker, Job, JobTransactions, ModelInfo, WorkerModel, ServedModel, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics } from "./types.js";
17
18
  /**
@@ -133,8 +134,8 @@ export declare class LightNode {
133
134
  * (especially in registry-proxy environments like StackBlitz where lockfiles
134
135
  * may pin an older minor than the local install command suggests).
135
136
  */
136
- export declare const SDK_VERSION = "0.7.9";
137
- export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, resolveJobTransactions, fetchWorkerModels, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, Conversation, chat, runInferenceBatch, Agent, parseAgentOutput, workerPreflight, workerWatch, Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, WorkerOperator, WORKER_REGISTRY_ABI, JOB_REGISTRY_OPERATOR_ABI, AI_CONFIG_ABI, JOB_STATE, decodeWorkerError, WorkerOpError, isWorkerOpError, };
137
+ export declare const SDK_VERSION = "0.7.11";
138
+ export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, resolveJobTransactions, siweSignIn, siweChallenge, siweVerify, fetchWorkerModels, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, Conversation, chat, runInferenceBatch, Agent, parseAgentOutput, workerPreflight, workerWatch, Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, WorkerOperator, WORKER_REGISTRY_ABI, JOB_REGISTRY_OPERATOR_ABI, AI_CONFIG_ABI, JOB_STATE, decodeWorkerError, WorkerOpError, isWorkerOpError, };
138
139
  export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
139
140
  export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
140
141
  export type { ChatRole, ChatMessage, ConversationOptions, ConversationSendResult } from "./chat.js";
@@ -146,3 +147,4 @@ export type { DaoChain, DaoAddresses, ProposalSummary, ProposalRow, DaoConfig }
146
147
  export type { BaseModel, ModelVariant, AccessTier, AccessPolicy, Benchmark, OnchainModelRegistryOptions } from "./onchain-models.js";
147
148
  export type { MinimalWalletClient, MinimalPublicClient, WorkerOperatorOpts, WorkerProtocolConfig, WorkerStatus, DeregisterReadiness, StuckJob, EarningsBreakdown, OnchainJob, JobState, DecodedWorkerError, } from "./worker-operator.js";
148
149
  export type { NetworkId, NetworkConfig, Worker, Job, JobTransactions, ModelInfo, WorkerModel, ServedModel, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics };
150
+ export type { SiweWalletClient, SiweChallenge, SiweVerifyResult, SiweSession } from "./auth.js";
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, G
13
13
  import { OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, } from "./onchain-models.js";
14
14
  import { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, } from "./errors.js";
15
15
  import { GatewayClient, GatewayHttpError } from "./gateway.js";
16
+ import { siweSignIn, siweChallenge, siweVerify } from "./auth.js";
16
17
  import * as crypto from "./crypto.js";
17
18
  /**
18
19
  * Read-only client for a LightChain AI network. Pure reads from the public indexer
@@ -212,11 +213,15 @@ export class LightNode {
212
213
  * (especially in registry-proxy environments like StackBlitz where lockfiles
213
214
  * may pin an older minor than the local install command suggests).
214
215
  */
215
- export const SDK_VERSION = "0.7.9";
216
+ export const SDK_VERSION = "0.7.11";
216
217
  export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei,
217
218
  // v0.7.3 per-job transaction-hash resolver (lifts the upstream
218
219
  // subgraph's "block-only" Job entity to a deep-linkable Job + tx pair).
219
220
  resolveJobTransactions,
221
+ // v0.7.10 SIWE sign-in against the consumer-api: returns a JWT bearer
222
+ // the worker-gateway accepts. End-to-end wallet-signed inference with
223
+ // no shared demo-wallet state.
224
+ siweSignIn, siweChallenge, siweVerify,
220
225
  // v0.7.4 per-worker model-registration list (the authoritative "what is
221
226
  // this worker offering to serve" signal, not derived from past jobs).
222
227
  fetchWorkerModels, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost,
package/dist/networks.js CHANGED
@@ -12,6 +12,7 @@ export const NETWORKS = {
12
12
  rpc: "https://rpc.mainnet.lightchain.ai",
13
13
  explorer: "https://mainnet.lightscan.app",
14
14
  workerGateway: "https://worker-gateway.mainnet.lightchain.ai",
15
+ consumerApi: "https://chat-api.mainnet.lightchain.ai",
15
16
  subgraph: "https://workers-api.mainnet.lightchain.ai/graphql",
16
17
  workerRegistry: "0x0000000000000000000000000000000000001002",
17
18
  aiConfig: "0x24D11533C354092ed6E18b964257819cE78Ce77D",
@@ -31,6 +32,7 @@ export const NETWORKS = {
31
32
  rpc: "https://rpc.testnet.lightchain.ai",
32
33
  explorer: "https://testnet.lightscan.app",
33
34
  workerGateway: "https://worker-gateway.testnet.lightchain.ai",
35
+ consumerApi: "https://chat-api.testnet.lightchain.ai",
34
36
  subgraph: "https://workers-api.testnet.lightchain.ai/graphql",
35
37
  workerRegistry: "0x0000000000000000000000000000000000001002",
36
38
  aiConfig: "0xeCF4Ca5Ba6D97ae586993e170764a1E92231b67e",
package/dist/types.d.ts CHANGED
@@ -6,6 +6,16 @@ export interface NetworkConfig {
6
6
  rpc: string;
7
7
  explorer: string;
8
8
  workerGateway: string;
9
+ /**
10
+ * LightChain consumer-api host (SIWE sign-in + JWT issuance). The worker
11
+ * gateway accepts the JWT minted here as its Authorization Bearer.
12
+ *
13
+ * - `GET {consumerApi}/api/auth/challenge?address=0x...` -> { nonce, message }
14
+ * - `POST {consumerApi}/api/auth/verify` body { message, signature } -> { token, ... }
15
+ *
16
+ * Wrapped end-to-end by {@link siweSignIn}.
17
+ */
18
+ consumerApi: string;
9
19
  subgraph: string;
10
20
  /** Genesis predeploy, same address on both networks. */
11
21
  workerRegistry: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightnode-sdk",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
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",