@tangle-network/sandbox 0.60.16-develop.20261006031110.95c8cc9 → 0.60.16-develop.20261006041203.03ae563

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,503 @@
1
+ //#region src/gpu-lease/intent.d.ts
2
+ declare const INTENT_VERSION = 1;
3
+ /** keccak256 of the canonical intent string. */
4
+ declare function gpuLeaseIntentHash(input: {
5
+ intentVersion: number;
6
+ gpuClass: string;
7
+ durationSeconds: bigint | number;
8
+ confidentiality: number;
9
+ region: string;
10
+ }): `0x${string}`;
11
+ /** ABI shape of GpuLeaseRequest — mirrors the blueprint's sol! type. */
12
+ declare const GPU_LEASE_REQUEST_ABI: never;
13
+ interface GpuLeaseRequestParams {
14
+ intentVersion: number;
15
+ intentHash: `0x${string}`;
16
+ pricePerSecondWei: bigint;
17
+ durationSeconds: bigint;
18
+ confidentiality: number;
19
+ gpuClass: string;
20
+ region: string;
21
+ lessee: `0x${string}`;
22
+ leaseId: `0x${string}`;
23
+ }
24
+ /**
25
+ * ABI-encoded GpuLeaseRequest — the LEASE job calldata payload.
26
+ * Positional (ABI order) — order is pinned by the blueprint's generated
27
+ * encodeType in metadata/blueprint.json.
28
+ */
29
+ declare function encodeGpuLeaseRequest(params: GpuLeaseRequestParams): `0x${string}`;
30
+ //#endregion
31
+ //#region src/gpu-lease/types.d.ts
32
+ /**
33
+ * GPU lease client types — mirrors the gpu-lease-blueprint operator API
34
+ * (gpu-lease-blueprint/gpu-lease-blueprint-lib/src/api.rs) and the on-chain
35
+ * ABI (contracts/src/GpuLeaseBlueprint.sol sol! mirror).
36
+ *
37
+ * Every on-chain and off-chain surface is schemaVersioned; unknown versions
38
+ * fail closed (SPEC §3 of the blueprint).
39
+ */
40
+ /** The only schema this client understands. */
41
+ declare const GPU_LEASE_SCHEMA_VERSION: 1;
42
+ interface ClassCapability {
43
+ id: string;
44
+ tee: boolean;
45
+ unit: string;
46
+ /** Indicative policy price (wei/second) — the binding price is the quote. */
47
+ pricePerSecond: string;
48
+ idleCount: number;
49
+ }
50
+ interface Capabilities {
51
+ schemaVersion: number;
52
+ compute: {
53
+ kind: string;
54
+ classes: ClassCapability[];
55
+ };
56
+ }
57
+ interface OperatorQuote {
58
+ schemaVersion: number;
59
+ gpuClass: string;
60
+ /** The operator's EVM address — who the escrow names (I5 binding).
61
+ * When the quote is signed this MUST equal the recovered signer (verified
62
+ * in quotes.ts); config-supplied values are interim-trust only. */
63
+ operatorAddress: `0x${string}`;
64
+ /** EIP-191 personal_sign over the canonical quote payload (0x RSV). */
65
+ signature?: `0x${string}`;
66
+ /** wei/second, from the operator's signed policy at request time. */
67
+ pricePerSecond: string;
68
+ durationSeconds: number;
69
+ confidentiality: number;
70
+ /** wei — pricePerSecond × durationSeconds exactly. */
71
+ escrowTotal: string;
72
+ /** Utilization the price was struck at (bps of 10_000) — the surge "why". */
73
+ utilizationBps: number;
74
+ /** keccak256(canonical intent) — binds the RFQ and the vault lease (I5). */
75
+ intentHash: `0x${string}`;
76
+ /** Unix seconds after which the quote is stale. */
77
+ validUntil: number;
78
+ }
79
+ interface LeaseStatus {
80
+ schemaVersion: number;
81
+ leaseId: `0x${string}`;
82
+ deviceId: string;
83
+ lessee: string;
84
+ /** Unix seconds — the chain-of-truth session end (parking meter). */
85
+ expiresAt: number;
86
+ endpoint: string;
87
+ }
88
+ interface SessionChallenge {
89
+ nonce: `0x${string}`;
90
+ /** The exact string the lessee must EIP-191 personal_sign. */
91
+ message: string;
92
+ expiresAt: number;
93
+ }
94
+ interface SessionCredential {
95
+ token: string;
96
+ leaseId: `0x${string}`;
97
+ scopes: string[];
98
+ expiresAt: number;
99
+ }
100
+ /** A validated, ready-to-escrow quote. */
101
+ interface ValidatedQuote {
102
+ quote: OperatorQuote;
103
+ pricePerSecondWei: bigint;
104
+ escrowTotalWei: bigint;
105
+ }
106
+ /** A resolved lease handed to the caller by `gpuLease()`. */
107
+ interface GpuLease {
108
+ leaseId: `0x${string}`;
109
+ /** Public endpoint descriptor (versioned union; v1 = local-attach). */
110
+ endpoint: string;
111
+ schemaVersion: number;
112
+ quote: ValidatedQuote;
113
+ }
114
+ interface RefundReceipt {
115
+ leaseId: `0x${string}`;
116
+ /** Exact wei refunded: pricePerSecond × remainingSeconds at settle time. */
117
+ refundWei: bigint;
118
+ operatorTakeWei: bigint;
119
+ }
120
+ //#endregion
121
+ //#region src/gpu-lease/operators.d.ts
122
+ interface QuoteParams {
123
+ gpuClass: string;
124
+ durationSeconds: number;
125
+ confidentiality?: number;
126
+ region?: string;
127
+ }
128
+ interface OperatorEndpoint {
129
+ /** Operator base URL, e.g. https://gpu.example.com */
130
+ baseUrl: string;
131
+ /** The operator's EVM address (escrow + job binding). */
132
+ address: `0x${string}`;
133
+ fetch?: typeof fetch;
134
+ }
135
+ /** JSON request body — named at the I/O boundary (anti-slop: no `unknown`). */
136
+ type JsonRequestBody = Record<string, unknown>;
137
+ declare class GpuLeaseOperatorClient {
138
+ private readonly endpoint;
139
+ constructor(endpoint: OperatorEndpoint);
140
+ capabilities(): Promise<Capabilities>;
141
+ quote(params: QuoteParams): Promise<OperatorQuote>;
142
+ leaseStatus(leaseId: `0x${string}`): Promise<LeaseStatus>;
143
+ sessionChallenge(leaseId: `0x${string}`, lessee: string): Promise<SessionChallenge>;
144
+ sessionVerify(nonce: `0x${string}`, signature: `0x${string}`): Promise<SessionCredential>;
145
+ }
146
+ //#endregion
147
+ //#region src/gpu-lease/output.d.ts
148
+ interface GpuLeaseOutputDecoded {
149
+ leaseId: `0x${string}`;
150
+ endpoint: string;
151
+ schemaVersion: number;
152
+ }
153
+ /** Decode + fail closed on unknown schemaVersion (blueprint SPEC §3). */
154
+ declare function decodeGpuLeaseOutput(encoded: `0x${string}`): GpuLeaseOutputDecoded;
155
+ //#endregion
156
+ //#region src/gpu-lease/quotes.d.ts
157
+ /** The exact string the operator signs — mirrors the blueprint's
158
+ * `canonical_quote_payload` byte-for-byte. Every priced field is bound. */
159
+ declare function canonicalQuotePayload(quote: {
160
+ gpuClass: string;
161
+ pricePerSecond: string;
162
+ durationSeconds: number;
163
+ confidentiality: number;
164
+ escrowTotal: string;
165
+ intentHash: string;
166
+ validUntil: number;
167
+ }): string;
168
+ /** Offline EIP-191 recovery over the canonical payload. */
169
+ declare function verifyOperatorQuote(quote: OperatorQuote): Promise<`0x${string}`>;
170
+ type QuoteRejection = {
171
+ kind: "stale";
172
+ validUntil: number;
173
+ now: number;
174
+ } | {
175
+ kind: "class-mismatch";
176
+ requested: string;
177
+ quoted: string;
178
+ } | {
179
+ kind: "escrow-inconsistent";
180
+ expected: bigint;
181
+ actual: bigint;
182
+ } | {
183
+ kind: "confidentiality-mismatch";
184
+ requested: number;
185
+ quoted: number;
186
+ } | {
187
+ kind: "signature-mismatch";
188
+ claimed: string;
189
+ recovered: string;
190
+ } | {
191
+ kind: "unsigned";
192
+ operator: string;
193
+ };
194
+ interface QuoteFilterInput {
195
+ requestedClass: string;
196
+ requestedDurationSeconds: number;
197
+ requestedConfidentiality: number;
198
+ now?: () => number;
199
+ }
200
+ /**
201
+ * Validate one quote. Fail-closed on every field the RFQ binds:
202
+ * class, duration-derived escrow, confidentiality, and expiry.
203
+ */
204
+ declare function validateQuote(quote: OperatorQuote, input: QuoteFilterInput): ValidatedQuote | QuoteRejection;
205
+ /**
206
+ * Collect validated quotes from N operators and rank cheapest-first.
207
+ * Operators that fail validation (or whose fetch rejects) are reported,
208
+ * never silently dropped — the buyer sees why an operator lost.
209
+ */
210
+ declare function selectQuotes(quotes: {
211
+ operator: string;
212
+ quote: OperatorQuote;
213
+ }[], input: QuoteFilterInput): {
214
+ ranked: {
215
+ operator: string;
216
+ validated: ValidatedQuote;
217
+ }[];
218
+ rejected: {
219
+ operator: string;
220
+ reason: QuoteRejection;
221
+ }[];
222
+ };
223
+ /**
224
+ * Enforce the signed RFQ envelope when present:
225
+ * - signature present => it MUST recover to the claimed operator address;
226
+ * invalid signature => reject (config-trust is dead on this path).
227
+ * - no signature => the endpoint-config address is used (interim fallback);
228
+ * strict mode rejects unsigned quotes outright.
229
+ */
230
+ declare function validateQuoteIdentity(quote: OperatorQuote, opts?: {
231
+ strict?: boolean;
232
+ }): Promise<ValidatedQuote | QuoteRejection>;
233
+ //#endregion
234
+ //#region src/gpu-lease/vault.d.ts
235
+ declare const GPU_LEASE_VAULT_ABI: readonly [{
236
+ readonly name: "create";
237
+ readonly type: "function";
238
+ readonly stateMutability: "payable";
239
+ readonly inputs: readonly [{
240
+ readonly type: "address";
241
+ readonly name: "operator";
242
+ }, {
243
+ readonly type: "uint128";
244
+ readonly name: "pricePerSecond";
245
+ }, {
246
+ readonly type: "uint64";
247
+ readonly name: "durationSeconds";
248
+ }, {
249
+ readonly type: "bytes32";
250
+ readonly name: "intentHash";
251
+ }, {
252
+ readonly type: "uint8";
253
+ readonly name: "confidentiality";
254
+ }, {
255
+ readonly type: "bytes";
256
+ readonly name: "endpointInfo";
257
+ }];
258
+ readonly outputs: readonly [{
259
+ readonly type: "bytes32";
260
+ readonly name: "leaseId";
261
+ }];
262
+ }, {
263
+ readonly name: "extend";
264
+ readonly type: "function";
265
+ readonly stateMutability: "payable";
266
+ readonly inputs: readonly [{
267
+ readonly type: "bytes32";
268
+ readonly name: "leaseId";
269
+ }, {
270
+ readonly type: "uint64";
271
+ readonly name: "addSeconds";
272
+ }];
273
+ readonly outputs: readonly [];
274
+ }, {
275
+ readonly name: "release";
276
+ readonly type: "function";
277
+ readonly stateMutability: "nonpayable";
278
+ readonly inputs: readonly [{
279
+ readonly type: "bytes32";
280
+ readonly name: "leaseId";
281
+ }];
282
+ readonly outputs: readonly [];
283
+ }, {
284
+ readonly name: "reap";
285
+ readonly type: "function";
286
+ readonly stateMutability: "nonpayable";
287
+ readonly inputs: readonly [{
288
+ readonly type: "bytes32";
289
+ readonly name: "leaseId";
290
+ }];
291
+ readonly outputs: readonly [];
292
+ }, {
293
+ readonly name: "withdrawEarnings";
294
+ readonly type: "function";
295
+ readonly stateMutability: "nonpayable";
296
+ readonly inputs: readonly [];
297
+ readonly outputs: readonly [];
298
+ }, {
299
+ readonly name: "totalEscrowed";
300
+ readonly type: "function";
301
+ readonly stateMutability: "view";
302
+ readonly inputs: readonly [];
303
+ readonly outputs: readonly [{
304
+ readonly type: "uint256";
305
+ }];
306
+ }, {
307
+ readonly name: "operatorEarnings";
308
+ readonly type: "function";
309
+ readonly stateMutability: "view";
310
+ readonly inputs: readonly [];
311
+ readonly outputs: readonly [{
312
+ readonly type: "uint256";
313
+ }];
314
+ }, {
315
+ readonly name: "leases";
316
+ readonly type: "function";
317
+ readonly stateMutability: "view";
318
+ readonly inputs: readonly [{
319
+ readonly type: "bytes32";
320
+ }];
321
+ readonly outputs: readonly [{
322
+ readonly type: "address";
323
+ readonly name: "operator";
324
+ }, {
325
+ readonly type: "address";
326
+ readonly name: "lessee";
327
+ }, {
328
+ readonly type: "uint128";
329
+ readonly name: "escrow";
330
+ }, {
331
+ readonly type: "uint128";
332
+ readonly name: "pricePerSecond";
333
+ }, {
334
+ readonly type: "uint64";
335
+ readonly name: "expiry";
336
+ }, {
337
+ readonly type: "bytes32";
338
+ readonly name: "intentHash";
339
+ }, {
340
+ readonly type: "uint8";
341
+ readonly name: "confidentiality";
342
+ }, {
343
+ readonly type: "uint8";
344
+ readonly name: "state";
345
+ }];
346
+ }, {
347
+ readonly name: "LeaseCreated";
348
+ readonly type: "event";
349
+ readonly inputs: readonly [{
350
+ readonly type: "bytes32";
351
+ readonly name: "leaseId";
352
+ readonly indexed: true;
353
+ }, {
354
+ readonly type: "address";
355
+ readonly name: "operator";
356
+ readonly indexed: true;
357
+ }, {
358
+ readonly type: "address";
359
+ readonly name: "lessee";
360
+ readonly indexed: true;
361
+ }, {
362
+ readonly type: "uint128";
363
+ readonly name: "escrow";
364
+ }, {
365
+ readonly type: "uint128";
366
+ readonly name: "pricePerSecond";
367
+ }, {
368
+ readonly type: "uint64";
369
+ readonly name: "expiry";
370
+ }, {
371
+ readonly type: "bytes32";
372
+ readonly name: "intentHash";
373
+ }, {
374
+ readonly type: "uint8";
375
+ readonly name: "confidentiality";
376
+ }, {
377
+ readonly type: "bytes";
378
+ readonly name: "endpointInfo";
379
+ }, {
380
+ readonly type: "uint16";
381
+ readonly name: "schemaVersion";
382
+ }];
383
+ }, {
384
+ readonly name: "LeaseReleased";
385
+ readonly type: "event";
386
+ readonly inputs: readonly [{
387
+ readonly type: "bytes32";
388
+ readonly name: "leaseId";
389
+ readonly indexed: true;
390
+ }, {
391
+ readonly type: "uint128";
392
+ readonly name: "refund";
393
+ }, {
394
+ readonly type: "uint128";
395
+ readonly name: "operatorTake";
396
+ }];
397
+ }, {
398
+ readonly name: "LeaseReaped";
399
+ readonly type: "event";
400
+ readonly inputs: readonly [{
401
+ readonly type: "bytes32";
402
+ readonly name: "leaseId";
403
+ readonly indexed: true;
404
+ }, {
405
+ readonly type: "uint128";
406
+ readonly name: "operatorTake";
407
+ }, {
408
+ readonly type: "address";
409
+ readonly name: "caller";
410
+ }];
411
+ }];
412
+ /** Vault lease states (mirrors the contract). */
413
+ declare const LEASE_STATE: {
414
+ readonly LIVE: 0;
415
+ readonly RELEASED: 1;
416
+ readonly REAPED: 2;
417
+ };
418
+ interface VaultLeaseView {
419
+ operator: `0x${string}`;
420
+ lessee: `0x${string}`;
421
+ escrowWei: bigint;
422
+ pricePerSecondWei: bigint;
423
+ /** Unix seconds — the chain clock is the parking meter's truth. */
424
+ expiry: bigint;
425
+ intentHash: `0x${string}`;
426
+ confidentiality: number;
427
+ state: number;
428
+ }
429
+ /**
430
+ * Exact refund projection (I3): pricePerSecond × max(expiry − now, 0).
431
+ * Exact by construction because escrow === price × paidSeconds in the vault.
432
+ */
433
+ declare function projectRefundWei(lease: VaultLeaseView, nowSeconds: bigint): {
434
+ refundWei: bigint;
435
+ operatorTakeWei: bigint;
436
+ };
437
+ /** I2 from the client side: unpayable time can never be requested. */
438
+ declare function maxAffordableSeconds(escrowWei: bigint, pricePerSecondWei: bigint): bigint;
439
+ //#endregion
440
+ //#region src/gpu-lease/resolver.d.ts
441
+ /** Job ids — MUST match the blueprint router (sequential, no gaps). */
442
+ declare const GPU_LEASE_JOBS: {
443
+ readonly LEASE: 0;
444
+ readonly RELEASE: 1;
445
+ readonly EXTEND: 2;
446
+ readonly REAP: 3;
447
+ };
448
+ interface GpuLeaseChain {
449
+ /** The buyer's address (the RFQ binds the requester — I5). */
450
+ readonly account: `0x${string}`;
451
+ /** Create the escrowed lease on the vault; returns the vault leaseId. */
452
+ createVaultLease(input: {
453
+ operator: `0x${string}`;
454
+ pricePerSecondWei: bigint;
455
+ durationSeconds: bigint;
456
+ intentHash: `0x${string}`;
457
+ confidentiality: number;
458
+ endpointInfo: string;
459
+ escrowWei: bigint;
460
+ }): Promise<`0x${string}`>;
461
+ /** Submit a job through tnt-core; resolves with the result payload. */
462
+ submitJob(jobId: number, encodedInput: `0x${string}`): Promise<`0x${string}`>;
463
+ /** Read a vault lease (chain clock truth for projections). */
464
+ readLease(leaseId: `0x${string}`): Promise<VaultLeaseView>;
465
+ /** The chain's current timestamp (unix seconds) — meter truth, not Date.now(). */
466
+ chainNow?(): Promise<bigint>;
467
+ /** Voluntary release on the vault; returns {refund, take} wei. */
468
+ releaseLease(leaseId: `0x${string}`): Promise<{
469
+ refundWei: bigint;
470
+ operatorTakeWei: bigint;
471
+ }>;
472
+ }
473
+ interface GpuLeaseOptions {
474
+ gpuClass: string;
475
+ durationSeconds: number;
476
+ confidentiality?: number;
477
+ region?: string;
478
+ /** Operators to request quotes from (multi-quote = the decentralized scheduler). */
479
+ operators: OperatorEndpoint[];
480
+ chain: GpuLeaseChain;
481
+ now?: () => number;
482
+ }
483
+ interface GpuLeaseOutcome {
484
+ lease: GpuLease;
485
+ /** Operators ranked cheapest-first. */
486
+ rankedOperators: string[];
487
+ }
488
+ /** Resolve a GPU lease end-to-end. Throws with the full picture on failure. */
489
+ declare function gpuLease(options: GpuLeaseOptions): Promise<GpuLeaseOutcome>;
490
+ /** Teardown: voluntary release with an exact-refund receipt (I3/I4). */
491
+ declare function releaseGpuLease(leaseId: `0x${string}`, chain: GpuLeaseChain): Promise<RefundReceipt>;
492
+ /**
493
+ * Refund projection for UI meters. Uses the chain clock when the injected
494
+ * chain provides `chainNow()`; otherwise requires an explicit timestamp.
495
+ * Never Date.now() — the drift lesson from the blueprint's own E2E.
496
+ */
497
+ declare function projectGpuLeaseRefund(leaseId: `0x${string}`, chain: GpuLeaseChain, explicitNowSeconds?: bigint): Promise<{
498
+ refundWei: bigint;
499
+ operatorTakeWei: bigint;
500
+ nowSeconds: bigint;
501
+ }>;
502
+ //#endregion
503
+ export { Capabilities, ClassCapability, GPU_LEASE_JOBS, GPU_LEASE_REQUEST_ABI, GPU_LEASE_SCHEMA_VERSION, GPU_LEASE_VAULT_ABI, GpuLease, GpuLeaseChain, GpuLeaseOperatorClient, GpuLeaseOptions, GpuLeaseOutcome, GpuLeaseOutputDecoded, GpuLeaseRequestParams, INTENT_VERSION, JsonRequestBody, LEASE_STATE, LeaseStatus, OperatorEndpoint, OperatorQuote, QuoteFilterInput, QuoteParams, QuoteRejection, RefundReceipt, SessionChallenge, SessionCredential, ValidatedQuote, VaultLeaseView, canonicalQuotePayload, decodeGpuLeaseOutput, encodeGpuLeaseRequest, gpuLease, gpuLeaseIntentHash, maxAffordableSeconds, projectGpuLeaseRefund, projectRefundWei, releaseGpuLease, selectQuotes, validateQuote, validateQuoteIdentity, verifyOperatorQuote };
@@ -0,0 +1,410 @@
1
+ import { decodeAbiParameters, encodeAbiParameters, keccak256, parseAbi, parseAbiParameters, recoverMessageAddress, stringToHex } from "viem";
2
+ //#region src/gpu-lease/intent.ts
3
+ /**
4
+ * Canonical GPU-lease intent + job ABI encoding — MUST match:
5
+ * gpu-lease-blueprint/gpu-lease-blueprint-lib/src/jobs/mod.rs (intent_hash)
6
+ * gpu-lease-blueprint/contracts/src/GpuLeaseBlueprint.sol (GpuLeaseRequest)
7
+ * gpu-lease-blueprint/metadata/blueprint.json (encodeType, generated).
8
+ */
9
+ const INTENT_VERSION = 1;
10
+ /** keccak256 of the canonical intent string. */
11
+ function gpuLeaseIntentHash(input) {
12
+ return keccak256(stringToHex(`gpu-lease-intent|v${input.intentVersion}|${input.gpuClass}|${input.durationSeconds}|${input.confidentiality}|${input.region}`));
13
+ }
14
+ /** ABI shape of GpuLeaseRequest — mirrors the blueprint's sol! type. */
15
+ const GPU_LEASE_REQUEST_ABI = parseAbiParameters("(uint8 intentVersion, bytes32 intentHash, uint128 pricePerSecond, uint64 durationSeconds, uint8 confidentiality, string gpuClass, string region, address lessee, bytes32 leaseId)");
16
+ /**
17
+ * ABI-encoded GpuLeaseRequest — the LEASE job calldata payload.
18
+ * Positional (ABI order) — order is pinned by the blueprint's generated
19
+ * encodeType in metadata/blueprint.json.
20
+ */
21
+ function encodeGpuLeaseRequest(params) {
22
+ return encodeAbiParameters(GPU_LEASE_REQUEST_ABI, [[
23
+ params.intentVersion,
24
+ params.intentHash,
25
+ params.pricePerSecondWei,
26
+ params.durationSeconds,
27
+ params.confidentiality,
28
+ params.gpuClass,
29
+ params.region,
30
+ params.lessee,
31
+ params.leaseId
32
+ ]]);
33
+ }
34
+ //#endregion
35
+ //#region src/gpu-lease/types.ts
36
+ /**
37
+ * GPU lease client types — mirrors the gpu-lease-blueprint operator API
38
+ * (gpu-lease-blueprint/gpu-lease-blueprint-lib/src/api.rs) and the on-chain
39
+ * ABI (contracts/src/GpuLeaseBlueprint.sol sol! mirror).
40
+ *
41
+ * Every on-chain and off-chain surface is schemaVersioned; unknown versions
42
+ * fail closed (SPEC §3 of the blueprint).
43
+ */
44
+ /** The only schema this client understands. */
45
+ const GPU_LEASE_SCHEMA_VERSION = 1;
46
+ //#endregion
47
+ //#region src/gpu-lease/operators.ts
48
+ function assertKnownSchema(actual, where) {
49
+ if (actual !== 1) throw new Error(`${where}: unknown schemaVersion ${actual} (client understands ${GPU_LEASE_VERSION_TEXT}) — failing closed`);
50
+ }
51
+ const GPU_LEASE_VERSION_TEXT = String(1);
52
+ async function getJson(endpoint, path) {
53
+ const res = await (endpoint.fetch ?? fetch)(new URL(path, endpoint.baseUrl).toString(), { headers: { accept: "application/json" } });
54
+ if (!res.ok) throw new Error(`GET ${path}: operator returned ${res.status}`);
55
+ const body = await res.json();
56
+ assertKnownSchema(body.schemaVersion, `GET ${path}`);
57
+ return body;
58
+ }
59
+ async function postJson(endpoint, path, payload, requireSchema = false) {
60
+ const res = await (endpoint.fetch ?? fetch)(new URL(path, endpoint.baseUrl).toString(), {
61
+ method: "POST",
62
+ headers: {
63
+ "content-type": "application/json",
64
+ accept: "application/json"
65
+ },
66
+ body: JSON.stringify(payload)
67
+ });
68
+ if (!res.ok) {
69
+ const detail = await res.text().catch(() => "");
70
+ throw new Error(`POST ${path}: operator returned ${res.status} ${detail.slice(0, 200)}`);
71
+ }
72
+ const body = await res.json();
73
+ if (requireSchema) assertKnownSchema(body.schemaVersion, `POST ${path}`);
74
+ return body;
75
+ }
76
+ var GpuLeaseOperatorClient = class {
77
+ constructor(endpoint) {
78
+ this.endpoint = endpoint;
79
+ }
80
+ capabilities() {
81
+ return getJson(this.endpoint, "/api/capabilities");
82
+ }
83
+ async quote(params) {
84
+ const quote = await postJson(this.endpoint, "/api/quote", {
85
+ gpuClass: params.gpuClass,
86
+ durationSeconds: params.durationSeconds,
87
+ confidentiality: params.confidentiality ?? 0,
88
+ ...params.region !== void 0 ? { region: params.region } : {}
89
+ }, true);
90
+ return {
91
+ ...quote,
92
+ operatorAddress: quote.operatorAddress ?? this.endpoint.address
93
+ };
94
+ }
95
+ leaseStatus(leaseId) {
96
+ return getJson(this.endpoint, `/api/leases/${leaseId.replace(/^0x/, "")}`);
97
+ }
98
+ sessionChallenge(leaseId, lessee) {
99
+ return postJson(this.endpoint, "/api/session/challenge", {
100
+ leaseId,
101
+ lessee
102
+ });
103
+ }
104
+ sessionVerify(nonce, signature) {
105
+ return postJson(this.endpoint, "/api/session/verify", {
106
+ nonce,
107
+ signature
108
+ });
109
+ }
110
+ };
111
+ //#endregion
112
+ //#region src/gpu-lease/output.ts
113
+ /**
114
+ * GpuLeaseOutput decoding — the LEASE job result payload. The operator API
115
+ * does NOT carry this (it is a job RESULT); it arrives from the chain via
116
+ * the injected job client.
117
+ */
118
+ const GPU_LEASE_OUTPUT_ABI = parseAbiParameters("(bytes32 leaseId, string endpoint, uint16 schemaVersion)");
119
+ /** Decode + fail closed on unknown schemaVersion (blueprint SPEC §3). */
120
+ function decodeGpuLeaseOutput(encoded) {
121
+ const [decoded] = decodeAbiParameters(GPU_LEASE_OUTPUT_ABI, encoded);
122
+ if (decoded.schemaVersion !== 1) throw new Error(`GpuLeaseOutput: unknown schemaVersion ${decoded.schemaVersion} — failing closed`);
123
+ return decoded;
124
+ }
125
+ //#endregion
126
+ //#region src/gpu-lease/quotes.ts
127
+ /**
128
+ * Multi-operator RFQ: collect quotes, validate each against fail-closed
129
+ * rules, rank by price. Pure — no I/O. Mirrors the validated-quote flow
130
+ * proven in blueprint #1568 and the operator policy's ceiling semantics.
131
+ */
132
+ /** The exact string the operator signs — mirrors the blueprint's
133
+ * `canonical_quote_payload` byte-for-byte. Every priced field is bound. */
134
+ function canonicalQuotePayload(quote) {
135
+ return `gpu-lease-quote|v1|${quote.gpuClass}|${quote.pricePerSecond}|${quote.durationSeconds}|${quote.confidentiality}|${quote.escrowTotal}|${quote.intentHash}|${quote.validUntil}`;
136
+ }
137
+ /** Offline EIP-191 recovery over the canonical payload. */
138
+ async function verifyOperatorQuote(quote) {
139
+ return (await recoverMessageAddress({
140
+ message: canonicalQuotePayload(quote),
141
+ signature: quote.signature
142
+ })).toLowerCase();
143
+ }
144
+ /**
145
+ * Validate one quote. Fail-closed on every field the RFQ binds:
146
+ * class, duration-derived escrow, confidentiality, and expiry.
147
+ */
148
+ function validateQuote(quote, input) {
149
+ const now = (input.now ?? Date.now)() / 1e3;
150
+ if (quote.gpuClass !== input.requestedClass) return {
151
+ kind: "class-mismatch",
152
+ requested: input.requestedClass,
153
+ quoted: quote.gpuClass
154
+ };
155
+ if (quote.confidentiality !== input.requestedConfidentiality) return {
156
+ kind: "confidentiality-mismatch",
157
+ requested: input.requestedConfidentiality,
158
+ quoted: quote.confidentiality
159
+ };
160
+ const price = BigInt(quote.pricePerSecond);
161
+ const expectedEscrow = price * BigInt(quote.durationSeconds);
162
+ const actualEscrow = BigInt(quote.escrowTotal);
163
+ if (expectedEscrow !== actualEscrow) return {
164
+ kind: "escrow-inconsistent",
165
+ expected: expectedEscrow,
166
+ actual: actualEscrow
167
+ };
168
+ if (quote.validUntil <= now) return {
169
+ kind: "stale",
170
+ validUntil: quote.validUntil,
171
+ now
172
+ };
173
+ return {
174
+ quote,
175
+ pricePerSecondWei: price,
176
+ escrowTotalWei: actualEscrow
177
+ };
178
+ }
179
+ /**
180
+ * Collect validated quotes from N operators and rank cheapest-first.
181
+ * Operators that fail validation (or whose fetch rejects) are reported,
182
+ * never silently dropped — the buyer sees why an operator lost.
183
+ */
184
+ function selectQuotes(quotes, input) {
185
+ const ranked = [];
186
+ const rejected = [];
187
+ for (const { operator, quote } of quotes) {
188
+ const result = validateQuote(quote, input);
189
+ if ("kind" in result) rejected.push({
190
+ operator,
191
+ reason: result
192
+ });
193
+ else ranked.push({
194
+ operator,
195
+ validated: result
196
+ });
197
+ }
198
+ ranked.sort((a, b) => {
199
+ if (a.validated.pricePerSecondWei !== b.validated.pricePerSecondWei) return a.validated.pricePerSecondWei < b.validated.pricePerSecondWei ? -1 : 1;
200
+ return a.validated.escrowTotalWei < b.validated.escrowTotalWei ? -1 : 1;
201
+ });
202
+ return {
203
+ ranked,
204
+ rejected
205
+ };
206
+ }
207
+ /**
208
+ * Enforce the signed RFQ envelope when present:
209
+ * - signature present => it MUST recover to the claimed operator address;
210
+ * invalid signature => reject (config-trust is dead on this path).
211
+ * - no signature => the endpoint-config address is used (interim fallback);
212
+ * strict mode rejects unsigned quotes outright.
213
+ */
214
+ async function validateQuoteIdentity(quote, opts = {}) {
215
+ const base = {
216
+ quote,
217
+ pricePerSecondWei: BigInt(quote.pricePerSecond),
218
+ escrowTotalWei: BigInt(quote.escrowTotal)
219
+ };
220
+ if (quote.signature) {
221
+ const recovered = await verifyOperatorQuote(quote).catch(() => null);
222
+ const claimed = quote.operatorAddress.toLowerCase();
223
+ if (recovered === null || recovered !== claimed) return {
224
+ kind: "signature-mismatch",
225
+ claimed,
226
+ recovered: recovered ?? "unrecoverable"
227
+ };
228
+ return base;
229
+ }
230
+ if (opts.strict) return {
231
+ kind: "unsigned",
232
+ operator: quote.operatorAddress
233
+ };
234
+ return base;
235
+ }
236
+ //#endregion
237
+ //#region src/gpu-lease/vault.ts
238
+ /**
239
+ * Vault money semantics — the client mirror of GpuLeaseVault.sol
240
+ * (gpu-lease-blueprint/contracts/src/GpuLeaseVault.sol), invariants I1–I5.
241
+ * Exact integers everywhere; wei only, never floats.
242
+ */
243
+ const GPU_LEASE_VAULT_ABI = parseAbi([
244
+ "function create(address operator, uint128 pricePerSecond, uint64 durationSeconds, bytes32 intentHash, uint8 confidentiality, bytes endpointInfo) external payable returns (bytes32 leaseId)",
245
+ "function extend(bytes32 leaseId, uint64 addSeconds) external payable",
246
+ "function release(bytes32 leaseId) external",
247
+ "function reap(bytes32 leaseId) external",
248
+ "function withdrawEarnings() external",
249
+ "function totalEscrowed() external view returns (uint256)",
250
+ "function operatorEarnings() external view returns (uint256)",
251
+ "function leases(bytes32) external view returns (address operator, address lessee, uint128 escrow, uint128 pricePerSecond, uint64 expiry, bytes32 intentHash, uint8 confidentiality, uint8 state)",
252
+ "event LeaseCreated(bytes32 indexed leaseId, address indexed operator, address indexed lessee, uint128 escrow, uint128 pricePerSecond, uint64 expiry, bytes32 intentHash, uint8 confidentiality, bytes endpointInfo, uint16 schemaVersion)",
253
+ "event LeaseReleased(bytes32 indexed leaseId, uint128 refund, uint128 operatorTake)",
254
+ "event LeaseReaped(bytes32 indexed leaseId, uint128 operatorTake, address caller)"
255
+ ]);
256
+ /** Vault lease states (mirrors the contract). */
257
+ const LEASE_STATE = {
258
+ LIVE: 0,
259
+ RELEASED: 1,
260
+ REAPED: 2
261
+ };
262
+ /**
263
+ * Exact refund projection (I3): pricePerSecond × max(expiry − now, 0).
264
+ * Exact by construction because escrow === price × paidSeconds in the vault.
265
+ */
266
+ function projectRefundWei(lease, nowSeconds) {
267
+ const remaining = lease.expiry > nowSeconds ? lease.expiry - nowSeconds : 0n;
268
+ const refundWei = lease.pricePerSecondWei * remaining;
269
+ return {
270
+ refundWei,
271
+ operatorTakeWei: lease.escrowWei - refundWei
272
+ };
273
+ }
274
+ /** I2 from the client side: unpayable time can never be requested. */
275
+ function maxAffordableSeconds(escrowWei, pricePerSecondWei) {
276
+ if (pricePerSecondWei <= 0n) throw new Error("pricePerSecond must be positive");
277
+ return escrowWei / pricePerSecondWei;
278
+ }
279
+ //#endregion
280
+ //#region src/gpu-lease/resolver.ts
281
+ /**
282
+ * `gpuLease()` — the SPEC §5 resolver: collect + validate quotes → escrow on
283
+ * the vault → LEASE job through tnt-core → teardown with an exact refund
284
+ * receipt. Chain and job submission are INJECTED (transport-agnostic: works
285
+ * with viem wallets, the TangleDriver, or test doubles).
286
+ */
287
+ /** Job ids — MUST match the blueprint router (sequential, no gaps). */
288
+ const GPU_LEASE_JOBS = {
289
+ LEASE: 0,
290
+ RELEASE: 1,
291
+ EXTEND: 2,
292
+ REAP: 3
293
+ };
294
+ /** Resolve a GPU lease end-to-end. Throws with the full picture on failure. */
295
+ async function gpuLease(options) {
296
+ const { gpuClass, durationSeconds, region = "any" } = options;
297
+ const confidentiality = options.confidentiality ?? 0;
298
+ const collected = (await Promise.allSettled(options.operators.map(async (endpoint) => {
299
+ const quote = await new GpuLeaseOperatorClient(endpoint).quote({
300
+ gpuClass,
301
+ durationSeconds,
302
+ confidentiality,
303
+ region
304
+ });
305
+ return {
306
+ operator: endpoint.baseUrl,
307
+ quote
308
+ };
309
+ }))).flatMap((s) => s.status === "fulfilled" ? [s.value] : []);
310
+ if (collected.length === 0) throw new Error("gpuLease: no operator returned a quote");
311
+ const { ranked, rejected } = selectQuotes(collected, {
312
+ requestedClass: gpuClass,
313
+ requestedDurationSeconds: durationSeconds,
314
+ requestedConfidentiality: confidentiality,
315
+ now: options.now
316
+ });
317
+ if (ranked.length === 0) {
318
+ const reasons = rejected.map((r) => `${r.operator}: ${r.reason.kind}`).join(", ");
319
+ throw new Error(`gpuLease: every quote failed validation (${reasons})`);
320
+ }
321
+ let best;
322
+ for (const candidate of ranked) {
323
+ const identity = await validateQuoteIdentity(candidate.validated.quote);
324
+ if ("kind" in identity) {
325
+ rejected.push({
326
+ operator: candidate.operator,
327
+ reason: identity
328
+ });
329
+ continue;
330
+ }
331
+ best = {
332
+ ...candidate,
333
+ validated: identity
334
+ };
335
+ break;
336
+ }
337
+ if (!best) {
338
+ const reasons = rejected.map((r) => `${r.operator}: ${r.reason.kind}`).join(", ");
339
+ throw new Error(`gpuLease: no quote passed identity verification (${reasons})`);
340
+ }
341
+ const operatorAddress = best.validated.quote.operatorAddress;
342
+ const intentHash = gpuLeaseIntentHash({
343
+ intentVersion: 1,
344
+ gpuClass,
345
+ durationSeconds,
346
+ confidentiality,
347
+ region
348
+ });
349
+ const leaseId = await options.chain.createVaultLease({
350
+ operator: operatorAddress,
351
+ pricePerSecondWei: best.validated.pricePerSecondWei,
352
+ durationSeconds: BigInt(durationSeconds),
353
+ intentHash,
354
+ confidentiality,
355
+ endpointInfo: "{}",
356
+ escrowWei: best.validated.escrowTotalWei
357
+ });
358
+ const encoded = encodeGpuLeaseRequest({
359
+ intentVersion: 1,
360
+ intentHash,
361
+ pricePerSecondWei: best.validated.pricePerSecondWei,
362
+ durationSeconds: BigInt(durationSeconds),
363
+ confidentiality,
364
+ gpuClass,
365
+ region,
366
+ lessee: options.chain.account,
367
+ leaseId
368
+ });
369
+ const output = decodeGpuLeaseOutput(await options.chain.submitJob(GPU_LEASE_JOBS.LEASE, encoded));
370
+ if (output.leaseId !== leaseId) throw new Error(`gpuLease: job returned leaseId ${output.leaseId}, expected the vault leaseId ${leaseId} (one identity across money and routing)`);
371
+ return {
372
+ lease: {
373
+ leaseId,
374
+ endpoint: output.endpoint,
375
+ schemaVersion: output.schemaVersion,
376
+ quote: best.validated
377
+ },
378
+ rankedOperators: ranked.map((r) => r.operator)
379
+ };
380
+ }
381
+ /** Teardown: voluntary release with an exact-refund receipt (I3/I4). */
382
+ async function releaseGpuLease(leaseId, chain) {
383
+ const { refundWei, operatorTakeWei } = await chain.releaseLease(leaseId);
384
+ return {
385
+ leaseId,
386
+ refundWei,
387
+ operatorTakeWei
388
+ };
389
+ }
390
+ /**
391
+ * Refund projection for UI meters. Uses the chain clock when the injected
392
+ * chain provides `chainNow()`; otherwise requires an explicit timestamp.
393
+ * Never Date.now() — the drift lesson from the blueprint's own E2E.
394
+ */
395
+ async function projectGpuLeaseRefund(leaseId, chain, explicitNowSeconds) {
396
+ const lease = await chain.readLease(leaseId);
397
+ let nowSeconds = explicitNowSeconds;
398
+ if (nowSeconds === void 0) {
399
+ if (!chain.chainNow) throw new Error("projectGpuLeaseRefund: chain has no chainNow() — pass explicitNowSeconds");
400
+ nowSeconds = await chain.chainNow();
401
+ }
402
+ const { refundWei, operatorTakeWei } = projectRefundWei(lease, nowSeconds);
403
+ return {
404
+ refundWei,
405
+ operatorTakeWei,
406
+ nowSeconds
407
+ };
408
+ }
409
+ //#endregion
410
+ export { GPU_LEASE_JOBS, GPU_LEASE_REQUEST_ABI, GPU_LEASE_SCHEMA_VERSION, GPU_LEASE_VAULT_ABI, GpuLeaseOperatorClient, INTENT_VERSION, LEASE_STATE, canonicalQuotePayload, decodeGpuLeaseOutput, encodeGpuLeaseRequest, gpuLease, gpuLeaseIntentHash, maxAffordableSeconds, projectGpuLeaseRefund, projectRefundWei, releaseGpuLease, selectQuotes, validateQuote, validateQuoteIdentity, verifyOperatorQuote };
package/dist/index.d.ts CHANGED
@@ -870,8 +870,8 @@ declare const manageSandboxesInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
870
870
  off: "off";
871
871
  }>>;
872
872
  logs: z.ZodOptional<z.ZodEnum<{
873
- off: "off";
874
873
  on: "on";
874
+ off: "off";
875
875
  }>>;
876
876
  }, z.core.$strict>>;
877
877
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -1199,8 +1199,8 @@ declare const manageSandboxesInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1199
1199
  off: "off";
1200
1200
  }>>;
1201
1201
  logs: z.ZodOptional<z.ZodEnum<{
1202
- off: "off";
1203
1202
  on: "on";
1203
+ off: "off";
1204
1204
  }>>;
1205
1205
  }, z.core.$strict>>;
1206
1206
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -1538,8 +1538,8 @@ declare const manageSandboxesInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1538
1538
  off: "off";
1539
1539
  }>>;
1540
1540
  logs: z.ZodOptional<z.ZodEnum<{
1541
- off: "off";
1542
1541
  on: "on";
1542
+ off: "off";
1543
1543
  }>>;
1544
1544
  }, z.core.$strict>>;
1545
1545
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -1862,8 +1862,8 @@ declare const manageSandboxesInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1862
1862
  off: "off";
1863
1863
  }>>;
1864
1864
  logs: z.ZodOptional<z.ZodEnum<{
1865
- off: "off";
1866
1865
  on: "on";
1866
+ off: "off";
1867
1867
  }>>;
1868
1868
  }, z.core.$strict>>;
1869
1869
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -2191,8 +2191,8 @@ declare const manageSandboxesInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2191
2191
  off: "off";
2192
2192
  }>>;
2193
2193
  logs: z.ZodOptional<z.ZodEnum<{
2194
- off: "off";
2195
2194
  on: "on";
2195
+ off: "off";
2196
2196
  }>>;
2197
2197
  }, z.core.$strict>>;
2198
2198
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -2625,8 +2625,8 @@ declare const MANAGE_SANDBOXES_PARAMETERS: z.core.ZodStandardJSONSchemaPayload<z
2625
2625
  off: "off";
2626
2626
  }>>;
2627
2627
  logs: z.ZodOptional<z.ZodEnum<{
2628
- off: "off";
2629
2628
  on: "on";
2629
+ off: "off";
2630
2630
  }>>;
2631
2631
  }, z.core.$strict>>;
2632
2632
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -2954,8 +2954,8 @@ declare const MANAGE_SANDBOXES_PARAMETERS: z.core.ZodStandardJSONSchemaPayload<z
2954
2954
  off: "off";
2955
2955
  }>>;
2956
2956
  logs: z.ZodOptional<z.ZodEnum<{
2957
- off: "off";
2958
2957
  on: "on";
2958
+ off: "off";
2959
2959
  }>>;
2960
2960
  }, z.core.$strict>>;
2961
2961
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -3293,8 +3293,8 @@ declare const MANAGE_SANDBOXES_PARAMETERS: z.core.ZodStandardJSONSchemaPayload<z
3293
3293
  off: "off";
3294
3294
  }>>;
3295
3295
  logs: z.ZodOptional<z.ZodEnum<{
3296
- off: "off";
3297
3296
  on: "on";
3297
+ off: "off";
3298
3298
  }>>;
3299
3299
  }, z.core.$strict>>;
3300
3300
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -3617,8 +3617,8 @@ declare const MANAGE_SANDBOXES_PARAMETERS: z.core.ZodStandardJSONSchemaPayload<z
3617
3617
  off: "off";
3618
3618
  }>>;
3619
3619
  logs: z.ZodOptional<z.ZodEnum<{
3620
- off: "off";
3621
3620
  on: "on";
3621
+ off: "off";
3622
3622
  }>>;
3623
3623
  }, z.core.$strict>>;
3624
3624
  idempotencyKey: z.ZodOptional<z.ZodString>;
@@ -3946,8 +3946,8 @@ declare const MANAGE_SANDBOXES_PARAMETERS: z.core.ZodStandardJSONSchemaPayload<z
3946
3946
  off: "off";
3947
3947
  }>>;
3948
3948
  logs: z.ZodOptional<z.ZodEnum<{
3949
- off: "off";
3950
3949
  on: "on";
3950
+ off: "off";
3951
3951
  }>>;
3952
3952
  }, z.core.$strict>>;
3953
3953
  idempotencyKey: z.ZodOptional<z.ZodString>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox",
3
- "version": "0.60.16-develop.20261006031110.95c8cc9",
3
+ "version": "0.60.16-develop.20261006041203.03ae563",
4
4
  "description": "Client SDK for the Tangle Sandbox platform - build AI agent applications with dev containers",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -152,10 +152,10 @@
152
152
  "yjs": "13.6.30",
153
153
  "@products/sandbox-test-utils": "0.0.0",
154
154
  "@repo/shared": "0.0.0",
155
+ "@tangle-network/agent-profile-materialize": "0.20.9",
156
+ "@tangle-network/sdk": "3.3.41",
155
157
  "@tangle-network/cli-agent-registry": "0.5.0",
156
158
  "@tangle-network/runtime-contracts": "0.14.0",
157
- "@tangle-network/sdk": "3.3.41",
158
- "@tangle-network/agent-profile-materialize": "0.20.9",
159
159
  "@tangle-network/sse-core": "0.0.0"
160
160
  },
161
161
  "scripts": {