@escro/sdk 0.1.4 → 0.2.0

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/index.js CHANGED
@@ -5,15 +5,118 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5
5
  // src/client.ts
6
6
  import { Connection as Connection2, PublicKey as PublicKey2 } from "@solana/web3.js";
7
7
 
8
- // src/api.ts
8
+ // ../shared/dist/index.js
9
+ import { createHash } from "crypto";
9
10
  import nacl from "tweetnacl";
10
11
  import bs58 from "bs58";
12
+ import nacl2 from "tweetnacl";
13
+ import bs582 from "bs58";
14
+ var EscrowState = /* @__PURE__ */ ((EscrowState2) => {
15
+ EscrowState2["CREATED"] = "CREATED";
16
+ EscrowState2["FUNDED"] = "FUNDED";
17
+ EscrowState2["IN_PROGRESS"] = "IN_PROGRESS";
18
+ EscrowState2["SUBMITTED"] = "SUBMITTED";
19
+ EscrowState2["COMPLETED"] = "COMPLETED";
20
+ EscrowState2["CANCELLED"] = "CANCELLED";
21
+ EscrowState2["DISPUTED"] = "DISPUTED";
22
+ EscrowState2["REFUNDED"] = "REFUNDED";
23
+ return EscrowState2;
24
+ })(EscrowState || {});
25
+ function canonicalize(value) {
26
+ if (value === null) return "null";
27
+ if (Array.isArray(value)) {
28
+ return "[" + value.map((v) => v === void 0 ? "null" : canonicalize(v)).join(",") + "]";
29
+ }
30
+ if (typeof value === "object") {
31
+ const obj = value;
32
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
33
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k])).join(",") + "}";
34
+ }
35
+ return JSON.stringify(value);
36
+ }
37
+ var PROGRAM_ID_DEVNET = "EjLpJ3obUYk1f2KDensU7UdGBZTxSdxufm7CSnMA3H5C";
38
+ var PROGRAM_ID_MAINNET = "4CwLNRF4mFXutiBGjtMHQBxh8whW8T8EyvJXdRhY1rvr";
39
+ var ONCHAIN_REFUND_GRACE_SECONDS = 900;
40
+ var ONCHAIN_TIMEOUT_SECONDS = 86400;
41
+ var CLOCK_SKEW_SECONDS = 60;
42
+ var SCANNER_REFUND_GRACE_SECONDS = ONCHAIN_REFUND_GRACE_SECONDS + CLOCK_SKEW_SECONDS;
43
+ var SCANNER_TIMEOUT_SECONDS = ONCHAIN_TIMEOUT_SECONDS + CLOCK_SKEW_SECONDS;
44
+ var MAINNET_PROGRAM_ID_PLACEHOLDER = "11111111111111111111111111111111";
45
+ function getProgramId(cluster) {
46
+ return cluster === "mainnet" ? PROGRAM_ID_MAINNET : PROGRAM_ID_DEVNET;
47
+ }
48
+ function isPlaceholderProgramId(id) {
49
+ return id === MAINNET_PROGRAM_ID_PLACEHOLDER;
50
+ }
51
+ function assertProgramIdNotPlaceholder(id, cluster) {
52
+ if (cluster === "mainnet" && isPlaceholderProgramId(id)) {
53
+ throw new Error(
54
+ `PROGRAM_ID_MAINNET is still the System Program placeholder (${MAINNET_PROGRAM_ID_PLACEHOLDER}). Run scripts/deploy-mainnet.sh to bootstrap mainnet, then update the placeholder in packages/shared/src/config.ts, packages/program/programs/escro/src/lib.rs, packages/program/Anchor.toml, and infra/bin/escro.ts.`
55
+ );
56
+ }
57
+ }
58
+ function hashBody(rawBody) {
59
+ return createHash("sha256").update(rawBody, "utf8").digest("hex");
60
+ }
61
+ function buildSignedMessage(timestamp, method, fullPath, bodyHash) {
62
+ return `escro:${String(timestamp)}:${method}:${fullPath}:${bodyHash}`;
63
+ }
64
+ function signRequestRaw(secretKey, walletAddress, method, fullPath, rawBody) {
65
+ const timestamp = Date.now();
66
+ const bodyHash = hashBody(rawBody);
67
+ const message = buildSignedMessage(timestamp, method, fullPath, bodyHash);
68
+ const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), secretKey);
69
+ return {
70
+ "x-wallet-address": walletAddress,
71
+ "x-signature": bs58.encode(sigBytes),
72
+ "x-timestamp": String(timestamp),
73
+ "x-content-sha256": bodyHash
74
+ };
75
+ }
76
+ function signRequest(wallet, method, fullPath, rawBody) {
77
+ return signRequestRaw(wallet.secretKey, wallet.publicKey.toBase58(), method, fullPath, rawBody);
78
+ }
79
+ function canonicalBotDescriptorMessage(descriptor) {
80
+ return canonicalize({
81
+ wallet: descriptor.wallet,
82
+ role: descriptor.role,
83
+ capabilities: descriptor.capabilities,
84
+ serviceRef: descriptor.serviceRef,
85
+ pricingHint: descriptor.pricingHint,
86
+ endpoint: descriptor.endpoint,
87
+ signedAt: descriptor.signedAt
88
+ });
89
+ }
90
+ function verifyBotDescriptorSignature(descriptor) {
91
+ try {
92
+ const { signature, ...unsigned } = descriptor;
93
+ const message = new TextEncoder().encode(canonicalBotDescriptorMessage(unsigned));
94
+ const signatureBytes = bs582.decode(signature);
95
+ if (signatureBytes.length !== 64) return false;
96
+ const pubkeyBytes = bs582.decode(descriptor.wallet);
97
+ if (pubkeyBytes.length !== 32) return false;
98
+ return nacl2.sign.detached.verify(message, signatureBytes, pubkeyBytes);
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ // src/payments.ts
105
+ var RAILS = ["escrow", "x402"];
106
+ var ENABLED_RAILS = ["escrow"];
107
+ var RAILS_DOCS_URL = "https://docs.escro.ai/rails";
108
+ var EscrowPaymentRouter = class {
109
+ selectRail(tx) {
110
+ const preferred = tx.mode === "sync" ? "x402" : "escrow";
111
+ return ENABLED_RAILS.includes(preferred) ? preferred : "escrow";
112
+ }
113
+ };
11
114
 
12
115
  // src/errors.ts
13
116
  var EscroError = class extends Error {
14
117
  constructor(message, code = "ESCRO_ERROR") {
15
118
  super(message);
16
- this.code = code;
119
+ __publicField(this, "code", code);
17
120
  this.name = "EscroError";
18
121
  }
19
122
  };
@@ -23,6 +126,15 @@ var EscrowNotFoundError = class extends EscroError {
23
126
  this.name = "EscrowNotFoundError";
24
127
  }
25
128
  };
129
+ var ReputationUnavailableError = class extends EscroError {
130
+ constructor() {
131
+ super(
132
+ "Reputation endpoint is unavailable (HTTP 404): this deployment has no reputation source mounted.",
133
+ "REPUTATION_UNAVAILABLE"
134
+ );
135
+ this.name = "ReputationUnavailableError";
136
+ }
137
+ };
26
138
  var EscrowStateError = class extends EscroError {
27
139
  constructor(message) {
28
140
  super(message, "ESCROW_INVALID_STATE");
@@ -41,25 +153,27 @@ var UnauthorizedError = class extends EscroError {
41
153
  this.name = "UnauthorizedError";
42
154
  }
43
155
  };
156
+ var EscroRailNotEnabledError = class extends EscroError {
157
+ constructor(rail) {
158
+ super(
159
+ `Payment rail "${rail}" is not enabled. Only the escrow rail is available today; the x402 rail is reserved and lands in Phase 9. See ${RAILS_DOCS_URL}`,
160
+ "ESCRO_RAIL_NOT_ENABLED"
161
+ );
162
+ __publicField(this, "rail", rail);
163
+ this.name = "EscroRailNotEnabledError";
164
+ }
165
+ };
44
166
 
45
167
  // src/api.ts
46
- function authHeaders(wallet, method, path) {
47
- const timestamp = Date.now();
48
- const message = `escro:${timestamp}:${method}:${path}`;
49
- const msgBytes = new TextEncoder().encode(message);
50
- const sigBytes = nacl.sign.detached(msgBytes, wallet.secretKey);
51
- return {
52
- "x-wallet-address": wallet.publicKey.toBase58(),
53
- "x-signature": bs58.encode(sigBytes),
54
- "x-timestamp": String(timestamp)
55
- };
168
+ function authHeaders(wallet, method, path, rawBody = "") {
169
+ return signRequest(wallet, method, path, rawBody);
56
170
  }
57
171
  function deserialize(raw) {
58
172
  return { ...raw, amount: BigInt(raw.amount) };
59
173
  }
60
174
  async function handleError(res, address) {
61
175
  const body = await res.json().catch(() => ({}));
62
- const msg = body.error ?? `HTTP ${res.status}`;
176
+ const msg = body.error ?? `HTTP ${String(res.status)}`;
63
177
  if (res.status === 404) throw new EscrowNotFoundError(address ?? msg);
64
178
  if (res.status === 409) throw new EscrowStateError(msg);
65
179
  if (res.status === 401 || res.status === 403) throw new UnauthorizedError(msg);
@@ -67,8 +181,13 @@ async function handleError(res, address) {
67
181
  }
68
182
  var ApiClient = class {
69
183
  constructor(baseUrl, wallet) {
70
- this.baseUrl = baseUrl;
71
- this.wallet = wallet;
184
+ __publicField(this, "baseUrl", baseUrl);
185
+ __publicField(this, "wallet", wallet);
186
+ const url = new URL(baseUrl);
187
+ const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
188
+ if (url.protocol !== "https:" && !isLocalhost) {
189
+ throw new Error("apiUrl must use HTTPS in production");
190
+ }
72
191
  }
73
192
  url(path) {
74
193
  return `${this.baseUrl}${path}`;
@@ -80,10 +199,11 @@ var ApiClient = class {
80
199
  if (!res.ok) await handleError(res, address);
81
200
  return deserialize(await res.json());
82
201
  }
83
- /** GET /v1/escrows?assignedTo=...&state=... */
202
+ /** GET /v1/escrows?assignedTo=...&maker=...&state=... */
84
203
  async listEscrows(params) {
85
204
  const qs = new URLSearchParams();
86
205
  if (params.assignedTo) qs.set("assignedTo", params.assignedTo);
206
+ if (params.maker) qs.set("maker", params.maker);
87
207
  if (params.state) qs.set("state", params.state);
88
208
  if (params.limit != null) qs.set("limit", String(params.limit));
89
209
  if (params.offset != null) qs.set("offset", String(params.offset));
@@ -98,21 +218,61 @@ var ApiClient = class {
98
218
  offset: body.offset
99
219
  };
100
220
  }
221
+ /**
222
+ * GET /v1/bots?capability=...&role=...&limit=...&offset= : EscroBots registry
223
+ * read. Unauthenticated public read (no auth headers). Returns descriptors in
224
+ * registry order (newest first); descriptors are self-reported claims.
225
+ */
226
+ async listBots(params) {
227
+ const qs = new URLSearchParams();
228
+ if (params.capability != null) qs.set("capability", params.capability);
229
+ if (params.role != null) qs.set("role", params.role);
230
+ if (params.limit != null) qs.set("limit", String(params.limit));
231
+ if (params.offset != null) qs.set("offset", String(params.offset));
232
+ const path = `/v1/bots?${qs.toString()}`;
233
+ const res = await fetch(this.url(path));
234
+ if (!res.ok) await handleError(res);
235
+ const body = await res.json();
236
+ return { items: body.items, total: body.total, limit: body.limit, offset: body.offset };
237
+ }
238
+ /**
239
+ * GET /rep : EscroRep bulk export (ARCHITECTURE §9). Unauthenticated public
240
+ * read (no auth headers). Returns every indexed wallet's RAW components exactly
241
+ * once (no composite score). Ranking layers join against this ONE bulk fetch:
242
+ * the per-wallet endpoint shares a 10/min public-metrics budget, so a
243
+ * per-candidate fan-out would rate-limit itself. A 404 means the deployment
244
+ * mounted no reputation source (unknown wallets return zeroed rep, never 404),
245
+ * so it surfaces as {@link ReputationUnavailableError}, not EscrowNotFoundError.
246
+ */
247
+ async listReputations() {
248
+ const res = await fetch(this.url("/rep"));
249
+ if (!res.ok) {
250
+ if (res.status === 404) throw new ReputationUnavailableError();
251
+ await handleError(res);
252
+ }
253
+ const body = await res.json();
254
+ return body.wallets;
255
+ }
101
256
  /**
102
257
  * POST /v1/escrows — create escrow, returns unsigned tx + ids.
103
258
  * Requires auth.
104
259
  */
105
260
  async createEscrow(params) {
106
261
  const path = "/v1/escrows";
262
+ const bodyObj = {
263
+ taskSpec: params.taskSpec,
264
+ amountUsdc: params.amountUsdc,
265
+ deadlineUnix: params.deadlineUnix,
266
+ assignedWorker: params.assignedWorker
267
+ };
268
+ const rawBody = JSON.stringify(bodyObj);
107
269
  const res = await fetch(this.url(path), {
108
270
  method: "POST",
109
- headers: { ...authHeaders(this.wallet, "POST", path), "content-type": "application/json" },
110
- body: JSON.stringify({
111
- taskSpec: params.taskSpec,
112
- amountUsdc: params.amountUsdc,
113
- deadlineSeconds: params.deadlineSeconds,
114
- assignedWorker: params.assignedWorker
115
- })
271
+ headers: {
272
+ ...authHeaders(this.wallet, "POST", path, rawBody),
273
+ "content-type": "application/json"
274
+ },
275
+ body: rawBody
116
276
  });
117
277
  if (!res.ok) await handleError(res);
118
278
  return res.json();
@@ -136,13 +296,17 @@ var ApiClient = class {
136
296
  */
137
297
  async submitDeliverable(address, params) {
138
298
  const path = `/v1/escrows/${address}/submit`;
299
+ const rawBody = JSON.stringify({
300
+ contentHash: params.contentHash,
301
+ proofUri: params.proofUri
302
+ });
139
303
  const res = await fetch(this.url(path), {
140
304
  method: "POST",
141
- headers: { ...authHeaders(this.wallet, "POST", path), "content-type": "application/json" },
142
- body: JSON.stringify({
143
- contentHash: params.contentHash,
144
- proofUri: params.proofUri
145
- })
305
+ headers: {
306
+ ...authHeaders(this.wallet, "POST", path, rawBody),
307
+ "content-type": "application/json"
308
+ },
309
+ body: rawBody
146
310
  });
147
311
  if (!res.ok) await handleError(res, address);
148
312
  return res.json();
@@ -178,14 +342,29 @@ var ApiClient = class {
178
342
  */
179
343
  async raiseDispute(address, params) {
180
344
  const path = `/v1/escrows/${address}/dispute`;
345
+ const rawBody = JSON.stringify({ reason: params.reason, evidence: params.evidence });
181
346
  const res = await fetch(this.url(path), {
182
347
  method: "POST",
183
- headers: { ...authHeaders(this.wallet, "POST", path), "content-type": "application/json" },
184
- body: JSON.stringify({ reason: params.reason, evidence: params.evidence })
348
+ headers: {
349
+ ...authHeaders(this.wallet, "POST", path, rawBody),
350
+ "content-type": "application/json"
351
+ },
352
+ body: rawBody
185
353
  });
186
354
  if (!res.ok) await handleError(res, address);
187
355
  return res.json();
188
356
  }
357
+ /**
358
+ * GET /v1/stats — public protocol-wide stats.
359
+ * No auth required. Upstream typing in {@link Escro.getStats} narrows the
360
+ * return shape.
361
+ */
362
+ async getStats() {
363
+ const path = "/v1/stats";
364
+ const res = await fetch(this.url(path));
365
+ if (!res.ok) await handleError(res);
366
+ return res.json();
367
+ }
189
368
  };
190
369
 
191
370
  // src/poll.ts
@@ -202,7 +381,7 @@ function sleep(ms) {
202
381
  }
203
382
  async function pollUntil(address, fetcher, predicate, timeoutMs = MS_24_H) {
204
383
  const deadline = Date.now() + timeoutMs;
205
- while (true) {
384
+ for (; ; ) {
206
385
  const result = await fetcher();
207
386
  if (predicate(result)) return result;
208
387
  const elapsed = Date.now() - (deadline - timeoutMs);
@@ -219,8 +398,9 @@ async function pollUntil(address, fetcher, predicate, timeoutMs = MS_24_H) {
219
398
  }
220
399
 
221
400
  // src/solana.ts
222
- import { createHash } from "crypto";
401
+ import { createHash as createHash2 } from "crypto";
223
402
  import {
403
+ ComputeBudgetProgram,
224
404
  PublicKey,
225
405
  Transaction,
226
406
  TransactionInstruction
@@ -229,41 +409,111 @@ import {
229
409
  TOKEN_PROGRAM_ID,
230
410
  ASSOCIATED_TOKEN_PROGRAM_ID,
231
411
  getAssociatedTokenAddressSync,
232
- createAssociatedTokenAccountInstruction
412
+ createAssociatedTokenAccountIdempotentInstruction
233
413
  } from "@solana/spl-token";
234
414
  function disc(name) {
235
- return Buffer.from(
236
- createHash("sha256").update(`global:${name}`).digest()
237
- ).subarray(0, 8);
415
+ return Buffer.from(createHash2("sha256").update(`global:${name}`).digest()).subarray(0, 8);
238
416
  }
239
417
  var DISC_RELEASE_PAYMENT = disc("release_payment");
240
- var PROGRAM_ID = new PublicKey("EjLpJ3obUYk1f2KDensU7UdGBZTxSdxufm7CSnMA3H5C");
241
- async function fetchPlatformConfig(connection) {
242
- const [pda] = PublicKey.findProgramAddressSync([Buffer.from("config")], PROGRAM_ID);
418
+ function resolveProgramId(cluster) {
419
+ const id = getProgramId(cluster);
420
+ assertProgramIdNotPlaceholder(id, cluster);
421
+ return new PublicKey(id);
422
+ }
423
+ function allowedProgramIds(programId) {
424
+ return /* @__PURE__ */ new Set([
425
+ programId.toBase58(),
426
+ TOKEN_PROGRAM_ID.toBase58(),
427
+ ASSOCIATED_TOKEN_PROGRAM_ID.toBase58(),
428
+ ComputeBudgetProgram.programId.toBase58()
429
+ ]);
430
+ }
431
+ var MAX_INSTRUCTIONS = 6;
432
+ var FORBIDDEN_TOKEN_IX_TAGS = /* @__PURE__ */ new Set([
433
+ 3,
434
+ // Transfer
435
+ 4,
436
+ // Approve
437
+ 6,
438
+ // SetAuthority
439
+ 7,
440
+ // MintTo
441
+ 8,
442
+ // Burn
443
+ 9,
444
+ // CloseAccount
445
+ 12,
446
+ // TransferChecked
447
+ 13,
448
+ // ApproveChecked
449
+ 14,
450
+ // MintToChecked
451
+ 15
452
+ // BurnChecked
453
+ ]);
454
+ var EXPECTED_PLATFORM_CONFIG_VERSION = 1;
455
+ var VERSION_OFFSET = 8;
456
+ var MINT_OFFSET = 43;
457
+ var TREASURY_OFFSET = 75;
458
+ async function fetchPlatformConfig(connection, cluster) {
459
+ const programId = resolveProgramId(cluster);
460
+ const [pda] = PublicKey.findProgramAddressSync([Buffer.from("config")], programId);
243
461
  const info = await connection.getAccountInfo(pda);
244
462
  if (!info) throw new Error("PlatformConfig account not found on-chain");
463
+ const version = info.data[VERSION_OFFSET];
464
+ if (version !== EXPECTED_PLATFORM_CONFIG_VERSION) {
465
+ throw new Error(
466
+ `PlatformConfig version mismatch: expected ${String(EXPECTED_PLATFORM_CONFIG_VERSION)}, got ${String(version)}`
467
+ );
468
+ }
245
469
  return {
246
- mint: new PublicKey(info.data.slice(42, 74)),
247
- treasury: new PublicKey(info.data.slice(74, 106))
470
+ mint: new PublicKey(info.data.subarray(MINT_OFFSET, MINT_OFFSET + 32)),
471
+ treasury: new PublicKey(info.data.subarray(TREASURY_OFFSET, TREASURY_OFFSET + 32))
248
472
  };
249
473
  }
250
- async function signAndSend(base64Tx, wallet, connection) {
474
+ function validateTransaction(tx, cluster) {
475
+ const programId = resolveProgramId(cluster);
476
+ const allowed = allowedProgramIds(programId);
477
+ const tokenProgram = TOKEN_PROGRAM_ID.toBase58();
478
+ for (const ix of tx.instructions) {
479
+ const id = ix.programId.toBase58();
480
+ if (!allowed.has(id)) {
481
+ throw new Error(
482
+ `Unexpected program in transaction: ${id}. Allowed: ${[...allowed].join(", ")}`
483
+ );
484
+ }
485
+ if (id === tokenProgram) {
486
+ const tag = ix.data.length > 0 ? ix.data[0] : void 0;
487
+ if (tag !== void 0 && FORBIDDEN_TOKEN_IX_TAGS.has(tag)) {
488
+ throw new Error(
489
+ `Refusing to sign a fund-moving SPL Token instruction (tag ${String(tag)}) in a server-built transaction. Escro never requires the wallet to directly sign a token transfer/approve/burn/close; all token movement is a CPI inside the escro program.`
490
+ );
491
+ }
492
+ }
493
+ }
494
+ if (tx.instructions.length > MAX_INSTRUCTIONS) {
495
+ throw new Error(
496
+ `Too many instructions (${String(tx.instructions.length)}). Max allowed: ${String(MAX_INSTRUCTIONS)}`
497
+ );
498
+ }
499
+ }
500
+ async function signAndSend(base64Tx, wallet, connection, cluster) {
251
501
  const buf = Buffer.from(base64Tx, "base64");
252
502
  const tx = Transaction.from(buf);
503
+ validateTransaction(tx, cluster);
253
504
  tx.partialSign(wallet);
254
505
  const raw = tx.serialize();
506
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
255
507
  const signature = await connection.sendRawTransaction(raw, {
256
508
  skipPreflight: false
257
509
  });
258
- await connection.confirmTransaction(signature, "confirmed");
510
+ await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, "confirmed");
259
511
  return signature;
260
512
  }
261
513
  async function buildReleasePaymentTx(params) {
262
- const { buyer, escrowPda, worker, mint, treasury, connection } = params;
263
- const [platformConfig] = PublicKey.findProgramAddressSync(
264
- [Buffer.from("config")],
265
- PROGRAM_ID
266
- );
514
+ const { buyer, escrowPda, worker, mint, treasury, connection, cluster } = params;
515
+ const programId = resolveProgramId(cluster);
516
+ const [platformConfig] = PublicKey.findProgramAddressSync([Buffer.from("config")], programId);
267
517
  const vault = getAssociatedTokenAddressSync(
268
518
  mint,
269
519
  escrowPda,
@@ -281,16 +531,12 @@ async function buildReleasePaymentTx(params) {
281
531
  const treasuryAta = getAssociatedTokenAddressSync(
282
532
  mint,
283
533
  treasury,
284
- false,
534
+ true,
285
535
  TOKEN_PROGRAM_ID,
286
536
  ASSOCIATED_TOKEN_PROGRAM_ID
287
537
  );
288
- const [workerAtaInfo, treasuryAtaInfo] = await connection.getMultipleAccountsInfo([
289
- workerAta,
290
- treasuryAta
291
- ]);
292
538
  const ix = new TransactionInstruction({
293
- programId: PROGRAM_ID,
539
+ programId,
294
540
  keys: [
295
541
  { pubkey: buyer, isSigner: true, isWritable: true },
296
542
  { pubkey: platformConfig, isSigner: false, isWritable: false },
@@ -310,16 +556,8 @@ async function buildReleasePaymentTx(params) {
310
556
  const tx = new Transaction();
311
557
  tx.recentBlockhash = blockhash;
312
558
  tx.feePayer = buyer;
313
- if (!workerAtaInfo) {
314
- tx.add(
315
- createAssociatedTokenAccountInstruction(buyer, workerAta, worker, mint)
316
- );
317
- }
318
- if (!treasuryAtaInfo) {
319
- tx.add(
320
- createAssociatedTokenAccountInstruction(buyer, treasuryAta, treasury, mint)
321
- );
322
- }
559
+ tx.add(createAssociatedTokenAccountIdempotentInstruction(buyer, workerAta, worker, mint));
560
+ tx.add(createAssociatedTokenAccountIdempotentInstruction(buyer, treasuryAta, treasury, mint));
323
561
  tx.add(ix);
324
562
  return tx.serialize({ requireAllSignatures: false }).toString("base64");
325
563
  }
@@ -330,7 +568,10 @@ var Escro = class {
330
568
  __publicField(this, "connection");
331
569
  __publicField(this, "api");
332
570
  __publicField(this, "options");
571
+ __publicField(this, "cluster");
572
+ __publicField(this, "router", new EscrowPaymentRouter());
333
573
  this.options = options;
574
+ this.cluster = options.cluster;
334
575
  this.connection = new Connection2(options.rpcUrl, "confirmed");
335
576
  this.api = new ApiClient(options.apiUrl, options.wallet);
336
577
  }
@@ -343,12 +584,19 @@ var Escro = class {
343
584
  */
344
585
  async createEscrow(params) {
345
586
  const { escrowId, escrowPda, unsignedTx } = await this.api.createEscrow(params);
346
- const signature = await signAndSend(unsignedTx, this.options.wallet, this.connection);
587
+ const signature = await signAndSend(
588
+ unsignedTx,
589
+ this.options.wallet,
590
+ this.connection,
591
+ this.cluster
592
+ );
347
593
  return { escrowId, escrowPda, signature };
348
594
  }
349
595
  /**
350
596
  * Release payment to the worker. Buyer must be the wallet in the constructor.
351
- * Updates API DB state, then builds and submits the `release_payment` on-chain tx.
597
+ * Builds and submits the `release_payment` on-chain tx FIRST (the chain is the
598
+ * source of truth), then confirms DB state. The DB confirmation is best-effort:
599
+ * the oracle sweep reconciles it if it fails.
352
600
  *
353
601
  * @returns Solana transaction signature.
354
602
  */
@@ -357,17 +605,27 @@ var Escro = class {
357
605
  if (!escrow.taker) {
358
606
  throw new EscrowNotFoundError(`Escrow ${address} has no assigned worker`);
359
607
  }
360
- const { treasury, mint } = await fetchPlatformConfig(this.connection);
361
- await this.api.releasePaymentDb(address);
608
+ const { treasury, mint } = await fetchPlatformConfig(this.connection, this.cluster);
362
609
  const unsignedTx = await buildReleasePaymentTx({
363
610
  buyer: this.options.wallet.publicKey,
364
611
  escrowPda: new PublicKey2(address),
365
612
  worker: new PublicKey2(escrow.taker),
366
613
  mint,
367
614
  treasury,
368
- connection: this.connection
615
+ connection: this.connection,
616
+ cluster: this.cluster
369
617
  });
370
- return signAndSend(unsignedTx, this.options.wallet, this.connection);
618
+ const signature = await signAndSend(
619
+ unsignedTx,
620
+ this.options.wallet,
621
+ this.connection,
622
+ this.cluster
623
+ );
624
+ try {
625
+ await this.api.releasePaymentDb(address);
626
+ } catch {
627
+ }
628
+ return signature;
371
629
  }
372
630
  /**
373
631
  * Raise a dispute for an escrow. Either the buyer or the assigned worker
@@ -377,7 +635,7 @@ var Escro = class {
377
635
  */
378
636
  async raiseDispute(address, params) {
379
637
  const { unsignedTx } = await this.api.raiseDispute(address, params);
380
- return signAndSend(unsignedTx, this.options.wallet, this.connection);
638
+ return signAndSend(unsignedTx, this.options.wallet, this.connection, this.cluster);
381
639
  }
382
640
  /**
383
641
  * Cancel a funded escrow before any worker has claimed it.
@@ -388,7 +646,27 @@ var Escro = class {
388
646
  */
389
647
  async cancelEscrow(address) {
390
648
  const { unsignedTx } = await this.api.cancelEscrow(address);
391
- return signAndSend(unsignedTx, this.options.wallet, this.connection);
649
+ return signAndSend(unsignedTx, this.options.wallet, this.connection, this.cluster);
650
+ }
651
+ // ── Payments (dual-path; x402 reserved) ───────────────────────────────────────
652
+ /**
653
+ * Reserved synchronous-payment entrypoint (ARCHITECTURE §7a). `pay()` settles
654
+ * over the x402 rail, which is not implemented until Phase 9; today it rejects
655
+ * with {@link EscroRailNotEnabledError}. The signature is FROZEN so enabling
656
+ * x402 later is not a breaking change for kit-generated bots. For asynchronous,
657
+ * verification-needed work use {@link createEscrow}, the core product.
658
+ *
659
+ * Not `async`: it returns a rejected promise (never awaits) so callers can
660
+ * `await`/`.catch()` it uniformly, and it exercises the same {@link PaymentRouter}
661
+ * routing seam Phase 9 will implement.
662
+ */
663
+ pay(params) {
664
+ const rail = this.router.selectRail({
665
+ to: params.to,
666
+ amountMicroUsdc: params.amountMicroUsdc,
667
+ mode: "sync"
668
+ });
669
+ return Promise.reject(new EscroRailNotEnabledError(rail === "escrow" ? "x402" : rail));
392
670
  }
393
671
  // ── Worker Bot ───────────────────────────────────────────────────────────────
394
672
  /**
@@ -399,17 +677,17 @@ var Escro = class {
399
677
  */
400
678
  async claimTask(address) {
401
679
  const { unsignedTx } = await this.api.claimTask(address);
402
- return signAndSend(unsignedTx, this.options.wallet, this.connection);
680
+ return signAndSend(unsignedTx, this.options.wallet, this.connection, this.cluster);
403
681
  }
404
682
  /**
405
- * Submit a deliverable for oracle evaluation. Worker must be the wallet.
683
+ * Submit a deliverable for the buyer to review. Worker must be the wallet.
406
684
  * Signs and submits the `submit_deliverable` on-chain tx.
407
685
  *
408
686
  * @returns Solana transaction signature.
409
687
  */
410
688
  async submitDeliverable(address, params) {
411
689
  const { unsignedTx } = await this.api.submitDeliverable(address, params);
412
- return signAndSend(unsignedTx, this.options.wallet, this.connection);
690
+ return signAndSend(unsignedTx, this.options.wallet, this.connection, this.cluster);
413
691
  }
414
692
  /**
415
693
  * List tasks assigned to the wallet's public key.
@@ -440,6 +718,84 @@ var Escro = class {
440
718
  options.timeoutMs
441
719
  );
442
720
  }
721
+ /**
722
+ * List escrows CREATED by the wallet's public key (the maker / buyer), the
723
+ * mirror of {@link getMyTasks} for the buyer side. Optionally filter by state
724
+ * and paginate.
725
+ *
726
+ * A Buyer Bot uses this to sum its own outstanding (still-locked) escrows for
727
+ * the client-side spend cap: the program is the source of truth, so the total
728
+ * is read from chain state (via the API), never a local tally a crash could lose.
729
+ *
730
+ * @param state - Filter by one or more states (first state used as API filter).
731
+ * @param options - Polling, pagination (limit/offset).
732
+ */
733
+ async getMyEscrows(state, options) {
734
+ const maker = this.options.wallet.publicKey.toBase58();
735
+ const params = {
736
+ maker,
737
+ ...state?.[0] != null && { state: state[0] },
738
+ ...options?.limit != null && { limit: options.limit },
739
+ ...options?.offset != null && { offset: options.offset }
740
+ };
741
+ if (!options?.poll) {
742
+ return this.api.listEscrows(params);
743
+ }
744
+ const fetcher = () => this.api.listEscrows(params).then((result) => result.items.length > 0 ? result : null);
745
+ return pollUntil(
746
+ maker,
747
+ fetcher,
748
+ (r) => r !== null,
749
+ options.timeoutMs
750
+ );
751
+ }
752
+ // ── EscroBots discovery ──────────────────────────────────────────────────────
753
+ /**
754
+ * Find worker bots in the EscroBots registry, optionally filtered by
755
+ * capability. Queries `GET /v1/bots?role=worker` (the API includes `both`
756
+ * bots) and returns candidates in REGISTRY ORDER: this method does no
757
+ * ranking, scoring, or reordering (ARCHITECTURE §8: no curation; §13: not a
758
+ * marketplace). Escro-shipped code never ranks; the caller's own bot decides.
759
+ *
760
+ * Each candidate keeps the CLAIM (`descriptor`) separate from the AUTHORSHIP
761
+ * check (`signatureValid`, verified locally so a third party need not trust the
762
+ * API). Capabilities remain unverified claims; on-chain history (EscroRep,
763
+ * Phase 7) is their verification, deliberately not merged in here.
764
+ */
765
+ async findWorkers(params = {}) {
766
+ const { items } = await this.api.listBots({
767
+ role: "worker",
768
+ ...params.capability != null && { capability: params.capability },
769
+ ...params.limit != null && { limit: params.limit },
770
+ ...params.offset != null && { offset: params.offset }
771
+ });
772
+ return items.map((descriptor) => ({
773
+ descriptor,
774
+ signatureValid: verifyBotDescriptorSignature(descriptor)
775
+ }));
776
+ }
777
+ /**
778
+ * Fetch the EscroRep bulk export: every indexed wallet's RAW reputation
779
+ * components (ARCHITECTURE §9), each wallet exactly once, in one fetch. Returns
780
+ * the raw components verbatim - this method does NO ranking, scoring, or default
781
+ * weighting (Escro-shipped code never ranks; the caller's own bot weights and
782
+ * decides). Selection layers join these components to {@link findWorkers}
783
+ * candidates by wallet. Throws {@link ReputationUnavailableError} when the
784
+ * deployment mounts no reputation source (HTTP 404).
785
+ */
786
+ async listReputations() {
787
+ return this.api.listReputations();
788
+ }
789
+ // ── Stats ────────────────────────────────────────────────────────────────────
790
+ /**
791
+ * Fetch protocol-wide stats from the public `GET /v1/stats` endpoint.
792
+ *
793
+ * @returns protocol-wide {@link ProtocolStats}.
794
+ */
795
+ async getStats() {
796
+ const data = await this.api.getStats();
797
+ return data;
798
+ }
443
799
  // ── Shared ───────────────────────────────────────────────────────────────────
444
800
  /**
445
801
  * Fetch an escrow by its PDA address.
@@ -452,18 +808,1355 @@ var Escro = class {
452
808
  }
453
809
  return pollUntil(
454
810
  address,
455
- () => this.api.getEscrow(address).catch((e) => e instanceof EscrowNotFoundError ? null : Promise.reject(e)),
811
+ () => this.api.getEscrow(address).catch((e) => {
812
+ if (e instanceof EscrowNotFoundError) return null;
813
+ throw e instanceof Error ? e : new Error(String(e));
814
+ }),
456
815
  (r) => r !== null,
457
816
  options.timeoutMs
458
817
  );
459
818
  }
460
819
  };
820
+
821
+ // src/adapter.ts
822
+ function serviceResultToSubmitParams(result) {
823
+ return {
824
+ contentHash: result.contentHash,
825
+ ...result.proofUri !== void 0 && { proofUri: result.proofUri }
826
+ };
827
+ }
828
+
829
+ // src/adapter-contract.ts
830
+ import { createHash as createHash3 } from "crypto";
831
+ var AdapterContractError = class extends EscroError {
832
+ constructor(adapterId, violations) {
833
+ super(
834
+ `ServiceAdapter "${adapterId}" violates the adapter contract:
835
+ - ${violations.join("\n- ")}`,
836
+ "ADAPTER_CONTRACT_VIOLATION"
837
+ );
838
+ __publicField(this, "violations", violations);
839
+ this.name = "AdapterContractError";
840
+ }
841
+ };
842
+ function errMessage(err) {
843
+ return err instanceof Error ? err.message : String(err);
844
+ }
845
+ function sha256Hex(content) {
846
+ return createHash3("sha256").update(content).digest("hex");
847
+ }
848
+ function validateServiceResult(result) {
849
+ if (typeof result !== "object" || result === null) {
850
+ return ["`execute()` must resolve to a ServiceResult object"];
851
+ }
852
+ const violations = [];
853
+ const r = result;
854
+ const contentIsValid = typeof r.content === "string" || Buffer.isBuffer(r.content);
855
+ if (!contentIsValid) {
856
+ violations.push("`content` must be a string or Buffer");
857
+ }
858
+ if (typeof r.contentHash !== "string" || r.contentHash.trim() === "") {
859
+ violations.push("`contentHash` must be a non-empty string");
860
+ } else if (contentIsValid && r.contentHash !== sha256Hex(r.content)) {
861
+ violations.push("`contentHash` must equal the SHA-256 hex digest of `content`");
862
+ }
863
+ if (r.proofUri !== void 0 && typeof r.proofUri !== "string") {
864
+ violations.push("`proofUri`, when present, must be a string");
865
+ }
866
+ if (typeof r.metadata !== "object" || r.metadata === null || Array.isArray(r.metadata)) {
867
+ violations.push("`metadata` must be a plain object");
868
+ }
869
+ return violations;
870
+ }
871
+ async function assertServiceAdapterContract(adapter, options) {
872
+ const a = adapter;
873
+ const violations = [];
874
+ const id = typeof a.id === "string" && a.id.trim() !== "" ? a.id : "(unknown)";
875
+ if (typeof a.id !== "string" || a.id.trim() === "") {
876
+ violations.push("`id` must be a non-empty string");
877
+ }
878
+ if (!Array.isArray(a.capabilities) || a.capabilities.length === 0 || a.capabilities.some((c) => typeof c !== "string" || c.trim() === "")) {
879
+ violations.push("`capabilities` must be a non-empty array of non-empty strings");
880
+ }
881
+ if (typeof a.healthCheck !== "function") {
882
+ violations.push("`healthCheck` must be a function");
883
+ }
884
+ if (typeof a.execute !== "function") {
885
+ violations.push("`execute` must be a function");
886
+ }
887
+ if (typeof a.healthCheck === "function") {
888
+ try {
889
+ const healthy = await a.healthCheck();
890
+ if (typeof healthy !== "boolean") {
891
+ violations.push("`healthCheck()` must resolve to a boolean");
892
+ }
893
+ } catch (err) {
894
+ violations.push(`\`healthCheck()\` threw: ${errMessage(err)}`);
895
+ }
896
+ }
897
+ let result;
898
+ if (typeof a.execute === "function") {
899
+ try {
900
+ result = await a.execute(options.sampleSpec);
901
+ violations.push(...validateServiceResult(result));
902
+ } catch (err) {
903
+ violations.push(`\`execute()\` threw: ${errMessage(err)}`);
904
+ result = void 0;
905
+ }
906
+ }
907
+ if (violations.length > 0 || result === void 0) {
908
+ throw new AdapterContractError(
909
+ id,
910
+ violations.length > 0 ? violations : ["`execute()` produced no result"]
911
+ );
912
+ }
913
+ return result;
914
+ }
915
+
916
+ // src/adapters/bittensor/index.ts
917
+ import { createHash as createHash4 } from "crypto";
918
+ function tao(amount) {
919
+ return amount;
920
+ }
921
+ function sha256Hex2(content) {
922
+ return createHash4("sha256").update(content).digest("hex");
923
+ }
924
+ var REFERENCE_MICRO_USDC_PER_TAO = 3e8;
925
+ var defaultSwap = (microUsdc) => Promise.resolve(tao(microUsdc / REFERENCE_MICRO_USDC_PER_TAO));
926
+ var defaultPin = (content) => Promise.resolve(`ipfs://placeholder-${sha256Hex2(content).slice(0, 16)}`);
927
+ function createBittensorSubnetAdapter(config) {
928
+ const id = `bittensor:${config.subnet}`;
929
+ return {
930
+ id,
931
+ capabilities: config.capabilities,
932
+ healthCheck: config.checkHealth,
933
+ async execute(spec) {
934
+ const budgetTao = await config.swap(config.taskBudgetMicroUsdc);
935
+ const content = await config.callSubnet(spec, budgetTao);
936
+ const contentHash = sha256Hex2(content);
937
+ const proofUri = await config.pin(content);
938
+ return {
939
+ content,
940
+ contentHash,
941
+ proofUri,
942
+ metadata: { adapter: id, subnet: config.subnet }
943
+ };
944
+ }
945
+ };
946
+ }
947
+
948
+ // src/adapters/gradients/index.ts
949
+ var GRADIENTS_ADAPTER_ID = "bittensor:sn56";
950
+ var GRADIENTS_CAPABILITIES = ["fine-tune"];
951
+ var DEFAULT_SUBNET = "sn56";
952
+ var DEFAULT_TASK_BUDGET_MICRO_USDC = 5e6;
953
+ var GRADIENTS_DEMO_MARKER = "gradients-sn56-demo-v1";
954
+ var GRADIENTS_DEMO_EVAL_FLOOR = 0.8;
955
+ var DEMO_DATASET = [3, 7, 11, 15, 19];
956
+ function demoExpectedOutputs() {
957
+ return DEMO_DATASET.map((x) => 2 * x + 1);
958
+ }
959
+ var gradientsDemoJobSpec = {
960
+ version: "1.0.0",
961
+ // TaskType has no fine-tune category; "custom" is the canonical fallback.
962
+ taskType: "custom",
963
+ description: "Fine-tune the reference model on the canned SN56 demo dataset and return its predictions on the same inputs. The target map is y = 2x + 1. Acceptance is deterministic and recomputed by the buyer: prediction accuracy must be >= 0.8; the worker's self-reported eval score is not trusted.",
964
+ acceptanceCriteria: [
965
+ {
966
+ id: "ac-eval-floor",
967
+ description: "Prediction accuracy on the demo dataset is >= 0.8, recomputed by the buyer from the deliverable.",
968
+ required: true
969
+ }
970
+ ],
971
+ deliverableFormat: { type: "json" },
972
+ metadata: {
973
+ demo: GRADIENTS_DEMO_MARKER,
974
+ dataset: [...DEMO_DATASET],
975
+ target: "y = 2x + 1",
976
+ evalFloor: GRADIENTS_DEMO_EVAL_FLOOR
977
+ }
978
+ };
979
+ function solveGradientsDemo() {
980
+ const predictions = demoExpectedOutputs();
981
+ return canonicalize({
982
+ checkpoint: "reference-sn56-demo-checkpoint",
983
+ eval: { metric: "accuracy", score: 1 },
984
+ predictions
985
+ });
986
+ }
987
+ function parseDemoDeliverable(content) {
988
+ const text = Buffer.isBuffer(content) ? content.toString("utf8") : content;
989
+ let parsed;
990
+ try {
991
+ parsed = JSON.parse(text);
992
+ } catch {
993
+ return null;
994
+ }
995
+ if (typeof parsed !== "object" || parsed === null) {
996
+ return null;
997
+ }
998
+ const predictions = parsed.predictions;
999
+ if (!Array.isArray(predictions) || predictions.some((p) => typeof p !== "number")) {
1000
+ return null;
1001
+ }
1002
+ return { predictions };
1003
+ }
1004
+ function scoreGradientsDemoDeliverable(content) {
1005
+ const parsed = parseDemoDeliverable(content);
1006
+ if (parsed === null) {
1007
+ return 0;
1008
+ }
1009
+ const expected = demoExpectedOutputs();
1010
+ let matches = 0;
1011
+ for (let i = 0; i < expected.length; i++) {
1012
+ if (parsed.predictions[i] === expected[i]) {
1013
+ matches++;
1014
+ }
1015
+ }
1016
+ return matches / expected.length;
1017
+ }
1018
+ function referencePlaceholder(spec) {
1019
+ return canonicalize({
1020
+ checkpoint: "reference-sn56-checkpoint",
1021
+ eval: { metric: "accuracy", score: 0 },
1022
+ note: "reference stub - replace the callSubnet port with a real SN56 miner call.",
1023
+ taskType: spec.taskType
1024
+ });
1025
+ }
1026
+ var defaultCallSubnet = (spec) => Promise.resolve(
1027
+ spec.metadata.demo === GRADIENTS_DEMO_MARKER ? solveGradientsDemo() : referencePlaceholder(spec)
1028
+ );
1029
+ function createGradientsAdapter(config = {}) {
1030
+ return createBittensorSubnetAdapter({
1031
+ subnet: config.subnet ?? DEFAULT_SUBNET,
1032
+ capabilities: config.capabilities ?? [...GRADIENTS_CAPABILITIES],
1033
+ taskBudgetMicroUsdc: config.taskBudgetMicroUsdc ?? DEFAULT_TASK_BUDGET_MICRO_USDC,
1034
+ swap: config.swap ?? defaultSwap,
1035
+ callSubnet: config.callSubnet ?? defaultCallSubnet,
1036
+ pin: config.pin ?? defaultPin,
1037
+ checkHealth: config.checkHealth ?? (() => Promise.resolve(true))
1038
+ });
1039
+ }
1040
+
1041
+ // src/adapters/ridges/index.ts
1042
+ import { createHash as createHash5 } from "crypto";
1043
+ var RIDGES_ADAPTER_ID = "ridges";
1044
+ var RIDGES_CAPABILITIES = ["code-generation"];
1045
+ var DEFAULT_SERVICE = "ridges";
1046
+ function sha256Hex3(content) {
1047
+ return createHash5("sha256").update(content).digest("hex");
1048
+ }
1049
+ var RIDGES_DEMO_MARKER = "ridges-demo-v1";
1050
+ var RIDGES_DEMO_COVERAGE_TARGET = 0.8;
1051
+ var DEMO_INPUTS = [1, 2, 3, 5, 15];
1052
+ function fizzbuzz(n) {
1053
+ if (n % 15 === 0) return "FizzBuzz";
1054
+ if (n % 3 === 0) return "Fizz";
1055
+ if (n % 5 === 0) return "Buzz";
1056
+ return String(n);
1057
+ }
1058
+ var ridgesDemoJobSpec = {
1059
+ version: "1.0.0",
1060
+ taskType: "code_generation",
1061
+ description: "Implement FizzBuzz for the fixed demo battery and return a test report: for each battery input, the output your implementation produced. Acceptance is deterministic and recomputed by the buyer: every delivered test must pass (exit 0) and coverage of the battery must be >= 0.8; the worker's self-reported pass/coverage is not trusted.",
1062
+ acceptanceCriteria: [
1063
+ {
1064
+ id: "ac-tests-pass",
1065
+ description: "Every delivered test passes (exit 0), recomputed by the buyer from the test report.",
1066
+ required: true
1067
+ },
1068
+ {
1069
+ id: "ac-coverage",
1070
+ description: "Coverage of the demo battery is >= 0.8, recomputed by the buyer.",
1071
+ required: true
1072
+ }
1073
+ ],
1074
+ deliverableFormat: { type: "json" },
1075
+ metadata: {
1076
+ demo: RIDGES_DEMO_MARKER,
1077
+ kata: "fizzbuzz",
1078
+ inputs: [...DEMO_INPUTS],
1079
+ coverageTarget: RIDGES_DEMO_COVERAGE_TARGET
1080
+ }
1081
+ };
1082
+ var REFERENCE_SOLUTION = 'export function fizzbuzz(n: number): string {\n if (n % 15 === 0) return "FizzBuzz";\n if (n % 3 === 0) return "Fizz";\n if (n % 5 === 0) return "Buzz";\n return String(n);\n}\n';
1083
+ function solveRidgesDemo() {
1084
+ const tests = DEMO_INPUTS.map((input) => ({ input, output: fizzbuzz(input) }));
1085
+ return canonicalize({
1086
+ language: "typescript",
1087
+ solution: REFERENCE_SOLUTION,
1088
+ coverage: 1,
1089
+ passed: true,
1090
+ tests
1091
+ });
1092
+ }
1093
+ function parseTestReport(content) {
1094
+ const text = Buffer.isBuffer(content) ? content.toString("utf8") : content;
1095
+ let parsed;
1096
+ try {
1097
+ parsed = JSON.parse(text);
1098
+ } catch {
1099
+ return null;
1100
+ }
1101
+ if (typeof parsed !== "object" || parsed === null) {
1102
+ return null;
1103
+ }
1104
+ const tests = parsed.tests;
1105
+ if (!Array.isArray(tests)) {
1106
+ return null;
1107
+ }
1108
+ const results = [];
1109
+ for (const t of tests) {
1110
+ if (typeof t !== "object" || t === null) {
1111
+ return null;
1112
+ }
1113
+ const { input, output } = t;
1114
+ if (typeof input !== "number" || typeof output !== "string") {
1115
+ return null;
1116
+ }
1117
+ results.push({ input, output });
1118
+ }
1119
+ return results;
1120
+ }
1121
+ function scoreRidgesDemoDeliverable(content) {
1122
+ const results = parseTestReport(content);
1123
+ if (results === null) {
1124
+ return 0;
1125
+ }
1126
+ const battery = new Set(DEMO_INPUTS);
1127
+ const coveredInputs = /* @__PURE__ */ new Set();
1128
+ for (const { input, output } of results) {
1129
+ if (!battery.has(input)) {
1130
+ continue;
1131
+ }
1132
+ if (output !== fizzbuzz(input)) {
1133
+ return 0;
1134
+ }
1135
+ coveredInputs.add(input);
1136
+ }
1137
+ return coveredInputs.size / DEMO_INPUTS.length;
1138
+ }
1139
+ function referencePlaceholder2(spec) {
1140
+ return canonicalize({
1141
+ language: "typescript",
1142
+ solution: "// reference stub - replace the runAgent port with a real Ridges agent call.",
1143
+ passed: false,
1144
+ tests: [],
1145
+ taskType: spec.taskType
1146
+ });
1147
+ }
1148
+ var defaultRunAgent = (spec) => Promise.resolve(
1149
+ spec.metadata.demo === RIDGES_DEMO_MARKER ? solveRidgesDemo() : referencePlaceholder2(spec)
1150
+ );
1151
+ var defaultPin2 = (content) => Promise.resolve(`ipfs://placeholder-${sha256Hex3(content).slice(0, 16)}`);
1152
+ function createRidgesAdapter(config = {}) {
1153
+ const service = config.service ?? DEFAULT_SERVICE;
1154
+ const capabilities = config.capabilities ?? [...RIDGES_CAPABILITIES];
1155
+ const runAgent = config.runAgent ?? defaultRunAgent;
1156
+ const pin = config.pin ?? defaultPin2;
1157
+ const checkHealth = config.checkHealth ?? (() => Promise.resolve(true));
1158
+ return {
1159
+ id: service,
1160
+ capabilities,
1161
+ healthCheck: checkHealth,
1162
+ async execute(spec) {
1163
+ const content = await runAgent(spec);
1164
+ const contentHash = sha256Hex3(content);
1165
+ const proofUri = await pin(content);
1166
+ return {
1167
+ content,
1168
+ contentHash,
1169
+ proofUri,
1170
+ metadata: { adapter: service }
1171
+ };
1172
+ }
1173
+ };
1174
+ }
1175
+
1176
+ // src/adapters/404gen/index.ts
1177
+ var GEN404_ADAPTER_ID = "bittensor:sn17";
1178
+ var GEN404_CAPABILITIES = ["3d-generation"];
1179
+ var DEFAULT_SUBNET2 = "sn17";
1180
+ var DEFAULT_TASK_BUDGET_MICRO_USDC2 = 5e6;
1181
+ var GEN404_DEMO_MARKER = "404gen-sn17-demo-v1";
1182
+ var GEN404_DEMO_EVAL_FLOOR = 1;
1183
+ var DEMO_EXPECTATION = {
1184
+ requestedAssets: 1,
1185
+ allowedFormats: ["GLB", "PLY"],
1186
+ minPolygons: 1e3,
1187
+ maxPolygons: 1e6
1188
+ };
1189
+ var gen404DemoJobSpec = {
1190
+ version: "1.0.0",
1191
+ // TaskType has no 3D category; "custom" is the canonical fallback.
1192
+ taskType: "custom",
1193
+ description: "Generate the canned SN17 demo batch: one 3D asset in a GLB or PLY container with a polygon count between 1000 and 1000000. Acceptance is deterministic and recomputed by the buyer: every requested asset must be present, in an allowed format, and in polygon range; the worker's self-reported validity is not trusted.",
1194
+ acceptanceCriteria: [
1195
+ {
1196
+ id: "ac-format-valid",
1197
+ description: "Every asset is in an allowed container format (GLB or PLY), recomputed by the buyer.",
1198
+ required: true
1199
+ },
1200
+ {
1201
+ id: "ac-polygon-range",
1202
+ description: "Every asset's polygon count is within [1000, 1000000], recomputed by the buyer.",
1203
+ required: true
1204
+ },
1205
+ {
1206
+ id: "ac-batch-complete",
1207
+ description: "The batch contains all requested assets (completeness), recomputed by the buyer.",
1208
+ required: true
1209
+ }
1210
+ ],
1211
+ deliverableFormat: { type: "json" },
1212
+ metadata: {
1213
+ demo: GEN404_DEMO_MARKER,
1214
+ requestedAssets: DEMO_EXPECTATION.requestedAssets,
1215
+ allowedFormats: [...DEMO_EXPECTATION.allowedFormats],
1216
+ minPolygons: DEMO_EXPECTATION.minPolygons,
1217
+ maxPolygons: DEMO_EXPECTATION.maxPolygons
1218
+ }
1219
+ };
1220
+ function solveGen404Demo() {
1221
+ return canonicalize({
1222
+ assets: [{ format: "GLB", polygonCount: 5e3, uri: "ipfs://404gen-sn17-demo-asset-0" }],
1223
+ eval: { metric: "validity", score: 1 }
1224
+ });
1225
+ }
1226
+ function parseBatch(content) {
1227
+ const text = Buffer.isBuffer(content) ? content.toString("utf8") : content;
1228
+ let parsed;
1229
+ try {
1230
+ parsed = JSON.parse(text);
1231
+ } catch {
1232
+ return null;
1233
+ }
1234
+ if (typeof parsed !== "object" || parsed === null) {
1235
+ return null;
1236
+ }
1237
+ const assets = parsed.assets;
1238
+ if (!Array.isArray(assets)) {
1239
+ return null;
1240
+ }
1241
+ const cleaned = [];
1242
+ for (const asset of assets) {
1243
+ if (typeof asset !== "object" || asset === null) {
1244
+ return null;
1245
+ }
1246
+ const format = asset.format;
1247
+ const polygonCount = asset.polygonCount;
1248
+ if (typeof format !== "string" || typeof polygonCount !== "number") {
1249
+ return null;
1250
+ }
1251
+ cleaned.push({ format, polygonCount });
1252
+ }
1253
+ return { assets: cleaned };
1254
+ }
1255
+ function isAssetValid(asset) {
1256
+ const formatOk = DEMO_EXPECTATION.allowedFormats.some(
1257
+ (f) => f.toLowerCase() === asset.format.toLowerCase()
1258
+ );
1259
+ const rangeOk = asset.polygonCount >= DEMO_EXPECTATION.minPolygons && asset.polygonCount <= DEMO_EXPECTATION.maxPolygons;
1260
+ return formatOk && rangeOk;
1261
+ }
1262
+ function score404genDemoDeliverable(content) {
1263
+ const batch = parseBatch(content);
1264
+ if (batch === null) {
1265
+ return 0;
1266
+ }
1267
+ if (batch.assets.some((asset) => !isAssetValid(asset))) {
1268
+ return 0;
1269
+ }
1270
+ return Math.min(batch.assets.length / DEMO_EXPECTATION.requestedAssets, 1);
1271
+ }
1272
+ function referencePlaceholder3(spec) {
1273
+ return canonicalize({
1274
+ assets: [{ format: "GLB", polygonCount: 0, uri: "ipfs://placeholder" }],
1275
+ note: "reference stub - replace the callSubnet port with a real SN17 miner call.",
1276
+ taskType: spec.taskType
1277
+ });
1278
+ }
1279
+ var defaultCallSubnet2 = (spec) => Promise.resolve(
1280
+ spec.metadata.demo === GEN404_DEMO_MARKER ? solveGen404Demo() : referencePlaceholder3(spec)
1281
+ );
1282
+ function create404genAdapter(config = {}) {
1283
+ return createBittensorSubnetAdapter({
1284
+ subnet: config.subnet ?? DEFAULT_SUBNET2,
1285
+ capabilities: config.capabilities ?? [...GEN404_CAPABILITIES],
1286
+ taskBudgetMicroUsdc: config.taskBudgetMicroUsdc ?? DEFAULT_TASK_BUDGET_MICRO_USDC2,
1287
+ swap: config.swap ?? defaultSwap,
1288
+ callSubnet: config.callSubnet ?? defaultCallSubnet2,
1289
+ pin: config.pin ?? defaultPin,
1290
+ checkHealth: config.checkHealth ?? (() => Promise.resolve(true))
1291
+ });
1292
+ }
1293
+
1294
+ // src/adapters/lium/index.ts
1295
+ var LIUM_ADAPTER_ID = "bittensor:sn51";
1296
+ var LIUM_CAPABILITIES = ["gpu-compute"];
1297
+ var DEFAULT_SUBNET3 = "sn51";
1298
+ var DEFAULT_TASK_BUDGET_MICRO_USDC3 = 2e7;
1299
+ var LIUM_DEMO_MARKER = "lium-sn51-demo-v1";
1300
+ var LIUM_DEMO_ACCEPT_FLOOR = 1;
1301
+ var DEMO_SEED = "lium-sn51-demo-seed";
1302
+ var DEMO_ROUNDS = 1e3;
1303
+ function computeLiumDemoArtifact() {
1304
+ let h = DEMO_SEED;
1305
+ for (let i = 0; i < DEMO_ROUNDS; i++) {
1306
+ h = sha256Hex2(h);
1307
+ }
1308
+ return h;
1309
+ }
1310
+ var liumDemoJobSpec = {
1311
+ version: "1.0.0",
1312
+ // TaskType has no GPU-compute category; "custom" is the canonical fallback.
1313
+ taskType: "custom",
1314
+ description: "Run the canned SN51 scripted GPU job (a fixed hash-chain compute over a known seed) and return its output artifact. Acceptance is deterministic and recomputed by the buyer: the job must complete, an output artifact must be present, and the artifact must match the buyer's independent recompute; the worker's self-reported artifact hash is not trusted.",
1315
+ acceptanceCriteria: [
1316
+ {
1317
+ id: "ac-completion",
1318
+ description: "The scripted job ran to completion (status=completed).",
1319
+ required: true
1320
+ },
1321
+ {
1322
+ id: "ac-artifact-present",
1323
+ description: "An output artifact is present in the deliverable.",
1324
+ required: true
1325
+ },
1326
+ {
1327
+ id: "ac-hash-match",
1328
+ description: "The output artifact matches the buyer's independent recompute of the scripted job.",
1329
+ required: true
1330
+ }
1331
+ ],
1332
+ deliverableFormat: { type: "json" },
1333
+ metadata: {
1334
+ demo: LIUM_DEMO_MARKER,
1335
+ seed: DEMO_SEED,
1336
+ rounds: DEMO_ROUNDS,
1337
+ acceptFloor: LIUM_DEMO_ACCEPT_FLOOR
1338
+ }
1339
+ };
1340
+ function solveLiumDemo() {
1341
+ const artifact = computeLiumDemoArtifact();
1342
+ return canonicalize({
1343
+ job: LIUM_DEMO_MARKER,
1344
+ status: "completed",
1345
+ rounds: DEMO_ROUNDS,
1346
+ artifact,
1347
+ // Self-reported and deliberately ignored by the buyer's recompute.
1348
+ artifactHash: sha256Hex2(artifact)
1349
+ });
1350
+ }
1351
+ function parseDemoDeliverable2(content) {
1352
+ const text = Buffer.isBuffer(content) ? content.toString("utf8") : content;
1353
+ let parsed;
1354
+ try {
1355
+ parsed = JSON.parse(text);
1356
+ } catch {
1357
+ return null;
1358
+ }
1359
+ if (typeof parsed !== "object" || parsed === null) {
1360
+ return null;
1361
+ }
1362
+ const obj = parsed;
1363
+ const status = typeof obj.status === "string" ? obj.status : "";
1364
+ const artifact = typeof obj.artifact === "string" ? obj.artifact : null;
1365
+ return { status, artifact };
1366
+ }
1367
+ function scoreLiumDemoDeliverable(content) {
1368
+ const parsed = parseDemoDeliverable2(content);
1369
+ if (parsed === null) {
1370
+ return 0;
1371
+ }
1372
+ if (parsed.status !== "completed") {
1373
+ return 0;
1374
+ }
1375
+ if (parsed.artifact === null) {
1376
+ return 0;
1377
+ }
1378
+ return parsed.artifact === computeLiumDemoArtifact() ? 1 : 0;
1379
+ }
1380
+ function referencePlaceholder4(spec) {
1381
+ return canonicalize({
1382
+ job: "lium-sn51-generic",
1383
+ status: "completed",
1384
+ artifact: null,
1385
+ note: "reference stub - replace the runJob port with a real SN51 GPU-rental call.",
1386
+ taskType: spec.taskType
1387
+ });
1388
+ }
1389
+ var defaultRunJob = (spec) => Promise.resolve(
1390
+ spec.metadata.demo === LIUM_DEMO_MARKER ? solveLiumDemo() : referencePlaceholder4(spec)
1391
+ );
1392
+ function createLiumAdapter(config = {}) {
1393
+ return createBittensorSubnetAdapter({
1394
+ subnet: config.subnet ?? DEFAULT_SUBNET3,
1395
+ capabilities: config.capabilities ?? [...LIUM_CAPABILITIES],
1396
+ taskBudgetMicroUsdc: config.taskBudgetMicroUsdc ?? DEFAULT_TASK_BUDGET_MICRO_USDC3,
1397
+ swap: config.swap ?? defaultSwap,
1398
+ callSubnet: config.runJob ?? defaultRunJob,
1399
+ pin: config.pin ?? defaultPin,
1400
+ checkHealth: config.checkHealth ?? (() => Promise.resolve(true))
1401
+ });
1402
+ }
1403
+
1404
+ // src/adapters/akash/index.ts
1405
+ import { createHash as createHash6 } from "crypto";
1406
+ function akt(amount) {
1407
+ return amount;
1408
+ }
1409
+ function sha256Hex4(content) {
1410
+ return createHash6("sha256").update(content).digest("hex");
1411
+ }
1412
+ var AKASH_ADAPTER_ID = "akash";
1413
+ var AKASH_CAPABILITIES = ["cloud-compute"];
1414
+ var DEFAULT_TASK_BUDGET_MICRO_USDC4 = 1e7;
1415
+ var REFERENCE_MICRO_USDC_PER_AKT = 3e6;
1416
+ var defaultSwap2 = (microUsdc) => Promise.resolve(akt(microUsdc / REFERENCE_MICRO_USDC_PER_AKT));
1417
+ var defaultPin3 = (content) => Promise.resolve(`ipfs://placeholder-${sha256Hex4(content).slice(0, 16)}`);
1418
+ var AKASH_DEMO_MARKER = "akash-demo-v1";
1419
+ var AKASH_DEMO_ACCEPT_FLOOR = 1;
1420
+ var DEMO_SEED2 = "akash-demo-seed";
1421
+ var DEMO_ROUNDS2 = 1e3;
1422
+ function computeAkashDemoArtifact() {
1423
+ let h = DEMO_SEED2;
1424
+ for (let i = 0; i < DEMO_ROUNDS2; i++) {
1425
+ h = sha256Hex4(h);
1426
+ }
1427
+ return h;
1428
+ }
1429
+ var akashDemoJobSpec = {
1430
+ version: "1.0.0",
1431
+ // TaskType has no cloud-compute category; "custom" is the canonical fallback.
1432
+ taskType: "custom",
1433
+ description: "Run the canned Akash scripted batch job (a fixed hash-chain compute over a known seed) on a deployment and return its output artifact. Acceptance is deterministic and recomputed by the buyer: the job must complete, an output artifact must be present, and the artifact must match the buyer's independent recompute; the worker's self-reported artifact hash is not trusted.",
1434
+ acceptanceCriteria: [
1435
+ {
1436
+ id: "ac-completion",
1437
+ description: "The scripted job ran to completion (status=completed).",
1438
+ required: true
1439
+ },
1440
+ {
1441
+ id: "ac-artifact-present",
1442
+ description: "An output artifact is present in the deliverable.",
1443
+ required: true
1444
+ },
1445
+ {
1446
+ id: "ac-hash-match",
1447
+ description: "The output artifact matches the buyer's independent recompute of the scripted job.",
1448
+ required: true
1449
+ }
1450
+ ],
1451
+ deliverableFormat: { type: "json" },
1452
+ metadata: {
1453
+ demo: AKASH_DEMO_MARKER,
1454
+ seed: DEMO_SEED2,
1455
+ rounds: DEMO_ROUNDS2,
1456
+ acceptFloor: AKASH_DEMO_ACCEPT_FLOOR
1457
+ }
1458
+ };
1459
+ function solveAkashDemo() {
1460
+ const artifact = computeAkashDemoArtifact();
1461
+ return canonicalize({
1462
+ job: AKASH_DEMO_MARKER,
1463
+ status: "completed",
1464
+ rounds: DEMO_ROUNDS2,
1465
+ artifact,
1466
+ // Self-reported and deliberately ignored by the buyer's recompute.
1467
+ artifactHash: sha256Hex4(artifact)
1468
+ });
1469
+ }
1470
+ function parseDemoDeliverable3(content) {
1471
+ const text = Buffer.isBuffer(content) ? content.toString("utf8") : content;
1472
+ let parsed;
1473
+ try {
1474
+ parsed = JSON.parse(text);
1475
+ } catch {
1476
+ return null;
1477
+ }
1478
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1479
+ return null;
1480
+ }
1481
+ const obj = parsed;
1482
+ const status = typeof obj.status === "string" ? obj.status : "";
1483
+ const artifact = typeof obj.artifact === "string" ? obj.artifact : null;
1484
+ return { status, artifact };
1485
+ }
1486
+ function scoreAkashDemoDeliverable(content) {
1487
+ const parsed = parseDemoDeliverable3(content);
1488
+ if (parsed === null) {
1489
+ return 0;
1490
+ }
1491
+ if (parsed.status !== "completed") {
1492
+ return 0;
1493
+ }
1494
+ if (parsed.artifact === null) {
1495
+ return 0;
1496
+ }
1497
+ return parsed.artifact === computeAkashDemoArtifact() ? 1 : 0;
1498
+ }
1499
+ function referencePlaceholder5(spec) {
1500
+ return canonicalize({
1501
+ job: "akash-generic",
1502
+ status: "completed",
1503
+ artifact: null,
1504
+ note: "reference stub - replace the runDeployment port with a real Akash deployment call.",
1505
+ taskType: spec.taskType
1506
+ });
1507
+ }
1508
+ var defaultRunDeployment = (spec) => Promise.resolve(
1509
+ spec.metadata.demo === AKASH_DEMO_MARKER ? solveAkashDemo() : referencePlaceholder5(spec)
1510
+ );
1511
+ function createAkashAdapter(config = {}) {
1512
+ const capabilities = config.capabilities ?? [...AKASH_CAPABILITIES];
1513
+ const taskBudgetMicroUsdc = config.taskBudgetMicroUsdc ?? DEFAULT_TASK_BUDGET_MICRO_USDC4;
1514
+ const swap = config.swap ?? defaultSwap2;
1515
+ const runDeployment = config.runDeployment ?? defaultRunDeployment;
1516
+ const pin = config.pin ?? defaultPin3;
1517
+ const checkHealth = config.checkHealth ?? (() => Promise.resolve(true));
1518
+ return {
1519
+ id: AKASH_ADAPTER_ID,
1520
+ capabilities,
1521
+ healthCheck: checkHealth,
1522
+ async execute(spec) {
1523
+ const budgetAkt = await swap(taskBudgetMicroUsdc);
1524
+ const content = await runDeployment(spec, budgetAkt);
1525
+ const contentHash = sha256Hex4(content);
1526
+ const proofUri = await pin(content);
1527
+ return {
1528
+ content,
1529
+ contentHash,
1530
+ proofUri,
1531
+ metadata: { adapter: AKASH_ADAPTER_ID }
1532
+ };
1533
+ }
1534
+ };
1535
+ }
1536
+
1537
+ // src/adapters/render/index.ts
1538
+ import { createHash as createHash7 } from "crypto";
1539
+ function renderAmount(amount) {
1540
+ return amount;
1541
+ }
1542
+ var RENDER_ADAPTER_ID = "render";
1543
+ var RENDER_CAPABILITIES = ["3d-rendering", "video-rendering", "gpu-inference"];
1544
+ var DEFAULT_SERVICE2 = "render";
1545
+ var DEFAULT_TASK_BUDGET_MICRO_USDC5 = 15e6;
1546
+ function sha256Hex5(content) {
1547
+ return createHash7("sha256").update(content).digest("hex");
1548
+ }
1549
+ var REFERENCE_MICRO_USDC_PER_RENDER = 2e6;
1550
+ var defaultSwap3 = (microUsdc) => Promise.resolve(renderAmount(microUsdc / REFERENCE_MICRO_USDC_PER_RENDER));
1551
+ var defaultPin4 = (content) => Promise.resolve(`ipfs://placeholder-${sha256Hex5(content).slice(0, 16)}`);
1552
+ var RENDER_DEMO_MARKER = "render-demo-v1";
1553
+ var RENDER_DEMO_ACCEPT_FLOOR = 1;
1554
+ var DEMO_SCENE = "render-demo-scene-v1";
1555
+ var DEMO_WIDTH = 16;
1556
+ var DEMO_HEIGHT = 16;
1557
+ function shade(x, y) {
1558
+ return x * 53 + y * 97 + (x ^ y) * 29 & 255;
1559
+ }
1560
+ function computeRenderDemoArtifact() {
1561
+ const raster = Buffer.alloc(DEMO_WIDTH * DEMO_HEIGHT);
1562
+ let i = 0;
1563
+ for (let y = 0; y < DEMO_HEIGHT; y++) {
1564
+ for (let x = 0; x < DEMO_WIDTH; x++) {
1565
+ raster[i++] = shade(x, y);
1566
+ }
1567
+ }
1568
+ return raster.toString("hex");
1569
+ }
1570
+ var renderDemoJobSpec = {
1571
+ version: "1.0.0",
1572
+ taskType: "image_generation",
1573
+ description: "Render the canned demo scene (a fixed procedural frame) and return the rendered frame. Acceptance is deterministic and recomputed by the buyer: the job must complete, a rendered frame must be present, and the frame must match the buyer's independent re-render; the worker's self-reported frame hash is not trusted.",
1574
+ acceptanceCriteria: [
1575
+ {
1576
+ id: "ac-completion",
1577
+ description: "The render job ran to completion (status=completed).",
1578
+ required: true
1579
+ },
1580
+ {
1581
+ id: "ac-frame-present",
1582
+ description: "A rendered frame is present in the deliverable.",
1583
+ required: true
1584
+ },
1585
+ {
1586
+ id: "ac-frame-match",
1587
+ description: "The rendered frame matches the buyer's independent re-render of the scene.",
1588
+ required: true
1589
+ }
1590
+ ],
1591
+ deliverableFormat: { type: "json" },
1592
+ metadata: {
1593
+ demo: RENDER_DEMO_MARKER,
1594
+ scene: DEMO_SCENE,
1595
+ width: DEMO_WIDTH,
1596
+ height: DEMO_HEIGHT,
1597
+ acceptFloor: RENDER_DEMO_ACCEPT_FLOOR
1598
+ }
1599
+ };
1600
+ function solveRenderDemo() {
1601
+ const frame = computeRenderDemoArtifact();
1602
+ return canonicalize({
1603
+ job: RENDER_DEMO_MARKER,
1604
+ status: "completed",
1605
+ width: DEMO_WIDTH,
1606
+ height: DEMO_HEIGHT,
1607
+ frame,
1608
+ // Self-reported and deliberately ignored by the buyer's recompute.
1609
+ frameHash: sha256Hex5(frame)
1610
+ });
1611
+ }
1612
+ function parseDemoDeliverable4(content) {
1613
+ const text = Buffer.isBuffer(content) ? content.toString("utf8") : content;
1614
+ let parsed;
1615
+ try {
1616
+ parsed = JSON.parse(text);
1617
+ } catch {
1618
+ return null;
1619
+ }
1620
+ if (typeof parsed !== "object" || parsed === null) {
1621
+ return null;
1622
+ }
1623
+ const obj = parsed;
1624
+ const status = typeof obj.status === "string" ? obj.status : "";
1625
+ const frame = typeof obj.frame === "string" ? obj.frame : null;
1626
+ return { status, frame };
1627
+ }
1628
+ function scoreRenderDemoDeliverable(content) {
1629
+ const parsed = parseDemoDeliverable4(content);
1630
+ if (parsed === null) {
1631
+ return 0;
1632
+ }
1633
+ if (parsed.status !== "completed") {
1634
+ return 0;
1635
+ }
1636
+ if (parsed.frame === null) {
1637
+ return 0;
1638
+ }
1639
+ return parsed.frame === computeRenderDemoArtifact() ? 1 : 0;
1640
+ }
1641
+ function referencePlaceholder6(spec) {
1642
+ return canonicalize({
1643
+ job: "render-generic",
1644
+ status: "completed",
1645
+ frame: null,
1646
+ note: "reference stub - replace the runJob port with a real Render Network job submission.",
1647
+ taskType: spec.taskType
1648
+ });
1649
+ }
1650
+ var defaultRunJob2 = (spec) => Promise.resolve(
1651
+ spec.metadata.demo === RENDER_DEMO_MARKER ? solveRenderDemo() : referencePlaceholder6(spec)
1652
+ );
1653
+ function createRenderAdapter(config = {}) {
1654
+ const service = config.service ?? DEFAULT_SERVICE2;
1655
+ const capabilities = config.capabilities ?? [...RENDER_CAPABILITIES];
1656
+ const taskBudgetMicroUsdc = config.taskBudgetMicroUsdc ?? DEFAULT_TASK_BUDGET_MICRO_USDC5;
1657
+ const swap = config.swap ?? defaultSwap3;
1658
+ const runJob = config.runJob ?? defaultRunJob2;
1659
+ const pin = config.pin ?? defaultPin4;
1660
+ const checkHealth = config.checkHealth ?? (() => Promise.resolve(true));
1661
+ return {
1662
+ id: service,
1663
+ capabilities,
1664
+ healthCheck: checkHealth,
1665
+ async execute(spec) {
1666
+ const budgetRender = await swap(taskBudgetMicroUsdc);
1667
+ const content = await runJob(spec, budgetRender);
1668
+ const contentHash = sha256Hex5(content);
1669
+ const proofUri = await pin(content);
1670
+ return {
1671
+ content,
1672
+ contentHash,
1673
+ proofUri,
1674
+ metadata: { adapter: service, network: "render" }
1675
+ };
1676
+ }
1677
+ };
1678
+ }
1679
+
1680
+ // src/adapters/gensyn/index.ts
1681
+ import { createHash as createHash8 } from "crypto";
1682
+ var GENSYN_ADAPTER_ID = "gensyn";
1683
+ var GENSYN_CAPABILITIES = ["verifiable-compute"];
1684
+ var GENSYN_REE_SCHEME = "gensyn-ree-v1";
1685
+ var GENSYN_DEMO_MARKER = "gensyn-ree-demo-v1";
1686
+ var GENSYN_DEMO_SEED = 12345;
1687
+ var GENSYN_DEMO_STEPS = 16;
1688
+ function sha256Hex6(content) {
1689
+ return createHash8("sha256").update(content).digest("hex");
1690
+ }
1691
+ var LCG_MULT = 1103515245n;
1692
+ var LCG_INC = 12345n;
1693
+ var LCG_MOD = 1n << 31n;
1694
+ function readComputeParams(spec) {
1695
+ const rawSeed = spec.metadata.seed;
1696
+ const rawSteps = spec.metadata.steps;
1697
+ const seed = typeof rawSeed === "number" && Number.isInteger(rawSeed) ? rawSeed : GENSYN_DEMO_SEED;
1698
+ const steps = typeof rawSteps === "number" && Number.isInteger(rawSteps) && rawSteps > 0 ? rawSteps : GENSYN_DEMO_STEPS;
1699
+ return { seed, steps };
1700
+ }
1701
+ var reproduceGensynCompute = (spec) => {
1702
+ const { seed, steps } = readComputeParams(spec);
1703
+ const states = [];
1704
+ let x = BigInt(seed) % LCG_MOD;
1705
+ if (x < 0n) {
1706
+ x += LCG_MOD;
1707
+ }
1708
+ for (let i = 0; i < steps; i++) {
1709
+ x = (LCG_MULT * x + LCG_INC) % LCG_MOD;
1710
+ states.push(Number(x));
1711
+ }
1712
+ return { seed, steps, states, result: states[states.length - 1] ?? seed };
1713
+ };
1714
+ function gensynDeliverable(exec) {
1715
+ return canonicalize({ result: exec.result, steps: exec.steps });
1716
+ }
1717
+ function commitmentOf(base) {
1718
+ return sha256Hex6(
1719
+ [base.scheme, base.specHash, base.outputHash, String(base.steps), base.transcriptHash].join(
1720
+ "\n"
1721
+ )
1722
+ );
1723
+ }
1724
+ function buildReeProof(spec, exec, content) {
1725
+ const base = {
1726
+ scheme: GENSYN_REE_SCHEME,
1727
+ specHash: sha256Hex6(canonicalize(spec)),
1728
+ outputHash: sha256Hex6(content),
1729
+ steps: exec.steps,
1730
+ transcriptHash: sha256Hex6(canonicalize(exec.states))
1731
+ };
1732
+ return { ...base, commitment: commitmentOf(base) };
1733
+ }
1734
+ var HEX_64 = /^[0-9a-f]{64}$/;
1735
+ function isHex64(v) {
1736
+ return typeof v === "string" && HEX_64.test(v);
1737
+ }
1738
+ function malformed(reason) {
1739
+ return { valid: false, reason: `malformed: ${reason}` };
1740
+ }
1741
+ function invalid(reason) {
1742
+ return { valid: false, reason: `invalid: ${reason}` };
1743
+ }
1744
+ function verifyReeProof(proof, ctx) {
1745
+ if (typeof proof !== "object" || proof === null) {
1746
+ return malformed("proof is not an object");
1747
+ }
1748
+ const p = proof;
1749
+ if (p.scheme !== GENSYN_REE_SCHEME) {
1750
+ return malformed(`scheme must be "${GENSYN_REE_SCHEME}"`);
1751
+ }
1752
+ if (!isHex64(p.specHash)) {
1753
+ return malformed("specHash must be a 64-char lowercase hex string");
1754
+ }
1755
+ if (!isHex64(p.outputHash)) {
1756
+ return malformed("outputHash must be a 64-char lowercase hex string");
1757
+ }
1758
+ if (!isHex64(p.transcriptHash)) {
1759
+ return malformed("transcriptHash must be a 64-char lowercase hex string");
1760
+ }
1761
+ if (!isHex64(p.commitment)) {
1762
+ return malformed("commitment must be a 64-char lowercase hex string");
1763
+ }
1764
+ if (typeof p.steps !== "number" || !Number.isInteger(p.steps) || p.steps <= 0) {
1765
+ return malformed("steps must be a positive integer");
1766
+ }
1767
+ const base = {
1768
+ scheme: GENSYN_REE_SCHEME,
1769
+ specHash: p.specHash,
1770
+ outputHash: p.outputHash,
1771
+ steps: p.steps,
1772
+ transcriptHash: p.transcriptHash
1773
+ };
1774
+ if (commitmentOf(base) !== p.commitment) {
1775
+ return invalid("commitment does not bind the proof fields (tampered)");
1776
+ }
1777
+ if (sha256Hex6(ctx.content) !== p.outputHash) {
1778
+ return invalid("outputHash does not match the delivered content");
1779
+ }
1780
+ if (sha256Hex6(canonicalize(ctx.spec)) !== p.specHash) {
1781
+ return invalid("specHash does not match the commissioned task spec");
1782
+ }
1783
+ const exec = (ctx.reproduce ?? reproduceGensynCompute)(ctx.spec);
1784
+ if (p.steps !== exec.steps) {
1785
+ return invalid("steps does not match the reproduced execution");
1786
+ }
1787
+ if (sha256Hex6(canonicalize(exec.states)) !== p.transcriptHash) {
1788
+ return invalid("transcriptHash is not reproducible from the task spec");
1789
+ }
1790
+ if (sha256Hex6(gensynDeliverable(exec)) !== p.outputHash) {
1791
+ return invalid("outputHash does not match the reproduced deliverable");
1792
+ }
1793
+ return {
1794
+ valid: true,
1795
+ reason: "REE proof verified: commitment, spec, transcript, and the reproduced deliverable all bind to the delivered content."
1796
+ };
1797
+ }
1798
+ function gensynAutoRelease(bundle, ctx) {
1799
+ const verdict = verifyReeProof(bundle.proof, {
1800
+ spec: ctx.spec,
1801
+ content: bundle.content,
1802
+ ...ctx.reproduce !== void 0 && { reproduce: ctx.reproduce }
1803
+ });
1804
+ return { release: verdict.valid, reason: verdict.reason };
1805
+ }
1806
+ var gensynDemoJobSpec = {
1807
+ version: "1.0.0",
1808
+ taskType: "custom",
1809
+ description: "Run the reference reproducible compute (a fixed LCG walk from the seed in metadata) and return its final result. Acceptance is a valid gensyn-ree-v1 REE proof: the buyer recomputes the transcript and confirms the proof binds the delivered content, this spec, and the reproducible execution. The worker's self-reported result is never trusted on its own.",
1810
+ acceptanceCriteria: [
1811
+ {
1812
+ id: "ac-ree-proof",
1813
+ description: "A valid gensyn-ree-v1 proof binding the delivered content, this spec, and a reproducible transcript.",
1814
+ required: true
1815
+ }
1816
+ ],
1817
+ deliverableFormat: { type: "json" },
1818
+ metadata: {
1819
+ demo: GENSYN_DEMO_MARKER,
1820
+ seed: GENSYN_DEMO_SEED,
1821
+ steps: GENSYN_DEMO_STEPS
1822
+ }
1823
+ };
1824
+ var defaultPin5 = (content) => Promise.resolve(`ipfs://gensyn-${sha256Hex6(content).slice(0, 16)}`);
1825
+ function createGensynAdapter(config = {}) {
1826
+ const runCompute = config.runCompute ?? reproduceGensynCompute;
1827
+ const pin = config.pin ?? defaultPin5;
1828
+ const checkHealth = config.checkHealth ?? (() => Promise.resolve(true));
1829
+ const capabilities = config.capabilities ?? [...GENSYN_CAPABILITIES];
1830
+ return {
1831
+ id: GENSYN_ADAPTER_ID,
1832
+ capabilities,
1833
+ healthCheck: checkHealth,
1834
+ async execute(spec) {
1835
+ const exec = runCompute(spec);
1836
+ const content = gensynDeliverable(exec);
1837
+ const contentHash = sha256Hex6(content);
1838
+ const proof = buildReeProof(spec, exec, content);
1839
+ const proofUri = await pin(content);
1840
+ return {
1841
+ content,
1842
+ contentHash,
1843
+ proofUri,
1844
+ metadata: { adapter: GENSYN_ADAPTER_ID, reeProof: proof }
1845
+ };
1846
+ }
1847
+ };
1848
+ }
1849
+
1850
+ // src/adapters/zkml/index.ts
1851
+ import { createHash as createHash9 } from "crypto";
1852
+ var ZKML_ADAPTER_ID = "zkml";
1853
+ var ZKML_CAPABILITIES = ["zk-verified-inference"];
1854
+ var ZKML_DEMO_MARKER = "zkml-demo-v1";
1855
+ var ZKML_PROOF_SCHEME = "escro-zkml-ref-v1";
1856
+ var ZKML_PROOF_URI_PREFIX = "zkml-proof:";
1857
+ function sha256Hex7(content) {
1858
+ return createHash9("sha256").update(content).digest("hex");
1859
+ }
1860
+ function computeZkmlVerificationTag(publicInputs) {
1861
+ return sha256Hex7(
1862
+ canonicalize({
1863
+ scheme: publicInputs.scheme,
1864
+ modelCommitment: publicInputs.modelCommitment,
1865
+ inputCommitment: publicInputs.inputCommitment,
1866
+ outputHash: publicInputs.outputHash
1867
+ })
1868
+ );
1869
+ }
1870
+ function buildZkmlProof(args) {
1871
+ const publicInputs = {
1872
+ scheme: args.scheme ?? ZKML_PROOF_SCHEME,
1873
+ modelCommitment: args.modelCommitment,
1874
+ inputCommitment: args.inputCommitment,
1875
+ outputHash: args.outputHash
1876
+ };
1877
+ return { ...publicInputs, verificationTag: computeZkmlVerificationTag(publicInputs) };
1878
+ }
1879
+ function encodeZkmlProofUri(proof) {
1880
+ return ZKML_PROOF_URI_PREFIX + Buffer.from(canonicalize(proof), "utf8").toString("base64url");
1881
+ }
1882
+ function isProofShaped(value) {
1883
+ if (typeof value !== "object" || value === null) {
1884
+ return false;
1885
+ }
1886
+ const p = value;
1887
+ return typeof p.scheme === "string" && typeof p.modelCommitment === "string" && typeof p.inputCommitment === "string" && typeof p.outputHash === "string" && typeof p.verificationTag === "string";
1888
+ }
1889
+ function decodeZkmlProofUri(uri) {
1890
+ if (!uri.startsWith(ZKML_PROOF_URI_PREFIX)) {
1891
+ return null;
1892
+ }
1893
+ const encoded = uri.slice(ZKML_PROOF_URI_PREFIX.length);
1894
+ let parsed;
1895
+ try {
1896
+ parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
1897
+ } catch {
1898
+ return null;
1899
+ }
1900
+ return isProofShaped(parsed) ? parsed : null;
1901
+ }
1902
+ function verifyZkmlProof(proof, expected) {
1903
+ if (!isProofShaped(proof)) {
1904
+ return { valid: false, category: "malformed", reason: "proof is missing required fields" };
1905
+ }
1906
+ if (proof.scheme !== ZKML_PROOF_SCHEME) {
1907
+ return {
1908
+ valid: false,
1909
+ category: "malformed",
1910
+ reason: `unsupported proof scheme "${proof.scheme}"`
1911
+ };
1912
+ }
1913
+ if (proof.verificationTag !== computeZkmlVerificationTag(proof)) {
1914
+ return {
1915
+ valid: false,
1916
+ category: "invalid",
1917
+ reason: "verification tag does not recompute (public inputs altered after the tag was computed)"
1918
+ };
1919
+ }
1920
+ if (proof.outputHash !== expected.contentHash) {
1921
+ return {
1922
+ valid: false,
1923
+ category: "invalid",
1924
+ reason: "proof attests a different output than the submitted deliverable"
1925
+ };
1926
+ }
1927
+ if (expected.modelCommitment !== void 0 && proof.modelCommitment !== expected.modelCommitment) {
1928
+ return { valid: false, category: "invalid", reason: "proof attests a different model" };
1929
+ }
1930
+ return { valid: true };
1931
+ }
1932
+ function verifyZkmlProofUri(uri, expected) {
1933
+ if (typeof uri !== "string" || uri === "") {
1934
+ return { valid: false, category: "malformed", reason: "no proof URI attached" };
1935
+ }
1936
+ const proof = decodeZkmlProofUri(uri);
1937
+ if (proof === null) {
1938
+ return {
1939
+ valid: false,
1940
+ category: "malformed",
1941
+ reason: "proof URI is not a decodable inline zkml proof"
1942
+ };
1943
+ }
1944
+ return verifyZkmlProof(proof, expected);
1945
+ }
1946
+ async function resolveZkmlProof(uri, fetchProof) {
1947
+ if (typeof uri !== "string" || uri === "") {
1948
+ return null;
1949
+ }
1950
+ if (uri.startsWith(ZKML_PROOF_URI_PREFIX)) {
1951
+ return decodeZkmlProofUri(uri);
1952
+ }
1953
+ return fetchProof ? await fetchProof(uri) : null;
1954
+ }
1955
+ function zkmlProofAutoRelease(opts) {
1956
+ const verify = opts.verify ?? verifyZkmlProof;
1957
+ return {
1958
+ name: "zkml-proof-auto-release",
1959
+ async evaluate(d) {
1960
+ const recomputed = sha256Hex7(await opts.fetchContent(d));
1961
+ if (recomputed !== d.contentHash) {
1962
+ return {
1963
+ accept: false,
1964
+ dispute: true,
1965
+ reason: "delivered artifact does not hash to the submitted contentHash (tampered or wrong)"
1966
+ };
1967
+ }
1968
+ const proof = await resolveZkmlProof(d.proofUri, opts.fetchProof);
1969
+ if (proof === null) {
1970
+ return { accept: false, dispute: true, reason: "no decodable zkml proof attached" };
1971
+ }
1972
+ const verdict = verify(proof, {
1973
+ contentHash: recomputed,
1974
+ ...opts.modelCommitment !== void 0 && { modelCommitment: opts.modelCommitment }
1975
+ });
1976
+ if (verdict.valid) {
1977
+ return {
1978
+ accept: true,
1979
+ dispute: false,
1980
+ reason: "zkml proof verified against the re-hashed deliverable"
1981
+ };
1982
+ }
1983
+ return {
1984
+ accept: false,
1985
+ dispute: true,
1986
+ reason: `zkml proof ${verdict.category}: ${verdict.reason}`
1987
+ };
1988
+ }
1989
+ };
1990
+ }
1991
+ var zkmlDemoJobSpec = {
1992
+ version: "1.0.0",
1993
+ taskType: "custom",
1994
+ description: "Run the canned zk-verified inference demo: classify the fixed demo input and attach a proof that the output is the correct result of the committed model on the committed input. Acceptance is deterministic and recomputed by the buyer: the attached proof must verify against the submitted deliverable's contentHash.",
1995
+ acceptanceCriteria: [
1996
+ {
1997
+ id: "ac-proof-verifies",
1998
+ description: "The attached proof verifies against the submitted deliverable's contentHash, recomputed by the buyer.",
1999
+ required: true
2000
+ }
2001
+ ],
2002
+ deliverableFormat: { type: "json" },
2003
+ metadata: { demo: ZKML_DEMO_MARKER }
2004
+ };
2005
+ var DEMO_MODEL_COMMITMENT = sha256Hex7("zkml-demo-classifier-v1");
2006
+ var DEMO_INPUT_COMMITMENT = sha256Hex7("zkml-demo-input-v1");
2007
+ function solveZkmlDemo() {
2008
+ return canonicalize({
2009
+ model: "zkml-demo-classifier-v1",
2010
+ output: { label: "cat", confidence: 0.98 }
2011
+ });
2012
+ }
2013
+ function referencePlaceholderOutput(spec) {
2014
+ return {
2015
+ content: canonicalize({
2016
+ note: "reference stub - replace the runInference port with a real zk-verified inference call.",
2017
+ taskType: spec.taskType
2018
+ }),
2019
+ modelCommitment: sha256Hex7("zkml-reference-stub-model"),
2020
+ inputCommitment: sha256Hex7(canonicalize(spec))
2021
+ };
2022
+ }
2023
+ var defaultRunInference = (spec) => spec.metadata.demo === ZKML_DEMO_MARKER ? {
2024
+ content: solveZkmlDemo(),
2025
+ modelCommitment: DEMO_MODEL_COMMITMENT,
2026
+ inputCommitment: DEMO_INPUT_COMMITMENT
2027
+ } : referencePlaceholderOutput(spec);
2028
+ function createZkmlAdapter(config = {}) {
2029
+ const id = config.id ?? ZKML_ADAPTER_ID;
2030
+ const capabilities = config.capabilities ?? [...ZKML_CAPABILITIES];
2031
+ const runInference = config.runInference ?? defaultRunInference;
2032
+ const prove = config.prove ?? ((args) => buildZkmlProof({
2033
+ modelCommitment: args.output.modelCommitment,
2034
+ inputCommitment: args.output.inputCommitment,
2035
+ outputHash: args.outputHash
2036
+ }));
2037
+ const pin = config.pin ?? ((proof) => encodeZkmlProofUri(proof));
2038
+ const checkHealth = config.checkHealth ?? (() => Promise.resolve(true));
2039
+ return {
2040
+ id,
2041
+ capabilities,
2042
+ async execute(spec) {
2043
+ const output = await runInference(spec);
2044
+ const contentHash = sha256Hex7(output.content);
2045
+ const proof = await prove({ spec, output, outputHash: contentHash });
2046
+ const proofUri = await pin(proof);
2047
+ return {
2048
+ content: output.content,
2049
+ contentHash,
2050
+ proofUri,
2051
+ metadata: {
2052
+ adapterId: id,
2053
+ scheme: proof.scheme,
2054
+ modelCommitment: proof.modelCommitment
2055
+ }
2056
+ };
2057
+ },
2058
+ healthCheck() {
2059
+ return checkHealth();
2060
+ }
2061
+ };
2062
+ }
461
2063
  export {
2064
+ AKASH_ADAPTER_ID,
2065
+ AKASH_CAPABILITIES,
2066
+ AKASH_DEMO_ACCEPT_FLOOR,
2067
+ AKASH_DEMO_MARKER,
2068
+ AdapterContractError,
2069
+ ApiClient,
2070
+ ENABLED_RAILS,
462
2071
  Escro,
463
2072
  EscroError,
2073
+ EscroRailNotEnabledError,
464
2074
  EscrowNotFoundError,
2075
+ EscrowPaymentRouter,
2076
+ EscrowState,
465
2077
  EscrowStateError,
466
2078
  EscrowTimeoutError,
467
- UnauthorizedError
2079
+ GEN404_ADAPTER_ID,
2080
+ GEN404_CAPABILITIES,
2081
+ GEN404_DEMO_EVAL_FLOOR,
2082
+ GEN404_DEMO_MARKER,
2083
+ GENSYN_ADAPTER_ID,
2084
+ GENSYN_CAPABILITIES,
2085
+ GENSYN_DEMO_MARKER,
2086
+ GENSYN_REE_SCHEME,
2087
+ GRADIENTS_ADAPTER_ID,
2088
+ GRADIENTS_CAPABILITIES,
2089
+ GRADIENTS_DEMO_EVAL_FLOOR,
2090
+ GRADIENTS_DEMO_MARKER,
2091
+ LIUM_ADAPTER_ID,
2092
+ LIUM_CAPABILITIES,
2093
+ LIUM_DEMO_ACCEPT_FLOOR,
2094
+ LIUM_DEMO_MARKER,
2095
+ RAILS,
2096
+ RAILS_DOCS_URL,
2097
+ REFERENCE_MICRO_USDC_PER_AKT,
2098
+ REFERENCE_MICRO_USDC_PER_RENDER,
2099
+ RENDER_ADAPTER_ID,
2100
+ RENDER_CAPABILITIES,
2101
+ RENDER_DEMO_ACCEPT_FLOOR,
2102
+ RENDER_DEMO_MARKER,
2103
+ RIDGES_ADAPTER_ID,
2104
+ RIDGES_CAPABILITIES,
2105
+ RIDGES_DEMO_COVERAGE_TARGET,
2106
+ RIDGES_DEMO_MARKER,
2107
+ ReputationUnavailableError,
2108
+ UnauthorizedError,
2109
+ ZKML_ADAPTER_ID,
2110
+ ZKML_CAPABILITIES,
2111
+ ZKML_DEMO_MARKER,
2112
+ ZKML_PROOF_SCHEME,
2113
+ ZKML_PROOF_URI_PREFIX,
2114
+ akashDemoJobSpec,
2115
+ akt,
2116
+ assertServiceAdapterContract,
2117
+ buildReeProof,
2118
+ buildZkmlProof,
2119
+ computeAkashDemoArtifact,
2120
+ computeLiumDemoArtifact,
2121
+ computeRenderDemoArtifact,
2122
+ create404genAdapter,
2123
+ createAkashAdapter,
2124
+ createGensynAdapter,
2125
+ createGradientsAdapter,
2126
+ createLiumAdapter,
2127
+ createRenderAdapter,
2128
+ createRidgesAdapter,
2129
+ createZkmlAdapter,
2130
+ decodeZkmlProofUri,
2131
+ encodeZkmlProofUri,
2132
+ gen404DemoJobSpec,
2133
+ gensynAutoRelease,
2134
+ gensynDemoJobSpec,
2135
+ gradientsDemoJobSpec,
2136
+ liumDemoJobSpec,
2137
+ renderAmount,
2138
+ renderDemoJobSpec,
2139
+ reproduceGensynCompute,
2140
+ ridgesDemoJobSpec,
2141
+ score404genDemoDeliverable,
2142
+ scoreAkashDemoDeliverable,
2143
+ scoreGradientsDemoDeliverable,
2144
+ scoreLiumDemoDeliverable,
2145
+ scoreRenderDemoDeliverable,
2146
+ scoreRidgesDemoDeliverable,
2147
+ serviceResultToSubmitParams,
2148
+ solveAkashDemo,
2149
+ solveGen404Demo,
2150
+ solveGradientsDemo,
2151
+ solveLiumDemo,
2152
+ solveRenderDemo,
2153
+ solveRidgesDemo,
2154
+ solveZkmlDemo,
2155
+ tao,
2156
+ verifyReeProof,
2157
+ verifyZkmlProof,
2158
+ verifyZkmlProofUri,
2159
+ zkmlDemoJobSpec,
2160
+ zkmlProofAutoRelease
468
2161
  };
469
2162
  //# sourceMappingURL=index.js.map