@tangle-network/sandbox 0.60.31-develop.20261010071444.b89558f → 0.60.31-develop.20261010073257.5e0a52f
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 +26 -4
- package/dist/gpu-lease/index.js +26 -5
- package/package.json +3 -3
|
@@ -63,11 +63,13 @@ interface OperatorQuote {
|
|
|
63
63
|
operatorAddress: `0x${string}`;
|
|
64
64
|
/** EIP-191 personal_sign over the canonical quote payload (0x RSV). */
|
|
65
65
|
signature?: `0x${string}`;
|
|
66
|
-
/** wei/second, from the operator's signed policy at request time. */
|
|
66
|
+
/** wei/second per device, from the operator's signed policy at request time. */
|
|
67
67
|
pricePerSecond: string;
|
|
68
68
|
durationSeconds: number;
|
|
69
69
|
confidentiality: number;
|
|
70
|
-
/**
|
|
70
|
+
/** Devices rented. The signed v2 payload binds this count. */
|
|
71
|
+
deviceCount: number;
|
|
72
|
+
/** wei — pricePerSecond × durationSeconds × deviceCount exactly. */
|
|
71
73
|
escrowTotal: string;
|
|
72
74
|
/** Utilization the price was struck at (bps of 10_000) — the surge "why". */
|
|
73
75
|
utilizationBps: number;
|
|
@@ -155,12 +157,14 @@ declare function decodeGpuLeaseOutput(encoded: `0x${string}`): GpuLeaseOutputDec
|
|
|
155
157
|
//#endregion
|
|
156
158
|
//#region src/gpu-lease/quotes.d.ts
|
|
157
159
|
/** The exact string the operator signs — mirrors the blueprint's
|
|
158
|
-
* `canonical_quote_payload` byte-for-byte. Every priced field is bound
|
|
160
|
+
* `canonical_quote_payload` (v2) byte-for-byte. Every priced field is bound,
|
|
161
|
+
* the device count included. */
|
|
159
162
|
declare function canonicalQuotePayload(quote: {
|
|
160
163
|
gpuClass: string;
|
|
161
164
|
pricePerSecond: string;
|
|
162
165
|
durationSeconds: number;
|
|
163
166
|
confidentiality: number;
|
|
167
|
+
deviceCount: number;
|
|
164
168
|
escrowTotal: string;
|
|
165
169
|
intentHash: string;
|
|
166
170
|
validUntil: number;
|
|
@@ -175,6 +179,14 @@ type QuoteRejection = {
|
|
|
175
179
|
kind: "class-mismatch";
|
|
176
180
|
requested: string;
|
|
177
181
|
quoted: string;
|
|
182
|
+
} | {
|
|
183
|
+
kind: "duration-mismatch";
|
|
184
|
+
requested: number;
|
|
185
|
+
quoted: number;
|
|
186
|
+
} | {
|
|
187
|
+
kind: "count-mismatch";
|
|
188
|
+
requested: number;
|
|
189
|
+
quoted: number;
|
|
178
190
|
} | {
|
|
179
191
|
kind: "escrow-inconsistent";
|
|
180
192
|
expected: bigint;
|
|
@@ -187,6 +199,10 @@ type QuoteRejection = {
|
|
|
187
199
|
kind: "signature-mismatch";
|
|
188
200
|
claimed: string;
|
|
189
201
|
recovered: string;
|
|
202
|
+
} | {
|
|
203
|
+
kind: "operator-not-pinned";
|
|
204
|
+
pinned: string;
|
|
205
|
+
recovered: string;
|
|
190
206
|
} | {
|
|
191
207
|
kind: "unsigned";
|
|
192
208
|
operator: string;
|
|
@@ -195,11 +211,13 @@ interface QuoteFilterInput {
|
|
|
195
211
|
requestedClass: string;
|
|
196
212
|
requestedDurationSeconds: number;
|
|
197
213
|
requestedConfidentiality: number;
|
|
214
|
+
requestedDeviceCount: number;
|
|
198
215
|
now?: () => number;
|
|
199
216
|
}
|
|
200
217
|
/**
|
|
201
218
|
* Validate one quote. Fail-closed on every field the RFQ binds:
|
|
202
|
-
* class, duration
|
|
219
|
+
* class, duration, device count, the escrow derived from them,
|
|
220
|
+
* confidentiality, and expiry.
|
|
203
221
|
*/
|
|
204
222
|
declare function validateQuote(quote: OperatorQuote, input: QuoteFilterInput): ValidatedQuote | QuoteRejection;
|
|
205
223
|
/**
|
|
@@ -226,9 +244,13 @@ declare function selectQuotes(quotes: {
|
|
|
226
244
|
* invalid signature => reject (config-trust is dead on this path).
|
|
227
245
|
* - no signature => the endpoint-config address is used (interim fallback);
|
|
228
246
|
* strict mode rejects unsigned quotes outright.
|
|
247
|
+
* - `pinnedOperator` set => the quote must be signed AND recover to that
|
|
248
|
+
* address. A valid signature from any other key is rejected, so an
|
|
249
|
+
* endpoint cannot substitute its own signer for the one the caller trusts.
|
|
229
250
|
*/
|
|
230
251
|
declare function validateQuoteIdentity(quote: OperatorQuote, opts?: {
|
|
231
252
|
strict?: boolean;
|
|
253
|
+
pinnedOperator?: `0x${string}`;
|
|
232
254
|
}): Promise<ValidatedQuote | QuoteRejection>;
|
|
233
255
|
//#endregion
|
|
234
256
|
//#region src/gpu-lease/vault.d.ts
|
package/dist/gpu-lease/index.js
CHANGED
|
@@ -130,9 +130,10 @@ function decodeGpuLeaseOutput(encoded) {
|
|
|
130
130
|
* proven in blueprint #1568 and the operator policy's ceiling semantics.
|
|
131
131
|
*/
|
|
132
132
|
/** The exact string the operator signs — mirrors the blueprint's
|
|
133
|
-
* `canonical_quote_payload` byte-for-byte. Every priced field is bound
|
|
133
|
+
* `canonical_quote_payload` (v2) byte-for-byte. Every priced field is bound,
|
|
134
|
+
* the device count included. */
|
|
134
135
|
function canonicalQuotePayload(quote) {
|
|
135
|
-
return `gpu-lease-quote|
|
|
136
|
+
return `gpu-lease-quote|v2|${quote.gpuClass}|${quote.pricePerSecond}|${quote.durationSeconds}|${quote.confidentiality}|${quote.deviceCount}|${quote.escrowTotal}|${quote.intentHash}|${quote.validUntil}`;
|
|
136
137
|
}
|
|
137
138
|
/** Offline EIP-191 recovery over the canonical payload. */
|
|
138
139
|
async function verifyOperatorQuote(quote) {
|
|
@@ -143,7 +144,8 @@ async function verifyOperatorQuote(quote) {
|
|
|
143
144
|
}
|
|
144
145
|
/**
|
|
145
146
|
* Validate one quote. Fail-closed on every field the RFQ binds:
|
|
146
|
-
* class, duration
|
|
147
|
+
* class, duration, device count, the escrow derived from them,
|
|
148
|
+
* confidentiality, and expiry.
|
|
147
149
|
*/
|
|
148
150
|
function validateQuote(quote, input) {
|
|
149
151
|
const now = (input.now ?? Date.now)() / 1e3;
|
|
@@ -152,13 +154,23 @@ function validateQuote(quote, input) {
|
|
|
152
154
|
requested: input.requestedClass,
|
|
153
155
|
quoted: quote.gpuClass
|
|
154
156
|
};
|
|
157
|
+
if (quote.durationSeconds !== input.requestedDurationSeconds) return {
|
|
158
|
+
kind: "duration-mismatch",
|
|
159
|
+
requested: input.requestedDurationSeconds,
|
|
160
|
+
quoted: quote.durationSeconds
|
|
161
|
+
};
|
|
162
|
+
if (quote.deviceCount !== input.requestedDeviceCount) return {
|
|
163
|
+
kind: "count-mismatch",
|
|
164
|
+
requested: input.requestedDeviceCount,
|
|
165
|
+
quoted: quote.deviceCount
|
|
166
|
+
};
|
|
155
167
|
if (quote.confidentiality !== input.requestedConfidentiality) return {
|
|
156
168
|
kind: "confidentiality-mismatch",
|
|
157
169
|
requested: input.requestedConfidentiality,
|
|
158
170
|
quoted: quote.confidentiality
|
|
159
171
|
};
|
|
160
172
|
const price = BigInt(quote.pricePerSecond);
|
|
161
|
-
const expectedEscrow = price * BigInt(quote.durationSeconds);
|
|
173
|
+
const expectedEscrow = price * BigInt(quote.durationSeconds) * BigInt(quote.deviceCount);
|
|
162
174
|
const actualEscrow = BigInt(quote.escrowTotal);
|
|
163
175
|
if (expectedEscrow !== actualEscrow) return {
|
|
164
176
|
kind: "escrow-inconsistent",
|
|
@@ -210,6 +222,9 @@ function selectQuotes(quotes, input) {
|
|
|
210
222
|
* invalid signature => reject (config-trust is dead on this path).
|
|
211
223
|
* - no signature => the endpoint-config address is used (interim fallback);
|
|
212
224
|
* strict mode rejects unsigned quotes outright.
|
|
225
|
+
* - `pinnedOperator` set => the quote must be signed AND recover to that
|
|
226
|
+
* address. A valid signature from any other key is rejected, so an
|
|
227
|
+
* endpoint cannot substitute its own signer for the one the caller trusts.
|
|
213
228
|
*/
|
|
214
229
|
async function validateQuoteIdentity(quote, opts = {}) {
|
|
215
230
|
const base = {
|
|
@@ -225,9 +240,14 @@ async function validateQuoteIdentity(quote, opts = {}) {
|
|
|
225
240
|
claimed,
|
|
226
241
|
recovered: recovered ?? "unrecoverable"
|
|
227
242
|
};
|
|
243
|
+
if (opts.pinnedOperator !== void 0 && recovered !== opts.pinnedOperator.toLowerCase()) return {
|
|
244
|
+
kind: "operator-not-pinned",
|
|
245
|
+
pinned: opts.pinnedOperator.toLowerCase(),
|
|
246
|
+
recovered
|
|
247
|
+
};
|
|
228
248
|
return base;
|
|
229
249
|
}
|
|
230
|
-
if (opts.strict) return {
|
|
250
|
+
if (opts.strict || opts.pinnedOperator !== void 0) return {
|
|
231
251
|
kind: "unsigned",
|
|
232
252
|
operator: quote.operatorAddress
|
|
233
253
|
};
|
|
@@ -312,6 +332,7 @@ async function gpuLease(options) {
|
|
|
312
332
|
requestedClass: gpuClass,
|
|
313
333
|
requestedDurationSeconds: durationSeconds,
|
|
314
334
|
requestedConfidentiality: confidentiality,
|
|
335
|
+
requestedDeviceCount: 1,
|
|
315
336
|
now: options.now
|
|
316
337
|
});
|
|
317
338
|
if (ranked.length === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/sandbox",
|
|
3
|
-
"version": "0.60.31-develop.
|
|
3
|
+
"version": "0.60.31-develop.20261010073257.5e0a52f",
|
|
4
4
|
"description": "Client SDK for the Tangle Sandbox platform - build AI agent applications with dev containers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -149,12 +149,12 @@
|
|
|
149
149
|
"vitest": "^4.1.11",
|
|
150
150
|
"ws": "^8.20.0",
|
|
151
151
|
"yjs": "13.6.30",
|
|
152
|
-
"@products/sandbox-test-utils": "0.0.0",
|
|
153
152
|
"@repo/shared": "0.0.0",
|
|
153
|
+
"@products/sandbox-test-utils": "0.0.0",
|
|
154
154
|
"@tangle-network/agent-profile-materialize": "0.22.2",
|
|
155
155
|
"@tangle-network/cli-agent-registry": "0.6.2",
|
|
156
|
-
"@tangle-network/runtime-contracts": "0.16.0",
|
|
157
156
|
"@tangle-network/sdk": "4.0.3",
|
|
157
|
+
"@tangle-network/runtime-contracts": "0.16.0",
|
|
158
158
|
"@tangle-network/sse-core": "0.0.0"
|
|
159
159
|
},
|
|
160
160
|
"scripts": {
|