@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.
- package/dist/gpu-lease/index.d.ts +503 -0
- package/dist/gpu-lease/index.js +410 -0
- package/dist/index.d.ts +10 -10
- package/package.json +3 -3
|
@@ -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.
|
|
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": {
|