@elgora/cli 11.0.0 → 12.1.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.
Files changed (3) hide show
  1. package/README.md +26 -6
  2. package/dist/cli.js +1744 -706
  3. package/package.json +4 -3
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import path5 from "path";
4
+ import path6 from "path";
5
5
  import { config as loadDotenv } from "dotenv";
6
6
  import { ZodError as ZodError2 } from "zod";
7
7
 
@@ -40,8 +40,8 @@ function bytesToBase64Url(bytes) {
40
40
  return chunks.join("");
41
41
  }
42
42
  function base64UrlToBytes(value) {
43
- const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
44
- return base64ToBytes(padded);
43
+ const padded2 = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
44
+ return base64ToBytes(padded2);
45
45
  }
46
46
  function base64ToBytes(value) {
47
47
  const binary = atob(value);
@@ -87,12 +87,24 @@ function safeDecodeUtf8(bytes) {
87
87
  import { Aes256Gcm, CipherSuite, HkdfSha256 } from "@hpke/core";
88
88
  import { DhkemX25519HkdfSha256 } from "@hpke/dhkem-x25519";
89
89
  import { bytesToHex, keccak256, toBytes } from "viem";
90
- import { z } from "zod";
90
+ import { z as z2 } from "zod";
91
91
 
92
92
  // ../../packages/core/src/constants.ts
93
93
  var BOUNTY_CHALLENGE_PROFILE = "elgora_markdown_bounty_challenge_v0";
94
+ var BOUNTY_CHALLENGE_PROFILE_V1 = "elgora_markdown_bounty_challenge_v1";
94
95
  var WINNER_TAKE_ALL_PAYOUT_POLICY = "winner_take_all";
96
+ var MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS = 1e6;
97
+ var BOUNTY_CHALLENGE_FRONTMATTER_KEYS = [
98
+ "profile",
99
+ "escrow_amount",
100
+ "submission_deadline",
101
+ "payout_policy"
102
+ ];
103
+ var BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS = [
104
+ "constraints"
105
+ ];
95
106
  var SUBMISSION_ENCRYPTION_TYPE = "guardian_timelock_hpke_aes_256_gcm_v0";
107
+ var SUBMISSION_ENCRYPTION_TYPE_V1 = "guardian_timelock_hpke_aes_256_gcm_v1";
96
108
  var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
97
109
  var READINESS_APPROVAL_VALIDITY_SECONDS = 7 * 24 * 60 * 60;
98
110
  var READINESS_TIMEOUT_MS = 18e4;
@@ -128,63 +140,127 @@ function canonicalizeJson(value) {
128
140
  throw new Error("Unsupported JSON value");
129
141
  }
130
142
 
143
+ // ../../packages/core/src/submission-path.ts
144
+ import { z } from "zod";
145
+ var SOLVER_SUBMISSION_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
146
+ var MAX_SOLVER_SUBMISSION_NAME_CHARACTERS = 255;
147
+ var MAX_SOLVER_SUBMISSION_PATH_CHARACTERS = 768;
148
+ var MAX_SOLVER_SUBMISSION_FILES_V1 = 1e4;
149
+ var SUBMISSION_ENCRYPTED_BUNDLE_V1_KIND = "elgora_private_artifact_encrypted_bundle_v1";
150
+ var NAME_TOO_LONG_MESSAGE = `Artifact filename exceeds ${MAX_SOLVER_SUBMISSION_NAME_CHARACTERS} ASCII characters (including extension). Solvers may shorten it only if the published challenge permits, before the deadline.`;
151
+ var solverSubmissionNameSchema = z.string().max(MAX_SOLVER_SUBMISSION_NAME_CHARACTERS, NAME_TOO_LONG_MESSAGE).regex(SOLVER_SUBMISSION_NAME_RE);
152
+ function describeSolverSubmissionPathIssue(value) {
153
+ if (value.length > MAX_SOLVER_SUBMISSION_PATH_CHARACTERS) {
154
+ return `"${value.slice(0, 64)}\u2026" is ${value.length} characters; a Submission path is at most ${MAX_SOLVER_SUBMISSION_PATH_CHARACTERS}.`;
155
+ }
156
+ for (const segment of value.split("/")) {
157
+ if (segment.length > MAX_SOLVER_SUBMISSION_NAME_CHARACTERS) {
158
+ return `"${value}" has a name longer than ${MAX_SOLVER_SUBMISSION_NAME_CHARACTERS} ASCII characters. Solvers may shorten it only if the published challenge permits, before the deadline.`;
159
+ }
160
+ if (!SOLVER_SUBMISSION_NAME_RE.test(segment)) {
161
+ return `"${value}" is not a valid Submission path: it is "/"-separated names, each starting with a letter or digit and using only letters, digits, ".", "_" and "-".`;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function findSolverSubmissionPathConflict(paths) {
167
+ const files = /* @__PURE__ */ new Map();
168
+ const folders = /* @__PURE__ */ new Map();
169
+ for (const path7 of paths) {
170
+ const folded = path7.toLowerCase();
171
+ const earlier = files.get(folded);
172
+ if (earlier !== void 0) {
173
+ return {
174
+ path: path7,
175
+ conflictsWith: earlier,
176
+ reason: earlier === path7 ? "duplicate" : "case"
177
+ };
178
+ }
179
+ files.set(folded, path7);
180
+ const segments = path7.split("/");
181
+ for (let depth = 1; depth < segments.length; depth += 1) {
182
+ const spelling = segments.slice(0, depth).join("/");
183
+ const known = folders.get(spelling.toLowerCase());
184
+ if (known === void 0) {
185
+ folders.set(spelling.toLowerCase(), { spelling, path: path7 });
186
+ } else if (known.spelling !== spelling) {
187
+ return { path: path7, conflictsWith: known.path, reason: "folder_case" };
188
+ }
189
+ }
190
+ }
191
+ for (const [folded, path7] of files) {
192
+ const nested = folders.get(folded);
193
+ if (nested !== void 0) {
194
+ return { path: path7, conflictsWith: nested.path, reason: "folder" };
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ function describeSolverSubmissionPathConflict(conflict) {
200
+ switch (conflict.reason) {
201
+ case "duplicate":
202
+ return `"${conflict.path}" appears twice`;
203
+ case "case":
204
+ return `"${conflict.path}" and "${conflict.conflictsWith}" differ only in case, so they would collide on a case-insensitive filesystem`;
205
+ case "folder_case":
206
+ return `"${conflict.path}" and "${conflict.conflictsWith}" spell the same folder in different case, so filesystems would disagree on whether they share it`;
207
+ case "folder":
208
+ return `"${conflict.path}" is a file, but "${conflict.conflictsWith}" needs it to be a folder`;
209
+ }
210
+ }
211
+
131
212
  // ../../packages/core/src/submission-encryption.ts
132
- var PRIVATE_ARTIFACT_ENVELOPE_KIND = "elgora_private_artifact_envelope_v0";
133
- var PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND = "elgora_private_artifact_public_manifest_v0";
134
- var PRIVATE_ARTIFACT_ENCRYPTED_BUNDLE_KIND = "elgora_private_artifact_encrypted_bundle_v0";
135
- var PRIVATE_ARTIFACT_WRAPPED_KEY_BAG_KIND = "elgora_private_artifact_wrapped_key_bag_v0";
136
- var PRIVATE_ARTIFACT_REVEAL_POLICY_KIND = "drand_timelock_reveal_policy_v0";
137
- var PRIVATE_ARTIFACT_HPKE_SUITE = "hpke-dhkem-x25519-hkdf-sha256+hkdf-sha256+aes-256-gcm@1";
213
+ var SUBMISSION_ENVELOPE_KIND = "elgora_private_artifact_envelope_v0";
214
+ var SUBMISSION_PUBLIC_MANIFEST_KIND = "elgora_private_artifact_public_manifest_v0";
215
+ var SUBMISSION_ENCRYPTED_BUNDLE_KIND = "elgora_private_artifact_encrypted_bundle_v0";
216
+ var SUBMISSION_WRAPPED_KEY_BAG_KIND = "elgora_private_artifact_wrapped_key_bag_v0";
217
+ var SUBMISSION_REVEAL_POLICY_KIND = "drand_timelock_reveal_policy_v0";
218
+ var SUBMISSION_HPKE_SUITE = "hpke-dhkem-x25519-hkdf-sha256+hkdf-sha256+aes-256-gcm@1";
138
219
  var MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES = 50 * 1024 * 1024;
139
220
  var MAX_SOLVER_SUBMISSION_FILES = 500;
140
- var MAX_SOLVER_SUBMISSION_NAME_CHARACTERS = 255;
141
- var MAX_SOLVER_SUBMISSION_ARTIFACT_BYTES = 37 * 1024 * 1024;
142
221
  var MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES = 250 * 1e3 * 1e3;
143
- var PRIVATE_ARTIFACT_HPKE_INFO_PREFIX = "elgora/private-artifact-envelope/hpke-aes-key/v0";
222
+ var SUBMISSION_HPKE_INFO_PREFIX = "elgora/private-artifact-envelope/hpke-aes-key/v0";
144
223
  var AES_256_KEY_BYTES = 32;
145
224
  var AES_GCM_IV_BYTES = 12;
146
- var PRIVATE_ARTIFACT_NONCE_BYTES = 16;
147
- var AES_GCM_TAG_BITS = 128;
225
+ var SUBMISSION_NONCE_BYTES = 16;
226
+ var AES_GCM_TAG_BYTES = 16;
227
+ var AES_GCM_TAG_BITS = AES_GCM_TAG_BYTES * 8;
148
228
  var BASE64URL_RE = /^[A-Za-z0-9_-]+$/;
149
- var SOLVER_SUBMISSION_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
150
229
  var MEDIA_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/;
151
230
  var KEY_ID_RE = /^[A-Za-z0-9][ -~]*$/;
152
- var base64UrlSchema = z.string().regex(BASE64URL_RE);
153
- var lowerAddressSchema = z.string().refine(isLowercaseHexAddress);
154
- var bytes32Schema = z.string().refine(isLowercaseBytes32);
155
- var sha256HexSchema = z.string().refine(isSha256Hex);
156
- var keyIdSchema = z.string().min(1).max(128).regex(KEY_ID_RE);
157
- var artifactNameSchema = z.string().max(
158
- MAX_SOLVER_SUBMISSION_NAME_CHARACTERS,
159
- `Artifact filename exceeds ${MAX_SOLVER_SUBMISSION_NAME_CHARACTERS} ASCII characters (including extension). Solvers may shorten it only if the published challenge permits, before the deadline.`
160
- ).regex(SOLVER_SUBMISSION_NAME_RE);
161
- var mediaTypeSchema = z.string().regex(MEDIA_TYPE_RE);
162
- var bountyIdSchema = z.string().refine(isBountyId);
163
- var chainIdSchema = z.number().int().positive().lte(Number.MAX_SAFE_INTEGER);
164
- var privateArtifactContextSchema = z.strictObject({
165
- type: z.literal("solver_submission"),
231
+ var base64UrlSchema = z2.string().regex(BASE64URL_RE);
232
+ var lowerAddressSchema = z2.string().refine(isLowercaseHexAddress);
233
+ var bytes32Schema = z2.string().refine(isLowercaseBytes32);
234
+ var sha256HexSchema = z2.string().refine(isSha256Hex);
235
+ var keyIdSchema = z2.string().min(1).max(128).regex(KEY_ID_RE);
236
+ var fileNameSchema = solverSubmissionNameSchema;
237
+ var mediaTypeSchema = z2.string().regex(MEDIA_TYPE_RE);
238
+ var bountyIdSchema = z2.string().refine(isBountyId);
239
+ var chainIdSchema = z2.number().int().positive().lte(Number.MAX_SAFE_INTEGER);
240
+ var submissionContextSchema = z2.strictObject({
241
+ type: z2.literal("solver_submission"),
166
242
  chain_id: chainIdSchema,
167
243
  hub_address: lowerAddressSchema,
168
244
  bounty_id: bountyIdSchema,
169
245
  actor_address: lowerAddressSchema
170
246
  });
171
- var guardianRecipientSchema = z.strictObject({
247
+ var guardianRecipientSchema = z2.strictObject({
172
248
  key_id: keyIdSchema,
173
- suite: z.literal(PRIVATE_ARTIFACT_HPKE_SUITE),
249
+ suite: z2.literal(SUBMISSION_HPKE_SUITE),
174
250
  public_key: base64UrlSchema.length(
175
251
  43,
176
252
  "public_key must encode a 32-byte X25519 public key"
177
253
  )
178
254
  });
179
- var guardianRecipientPrivateKeySchema = z.strictObject({
255
+ var guardianRecipientPrivateKeySchema = z2.strictObject({
180
256
  key_id: keyIdSchema,
181
- suite: z.literal(PRIVATE_ARTIFACT_HPKE_SUITE),
257
+ suite: z2.literal(SUBMISSION_HPKE_SUITE),
182
258
  private_key: base64UrlSchema.length(
183
259
  43,
184
260
  "private_key must encode a 32-byte X25519 private key"
185
261
  )
186
262
  });
187
- var guardianRecipientsSchema = z.array(guardianRecipientSchema).min(1).superRefine((recipients, ctx) => {
263
+ var guardianRecipientsSchema = z2.array(guardianRecipientSchema).min(1).superRefine((recipients, ctx) => {
188
264
  const seen = /* @__PURE__ */ new Set();
189
265
  for (const recipient of recipients) {
190
266
  if (seen.has(recipient.key_id)) {
@@ -197,84 +273,84 @@ var guardianRecipientsSchema = z.array(guardianRecipientSchema).min(1).superRefi
197
273
  seen.add(recipient.key_id);
198
274
  }
199
275
  });
200
- var guardianRecipientPrivateKeysSchema = z.array(guardianRecipientPrivateKeySchema).min(1);
201
- var artifactManifestEntrySchema = z.strictObject({
202
- name: artifactNameSchema,
276
+ var guardianRecipientPrivateKeysSchema = z2.array(guardianRecipientPrivateKeySchema).min(1);
277
+ var bundleFileEntrySchema = z2.strictObject({
278
+ name: fileNameSchema,
203
279
  media_type: mediaTypeSchema,
204
- size_bytes: z.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER),
280
+ size_bytes: z2.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER),
205
281
  sha256: sha256HexSchema
206
282
  });
207
- var privateArtifactPublicManifestSchema = z.strictObject({
208
- kind: z.literal(PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND),
209
- context: privateArtifactContextSchema
283
+ var submissionPublicManifestSchema = z2.strictObject({
284
+ kind: z2.literal(SUBMISSION_PUBLIC_MANIFEST_KIND),
285
+ context: submissionContextSchema
210
286
  });
211
- var encryptedBundleArtifactSchema = artifactManifestEntrySchema.extend({
287
+ var encryptedBundleFileSchema = bundleFileEntrySchema.extend({
212
288
  bytes: base64UrlSchema
213
289
  });
214
- var privateArtifactEncryptedBundleSchema = z.strictObject({
215
- kind: z.literal(PRIVATE_ARTIFACT_ENCRYPTED_BUNDLE_KIND),
216
- context: privateArtifactContextSchema,
217
- artifacts: z.array(encryptedBundleArtifactSchema).min(1).max(
290
+ var submissionEncryptedBundleSchema = z2.strictObject({
291
+ kind: z2.literal(SUBMISSION_ENCRYPTED_BUNDLE_KIND),
292
+ context: submissionContextSchema,
293
+ artifacts: z2.array(encryptedBundleFileSchema).min(1).max(
218
294
  MAX_SOLVER_SUBMISSION_FILES,
219
295
  `Submission exceeds the maximum of ${MAX_SOLVER_SUBMISSION_FILES} files.`
220
296
  ).refine(
221
- (artifacts) => new Set(artifacts.map((artifact) => artifact.name.toLowerCase())).size === artifacts.length,
222
- "artifact names must be unique case-insensitively"
297
+ (files) => new Set(files.map((file) => file.name.toLowerCase())).size === files.length,
298
+ "file names must be unique case-insensitively"
223
299
  )
224
300
  });
225
- var privateArtifactRevealPolicySchema = z.strictObject({
226
- kind: z.literal(PRIVATE_ARTIFACT_REVEAL_POLICY_KIND),
227
- network: z.string().min(1),
228
- chain_hash: z.string().min(1),
229
- round: z.string().refine(isPositiveInteger),
230
- not_before: z.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER)
301
+ var submissionRevealPolicySchema = z2.strictObject({
302
+ kind: z2.literal(SUBMISSION_REVEAL_POLICY_KIND),
303
+ network: z2.string().min(1),
304
+ chain_hash: z2.string().min(1),
305
+ round: z2.string().refine(isPositiveInteger),
306
+ not_before: z2.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER)
231
307
  });
232
- var wrappedRecipientKeySchema = z.strictObject({
308
+ var wrappedRecipientKeySchema = z2.strictObject({
233
309
  key_id: keyIdSchema,
234
- suite: z.literal(PRIVATE_ARTIFACT_HPKE_SUITE),
310
+ suite: z2.literal(SUBMISSION_HPKE_SUITE),
235
311
  enc: base64UrlSchema,
236
312
  ciphertext: base64UrlSchema
237
313
  });
238
- var wrappedKeyBagSchema = z.strictObject({
239
- kind: z.literal(PRIVATE_ARTIFACT_WRAPPED_KEY_BAG_KIND),
240
- keys: z.array(wrappedRecipientKeySchema).min(1)
314
+ var wrappedKeyBagSchema = z2.strictObject({
315
+ kind: z2.literal(SUBMISSION_WRAPPED_KEY_BAG_KIND),
316
+ keys: z2.array(wrappedRecipientKeySchema).min(1)
241
317
  });
242
- var privateArtifactEnvelopeSchema = z.strictObject({
243
- kind: z.literal(PRIVATE_ARTIFACT_ENVELOPE_KIND),
244
- alg: z.literal(SUBMISSION_ENCRYPTION_TYPE),
245
- context: privateArtifactContextSchema,
246
- public_manifest: privateArtifactPublicManifestSchema,
318
+ var submissionEnvelopeSchema = z2.strictObject({
319
+ kind: z2.literal(SUBMISSION_ENVELOPE_KIND),
320
+ alg: z2.enum([SUBMISSION_ENCRYPTION_TYPE, SUBMISSION_ENCRYPTION_TYPE_V1]),
321
+ context: submissionContextSchema,
322
+ public_manifest: submissionPublicManifestSchema,
247
323
  public_manifest_sha256: sha256HexSchema,
248
324
  encrypted_bundle_sha256: sha256HexSchema,
249
325
  ciphertext_sha256: sha256HexSchema,
250
326
  guardian_recipients: guardianRecipientsSchema,
251
327
  guardian_recipients_commitment: bytes32Schema,
252
- reveal_policy: privateArtifactRevealPolicySchema,
328
+ reveal_policy: submissionRevealPolicySchema,
253
329
  envelope_nonce: base64UrlSchema.length(
254
330
  22,
255
331
  "envelope_nonce must encode 16 bytes"
256
332
  ),
257
333
  iv: base64UrlSchema.length(16, "iv must encode 12 bytes"),
258
334
  wrapped_key_bag: base64UrlSchema,
259
- ciphertext_locator: z.string().min(1)
335
+ ciphertext_locator: z2.string().min(1)
260
336
  });
261
- function parsePrivateArtifactEnvelope(input) {
262
- return privateArtifactEnvelopeSchema.parse(input);
337
+ function parseSubmissionEnvelope(input) {
338
+ return submissionEnvelopeSchema.parse(input);
263
339
  }
264
- function serializePrivateArtifactEnvelope(envelope) {
340
+ function serializeSubmissionEnvelope(envelope) {
265
341
  return canonicalizeJson(
266
- privateArtifactEnvelopeSchema.parse(envelope)
342
+ submissionEnvelopeSchema.parse(envelope)
267
343
  );
268
344
  }
269
- function derivePrivateArtifactEnvelopeCommitment(envelope) {
270
- return keccak256(toBytes(serializePrivateArtifactEnvelope(envelope)));
345
+ function deriveSubmissionEnvelopeCommitment(envelope) {
346
+ return keccak256(toBytes(serializeSubmissionEnvelope(envelope)));
271
347
  }
272
348
  function deriveSolverSubmissionCiphertextLocator(input) {
273
349
  const { chain_id, hub_address, bounty_id, actor_address } = input.context;
274
350
  return `${chain_id}/${hub_address}/${bounty_id}/${actor_address}/${input.ciphertextSha256}.bin`;
275
351
  }
276
- async function verifyPrivateArtifactEnvelope(input) {
277
- const envelope = parsePrivateArtifactEnvelope(input);
352
+ async function verifySubmissionEnvelope(input) {
353
+ const envelope = parseSubmissionEnvelope(input);
278
354
  if (canonicalizeJson(envelope.public_manifest.context) !== canonicalizeJson(envelope.context)) {
279
355
  throw new Error("public_manifest context does not match envelope context");
280
356
  }
@@ -314,7 +390,7 @@ var x25519PrivateKeySchema = base64UrlSchema.length(
314
390
  43,
315
391
  "must encode a 32-byte X25519 private key"
316
392
  );
317
- var winningSolutionDeliveryContextSchema = z.strictObject({
393
+ var winningSolutionDeliveryContextSchema = z2.strictObject({
318
394
  chain_id: chainIdSchema,
319
395
  hub_address: lowerAddressSchema,
320
396
  bounty_id: bountyIdSchema,
@@ -395,7 +471,7 @@ function buildWinningSolutionDeliveryInfo(contextCommitment) {
395
471
  var GUARDIAN_ROSTER_KEY_PROOF_INFO = "elgora/guardian-roster-key-proof/hpke-fixture/v0";
396
472
  var GUARDIAN_ROSTER_KEY_PROOF_MESSAGE_TYPE = "elgora_guardian_roster_key_proof_v0";
397
473
  var GUARDIAN_ROSTER_KEY_PROOF_FIXTURE_BYTES = 32;
398
- var guardianRosterKeyProofContextSchema = z.strictObject({
474
+ var guardianRosterKeyProofContextSchema = z2.strictObject({
399
475
  chain_id: chainIdSchema,
400
476
  hub_address: lowerAddressSchema,
401
477
  proposed_account: lowerAddressSchema,
@@ -465,42 +541,30 @@ function buildGuardianRosterKeyProofSignedPayload(message, fixture) {
465
541
  return toBytes(`${message}
466
542
  ${bytesToHex(fixture).slice(2)}`);
467
543
  }
468
- async function sealPrivateArtifactEnvelope(input) {
469
- const context = privateArtifactContextSchema.parse(
470
- normalizeContext(input.context)
471
- );
472
- const revealPolicy = privateArtifactRevealPolicySchema.parse(
473
- input.reveal_policy
544
+ async function sealSubmissionEnvelope(input) {
545
+ const context = submissionContextSchema.parse(
546
+ normalizeSubmissionContext(input.context)
474
547
  );
548
+ const revealPolicy = submissionRevealPolicySchema.parse(input.reveal_policy);
475
549
  const recipients = canonicalizeGuardianRecipients(input.guardian_recipients);
476
550
  const guardianRecipientsCommitment = deriveGuardianRecipientsCommitment(recipients);
477
- const artifacts = await buildBundleArtifacts(input.artifacts);
478
- const publicManifest = privateArtifactPublicManifestSchema.parse({
479
- kind: PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND,
551
+ const publicManifest = submissionPublicManifestSchema.parse({
552
+ kind: SUBMISSION_PUBLIC_MANIFEST_KIND,
480
553
  context
481
554
  });
482
- const encryptedBundle = privateArtifactEncryptedBundleSchema.parse({
483
- kind: PRIVATE_ARTIFACT_ENCRYPTED_BUNDLE_KIND,
484
- context,
485
- artifacts: artifacts.map((artifact) => ({
486
- ...artifact,
487
- bytes: bytesToBase64Url(artifact.bytes)
488
- }))
489
- });
490
- const encryptedBundleBytes = toBytes(
491
- canonicalizeJson(encryptedBundle)
492
- );
555
+ const alg = input.encryption_type;
556
+ const encryptedBundleBytes = input.encryption_type === SUBMISSION_ENCRYPTION_TYPE_V1 ? input.archive : input.bundle;
493
557
  const publicManifestSha256 = await computeSha256Hex(
494
558
  toBytes(canonicalizeJson(publicManifest))
495
559
  );
496
560
  const encryptedBundleSha256 = await computeSha256Hex(encryptedBundleBytes);
497
561
  const randomBytes = input.random_bytes ?? secureRandomBytes;
498
562
  const rawAesKey = randomBytes(AES_256_KEY_BYTES);
499
- const envelopeNonce = randomBytes(PRIVATE_ARTIFACT_NONCE_BYTES);
563
+ const envelopeNonce = randomBytes(SUBMISSION_NONCE_BYTES);
500
564
  const iv = randomBytes(AES_GCM_IV_BYTES);
501
565
  const aadInput = {
502
- kind: PRIVATE_ARTIFACT_ENVELOPE_KIND,
503
- alg: SUBMISSION_ENCRYPTION_TYPE,
566
+ kind: SUBMISSION_ENVELOPE_KIND,
567
+ alg,
504
568
  context,
505
569
  public_manifest_sha256: publicManifestSha256,
506
570
  encrypted_bundle_sha256: encryptedBundleSha256,
@@ -512,7 +576,7 @@ async function sealPrivateArtifactEnvelope(input) {
512
576
  rawAesKey,
513
577
  iv,
514
578
  plaintext: encryptedBundleBytes,
515
- aad: buildPrivateArtifactAad(aadInput)
579
+ aad: buildSubmissionAad(aadInput)
516
580
  });
517
581
  const wrappedKeys = await wrapAesKeyForRecipients({
518
582
  rawAesKey,
@@ -520,7 +584,7 @@ async function sealPrivateArtifactEnvelope(input) {
520
584
  guardianRecipientsCommitment
521
585
  });
522
586
  const wrappedKeyBag = wrappedKeyBagSchema.parse({
523
- kind: PRIVATE_ARTIFACT_WRAPPED_KEY_BAG_KIND,
587
+ kind: SUBMISSION_WRAPPED_KEY_BAG_KIND,
524
588
  keys: wrappedKeys
525
589
  });
526
590
  const timelockedKeyBag = await input.timelock_encrypt({
@@ -532,9 +596,9 @@ async function sealPrivateArtifactEnvelope(input) {
532
596
  context,
533
597
  ciphertextSha256
534
598
  });
535
- const envelope = privateArtifactEnvelopeSchema.parse({
536
- kind: PRIVATE_ARTIFACT_ENVELOPE_KIND,
537
- alg: SUBMISSION_ENCRYPTION_TYPE,
599
+ const envelope = submissionEnvelopeSchema.parse({
600
+ kind: SUBMISSION_ENVELOPE_KIND,
601
+ alg,
538
602
  context,
539
603
  public_manifest: publicManifest,
540
604
  public_manifest_sha256: publicManifestSha256,
@@ -550,67 +614,65 @@ async function sealPrivateArtifactEnvelope(input) {
550
614
  });
551
615
  return { envelope, ciphertext };
552
616
  }
553
- async function openPrivateArtifactEnvelope(input) {
554
- const opened = await openPrivateArtifactEnvelopeWithRawKey(input);
555
- return {
556
- envelope: opened.envelope,
557
- recipient_key_id: opened.recipient_key_id,
558
- artifacts: opened.artifacts
559
- };
617
+ async function openSubmissionEnvelope(input) {
618
+ const { rawAesKey: _rawAesKey, ...opened } = await openSubmissionEnvelopeWithRawKey(input);
619
+ return opened;
560
620
  }
561
- async function openPrivateArtifactEnvelopeForDelivery(input) {
562
- const opened = await openPrivateArtifactEnvelopeWithRawKey(input);
621
+ async function openSubmissionEnvelopeForDelivery(input) {
622
+ const envelope = await verifySubmissionEnvelope(input.envelope);
623
+ const { rawAesKey, recipientKeyId } = await openEnvelopeAesKey(
624
+ envelope,
625
+ input
626
+ );
563
627
  const wrapped_key = await wrapWinningSolutionAesKey({
564
- rawAesKey: opened.rawAesKey,
628
+ rawAesKey,
565
629
  recipientPublicKey: input.delivery_recipient_public_key,
566
630
  context: input.context
567
631
  });
568
- return {
569
- envelope: opened.envelope,
570
- recipient_key_id: opened.recipient_key_id,
571
- wrapped_key
572
- };
632
+ return { envelope, recipient_key_id: recipientKeyId, wrapped_key };
573
633
  }
574
- async function openPrivateArtifactEnvelopeWithRawKey(input) {
575
- const envelope = await verifyPrivateArtifactEnvelope(input.envelope);
576
- await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
577
- const timelockedKeyBag = base64UrlToBytes(envelope.wrapped_key_bag);
634
+ async function openEnvelopeAesKey(envelope, input) {
578
635
  const wrappedKeyBagBytes = await input.timelock_decrypt({
579
- ciphertext: timelockedKeyBag,
636
+ ciphertext: base64UrlToBytes(envelope.wrapped_key_bag),
580
637
  reveal_policy: envelope.reveal_policy
581
638
  });
582
639
  const wrappedKeyBag = wrappedKeyBagSchema.parse(
583
640
  JSON.parse(new TextDecoder().decode(wrappedKeyBagBytes))
584
641
  );
585
- const openResult = await openAesKeyForRecipient({
642
+ return openAesKeyForRecipient({
586
643
  wrappedKeys: wrappedKeyBag.keys,
587
644
  privateKeys: input.guardian_private_keys,
588
645
  guardianRecipientsCommitment: envelope.guardian_recipients_commitment
589
646
  });
590
- const artifacts = await decryptAndVerifyBundle(envelope, input.ciphertext, {
647
+ }
648
+ async function openSubmissionEnvelopeWithRawKey(input) {
649
+ const envelope = await verifySubmissionEnvelope(input.envelope);
650
+ await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
651
+ const openResult = await openEnvelopeAesKey(envelope, input);
652
+ const bundle = await decryptAndVerifyBundle(envelope, input.ciphertext, {
591
653
  rawAesKey: openResult.rawAesKey
592
654
  });
593
655
  return {
594
656
  envelope,
595
657
  recipient_key_id: openResult.recipientKeyId,
596
- artifacts,
658
+ ...bundle,
597
659
  rawAesKey: openResult.rawAesKey
598
660
  };
599
661
  }
600
- async function openPrivateArtifactEnvelopeWithKnownAesKey(input) {
601
- const envelope = await verifyPrivateArtifactEnvelope(input.envelope);
662
+ async function openSubmissionEnvelopeWithKnownAesKey(input) {
663
+ const envelope = await verifySubmissionEnvelope(input.envelope);
602
664
  await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
603
- const artifacts = await decryptAndVerifyBundle(envelope, input.ciphertext, {
665
+ const bundle = await decryptAndVerifyBundle(envelope, input.ciphertext, {
604
666
  rawAesKey: input.rawAesKey
605
667
  });
606
- return { envelope, artifacts };
668
+ return { envelope, ...bundle };
607
669
  }
608
670
  async function decryptAndVerifyBundle(envelope, ciphertext, key) {
609
671
  const decryptedBytes = await decryptAes256Gcm({
610
672
  rawAesKey: key.rawAesKey,
611
673
  iv: base64UrlToBytes(envelope.iv),
612
674
  ciphertext,
613
- aad: buildPrivateArtifactAad({
675
+ aad: buildSubmissionAad({
614
676
  kind: envelope.kind,
615
677
  alg: envelope.alg,
616
678
  context: envelope.context,
@@ -621,63 +683,20 @@ async function decryptAndVerifyBundle(envelope, ciphertext, key) {
621
683
  envelope_nonce: envelope.envelope_nonce
622
684
  })
623
685
  });
624
- const encryptedBundle = privateArtifactEncryptedBundleSchema.parse(
625
- JSON.parse(new TextDecoder().decode(decryptedBytes))
626
- );
627
686
  if (await computeSha256Hex(decryptedBytes) !== envelope.encrypted_bundle_sha256) {
628
687
  throw new Error("encrypted_bundle_sha256 does not match decrypted bundle");
629
688
  }
630
- const rebuiltManifest = privateArtifactPublicManifestSchema.parse({
631
- kind: PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND,
632
- context: encryptedBundle.context
633
- });
634
- if (canonicalizeJson(rebuiltManifest) !== canonicalizeJson(envelope.public_manifest)) {
635
- throw new Error("decrypted bundle does not match public_manifest");
636
- }
637
- const artifacts = encryptedBundle.artifacts.map((artifact) => ({
638
- name: artifact.name,
639
- media_type: artifact.media_type,
640
- size_bytes: artifact.size_bytes,
641
- sha256: artifact.sha256,
642
- bytes: base64UrlToBytes(artifact.bytes)
643
- }));
644
- for (const artifact of artifacts) {
645
- if (artifact.bytes.byteLength !== artifact.size_bytes) {
646
- throw new Error(`artifact ${artifact.name} size_bytes mismatch`);
647
- }
648
- if (await computeSha256Hex(artifact.bytes) !== artifact.sha256) {
649
- throw new Error(`artifact ${artifact.name} sha256 mismatch`);
650
- }
651
- }
652
- return artifacts;
653
- }
654
- async function buildBundleArtifacts(artifacts) {
655
- const seen = /* @__PURE__ */ new Set();
656
- const parsed = artifacts.map((artifact) => ({
657
- name: artifactNameSchema.parse(artifact.name),
658
- media_type: mediaTypeSchema.parse(artifact.media_type),
659
- bytes: Uint8Array.from(artifact.bytes)
660
- })).sort((left, right) => compareCanonicalStrings(left.name, right.name));
661
- if (parsed.length === 0) {
662
- throw new Error(
663
- "private artifact envelope must include at least one artifact"
664
- );
689
+ if (envelope.alg === SUBMISSION_ENCRYPTION_TYPE_V1) {
690
+ return { alg: envelope.alg, archive: decryptedBytes };
665
691
  }
666
- return Promise.all(
667
- parsed.map(async (artifact) => {
668
- if (seen.has(artifact.name)) {
669
- throw new Error(`artifact name ${artifact.name} is duplicated`);
670
- }
671
- seen.add(artifact.name);
672
- return {
673
- name: artifact.name,
674
- media_type: artifact.media_type,
675
- size_bytes: artifact.bytes.byteLength,
676
- sha256: await computeSha256Hex(artifact.bytes),
677
- bytes: artifact.bytes
678
- };
679
- })
692
+ const encryptedBundle = submissionEncryptedBundleSchema.parse(
693
+ JSON.parse(new TextDecoder().decode(decryptedBytes))
680
694
  );
695
+ const files = encryptedBundle.artifacts.map((file) => ({
696
+ ...file,
697
+ bytes: base64UrlToBytes(file.bytes)
698
+ }));
699
+ return { alg: envelope.alg, files };
681
700
  }
682
701
  async function assertEnvelopeDigests(envelope) {
683
702
  const manifestSha256 = await computeSha256Hex(
@@ -687,7 +706,7 @@ async function assertEnvelopeDigests(envelope) {
687
706
  throw new Error("public_manifest_sha256 does not match public_manifest");
688
707
  }
689
708
  }
690
- function buildPrivateArtifactAad(input) {
709
+ function buildSubmissionAad(input) {
691
710
  return toBytes(
692
711
  canonicalizeJson({
693
712
  kind: input.kind,
@@ -724,7 +743,7 @@ async function wrapAesKeyForRecipients(input) {
724
743
  }
725
744
  async function openAesKeyForRecipient(input) {
726
745
  const suite = createHpkeSuite();
727
- const wrappedKeys = z.array(wrappedRecipientKeySchema).parse(input.wrappedKeys);
746
+ const wrappedKeys = z2.array(wrappedRecipientKeySchema).parse(input.wrappedKeys);
728
747
  const privateKeys = guardianRecipientPrivateKeysSchema.parse(
729
748
  input.privateKeys
730
749
  );
@@ -764,7 +783,7 @@ async function openAesKeyForRecipient(input) {
764
783
  ...new Set(privateKeys.map((key) => key.key_id))
765
784
  ].sort();
766
785
  throw new Error(
767
- `no matching Guardian private key for private artifact envelope: this envelope was sealed for key_id ${JSON.stringify(wanted)}, but ELGORA_GUARDIAN_PRIVATE_KEYS_JSON configures key_id ${JSON.stringify(configured)}. key_id must be this Guardian's own registered roster name (the name passed to roster:add-guardian), not its account address.`
786
+ `no matching Guardian private key for the Submission envelope: this envelope was sealed for key_id ${JSON.stringify(wanted)}, but ELGORA_GUARDIAN_PRIVATE_KEYS_JSON configures key_id ${JSON.stringify(configured)}. key_id must be this Guardian's own registered roster name (the name passed to roster:add-guardian), not its account address.`
768
787
  );
769
788
  }
770
789
  throw new Error("matching Guardian private key could not open wrapped key", {
@@ -816,7 +835,7 @@ async function decryptAes256Gcm(input) {
816
835
  )
817
836
  );
818
837
  } catch (error) {
819
- throw new Error("private artifact ciphertext authentication failed", {
838
+ throw new Error("Submission ciphertext authentication failed", {
820
839
  cause: error
821
840
  });
822
841
  }
@@ -833,7 +852,7 @@ async function computeSha256Hex(bytes) {
833
852
  }
834
853
  function buildRecipientKeyInfo(guardianRecipientsCommitment) {
835
854
  return toBytes(
836
- `${PRIVATE_ARTIFACT_HPKE_INFO_PREFIX}:${guardianRecipientsCommitment}`
855
+ `${SUBMISSION_HPKE_INFO_PREFIX}:${guardianRecipientsCommitment}`
837
856
  );
838
857
  }
839
858
  function createHpkeSuite() {
@@ -845,7 +864,7 @@ function createHpkeSuite() {
845
864
  }
846
865
  function getWebCrypto() {
847
866
  if (!globalThis.crypto?.subtle) {
848
- throw new Error("WebCrypto is required for private artifact encryption");
867
+ throw new Error("WebCrypto is required for Submission encryption");
849
868
  }
850
869
  return globalThis.crypto;
851
870
  }
@@ -868,7 +887,7 @@ function toArrayBufferView(bytes) {
868
887
  function normalizeBountyId(value) {
869
888
  return bountyIdSchema.parse(value.toString());
870
889
  }
871
- function normalizeContext(context) {
890
+ function normalizeSubmissionContext(context) {
872
891
  return {
873
892
  ...context,
874
893
  hub_address: context.hub_address.toLowerCase(),
@@ -892,29 +911,29 @@ var AGE_ARMOR_END = "-----END AGE ENCRYPTED FILE-----";
892
911
  var AGE_VERSION = "age-encryption.org/v1";
893
912
  var TLOCK_STANZA_PREFIX = "-> tlock ";
894
913
  var AGE_HEADER_SEPARATOR_BYTES = new Uint8Array([10, 45, 45, 45, 32]);
895
- var PrivateArtifactTimelockOpenError = class extends Error {
914
+ var SubmissionTimelockOpenError = class extends Error {
896
915
  code;
897
916
  constructor(code, message, options) {
898
917
  super(message, options);
899
- this.name = "PrivateArtifactTimelockOpenError";
918
+ this.name = "SubmissionTimelockOpenError";
900
919
  this.code = code;
901
920
  }
902
921
  };
903
- function buildPrivateArtifactRevealPolicyForDeadline(input) {
922
+ function buildSubmissionRevealPolicyForDeadline(input) {
904
923
  const revealDelaySeconds = input.revealDelaySeconds ?? DEFAULT_TIMELOCK_REVEAL_DELAY_SECONDS;
905
924
  const notBefore = addUnixSeconds(
906
925
  input.submissionDeadline,
907
926
  revealDelaySeconds
908
927
  );
909
928
  return {
910
- kind: PRIVATE_ARTIFACT_REVEAL_POLICY_KIND,
929
+ kind: SUBMISSION_REVEAL_POLICY_KIND,
911
930
  network: TIMELOCK_DRAND_NETWORK,
912
931
  chain_hash: DEFAULT_TIMELOCK_DRAND_CHAIN_HASH,
913
932
  round: deriveFirstRoundAtOrAfter(notBefore).toString(),
914
933
  not_before: notBefore
915
934
  };
916
935
  }
917
- function getPrivateArtifactTimelockReleaseState(input) {
936
+ function getSubmissionTimelockReleaseState(input) {
918
937
  const releaseTime = Math.max(
919
938
  input.revealPolicy.not_before,
920
939
  roundReleaseUnixSeconds(input.revealPolicy.round)
@@ -924,7 +943,7 @@ function getPrivateArtifactTimelockReleaseState(input) {
924
943
  release_time: releaseTime
925
944
  };
926
945
  }
927
- function createPrivateArtifactTimelockEncrypt(input) {
946
+ function createSubmissionTimelockEncrypt(input) {
928
947
  const chainClient = input?.chainClient ?? mainnetClient();
929
948
  return async ({ plaintext, reveal_policy }) => {
930
949
  assertSupportedRevealPolicy(reveal_policy);
@@ -936,7 +955,7 @@ function createPrivateArtifactTimelockEncrypt(input) {
936
955
  return new TextEncoder().encode(ciphertext);
937
956
  };
938
957
  }
939
- function createPrivateArtifactTimelockDecrypt(input) {
958
+ function createSubmissionTimelockDecrypt(input) {
940
959
  const chainClient = classifyBeaconUnavailableErrors(
941
960
  input?.chainClient ?? mainnetClient()
942
961
  );
@@ -944,18 +963,18 @@ function createPrivateArtifactTimelockDecrypt(input) {
944
963
  return async ({ ciphertext, reveal_policy }) => {
945
964
  try {
946
965
  assertSupportedRevealPolicy(reveal_policy);
947
- assertPrivateArtifactTimelockCiphertextMatchesPolicy({
966
+ assertSubmissionTimelockCiphertextMatchesPolicy({
948
967
  ciphertext,
949
968
  revealPolicy: reveal_policy
950
969
  });
951
- const releaseState = getPrivateArtifactTimelockReleaseState({
970
+ const releaseState = getSubmissionTimelockReleaseState({
952
971
  revealPolicy: reveal_policy,
953
972
  nowSeconds: nowSeconds()
954
973
  });
955
974
  if (releaseState.status !== "released") {
956
- throw new PrivateArtifactTimelockOpenError(
975
+ throw new SubmissionTimelockOpenError(
957
976
  "timelock_not_released",
958
- `private artifact timelock is not released until ${releaseState.release_time}`
977
+ `Submission timelock is not released until ${releaseState.release_time}`
959
978
  );
960
979
  }
961
980
  const plaintext = await timelockDecrypt(
@@ -964,18 +983,18 @@ function createPrivateArtifactTimelockDecrypt(input) {
964
983
  );
965
984
  return Uint8Array.from(plaintext);
966
985
  } catch (error) {
967
- if (error instanceof PrivateArtifactTimelockOpenError) {
986
+ if (error instanceof SubmissionTimelockOpenError) {
968
987
  throw error;
969
988
  }
970
- throw new PrivateArtifactTimelockOpenError(
989
+ throw new SubmissionTimelockOpenError(
971
990
  "timelock_open_failed",
972
- "private artifact timelock ciphertext could not be opened",
991
+ "Submission timelock ciphertext could not be opened",
973
992
  { cause: error }
974
993
  );
975
994
  }
976
995
  };
977
996
  }
978
- function parsePrivateArtifactTimelockHeader(ciphertext) {
997
+ function parseSubmissionTimelockHeader(ciphertext) {
979
998
  const ageText = decodeAgePayload(safeDecodeUtf8(ciphertext));
980
999
  const [version, ...lines] = ageText.split("\n");
981
1000
  if (version !== AGE_VERSION) {
@@ -1001,12 +1020,12 @@ function parsePrivateArtifactTimelockHeader(ciphertext) {
1001
1020
  chain_hash: chainHash
1002
1021
  };
1003
1022
  }
1004
- function assertPrivateArtifactTimelockCiphertextMatchesPolicy(input) {
1005
- const header = parsePrivateArtifactTimelockHeader(input.ciphertext);
1006
- if (header.round !== input.revealPolicy.round) {
1023
+ function assertSubmissionTimelockCiphertextMatchesPolicy(input) {
1024
+ const header2 = parseSubmissionTimelockHeader(input.ciphertext);
1025
+ if (header2.round !== input.revealPolicy.round) {
1007
1026
  throw new Error("timelock ciphertext round does not match reveal_policy");
1008
1027
  }
1009
- if (header.chain_hash !== input.revealPolicy.chain_hash) {
1028
+ if (header2.chain_hash !== input.revealPolicy.chain_hash) {
1010
1029
  throw new Error(
1011
1030
  "timelock ciphertext chain_hash does not match reveal_policy"
1012
1031
  );
@@ -1049,14 +1068,14 @@ function assertUnixSeconds(value, label) {
1049
1068
  }
1050
1069
  }
1051
1070
  function assertSupportedRevealPolicy(revealPolicy) {
1052
- if (revealPolicy.kind !== PRIVATE_ARTIFACT_REVEAL_POLICY_KIND) {
1053
- throw new Error("unsupported private artifact reveal_policy kind");
1071
+ if (revealPolicy.kind !== SUBMISSION_REVEAL_POLICY_KIND) {
1072
+ throw new Error("unsupported Submission reveal_policy kind");
1054
1073
  }
1055
1074
  if (revealPolicy.network !== TIMELOCK_DRAND_NETWORK) {
1056
- throw new Error("unsupported private artifact timelock network");
1075
+ throw new Error("unsupported Submission timelock network");
1057
1076
  }
1058
1077
  if (revealPolicy.chain_hash !== DEFAULT_TIMELOCK_DRAND_CHAIN_HASH) {
1059
- throw new Error("unsupported private artifact timelock chain hash");
1078
+ throw new Error("unsupported Submission timelock chain hash");
1060
1079
  }
1061
1080
  }
1062
1081
  function decodeAgePayload(value) {
@@ -1077,8 +1096,8 @@ function decodeAgePayload(value) {
1077
1096
  }
1078
1097
  function decodeAgeHeader(bytes) {
1079
1098
  const separatorIndex = indexOfBytes(bytes, AGE_HEADER_SEPARATOR_BYTES);
1080
- const header = separatorIndex >= 0 ? bytes.subarray(0, separatorIndex) : bytes;
1081
- return safeDecodeUtf8(header);
1099
+ const header2 = separatorIndex >= 0 ? bytes.subarray(0, separatorIndex) : bytes;
1100
+ return safeDecodeUtf8(header2);
1082
1101
  }
1083
1102
  function indexOfBytes(haystack, needle) {
1084
1103
  for (let index = 0; index <= haystack.length - needle.length; index += 1) {
@@ -1113,9 +1132,9 @@ async function classifyBeaconFetch(operation) {
1113
1132
  try {
1114
1133
  return await operation();
1115
1134
  } catch (error) {
1116
- throw new PrivateArtifactTimelockOpenError(
1135
+ throw new SubmissionTimelockOpenError(
1117
1136
  "timelock_beacon_unavailable",
1118
- "drand beacon is unavailable for private artifact timelock",
1137
+ "drand beacon is unavailable for Submission timelock",
1119
1138
  { cause: error }
1120
1139
  );
1121
1140
  }
@@ -1185,8 +1204,8 @@ function readRevealDelaySeconds(args) {
1185
1204
  }
1186
1205
  return parsed;
1187
1206
  }
1188
- function readInput(path6) {
1189
- return path6 === "-" ? readStdinText(process.stdin) : readFile(path6, "utf8");
1207
+ function readInput(path7) {
1208
+ return path7 === "-" ? readStdinText(process.stdin) : readFile(path7, "utf8");
1190
1209
  }
1191
1210
  function usageError(usage) {
1192
1211
  return new Error(`Usage: ${usage}`);
@@ -1216,23 +1235,118 @@ function extractFlagValue(argv, flag) {
1216
1235
 
1217
1236
  // ../../packages/core/src/bounty-challenge-markdown.ts
1218
1237
  import { keccak256 as keccak2562, toBytes as toBytes2 } from "viem";
1219
- import { z as z2 } from "zod";
1238
+ import { z as z3 } from "zod";
1239
+
1240
+ // ../../packages/core/src/protocol-profile.ts
1241
+ var PROTOCOL_PROFILE_V0 = Object.freeze({
1242
+ profile: BOUNTY_CHALLENGE_PROFILE,
1243
+ submission: Object.freeze({
1244
+ maxCiphertextBytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES,
1245
+ maxExtractedBytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES,
1246
+ maxFiles: MAX_SOLVER_SUBMISSION_FILES,
1247
+ filename: Object.freeze({
1248
+ maxCharacters: MAX_SOLVER_SUBMISSION_NAME_CHARACTERS,
1249
+ firstCharacter: "letter_or_digit",
1250
+ allowedCharacters: "letters_digits_dot_underscore_hyphen",
1251
+ caseInsensitivelyUnique: true
1252
+ }),
1253
+ layout: Object.freeze({
1254
+ flat: true,
1255
+ regularFilesOnly: true,
1256
+ allowsSubdirectories: false,
1257
+ allowsSymlinks: false,
1258
+ allowsEmpty: false,
1259
+ allowsArchivedDirectoryEntries: false,
1260
+ allowsArchivedPathTraversal: false
1261
+ }),
1262
+ externalReferences: false
1263
+ }),
1264
+ challenge: Object.freeze({
1265
+ maxMarkdownCharacters: MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS,
1266
+ requiredFrontmatterKeys: Object.freeze([
1267
+ ...BOUNTY_CHALLENGE_FRONTMATTER_KEYS
1268
+ ]),
1269
+ optionalFrontmatterKeys: Object.freeze([
1270
+ ...BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS
1271
+ ]),
1272
+ payoutPolicies: Object.freeze([WINNER_TAKE_ALL_PAYOUT_POLICY])
1273
+ }),
1274
+ encryption: Object.freeze({
1275
+ type: SUBMISSION_ENCRYPTION_TYPE
1276
+ }),
1277
+ tightenable: Object.freeze({
1278
+ max_extracted_bytes: Object.freeze({
1279
+ comparison: "at_most",
1280
+ profileValue: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
1281
+ }),
1282
+ max_files: Object.freeze({
1283
+ comparison: "at_most",
1284
+ profileValue: MAX_SOLVER_SUBMISSION_FILES
1285
+ }),
1286
+ allowed_extensions: Object.freeze({ comparison: "subset" }),
1287
+ required_files: Object.freeze({ comparison: "filename_list" })
1288
+ })
1289
+ });
1290
+ var PROTOCOL_PROFILE_V1 = Object.freeze({
1291
+ profile: BOUNTY_CHALLENGE_PROFILE_V1,
1292
+ submission: Object.freeze({
1293
+ maxCiphertextBytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES,
1294
+ maxExtractedBytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES,
1295
+ maxFiles: MAX_SOLVER_SUBMISSION_FILES_V1,
1296
+ filename: PROTOCOL_PROFILE_V0.submission.filename,
1297
+ layout: Object.freeze({
1298
+ regularFilesOnly: true,
1299
+ allowsSubdirectories: true,
1300
+ allowsSymlinks: false,
1301
+ allowsEmpty: false,
1302
+ allowsArchivedDirectoryEntries: true,
1303
+ allowsArchivedPathTraversal: false
1304
+ }),
1305
+ path: Object.freeze({
1306
+ maxCharacters: MAX_SOLVER_SUBMISSION_PATH_CHARACTERS
1307
+ }),
1308
+ externalReferences: false
1309
+ }),
1310
+ challenge: PROTOCOL_PROFILE_V0.challenge,
1311
+ encryption: Object.freeze({
1312
+ type: SUBMISSION_ENCRYPTION_TYPE_V1
1313
+ }),
1314
+ tightenable: Object.freeze({
1315
+ max_extracted_bytes: Object.freeze({
1316
+ comparison: "at_most",
1317
+ profileValue: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
1318
+ }),
1319
+ max_files: Object.freeze({
1320
+ comparison: "at_most",
1321
+ profileValue: MAX_SOLVER_SUBMISSION_FILES_V1
1322
+ }),
1323
+ allowed_extensions: Object.freeze({ comparison: "subset" }),
1324
+ required_files: Object.freeze({ comparison: "path_list" })
1325
+ })
1326
+ });
1327
+ var PROTOCOL_PROFILES = /* @__PURE__ */ new Map([
1328
+ [PROTOCOL_PROFILE_V0.profile, PROTOCOL_PROFILE_V0],
1329
+ [PROTOCOL_PROFILE_V1.profile, PROTOCOL_PROFILE_V1]
1330
+ ]);
1331
+ var CURRENT_PROTOCOL_PROFILE = PROTOCOL_PROFILE_V1;
1332
+ var PROTOCOL_PROFILE_VERSIONS = Object.freeze([
1333
+ ...PROTOCOL_PROFILES.keys()
1334
+ ]);
1335
+ function getProtocolProfile(version) {
1336
+ return PROTOCOL_PROFILES.get(version) ?? null;
1337
+ }
1338
+ function isKnownProtocolProfile(version) {
1339
+ return PROTOCOL_PROFILES.has(version);
1340
+ }
1341
+
1342
+ // ../../packages/core/src/bounty-challenge-markdown.ts
1220
1343
  var FRONTMATTER_DELIMITER = "---";
1221
- var MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS = 1e6;
1222
- var BOUNTY_CHALLENGE_FRONTMATTER_KEYS = [
1223
- "profile",
1224
- "escrow_amount",
1225
- "submission_deadline",
1226
- "payout_policy"
1227
- ];
1228
- var BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS = [
1229
- "constraints"
1230
- ];
1344
+ var YAML_LIST_ITEM = /^\s+-\s/;
1231
1345
  var ALLOWED_FRONTMATTER_KEYS = [
1232
1346
  ...BOUNTY_CHALLENGE_FRONTMATTER_KEYS,
1233
1347
  ...BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS
1234
1348
  ].join(", ");
1235
- var ALLOWED_FRONTMATTER_KEYS_MESSAGE = `Use only the v0 template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`;
1349
+ var ALLOWED_FRONTMATTER_KEYS_MESSAGE = `Use only the template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`;
1236
1350
  var DEPLOYMENT_OWNED_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
1237
1351
  "chain_id",
1238
1352
  "token_address",
@@ -1257,7 +1371,7 @@ var BountyChallengeSpecValidationError = class extends Error {
1257
1371
  this.issues = issues;
1258
1372
  }
1259
1373
  };
1260
- var markdownSchema = z2.string().min(1, "Markdown body must not be empty").max(
1374
+ var markdownSchema = z3.string().min(1, "Markdown body must not be empty").max(
1261
1375
  MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS,
1262
1376
  `Markdown body must be at most ${MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS} characters`
1263
1377
  );
@@ -1341,16 +1455,24 @@ function splitConstraintsBlock(lines, issues) {
1341
1455
  if (entry.trim().length === 0) break;
1342
1456
  if (!/^\s+\S/.test(entry)) break;
1343
1457
  index += 1;
1344
- const match = entry.trim().match(/^([a-z][a-z0-9_]*):\s*(.+)$/);
1345
- const key = match?.[1];
1346
- const rawValue = match?.[2];
1347
- if (key === void 0 || rawValue === void 0) {
1458
+ const parsed = frontmatterEntry(entry.trim());
1459
+ if (!parsed || parsed.value === "") {
1460
+ if (parsed && YAML_LIST_ITEM.test(lines[index + 1] ?? "")) {
1461
+ while (YAML_LIST_ITEM.test(lines[index + 1] ?? "")) index += 1;
1462
+ seenKeys.add(parsed.key);
1463
+ issues.push({
1464
+ path: `frontmatter.constraints.${parsed.key}`,
1465
+ message: `"${parsed.key}" must be an inline list on one line, written as ${parsed.key}: [a, b]`
1466
+ });
1467
+ continue;
1468
+ }
1348
1469
  issues.push({
1349
1470
  path: `frontmatter.constraints.line_${index + 2}`,
1350
1471
  message: "Constraint entries must use snake_case_key: value"
1351
1472
  });
1352
1473
  continue;
1353
1474
  }
1475
+ const { key, value: rawValue } = parsed;
1354
1476
  if (seenKeys.has(key)) {
1355
1477
  issues.push({
1356
1478
  path: `frontmatter.constraints.${key}`,
@@ -1377,28 +1499,29 @@ function splitConstraintsBlock(lines, issues) {
1377
1499
  }
1378
1500
  return { scalarLines, constraints };
1379
1501
  }
1502
+ function frontmatterEntry(line) {
1503
+ const key = /^[a-z][a-z0-9_]*:/.exec(line)?.[0];
1504
+ if (!key) return null;
1505
+ const value = line.slice(key.length).trimStart();
1506
+ return hasLineBreak(value) ? null : { key: key.slice(0, -1), value };
1507
+ }
1508
+ function hasLineBreak(text) {
1509
+ return /[\r\u2028\u2029]/.test(text);
1510
+ }
1380
1511
  function parsePublicationTermsFrontmatter(lines, issues) {
1381
1512
  const values = /* @__PURE__ */ new Map();
1382
1513
  let foundUnsupportedKey = false;
1383
1514
  for (const { text: line, index } of lines) {
1384
1515
  if (line.trim().length === 0) continue;
1385
- const match = line.match(/^([a-z][a-z0-9_]*):\s*(.*)$/);
1386
- if (!match) {
1387
- issues.push({
1388
- path: `frontmatter.line_${index + 2}`,
1389
- message: "Frontmatter entries must use snake_case_key: value"
1390
- });
1391
- continue;
1392
- }
1393
- const rawKey = match[1];
1394
- const rawValue = match[2];
1395
- if (rawKey === void 0 || rawValue === void 0) {
1516
+ const parsed = frontmatterEntry(line);
1517
+ if (!parsed) {
1396
1518
  issues.push({
1397
1519
  path: `frontmatter.line_${index + 2}`,
1398
1520
  message: "Frontmatter entries must use snake_case_key: value"
1399
1521
  });
1400
1522
  continue;
1401
1523
  }
1524
+ const { key: rawKey, value: rawValue } = parsed;
1402
1525
  if (!isBountyChallengeFrontmatterKey(rawKey)) {
1403
1526
  if (DEPLOYMENT_OWNED_FRONTMATTER_KEYS.has(rawKey)) {
1404
1527
  if (!foundUnsupportedKey) {
@@ -1411,7 +1534,7 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1411
1534
  } else {
1412
1535
  issues.push({
1413
1536
  path: `frontmatter.${rawKey}`,
1414
- message: `Remove frontmatter key "${rawKey}"; use only the v0 template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`
1537
+ message: `Remove frontmatter key "${rawKey}"; use only the template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`
1415
1538
  });
1416
1539
  }
1417
1540
  continue;
@@ -1439,10 +1562,10 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1439
1562
  }
1440
1563
  }
1441
1564
  const profile = values.get("profile");
1442
- if (profile !== void 0 && profile !== BOUNTY_CHALLENGE_PROFILE) {
1565
+ if (profile !== void 0 && !isKnownProtocolProfile(profile)) {
1443
1566
  issues.push({
1444
1567
  path: "frontmatter.profile",
1445
- message: `Must equal ${BOUNTY_CHALLENGE_PROFILE}`
1568
+ message: `Must be a published protocol profile: ${PROTOCOL_PROFILE_VERSIONS.join(", ")}`
1446
1569
  });
1447
1570
  }
1448
1571
  const payoutPolicy = values.get("payout_policy");
@@ -1462,7 +1585,7 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1462
1585
  "frontmatter.escrow_amount",
1463
1586
  issues
1464
1587
  );
1465
- if (profile !== BOUNTY_CHALLENGE_PROFILE || payoutPolicy !== WINNER_TAKE_ALL_PAYOUT_POLICY || escrowAmount === null || submissionDeadline === null) {
1588
+ if (profile === void 0 || !isKnownProtocolProfile(profile) || payoutPolicy !== WINNER_TAKE_ALL_PAYOUT_POLICY || escrowAmount === null || submissionDeadline === null) {
1466
1589
  return null;
1467
1590
  }
1468
1591
  return {
@@ -1472,40 +1595,40 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1472
1595
  payoutPolicy
1473
1596
  };
1474
1597
  }
1475
- function normalizeFrontmatterScalar(rawValue, path6, issues) {
1598
+ function normalizeFrontmatterScalar(rawValue, path7, issues) {
1476
1599
  const value = rawValue.trim();
1477
1600
  if (value.length === 0) {
1478
- issues.push({ path: path6, message: "Frontmatter value must not be empty" });
1601
+ issues.push({ path: path7, message: "Frontmatter value must not be empty" });
1479
1602
  return null;
1480
1603
  }
1481
1604
  const quote = value[0];
1482
1605
  if (quote !== '"' && quote !== "'") return value;
1483
1606
  if (value.length < 2 || value.at(-1) !== quote) {
1484
1607
  issues.push({
1485
- path: path6,
1608
+ path: path7,
1486
1609
  message: "Quoted frontmatter values must close with the same quote"
1487
1610
  });
1488
1611
  return null;
1489
1612
  }
1490
1613
  return value.slice(1, -1);
1491
1614
  }
1492
- function parsePositiveSafeIntegerFrontmatter(value, path6, issues) {
1615
+ function parsePositiveSafeIntegerFrontmatter(value, path7, issues) {
1493
1616
  if (value === void 0) return null;
1494
1617
  if (!isPositiveInteger(value)) {
1495
- issues.push({ path: path6, message: "Must be a positive integer" });
1618
+ issues.push({ path: path7, message: "Must be a positive integer" });
1496
1619
  return null;
1497
1620
  }
1498
1621
  const parsed = Number(value);
1499
1622
  if (!Number.isSafeInteger(parsed) || parsed <= 0) {
1500
- issues.push({ path: path6, message: "Must be a positive safe integer" });
1623
+ issues.push({ path: path7, message: "Must be a positive safe integer" });
1501
1624
  return null;
1502
1625
  }
1503
1626
  return parsed;
1504
1627
  }
1505
- function parsePositiveIntegerStringFrontmatter(value, path6, issues) {
1628
+ function parsePositiveIntegerStringFrontmatter(value, path7, issues) {
1506
1629
  if (value === void 0) return null;
1507
1630
  if (!isPositiveInteger(value)) {
1508
- issues.push({ path: path6, message: "Must be positive token smallest units" });
1631
+ issues.push({ path: path7, message: "Must be positive token smallest units" });
1509
1632
  return null;
1510
1633
  }
1511
1634
  return value;
@@ -1529,36 +1652,36 @@ function isBountyChallengeFrontmatterKey(value) {
1529
1652
 
1530
1653
  // ../../packages/core/src/bounty-publication-response.ts
1531
1654
  import { isHex as isHex2 } from "viem";
1532
- import { z as z3 } from "zod";
1533
- var addressSchema = z3.string().refine(isHexAddress, "Must be a 20-byte EVM address").transform((value) => value);
1534
- var bytes32Schema2 = z3.string().refine(isHexBytes32, "Must be a bytes32 hex string").transform((value) => value);
1535
- var hexBytesSchema = z3.string().refine(isHex2, "Must be hex-encoded calldata").transform((value) => value);
1536
- var bountyChallengeContentRecordSchema = z3.object({
1537
- content_kind: z3.literal("bounty_challenge_spec"),
1655
+ import { z as z4 } from "zod";
1656
+ var addressSchema = z4.string().refine(isHexAddress, "Must be a 20-byte EVM address").transform((value) => value);
1657
+ var bytes32Schema2 = z4.string().refine(isHexBytes32, "Must be a bytes32 hex string").transform((value) => value);
1658
+ var hexBytesSchema = z4.string().refine(isHex2, "Must be hex-encoded calldata").transform((value) => value);
1659
+ var bountyChallengeContentRecordSchema = z4.object({
1660
+ content_kind: z4.literal("bounty_challenge_spec"),
1538
1661
  spec_commitment: bytes32Schema2,
1539
- byte_length: z3.number().int().positive(),
1662
+ byte_length: z4.number().int().positive(),
1540
1663
  token_address: addressSchema,
1541
- created_at: z3.string().datetime({ offset: true })
1664
+ created_at: z4.string().datetime({ offset: true })
1542
1665
  }).strict();
1543
- var bountyPublicationResponseSchema = z3.object({
1666
+ var bountyPublicationResponseSchema = z4.object({
1544
1667
  content_record: bountyChallengeContentRecordSchema,
1545
- create_bounty_transaction: z3.object({
1546
- chain_id: z3.number().int().positive(),
1668
+ create_bounty_transaction: z4.object({
1669
+ chain_id: z4.number().int().positive(),
1547
1670
  to: addressSchema,
1548
- value: z3.literal("0"),
1671
+ value: z4.literal("0"),
1549
1672
  data: hexBytesSchema,
1550
- function_name: z3.literal("create_bounty"),
1551
- args: z3.object({
1673
+ function_name: z4.literal("create_bounty"),
1674
+ args: z4.object({
1552
1675
  spec_commitment: bytes32Schema2,
1553
- escrow_amount: z3.string().refine(isPositiveInteger),
1554
- submission_deadline: z3.number().int().positive()
1676
+ escrow_amount: z4.string().refine(isPositiveInteger),
1677
+ submission_deadline: z4.number().int().positive()
1555
1678
  }).strict()
1556
1679
  }).strict(),
1557
- protocol_fee_policy: z3.object({
1680
+ protocol_fee_policy: z4.object({
1558
1681
  treasury_recipient: addressSchema,
1559
- treasury_fee_bps: z3.number().int().min(0).max(1e4),
1682
+ treasury_fee_bps: z4.number().int().min(0).max(1e4),
1560
1683
  guardian_fee_recipient: addressSchema,
1561
- guardian_fee_bps: z3.number().int().min(0).max(1e4)
1684
+ guardian_fee_bps: z4.number().int().min(0).max(1e4)
1562
1685
  }).strict()
1563
1686
  }).strict().superRefine((response, context) => {
1564
1687
  if (response.content_record.spec_commitment !== response.create_bounty_transaction.args.spec_commitment) {
@@ -1569,31 +1692,31 @@ var bountyPublicationResponseSchema = z3.object({
1569
1692
  });
1570
1693
  }
1571
1694
  });
1572
- var priorIssueSchema = z3.object({
1573
- issue_index: z3.number().int().nonnegative(),
1574
- message: z3.string().trim().min(1).max(1500)
1695
+ var priorIssueSchema = z4.object({
1696
+ issue_index: z4.number().int().nonnegative(),
1697
+ message: z4.string().trim().min(1).max(1500)
1575
1698
  }).strict();
1576
1699
  var priorIssueStatusSchema = priorIssueSchema.extend({
1577
- status: z3.enum(["resolved", "open"])
1700
+ status: z4.enum(["resolved", "open"])
1578
1701
  }).strict();
1579
- var readinessFindingSchema = z3.object({
1580
- path: z3.string().trim().min(1).max(120),
1581
- message: z3.string().trim().min(1).max(1500),
1582
- code: z3.string().min(1).optional(),
1583
- anchor: z3.string().trim().min(1).max(120).optional(),
1584
- hazard: z3.boolean().optional()
1702
+ var readinessFindingSchema = z4.object({
1703
+ path: z4.string().trim().min(1).max(120),
1704
+ message: z4.string().trim().min(1).max(1500),
1705
+ code: z4.string().min(1).optional(),
1706
+ anchor: z4.string().trim().min(1).max(120).optional(),
1707
+ hazard: z4.boolean().optional()
1585
1708
  });
1586
1709
  var MAX_READINESS_FINDINGS = 20;
1587
- var bountyReviewResponseSchema = z3.object({
1710
+ var bountyReviewResponseSchema = z4.object({
1588
1711
  content_record: bountyChallengeContentRecordSchema.omit({ created_at: true }),
1589
- readiness: z3.object({
1590
- decision: z3.literal("ready"),
1712
+ readiness: z4.object({
1713
+ decision: z4.literal("ready"),
1591
1714
  // Publication reuses the approval of these exact bytes until then.
1592
- approved_until: z3.string().datetime({ offset: true }),
1715
+ approved_until: z4.string().datetime({ offset: true }),
1593
1716
  // The dissenting pass's findings on a ready check; never blocking.
1594
- warnings: z3.array(readinessFindingSchema).max(MAX_READINESS_FINDINGS),
1595
- prior_issues_resolved: z3.array(priorIssueSchema),
1596
- prior_issues_open: z3.array(priorIssueSchema)
1717
+ warnings: z4.array(readinessFindingSchema).max(MAX_READINESS_FINDINGS),
1718
+ prior_issues_resolved: z4.array(priorIssueSchema),
1719
+ prior_issues_open: z4.array(priorIssueSchema)
1597
1720
  })
1598
1721
  });
1599
1722
 
@@ -1615,76 +1738,15 @@ function resolveSupportedChain(chainId) {
1615
1738
  }
1616
1739
  var FOUNDRY_CHAIN_ID = foundry.id;
1617
1740
 
1618
- // ../../packages/core/src/protocol-profile.ts
1619
- var PROTOCOL_PROFILE_V0 = Object.freeze({
1620
- profile: BOUNTY_CHALLENGE_PROFILE,
1621
- submission: Object.freeze({
1622
- maxCiphertextBytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES,
1623
- maxExtractedBytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES,
1624
- maxFiles: MAX_SOLVER_SUBMISSION_FILES,
1625
- filename: Object.freeze({
1626
- maxCharacters: MAX_SOLVER_SUBMISSION_NAME_CHARACTERS,
1627
- firstCharacter: "letter_or_digit",
1628
- allowedCharacters: "letters_digits_dot_underscore_hyphen",
1629
- caseInsensitivelyUnique: true
1630
- }),
1631
- layout: Object.freeze({
1632
- flat: true,
1633
- regularFilesOnly: true,
1634
- allowsSubdirectories: false,
1635
- allowsSymlinks: false,
1636
- allowsEmpty: false,
1637
- allowsArchivedDirectoryEntries: false,
1638
- allowsArchivedPathTraversal: false
1639
- }),
1640
- externalReferences: false
1641
- }),
1642
- challenge: Object.freeze({
1643
- maxMarkdownCharacters: MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS,
1644
- requiredFrontmatterKeys: Object.freeze([
1645
- ...BOUNTY_CHALLENGE_FRONTMATTER_KEYS
1646
- ]),
1647
- optionalFrontmatterKeys: Object.freeze([
1648
- ...BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS
1649
- ]),
1650
- payoutPolicies: Object.freeze([WINNER_TAKE_ALL_PAYOUT_POLICY])
1651
- }),
1652
- encryption: Object.freeze({
1653
- type: SUBMISSION_ENCRYPTION_TYPE
1654
- }),
1655
- tightenable: Object.freeze({
1656
- max_extracted_bytes: Object.freeze({
1657
- comparison: "at_most",
1658
- profileValue: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
1659
- }),
1660
- max_files: Object.freeze({
1661
- comparison: "at_most",
1662
- profileValue: MAX_SOLVER_SUBMISSION_FILES
1663
- }),
1664
- allowed_extensions: Object.freeze({ comparison: "subset" }),
1665
- required_files: Object.freeze({ comparison: "filename_list" })
1666
- })
1667
- });
1668
- var PROTOCOL_PROFILES = /* @__PURE__ */ new Map([
1669
- [PROTOCOL_PROFILE_V0.profile, PROTOCOL_PROFILE_V0]
1670
- ]);
1671
- var CURRENT_PROTOCOL_PROFILE = PROTOCOL_PROFILE_V0;
1672
- var PROTOCOL_PROFILE_VERSIONS = Object.freeze([
1673
- ...PROTOCOL_PROFILES.keys()
1674
- ]);
1675
- function getProtocolProfile(version) {
1676
- return PROTOCOL_PROFILES.get(version) ?? null;
1677
- }
1678
-
1679
1741
  // ../../packages/core/src/challenge-conformance.ts
1680
1742
  function checkChallengeConformance(input, profile = CURRENT_PROTOCOL_PROFILE) {
1681
1743
  const issues = [];
1682
1744
  const parsed = typeof input === "string" ? readFrontmatter(input, issues) : input;
1683
- const pinned = parsed === null ? null : resolveProfile(parsed.publicationTerms.profile, profile, issues);
1684
- if (parsed !== null && pinned !== null) {
1685
- checkTightenings(parsed.constraints, pinned, issues);
1745
+ const declaredProfile = parsed === null ? null : resolveProfile(parsed.publicationTerms.profile, profile, issues);
1746
+ if (parsed !== null && declaredProfile !== null) {
1747
+ checkTightenings(parsed.constraints, declaredProfile, issues);
1686
1748
  }
1687
- return { issues, profile: pinned };
1749
+ return { issues, profile: declaredProfile };
1688
1750
  }
1689
1751
  function readFrontmatter(markdown, issues) {
1690
1752
  try {
@@ -1703,16 +1765,16 @@ function readFrontmatter(markdown, issues) {
1703
1765
  }
1704
1766
  }
1705
1767
  function resolveProfile(declared, fallback2, issues) {
1706
- const pinned = getProtocolProfile(declared) ?? (declared === fallback2.profile ? fallback2 : null);
1707
- if (pinned === null) {
1768
+ const known = getProtocolProfile(declared) ?? (declared === fallback2.profile ? fallback2 : null);
1769
+ if (known === null) {
1708
1770
  issues.push({
1709
1771
  code: "profile_unknown",
1710
1772
  anchor: "frontmatter.profile",
1711
1773
  path: "frontmatter.profile",
1712
- message: `Unknown protocol profile "${declared}". A bounty can only pin a published profile version.`
1774
+ message: `Unknown protocol profile "${declared}". A bounty can only declare a published profile version.`
1713
1775
  });
1714
1776
  }
1715
- return pinned;
1777
+ return known;
1716
1778
  }
1717
1779
  function checkTightenings(constraints, profile, issues) {
1718
1780
  for (const constraint of constraints) {
@@ -1733,6 +1795,30 @@ function checkTightenings(constraints, profile, issues) {
1733
1795
  }
1734
1796
  checkListTightening(constraint, rule, profile, anchor, issues);
1735
1797
  }
1798
+ checkRequiredFilesAllowed(constraints, issues);
1799
+ }
1800
+ function checkRequiredFilesAllowed(constraints, issues) {
1801
+ const required2 = constraints.find((c) => c.key === "required_files");
1802
+ const allowed = constraints.find((c) => c.key === "allowed_extensions");
1803
+ if (required2 === void 0 || allowed === void 0) return;
1804
+ const anchors = [required2, allowed].map(
1805
+ (constraint) => `frontmatter.constraints.${constraint.key}`
1806
+ );
1807
+ if (issues.some((issue) => anchors.includes(issue.anchor))) return;
1808
+ const paths = parseInlineList(required2.value);
1809
+ const extensions = parseInlineList(allowed.value);
1810
+ if (paths === null || extensions === null) return;
1811
+ const excluded = paths.filter(
1812
+ (file) => !hasAllowedExtension(file, extensions)
1813
+ );
1814
+ if (excluded.length > 0) {
1815
+ issues.push({
1816
+ code: "tightening_malformed",
1817
+ anchor: anchors[0],
1818
+ path: anchors[0],
1819
+ message: `"required_files" names ${excluded.join(", ")}, which "allowed_extensions" excludes, so no Solver could satisfy both.`
1820
+ });
1821
+ }
1736
1822
  }
1737
1823
  function checkNumericTightening(constraint, rule, anchor, issues) {
1738
1824
  const ceiling = rule.profileValue;
@@ -1791,6 +1877,10 @@ function checkListTightening(constraint, rule, profile, anchor, issues) {
1791
1877
  return;
1792
1878
  }
1793
1879
  const { filename, maxFiles } = profile.submission;
1880
+ if (rule.comparison === "path_list") {
1881
+ checkRequiredPaths(constraint, members, maxFiles, anchor, issues);
1882
+ return;
1883
+ }
1794
1884
  const malformed = members.filter(
1795
1885
  (member) => member.length > filename.maxCharacters || !SOLVER_SUBMISSION_NAME_RE.test(member)
1796
1886
  );
@@ -1830,6 +1920,57 @@ function checkListTightening(constraint, rule, profile, anchor, issues) {
1830
1920
  });
1831
1921
  }
1832
1922
  }
1923
+ function checkRequiredPaths(constraint, members, maxFiles, anchor, issues) {
1924
+ const malformed = members.filter(
1925
+ (member) => describeSolverSubmissionPathIssue(member) !== null
1926
+ );
1927
+ if (malformed.length > 0) {
1928
+ issues.push({
1929
+ code: "tightening_malformed",
1930
+ anchor,
1931
+ path: anchor,
1932
+ message: `"${constraint.key}" names paths that do not satisfy the profile's path rule: ${malformed.join(", ")}. A path is "/"-separated names, each starting with a letter or digit and using only letters, digits, ".", "_" and "-".`
1933
+ });
1934
+ return;
1935
+ }
1936
+ const conflict = findSolverSubmissionPathConflict(members);
1937
+ if (conflict !== null) {
1938
+ issues.push({
1939
+ code: "tightening_malformed",
1940
+ anchor,
1941
+ path: anchor,
1942
+ message: `"${constraint.key}" cannot be satisfied: ${describeSolverSubmissionPathConflict(conflict)}.`
1943
+ });
1944
+ return;
1945
+ }
1946
+ if (members.length > maxFiles) {
1947
+ issues.push({
1948
+ code: "tightening_exceeds_profile",
1949
+ anchor,
1950
+ path: anchor,
1951
+ message: `"${constraint.key}" requires ${members.length} files, above the profile's ceiling of ${maxFiles}.`
1952
+ });
1953
+ }
1954
+ }
1955
+ function hasAllowedExtension(path7, extensions) {
1956
+ const name = path7.toLowerCase();
1957
+ return extensions.some((extension) => name.endsWith(extension.toLowerCase()));
1958
+ }
1959
+ function readChallengeConstraints(constraints) {
1960
+ const read = {};
1961
+ for (const { key, value } of constraints) {
1962
+ if (key === "max_extracted_bytes" && /^\d+$/.test(value)) {
1963
+ read.maxExtractedBytes = Number(value);
1964
+ } else if (key === "max_files" && /^\d+$/.test(value)) {
1965
+ read.maxFiles = Number(value);
1966
+ } else if (key === "required_files") {
1967
+ read.requiredFiles = parseInlineList(value) ?? [];
1968
+ } else if (key === "allowed_extensions") {
1969
+ read.allowedExtensions = parseInlineList(value) ?? [];
1970
+ }
1971
+ }
1972
+ return read;
1973
+ }
1833
1974
  function parseInlineList(value) {
1834
1975
  if (!value.startsWith("[") || !value.endsWith("]")) return null;
1835
1976
  return value.slice(1, -1).split(",").map((member) => member.trim().replace(/^["']|["']$/g, "")).filter((member) => member.length > 0);
@@ -1845,6 +1986,7 @@ var DEPLOYMENTS = {
1845
1986
  ELGORA_SUBGRAPH_ENDPOINT: "https://api.goldsky.com/api/public/project_cmtu7wslg04s501ovc2je3beh/subgraphs/elgora-elgorahub-base/production/gn",
1846
1987
  ELGORA_API_BASE_URL: "https://elgora.ai",
1847
1988
  agentIdentitySubgraphEndpoint: "https://gateway.thegraph.com/api/subgraphs/id/43s9hQRurMGjuYnC1r2ZwS6xSQktbFyXMPMqGKUFJojb",
1989
+ agentIdentityRegistryAddress: "0x8004a169fb4a3325136eb29fa0ceb6d2e539a432",
1848
1990
  agentExplorerBaseUrl: "https://8004scan.io/agents/base",
1849
1991
  deployBlock: 51752389,
1850
1992
  deployTransaction: "0xd62f407b770c8fca82a9704b30ed0605d9e3ed2b56278dcca152958063f7f469",
@@ -1857,6 +1999,7 @@ var DEPLOYMENTS = {
1857
1999
  ELGORA_SUBGRAPH_ENDPOINT: "https://api.goldsky.com/api/public/project_cmtu7wslg04s501ovc2je3beh/subgraphs/elgora-elgorahub/testnet-production/gn",
1858
2000
  ELGORA_API_BASE_URL: "https://testnet.elgora.ai",
1859
2001
  agentIdentitySubgraphEndpoint: "https://gateway.thegraph.com/api/subgraphs/id/4yYAvQLFjBhBtdRCY7eUWo181VNoTSLLFd5M7FXQAi6u",
2002
+ agentIdentityRegistryAddress: "0x8004a818bfb912233c491871b3d84c89a494bd9e",
1860
2003
  agentExplorerBaseUrl: "https://testnet.8004scan.io/agents/base-sepolia",
1861
2004
  deployBlock: 46353511,
1862
2005
  deployTransaction: "0xed3d3c8af3c69f9f8d4ec47da7179fe4b89bd4a4cd8d8232dbbd3d3f3d38e74b",
@@ -1894,9 +2037,9 @@ async function main(_env = process.env, argv = process.argv.slice(2)) {
1894
2037
  }
1895
2038
  }
1896
2039
  function readBountyChallengeMarkdown(argv) {
1897
- const path6 = argv[0];
1898
- if (!path6 || argv.length !== 1) throw usageError(USAGE);
1899
- return readInput(path6);
2040
+ const path7 = argv[0];
2041
+ if (!path7 || argv.length !== 1) throw usageError(USAGE);
2042
+ return readInput(path7);
1900
2043
  }
1901
2044
  var command = { main, OPTIONS, USAGE };
1902
2045
 
@@ -4167,8 +4310,11 @@ function settlementAmounts(escrowAmount, treasuryFeeBps, guardianFeeBps) {
4167
4310
  };
4168
4311
  }
4169
4312
 
4313
+ // ../../packages/core/src/api/bounty-list.ts
4314
+ import { z as z6 } from "zod";
4315
+
4170
4316
  // ../../packages/core/src/api/bounty-preparation.ts
4171
- import { ZodError, z as z4 } from "zod";
4317
+ import { ZodError, z as z5 } from "zod";
4172
4318
 
4173
4319
  // ../../packages/core/src/api/elgorahub.ts
4174
4320
  import {
@@ -4214,6 +4360,12 @@ var ELGORA_HUB_OWNERSHIP_READ_ABI = [
4214
4360
  getAbiItem2({ abi: ELGORA_HUB_ABI, name: "pendingOwner" }),
4215
4361
  ...ELGORA_HUB_ERRORS
4216
4362
  ];
4363
+ var SUBMISSION_CAP_READ_ABI = [
4364
+ getAbiItem2({ abi: ELGORA_HUB_ABI, name: "submissionGuard" }),
4365
+ ...parseAbi([
4366
+ "function maximumSubmissions(uint256 bountyId) view returns (uint256)"
4367
+ ])
4368
+ ];
4217
4369
  var ELGORA_HUB_BOUNTY_CREATED_EVENT = getAbiItem2({
4218
4370
  abi: ELGORA_HUB_ABI,
4219
4371
  name: "BountyCreated"
@@ -4401,14 +4553,59 @@ var BoundaryValidationError = class extends Error {
4401
4553
  this.issues = issues;
4402
4554
  }
4403
4555
  };
4404
- var artifactUploadUrlRequestSchema = z4.object({
4405
- ciphertext_sha256: z4.string().refine(isSha256Hex),
4406
- ciphertext_byte_length: z4.number().int().positive().max(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)
4556
+ var artifactUploadUrlRequestSchema = z5.object({
4557
+ ciphertext_sha256: z5.string().refine(isSha256Hex),
4558
+ ciphertext_byte_length: z5.number().int().positive().max(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)
4407
4559
  }).strict();
4408
4560
 
4561
+ // ../../packages/core/src/api/bounty-list.ts
4562
+ var BOUNTY_LIST_MAX_FIRST = 100;
4563
+ var integerString = z6.string().regex(/^(0|[1-9]\d*)$/, {
4564
+ message: "Must be a non-negative integer"
4565
+ });
4566
+ var bountyFilterFields = {
4567
+ min_amount: integerString.optional(),
4568
+ max_amount: integerString.optional(),
4569
+ min_time_left: integerString.optional(),
4570
+ max_time_left: integerString.optional()
4571
+ };
4572
+ var bountyListParamsSchema = z6.object({
4573
+ ...bountyFilterFields,
4574
+ status: z6.enum(BOUNTY_STATUSES).optional(),
4575
+ first: integerString.transform(Number).refine((value) => value >= 1 && value <= BOUNTY_LIST_MAX_FIRST, {
4576
+ message: `Must be from 1 to ${BOUNTY_LIST_MAX_FIRST}`
4577
+ }).optional(),
4578
+ skip: integerString.transform(Number).optional()
4579
+ }).strict();
4580
+ var hexSchema = z6.custom(
4581
+ (value) => typeof value === "string" && /^0x[0-9a-f]*$/i.test(value)
4582
+ );
4583
+ var listedBountySchema = z6.object({
4584
+ bounty_id: z6.string(),
4585
+ status: z6.enum(BOUNTY_STATUSES),
4586
+ poster: hexSchema,
4587
+ spec_commitment: hexSchema,
4588
+ escrow: z6.object({ token_address: hexSchema, amount: z6.string() }),
4589
+ submission_deadline: z6.number().int(),
4590
+ submission_count: z6.number().int(),
4591
+ winner: hexSchema.nullable(),
4592
+ created_at: z6.number().int()
4593
+ });
4594
+ var bountyListResponseSchema = z6.object({
4595
+ chain_id: z6.number().int(),
4596
+ hub_address: hexSchema,
4597
+ /** Bounties matching the filter, before `first` and `skip`. */
4598
+ total: z6.number().int(),
4599
+ bounties: z6.array(listedBountySchema)
4600
+ });
4601
+ function challengeMatchesKeywords(challenge, keywords) {
4602
+ const text = challenge.toLowerCase();
4603
+ return keywords.every((keyword) => text.includes(keyword.toLowerCase()));
4604
+ }
4605
+
4409
4606
  // ../../packages/core/src/api/config.ts
4410
4607
  import { createHash } from "crypto";
4411
- import { z as z5 } from "zod";
4608
+ import { z as z7 } from "zod";
4412
4609
  function withDeploymentEnvDefaults(env) {
4413
4610
  const merged = { ...env };
4414
4611
  const setIfBlank = (key, value) => {
@@ -4429,11 +4626,11 @@ function withDeploymentEnvDefaults(env) {
4429
4626
  setIfBlank("ELGORA_SUBGRAPH_ENDPOINT", deployment?.ELGORA_SUBGRAPH_ENDPOINT);
4430
4627
  return merged;
4431
4628
  }
4432
- var chainIdField = z5.coerce.number().int().positive().refine((chainId) => SUPPORTED_CHAINS[chainId] !== void 0, {
4629
+ var chainIdField = z7.coerce.number().int().positive().refine((chainId) => SUPPORTED_CHAINS[chainId] !== void 0, {
4433
4630
  message: `ELGORA_CHAIN_ID must be a supported chain: ${Object.keys(SUPPORTED_CHAINS).join(", ")}`
4434
4631
  });
4435
- var hubAddressField = z5.string().refine(isHexAddress, "ELGORA_HUB_ADDRESS must be a 20-byte EVM address").transform((value) => value.toLowerCase());
4436
- var publicDeploymentEnvSchema = z5.object({
4632
+ var hubAddressField = z7.string().refine(isHexAddress, "ELGORA_HUB_ADDRESS must be a 20-byte EVM address").transform((value) => value.toLowerCase());
4633
+ var publicDeploymentEnvSchema = z7.object({
4437
4634
  ELGORA_CHAIN_ID: chainIdField,
4438
4635
  ELGORA_HUB_ADDRESS: hubAddressField
4439
4636
  });
@@ -4449,30 +4646,30 @@ function loadPublicDeploymentConfig(env = process.env) {
4449
4646
  var envShape = {
4450
4647
  ELGORA_CHAIN_ID: chainIdField,
4451
4648
  ELGORA_HUB_ADDRESS: hubAddressField,
4452
- ELGORA_ESCROW_TOKEN_ADDRESS: z5.string().refine(
4649
+ ELGORA_ESCROW_TOKEN_ADDRESS: z7.string().refine(
4453
4650
  isHexAddress,
4454
4651
  "ELGORA_ESCROW_TOKEN_ADDRESS must be a 20-byte EVM address"
4455
4652
  ).transform((value) => value.toLowerCase()),
4456
- ELGORA_TIMELOCK_REVEAL_DELAY_SECONDS: z5.coerce.number().int().nonnegative().default(DEFAULT_TIMELOCK_REVEAL_DELAY_SECONDS),
4457
- ELGORA_RPC_URL: z5.string().min(1, "ELGORA_RPC_URL is required"),
4653
+ ELGORA_TIMELOCK_REVEAL_DELAY_SECONDS: z7.coerce.number().int().nonnegative().default(DEFAULT_TIMELOCK_REVEAL_DELAY_SECONDS),
4654
+ ELGORA_RPC_URL: z7.string().min(1, "ELGORA_RPC_URL is required"),
4458
4655
  /**
4459
4656
  * Only consulted for a chain with no recorded deployment — a local anvil
4460
4657
  * stack, whose addresses change on every deploy. A recorded deployment's
4461
4658
  * scheme always wins, because settlement validation compares against it.
4462
4659
  */
4463
- ELGORA_PAYOUT_SCHEME_ADDRESS: z5.string().refine(
4660
+ ELGORA_PAYOUT_SCHEME_ADDRESS: z7.string().refine(
4464
4661
  isHexAddress,
4465
4662
  "ELGORA_PAYOUT_SCHEME_ADDRESS must be a 20-byte EVM address"
4466
4663
  ).transform((value) => value.toLowerCase()).optional(),
4467
- ELGORA_DATABASE_URL: z5.string().min(1, "ELGORA_DATABASE_URL is required"),
4468
- ELGORA_SUBGRAPH_ENDPOINT: z5.string().min(1, "ELGORA_SUBGRAPH_ENDPOINT is required"),
4469
- ELGORA_ARTIFACT_STORAGE_ENDPOINT: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_ENDPOINT is required"),
4470
- ELGORA_ARTIFACT_STORAGE_REGION: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_REGION is required"),
4471
- ELGORA_ARTIFACT_STORAGE_BUCKET: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_BUCKET is required"),
4472
- ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID is required"),
4473
- ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY is required"),
4474
- ELGORA_DELIVERY_PUBLIC_KEY: z5.string().min(1, "ELGORA_DELIVERY_PUBLIC_KEY is required"),
4475
- ELGORA_DELIVERY_PRIVATE_KEY: z5.string().min(1, "ELGORA_DELIVERY_PRIVATE_KEY is required")
4664
+ ELGORA_DATABASE_URL: z7.string().min(1, "ELGORA_DATABASE_URL is required"),
4665
+ ELGORA_SUBGRAPH_ENDPOINT: z7.string().min(1, "ELGORA_SUBGRAPH_ENDPOINT is required"),
4666
+ ELGORA_ARTIFACT_STORAGE_ENDPOINT: z7.string().min(1, "ELGORA_ARTIFACT_STORAGE_ENDPOINT is required"),
4667
+ ELGORA_ARTIFACT_STORAGE_REGION: z7.string().min(1, "ELGORA_ARTIFACT_STORAGE_REGION is required"),
4668
+ ELGORA_ARTIFACT_STORAGE_BUCKET: z7.string().min(1, "ELGORA_ARTIFACT_STORAGE_BUCKET is required"),
4669
+ ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID: z7.string().min(1, "ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID is required"),
4670
+ ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY: z7.string().min(1, "ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY is required"),
4671
+ ELGORA_DELIVERY_PUBLIC_KEY: z7.string().min(1, "ELGORA_DELIVERY_PUBLIC_KEY is required"),
4672
+ ELGORA_DELIVERY_PRIVATE_KEY: z7.string().min(1, "ELGORA_DELIVERY_PRIVATE_KEY is required")
4476
4673
  };
4477
4674
  var publicationEnvShape = {
4478
4675
  ELGORA_CHAIN_ID: envShape.ELGORA_CHAIN_ID,
@@ -4480,88 +4677,89 @@ var publicationEnvShape = {
4480
4677
  ELGORA_ESCROW_TOKEN_ADDRESS: envShape.ELGORA_ESCROW_TOKEN_ADDRESS,
4481
4678
  ELGORA_DATABASE_URL: envShape.ELGORA_DATABASE_URL,
4482
4679
  ELGORA_SUBGRAPH_ENDPOINT: envShape.ELGORA_SUBGRAPH_ENDPOINT,
4483
- ELGORA_PUBLICATION_REVIEW_ENABLED: z5.string().trim().toLowerCase().pipe(z5.enum(["true", "false"])).default("true").transform((value) => value === "true"),
4484
- ELGORA_READINESS_TIMEOUT_MS: z5.coerce.number().int().min(1).max(READINESS_TIMEOUT_MS).default(READINESS_TIMEOUT_MS)
4680
+ ELGORA_PUBLICATION_REVIEW_ENABLED: z7.string().trim().toLowerCase().pipe(z7.enum(["true", "false"])).default("true").transform((value) => value === "true"),
4681
+ ELGORA_READINESS_TIMEOUT_MS: z7.coerce.number().int().min(1).max(READINESS_TIMEOUT_MS).default(READINESS_TIMEOUT_MS)
4485
4682
  };
4486
- var envSchema = z5.object(envShape);
4487
- var bountyPublicationEnvSchema = z5.object(publicationEnvShape);
4683
+ var envSchema = z7.object(envShape);
4684
+ var bountyPublicationEnvSchema = z7.object(publicationEnvShape);
4488
4685
 
4489
4686
  // ../../packages/core/src/api/solver-submission-responses.ts
4490
4687
  import { isHex as isHex3 } from "viem";
4491
- import { z as z6 } from "zod";
4492
- var hexAddressSchema = z6.string().refine(isHexAddress, "Must be a hex-encoded address");
4493
- var hexBytes32Schema = z6.string().refine(isHexBytes32, "Must be 32-byte hex");
4494
- var hexDataSchema = z6.string().refine((value) => isHex3(value), "Must be hex-encoded calldata");
4495
- var artifactUploadUrlResponseSchema = z6.object({
4496
- locator: z6.string().min(1),
4497
- upload_url: z6.string().min(1),
4498
- method: z6.literal("PUT"),
4499
- expires_at: z6.string().min(1)
4688
+ import { z as z8 } from "zod";
4689
+ var hexAddressSchema = z8.string().refine(isHexAddress, "Must be a hex-encoded address");
4690
+ var hexBytes32Schema = z8.string().refine(isHexBytes32, "Must be 32-byte hex");
4691
+ var hexDataSchema = z8.string().refine((value) => isHex3(value), "Must be hex-encoded calldata");
4692
+ var artifactUploadUrlResponseSchema = z8.object({
4693
+ locator: z8.string().min(1),
4694
+ upload_url: z8.string().min(1),
4695
+ method: z8.literal("PUT"),
4696
+ expires_at: z8.string().min(1)
4500
4697
  });
4501
- var solverSubmissionPrepareResponseSchema = z6.object({
4502
- content_record: z6.object({
4503
- content_kind: z6.literal(SOLVER_SUBMISSION_CONTENT_KIND),
4698
+ var solverSubmissionPrepareResponseSchema = z8.object({
4699
+ content_record: z8.object({
4700
+ content_kind: z8.literal(SOLVER_SUBMISSION_CONTENT_KIND),
4504
4701
  submission_commitment: hexBytes32Schema,
4505
- byte_length: z6.number().int().nonnegative(),
4506
- created_at: z6.string().min(1)
4702
+ byte_length: z8.number().int().nonnegative(),
4703
+ created_at: z8.string().min(1)
4507
4704
  }),
4508
- submit_transaction: z6.object({
4509
- chain_id: z6.number().int().positive(),
4705
+ submit_transaction: z8.object({
4706
+ chain_id: z8.number().int().positive(),
4510
4707
  to: hexAddressSchema,
4511
- value: z6.literal("0"),
4708
+ value: z8.literal("0"),
4512
4709
  data: hexDataSchema,
4513
- function_name: z6.literal("submit"),
4514
- args: z6.object({
4515
- bounty_id: z6.string().min(1),
4710
+ function_name: z8.literal("submit"),
4711
+ args: z8.object({
4712
+ bounty_id: z8.string().min(1),
4516
4713
  submission_commitment: hexBytes32Schema,
4517
4714
  guardian_roster_hash: hexBytes32Schema,
4518
- extra_data: z6.literal("0x")
4715
+ extra_data: z8.literal("0x")
4519
4716
  })
4520
4717
  })
4521
4718
  });
4522
- var solverSubmissionContentResponseSchema = z6.object({
4523
- content_record: z6.object({
4524
- bounty_id: z6.string().min(1),
4525
- canonical_json: z6.string().min(1)
4719
+ var solverSubmissionContentResponseSchema = z8.object({
4720
+ content_record: z8.object({
4721
+ bounty_id: z8.string().min(1),
4722
+ canonical_json: z8.string().min(1)
4526
4723
  }),
4527
- ciphertext_download_url: z6.string().min(1),
4528
- expires_at: z6.string().min(1)
4724
+ ciphertext_download_url: z8.string().min(1),
4725
+ expires_at: z8.string().min(1)
4529
4726
  });
4530
- var bountyGuardianRosterResponseSchema = z6.object({
4531
- bounty: z6.object({
4727
+ var bountySubmissionDetailResponseSchema = z8.object({
4728
+ bounty: z8.object({
4532
4729
  guardian_roster_hash: hexBytes32Schema,
4533
- guardian_roster: z6.array(
4534
- z6.object({
4535
- name: z6.string(),
4730
+ guardian_roster: z8.array(
4731
+ z8.object({
4732
+ name: z8.string(),
4536
4733
  account: hexAddressSchema,
4537
- encryption_public_key: z6.string().min(1)
4734
+ encryption_public_key: z8.string().min(1)
4538
4735
  })
4539
4736
  ).min(1)
4540
- })
4737
+ }),
4738
+ challenge: z8.string().nullable()
4541
4739
  });
4542
4740
 
4543
4741
  // ../../packages/core/src/api/verification-records.ts
4544
- import { z as z7 } from "zod";
4545
- var hexAddressSchema2 = z7.string().refine(isHexAddress, "Must be a hex-encoded address").transform((value) => value.toLowerCase());
4546
- var hexBytes32Schema2 = z7.string().refine(isHexBytes32, "Must be 32-byte hex").transform((value) => value.toLowerCase());
4547
- var integerStringSchema = z7.string().regex(/^(0|[1-9][0-9]*)$/);
4548
- var verdictEntrySchema = z7.object({
4549
- name: z7.string().min(1),
4550
- outcome: z7.enum(["awarded", "no_valid_submission"]).nullable(),
4742
+ import { z as z9 } from "zod";
4743
+ var hexAddressSchema2 = z9.string().refine(isHexAddress, "Must be a hex-encoded address").transform((value) => value.toLowerCase());
4744
+ var hexBytes32Schema2 = z9.string().refine(isHexBytes32, "Must be 32-byte hex").transform((value) => value.toLowerCase());
4745
+ var integerStringSchema = z9.string().regex(/^(0|[1-9][0-9]*)$/);
4746
+ var verdictEntrySchema = z9.object({
4747
+ name: z9.string().min(1),
4748
+ outcome: z9.enum(["awarded", "no_valid_submission"]).nullable(),
4551
4749
  winner: hexAddressSchema2.nullable(),
4552
4750
  report_commitment: hexBytes32Schema2.nullable(),
4553
- committed_at: z7.string().datetime({ offset: true }).nullable(),
4554
- supporting: z7.boolean()
4751
+ committed_at: z9.string().datetime({ offset: true }).nullable(),
4752
+ supporting: z9.boolean()
4555
4753
  }).strict();
4556
- var formattedVerificationRecordSchema = z7.object({
4557
- chain_id: z7.number().int().positive(),
4754
+ var formattedVerificationRecordSchema = z9.object({
4755
+ chain_id: z9.number().int().positive(),
4558
4756
  hub_address: hexAddressSchema2,
4559
4757
  bounty_id: integerStringSchema,
4560
- status: z7.enum(["awarded", "no_valid_submission", "timed_out"]),
4758
+ status: z9.enum(["awarded", "no_valid_submission", "timed_out"]),
4561
4759
  spec_commitment: hexBytes32Schema2,
4562
4760
  winner: hexAddressSchema2.nullable(),
4563
- verdicts: z7.record(z7.string(), verdictEntrySchema),
4564
- settlement: z7.object({
4761
+ verdicts: z9.record(z9.string(), verdictEntrySchema),
4762
+ settlement: z9.object({
4565
4763
  escrow_amount: integerStringSchema,
4566
4764
  settlement_recipient: hexAddressSchema2,
4567
4765
  settlement_amount: integerStringSchema,
@@ -4572,10 +4770,10 @@ var formattedVerificationRecordSchema = z7.object({
4572
4770
  settled_tx_hash: hexBytes32Schema2,
4573
4771
  settled_block_number: integerStringSchema
4574
4772
  }).strict(),
4575
- content_record: z7.object({
4576
- spec_content_byte_length: z7.number().int().positive()
4773
+ content_record: z9.object({
4774
+ spec_content_byte_length: z9.number().int().positive()
4577
4775
  }).strict(),
4578
- verified_at: z7.string().datetime({ offset: true })
4776
+ verified_at: z9.string().datetime({ offset: true })
4579
4777
  }).strict();
4580
4778
  function parseBountyVerificationRecord(input, expected) {
4581
4779
  const slice = input;
@@ -4597,66 +4795,66 @@ function parseBountyVerificationRecord(input, expected) {
4597
4795
  }
4598
4796
 
4599
4797
  // ../../packages/core/src/api/winning-solution-delivery.ts
4600
- import { z as z8 } from "zod";
4601
- var hexBytes32Schema3 = z8.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value !== ZERO_BYTES32, "Must not be zero").transform((value) => value.toLowerCase());
4602
- var hexAddressSchema3 = z8.string().refine((value) => /^0x[0-9a-fA-F]{40}$/.test(value), "Must be an address").transform((value) => value.toLowerCase());
4603
- var deliveryKeyIdSchema = z8.string().min(1).max(128);
4604
- var winningSolutionWrappedKeySchema = z8.strictObject({
4605
- enc: z8.string().min(1),
4606
- ciphertext: z8.string().min(1)
4798
+ import { z as z10 } from "zod";
4799
+ var hexBytes32Schema3 = z10.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value !== ZERO_BYTES32, "Must not be zero").transform((value) => value.toLowerCase());
4800
+ var hexAddressSchema3 = z10.string().refine((value) => /^0x[0-9a-fA-F]{40}$/.test(value), "Must be an address").transform((value) => value.toLowerCase());
4801
+ var deliveryKeyIdSchema = z10.string().min(1).max(128);
4802
+ var winningSolutionWrappedKeySchema = z10.strictObject({
4803
+ enc: z10.string().min(1),
4804
+ ciphertext: z10.string().min(1)
4607
4805
  });
4608
- var deliveryWrapRequestSchema = z8.strictObject({
4806
+ var deliveryWrapRequestSchema = z10.strictObject({
4609
4807
  solver_address: hexAddressSchema3,
4610
4808
  submission_commitment: hexBytes32Schema3,
4611
4809
  wrapped_key: winningSolutionWrappedKeySchema
4612
4810
  });
4613
- var deliveryWrapRecordSchema = z8.strictObject({
4614
- bounty_id: z8.string().min(1),
4811
+ var deliveryWrapRecordSchema = z10.strictObject({
4812
+ bounty_id: z10.string().min(1),
4615
4813
  guardian_address: hexAddressSchema3,
4616
4814
  solver_address: hexAddressSchema3,
4617
4815
  submission_commitment: hexBytes32Schema3,
4618
4816
  delivery_key_id: deliveryKeyIdSchema,
4619
- created_at: z8.string().min(1)
4817
+ created_at: z10.string().min(1)
4620
4818
  });
4621
- var deliveryWrapResponseSchema = z8.strictObject({
4622
- status: z8.literal("stored"),
4819
+ var deliveryWrapResponseSchema = z10.strictObject({
4820
+ status: z10.literal("stored"),
4623
4821
  wrap: deliveryWrapRecordSchema
4624
4822
  });
4625
- var deliveryRetrieveRequestSchema = z8.strictObject({
4823
+ var deliveryRetrieveRequestSchema = z10.strictObject({
4626
4824
  poster_ephemeral_public_key: x25519PublicKeySchema
4627
4825
  });
4628
- var deliveryRetrieveResponseSchema = z8.strictObject({
4826
+ var deliveryRetrieveResponseSchema = z10.strictObject({
4629
4827
  submission_commitment: hexBytes32Schema3,
4630
4828
  wrapped_key: winningSolutionWrappedKeySchema
4631
4829
  });
4632
- var deliveryStatusResponseSchema = z8.strictObject({
4633
- exists: z8.boolean()
4830
+ var deliveryStatusResponseSchema = z10.strictObject({
4831
+ exists: z10.boolean()
4634
4832
  });
4635
- var deliveryKeyConfigResponseSchema = z8.strictObject({
4833
+ var deliveryKeyConfigResponseSchema = z10.strictObject({
4636
4834
  delivery_key_id: deliveryKeyIdSchema,
4637
- public_key: z8.string().min(1)
4835
+ public_key: z10.string().min(1)
4638
4836
  });
4639
4837
 
4640
4838
  // ../../packages/core/src/api/written-verdicts.ts
4641
4839
  import { keccak256 as keccak2564, toBytes as toBytes3, zeroAddress } from "viem";
4642
- import { z as z9 } from "zod";
4840
+ import { z as z11 } from "zod";
4643
4841
  var WRITTEN_VERDICT_FORMAT_VERSION = 0;
4644
4842
  var FRONTMATTER_DELIMITER2 = "---";
4645
- var decimalSchema = z9.string().regex(/^[1-9][0-9]*$/, "Must be a positive integer").transform(Number).refine(Number.isSafeInteger, "Must be a safe integer");
4646
- var bountyIdSchema2 = z9.string().regex(/^(0|[1-9][0-9]*)$/, "Must be a non-negative integer");
4647
- var addressSchema2 = z9.string().refine(isHexAddress, "Must be a hex-encoded address").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4648
- var bytes32Schema3 = z9.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4843
+ var decimalSchema = z11.string().regex(/^[1-9][0-9]*$/, "Must be a positive integer").transform(Number).refine(Number.isSafeInteger, "Must be a safe integer");
4844
+ var bountyIdSchema2 = z11.string().regex(/^(0|[1-9][0-9]*)$/, "Must be a non-negative integer");
4845
+ var addressSchema2 = z11.string().refine(isHexAddress, "Must be a hex-encoded address").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4846
+ var bytes32Schema3 = z11.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4649
4847
  var commonFields = {
4650
- format_version: z9.literal(WRITTEN_VERDICT_FORMAT_VERSION.toString()).transform(() => WRITTEN_VERDICT_FORMAT_VERSION),
4848
+ format_version: z11.literal(WRITTEN_VERDICT_FORMAT_VERSION.toString()).transform(() => WRITTEN_VERDICT_FORMAT_VERSION),
4651
4849
  chain_id: decimalSchema,
4652
4850
  hub_address: addressSchema2,
4653
4851
  bounty_id: bountyIdSchema2,
4654
- verdict_body: z9.string().refine((value) => value.trim().length > 0, "Must not be empty")
4852
+ verdict_body: z11.string().refine((value) => value.trim().length > 0, "Must not be empty")
4655
4853
  };
4656
- var writtenVerdictSchema = z9.discriminatedUnion("outcome", [
4657
- z9.object({
4854
+ var writtenVerdictSchema = z11.discriminatedUnion("outcome", [
4855
+ z11.object({
4658
4856
  ...commonFields,
4659
- outcome: z9.literal("awarded"),
4857
+ outcome: z11.literal("awarded"),
4660
4858
  winner: addressSchema2.refine(
4661
4859
  (value) => value !== zeroAddress,
4662
4860
  "Must not be the zero address"
@@ -4666,10 +4864,10 @@ var writtenVerdictSchema = z9.discriminatedUnion("outcome", [
4666
4864
  "Must not be zero bytes"
4667
4865
  )
4668
4866
  }).strict(),
4669
- z9.object({
4867
+ z11.object({
4670
4868
  ...commonFields,
4671
- outcome: z9.literal("no_valid_submission"),
4672
- winner: z9.literal(zeroAddress)
4869
+ outcome: z11.literal("no_valid_submission"),
4870
+ winner: z11.literal(zeroAddress)
4673
4871
  }).strict()
4674
4872
  ]);
4675
4873
  function buildWrittenVerdictMarkdown(input) {
@@ -4751,10 +4949,10 @@ ${FRONTMATTER_DELIMITER2}
4751
4949
  }
4752
4950
  return result.data;
4753
4951
  }
4754
- function invalidWrittenVerdict(path6, message) {
4952
+ function invalidWrittenVerdict(path7, message) {
4755
4953
  return new BoundaryValidationError(
4756
4954
  "Invalid written Verdict",
4757
- [{ path: path6, message }],
4955
+ [{ path: path7, message }],
4758
4956
  "invalid_written_verdict"
4759
4957
  );
4760
4958
  }
@@ -4764,17 +4962,17 @@ import { parseEventLogs } from "viem";
4764
4962
 
4765
4963
  // ../../packages/core/src/api-client-config.ts
4766
4964
  var LOCAL_DEV_API_BASE_URL = "http://127.0.0.1:3000";
4767
- function loadApiClientConfig(env, apiBaseUrlOverride) {
4768
- return { apiBaseUrl: resolveApiBaseUrl(env, apiBaseUrlOverride) };
4965
+ function loadApiClientConfig(env) {
4966
+ return { apiBaseUrl: resolveApiBaseUrl(env) };
4769
4967
  }
4770
- function loadClientDeploymentConfig(env, apiBaseUrlOverride) {
4968
+ function loadClientDeploymentConfig(env) {
4771
4969
  return {
4772
4970
  ...loadPublicDeploymentConfig(env),
4773
- ...loadApiClientConfig(env, apiBaseUrlOverride)
4971
+ ...loadApiClientConfig(env)
4774
4972
  };
4775
4973
  }
4776
- function resolveApiBaseUrl(env, override) {
4777
- const value = override?.trim() || env.ELGORA_API_BASE_URL?.trim() || defaultApiBaseUrl(env);
4974
+ function resolveApiBaseUrl(env) {
4975
+ const value = env.ELGORA_API_BASE_URL?.trim() || defaultApiBaseUrl(env);
4778
4976
  try {
4779
4977
  new URL(value);
4780
4978
  } catch {
@@ -4839,12 +5037,12 @@ async function getAuthenticatedJson(apiBaseUrl, pathname, authorization) {
4839
5037
  });
4840
5038
  return parseJsonResponse(url, response);
4841
5039
  }
4842
- async function requestBountyGuardianRoster(input) {
5040
+ async function requestBountySubmissionDetail(input) {
4843
5041
  const body = await getJson(
4844
5042
  input.apiBaseUrl,
4845
5043
  `/api/bounties/${input.bountyId}`
4846
5044
  );
4847
- return bountyGuardianRosterResponseSchema.parse(body).bounty;
5045
+ return bountySubmissionDetailResponseSchema.parse(body);
4848
5046
  }
4849
5047
  async function requestDeploymentDeliveryPublicKey(input) {
4850
5048
  const body = await getJson(input.apiBaseUrl, "/api/deployment/delivery-key");
@@ -5351,8 +5549,8 @@ async function waitForSponsoredHash(config, transactionId, call) {
5351
5549
  `Privy ${call.function_name} transaction ${transactionId} had no transaction hash after ${SPONSORED_TIMEOUT_MS / 1e3} s`
5352
5550
  );
5353
5551
  }
5354
- async function privyRequest(config, method, path6, body) {
5355
- const url = `${PRIVY_API_URL}${path6}`;
5552
+ async function privyRequest(config, method, path7, body) {
5553
+ const url = `${PRIVY_API_URL}${path7}`;
5356
5554
  const headers = {
5357
5555
  authorization: `Basic ${Buffer.from(`${config.appId}:${config.appSecret}`).toString("base64")}`,
5358
5556
  "privy-app-id": config.appId
@@ -5384,12 +5582,12 @@ async function privyRequest(config, method, path6, body) {
5384
5582
  if (!response.ok) {
5385
5583
  const message = parsed && typeof parsed === "object" && typeof parsed.error === "string" ? parsed.error : `HTTP ${response.status}`;
5386
5584
  throw new PrivyRequestError(
5387
- `Privy ${method} ${path6} failed (${response.status}): ${message}`,
5585
+ `Privy ${method} ${path7} failed (${response.status}): ${message}`,
5388
5586
  response.status
5389
5587
  );
5390
5588
  }
5391
5589
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
5392
- throw new Error(`Privy ${method} ${path6} returned a non-object body`);
5590
+ throw new Error(`Privy ${method} ${path7} returned a non-object body`);
5393
5591
  }
5394
5592
  return parsed;
5395
5593
  }
@@ -6085,7 +6283,7 @@ function typedDataForRequest(request, block) {
6085
6283
  }
6086
6284
 
6087
6285
  // ../../packages/core/src/auth/request-approval-header.ts
6088
- import { z as z10 } from "zod";
6286
+ import { z as z12 } from "zod";
6089
6287
  async function buildAuthorizationHeader(params) {
6090
6288
  const url = new URL(params.requestUrl);
6091
6289
  const typedData = typedDataForRequest(
@@ -6112,8 +6310,8 @@ async function authorizeRequest(params) {
6112
6310
  block: await readChallengeBlock(params)
6113
6311
  });
6114
6312
  }
6115
- var challengeBlockSchema = z10.object({
6116
- error: z10.object({ block_number: z10.string(), block_hash: z10.string() })
6313
+ var challengeBlockSchema = z12.object({
6314
+ error: z12.object({ block_number: z12.string(), block_hash: z12.string() })
6117
6315
  });
6118
6316
  async function readChallengeBlock(params) {
6119
6317
  const response = await fetch(params.requestUrl, {
@@ -6147,38 +6345,20 @@ async function wrapWinningSolutionKey(input) {
6147
6345
  address: input.owner.address,
6148
6346
  sign: input.owner.signTypedData
6149
6347
  });
6150
- const { content_record, ciphertext_download_url } = solverSubmissionContentResponseSchema.parse(
6348
+ const { content_record } = solverSubmissionContentResponseSchema.parse(
6151
6349
  await getAuthenticatedJson(
6152
6350
  input.config.apiBaseUrl,
6153
6351
  contentPathname,
6154
6352
  contentAuthorization
6155
6353
  )
6156
6354
  );
6157
- if (content_record.bounty_id !== input.bountyId.toString()) {
6158
- throw new Error(
6159
- `Submission ${input.submissionCommitment} belongs to bounty_id ${content_record.bounty_id}, not ${input.bountyId}`
6160
- );
6161
- }
6162
- const envelope = parsePrivateArtifactEnvelope(
6355
+ const envelope = parseSubmissionEnvelope(
6163
6356
  JSON.parse(content_record.canonical_json)
6164
6357
  );
6165
- const envelopeCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
6166
- if (envelopeCommitment !== input.submissionCommitment) {
6167
- throw new Error(
6168
- `Elgora API returned an envelope whose commitment ${envelopeCommitment} does not match the requested submission_commitment ${input.submissionCommitment}`
6169
- );
6170
- }
6171
- if (envelope.context.chain_id !== input.config.chainId || envelope.context.hub_address.toLowerCase() !== input.config.hubAddress.toLowerCase() || envelope.context.bounty_id !== input.bountyId.toString() || envelope.context.actor_address.toLowerCase() !== input.solver.toLowerCase()) {
6172
- throw new Error(
6173
- `Submission ${input.submissionCommitment} does not belong to the requested ElgoraHub bounty and Solver`
6174
- );
6175
- }
6176
- const ciphertext = await getBytes(ciphertext_download_url);
6177
- const opened = await openPrivateArtifactEnvelopeForDelivery({
6358
+ const opened = await openSubmissionEnvelopeForDelivery({
6178
6359
  envelope,
6179
- ciphertext,
6180
6360
  guardian_private_keys: input.guardianPrivateKeys,
6181
- timelock_decrypt: createPrivateArtifactTimelockDecrypt(),
6361
+ timelock_decrypt: createSubmissionTimelockDecrypt(),
6182
6362
  delivery_recipient_public_key: deploymentDeliveryPublicKey,
6183
6363
  context: {
6184
6364
  chain_id: input.config.chainId,
@@ -6212,12 +6392,485 @@ async function wrapWinningSolutionKey(input) {
6212
6392
  "application/json",
6213
6393
  authorization
6214
6394
  );
6215
- return deliveryWrapResponseSchema.parse(responseBody);
6395
+ return deliveryWrapResponseSchema.parse(responseBody);
6396
+ }
6397
+
6398
+ // src/guardian-open.ts
6399
+ import path3 from "path";
6400
+
6401
+ // src/artifact-output.ts
6402
+ import { constants } from "fs";
6403
+ import { lstat, mkdir, open, readdir } from "fs/promises";
6404
+ import path2 from "path";
6405
+ var CREATE_FILE_FLAGS = constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW;
6406
+ var MAX_HOST_PATH_BYTES = process.platform === "darwin" ? 1023 : 4095;
6407
+ async function writeOpenedArtifacts(outputDir, files) {
6408
+ for (const file of files) {
6409
+ const issue = describeSolverSubmissionPathIssue(file.path);
6410
+ if (issue !== null) {
6411
+ throw new Error(`Refusing to write the Submission: ${issue}`);
6412
+ }
6413
+ }
6414
+ const conflict = findSolverSubmissionPathConflict(
6415
+ files.map((file) => file.path)
6416
+ );
6417
+ if (conflict !== null) {
6418
+ throw new Error(
6419
+ `Refusing to write the Submission: ${describeSolverSubmissionPathConflict(conflict)}.`
6420
+ );
6421
+ }
6422
+ const root = path2.resolve(outputDir);
6423
+ const targets = files.map((file) => {
6424
+ const target = path2.resolve(root, ...file.path.split("/"));
6425
+ const relative = path2.relative(root, target);
6426
+ if (relative === "" || relative.startsWith("..") || path2.isAbsolute(relative)) {
6427
+ throw new Error(
6428
+ `Refusing to write the Submission: "${file.path}" resolves outside ${outputDir}.`
6429
+ );
6430
+ }
6431
+ return { target, bytes: file.bytes };
6432
+ });
6433
+ const longest = targets.reduce(
6434
+ (max, { target }) => Math.max(max, Buffer.byteLength(target)),
6435
+ 0
6436
+ );
6437
+ if (longest > MAX_HOST_PATH_BYTES) {
6438
+ throw new Error(
6439
+ `Refusing to write the Submission: its longest path under ${outputDir} is ${longest} bytes, and this host opens at most ${MAX_HOST_PATH_BYTES}. Choose a shorter output directory.`
6440
+ );
6441
+ }
6442
+ await prepareOutputDirectory(root, outputDir);
6443
+ for (const { target, bytes } of targets) {
6444
+ await mkdir(path2.dirname(target), { recursive: true });
6445
+ await assertRealDirectoriesBelow(root, path2.dirname(target));
6446
+ const handle = await open(target, CREATE_FILE_FLAGS, 420);
6447
+ try {
6448
+ await handle.writeFile(bytes);
6449
+ } finally {
6450
+ await handle.close();
6451
+ }
6452
+ }
6453
+ }
6454
+ async function prepareOutputDirectory(root, outputDir) {
6455
+ const existing = await lstat(root).catch((error) => {
6456
+ if (error.code === "ENOENT") return null;
6457
+ throw error;
6458
+ });
6459
+ if (existing === null) {
6460
+ await mkdir(root, { recursive: true });
6461
+ return;
6462
+ }
6463
+ if (!existing.isDirectory()) {
6464
+ throw new Error(
6465
+ `Output directory ${outputDir} exists and is not a directory. Choose a new output directory.`
6466
+ );
6467
+ }
6468
+ if ((await readdir(root)).length > 0) {
6469
+ throw new Error(
6470
+ `Output directory ${outputDir} is not empty. Choose a new output directory, or remove this one, before opening the Submission again.`
6471
+ );
6472
+ }
6473
+ }
6474
+ async function assertRealDirectoriesBelow(root, directory) {
6475
+ let current = directory;
6476
+ while (current !== root) {
6477
+ if (!(await lstat(current)).isDirectory()) {
6478
+ throw new Error(
6479
+ `Refusing to write the Submission: ${current} is not a real directory.`
6480
+ );
6481
+ }
6482
+ current = path2.dirname(current);
6483
+ }
6484
+ }
6485
+
6486
+ // src/submission-archive.ts
6487
+ import { createHash as createHash2 } from "crypto";
6488
+ import { gunzipSync, gzipSync } from "zlib";
6489
+
6490
+ // ../../packages/tarts/src/tar.ts
6491
+ var BLOCK = 512;
6492
+ var NAME_FIELD = 100;
6493
+ var encoder = new TextEncoder();
6494
+ var decoder = new TextDecoder("utf-8");
6495
+ var TarFormatError = class extends Error {
6496
+ };
6497
+ function writeTar(files) {
6498
+ const blocks = [];
6499
+ for (const file of files) {
6500
+ const name = encoder.encode(file.path);
6501
+ if (name.byteLength > NAME_FIELD) {
6502
+ const record = paxRecord("path", file.path);
6503
+ blocks.push(header("PaxHeader", "x", record.byteLength), padded(record));
6504
+ }
6505
+ blocks.push(
6506
+ header(file.path, "0", file.bytes.byteLength),
6507
+ padded(file.bytes)
6508
+ );
6509
+ }
6510
+ blocks.push(new Uint8Array(2 * BLOCK));
6511
+ return concat(blocks);
6512
+ }
6513
+ function readTar(tar) {
6514
+ const entries = [];
6515
+ let path7;
6516
+ for (let offset = 0; offset + BLOCK <= tar.byteLength; ) {
6517
+ const block = tar.subarray(offset, offset + BLOCK);
6518
+ if (block.every((byte) => byte === 0)) return entries;
6519
+ if (!checksumMatches(block)) {
6520
+ throw new TarFormatError(
6521
+ `the header at byte ${offset} fails its checksum`
6522
+ );
6523
+ }
6524
+ const size2 = readSize(block.subarray(124, 136));
6525
+ const start = offset + BLOCK;
6526
+ if (start + size2 > tar.byteLength) {
6527
+ throw new TarFormatError(`the entry at byte ${offset} is truncated`);
6528
+ }
6529
+ const data = tar.subarray(start, start + size2);
6530
+ offset = start + Math.ceil(size2 / BLOCK) * BLOCK;
6531
+ const type = String.fromCharCode(block[156] ?? 0);
6532
+ if (type === "x") {
6533
+ path7 = readPaxPath(data) ?? path7;
6534
+ continue;
6535
+ }
6536
+ if (type === "L") {
6537
+ path7 = decoder.decode(nulTerminated(data));
6538
+ continue;
6539
+ }
6540
+ if (type === "g") continue;
6541
+ const prefix = decoder.decode(nulTerminated(block.subarray(345, 500)));
6542
+ const headerName = decoder.decode(
6543
+ nulTerminated(block.subarray(0, NAME_FIELD))
6544
+ );
6545
+ entries.push({
6546
+ name: path7 ?? (prefix ? `${prefix}/${headerName}` : headerName),
6547
+ type: type === "\0" ? "0" : type,
6548
+ data
6549
+ });
6550
+ path7 = void 0;
6551
+ }
6552
+ throw new TarFormatError("it is truncated: no end-of-archive block");
6553
+ }
6554
+ function header(name, type, size2) {
6555
+ const block = new Uint8Array(BLOCK);
6556
+ const put = (offset, value) => block.set(encoder.encode(value), offset);
6557
+ block.set(encoder.encode(name).subarray(0, NAME_FIELD), 0);
6558
+ put(100, "0000644\0");
6559
+ put(108, "0000000\0");
6560
+ put(116, "0000000\0");
6561
+ put(124, `${size2.toString(8).padStart(11, "0")}\0`);
6562
+ put(136, "00000000000\0");
6563
+ put(156, type);
6564
+ put(257, "ustar\0");
6565
+ put(263, "00");
6566
+ block.fill(32, 148, 156);
6567
+ const sum = block.reduce((total, byte) => total + byte, 0);
6568
+ put(148, `${sum.toString(8).padStart(6, "0")}\0 `);
6569
+ return block;
6570
+ }
6571
+ function paxRecord(key, value) {
6572
+ const body = ` ${key}=${value}
6573
+ `;
6574
+ const bodyLength = encoder.encode(body).byteLength;
6575
+ let length = bodyLength + 1;
6576
+ while (String(length).length + bodyLength !== length) length += 1;
6577
+ return encoder.encode(`${length}${body}`);
6578
+ }
6579
+ function readPaxPath(data) {
6580
+ let path7;
6581
+ let offset = 0;
6582
+ while (offset < data.byteLength) {
6583
+ const space = data.indexOf(32, offset);
6584
+ const length = Number.parseInt(latin1(data.subarray(offset, space)), 10);
6585
+ if (space < 0 || !Number.isSafeInteger(length) || length <= 0) {
6586
+ throw new TarFormatError("a pax header does not parse");
6587
+ }
6588
+ const record = data.subarray(space + 1, offset + length - 1);
6589
+ const equals = record.indexOf(61);
6590
+ if (equals >= 0 && latin1(record.subarray(0, equals)) === "path") {
6591
+ path7 = decoder.decode(record.subarray(equals + 1));
6592
+ }
6593
+ offset += length;
6594
+ }
6595
+ return path7;
6596
+ }
6597
+ function checksumMatches(block) {
6598
+ let sum = 0;
6599
+ for (let index = 0; index < BLOCK; index += 1) {
6600
+ sum += index >= 148 && index < 156 ? 32 : block[index] ?? 0;
6601
+ }
6602
+ return readOctal(block.subarray(148, 156)) === sum;
6603
+ }
6604
+ function readSize(field) {
6605
+ let size2;
6606
+ if (((field[0] ?? 0) & 128) !== 0) {
6607
+ size2 = 0;
6608
+ for (const [index, byte] of field.entries()) {
6609
+ size2 = size2 * 256 + (index === 0 ? byte & 127 : byte);
6610
+ }
6611
+ } else {
6612
+ size2 = readOctal(field);
6613
+ }
6614
+ if (!Number.isSafeInteger(size2) || size2 < 0) {
6615
+ throw new TarFormatError("a header declares no readable size");
6616
+ }
6617
+ return size2;
6618
+ }
6619
+ function readOctal(field) {
6620
+ const digits = latin1(field).replace(/^[ \0]+|[ \0]+$/g, "");
6621
+ return /^[0-7]+$/.test(digits) ? Number.parseInt(digits, 8) : Number.NaN;
6622
+ }
6623
+ function nulTerminated(field) {
6624
+ const end = field.indexOf(0);
6625
+ return end < 0 ? field : field.subarray(0, end);
6626
+ }
6627
+ function latin1(bytes) {
6628
+ return String.fromCharCode(...bytes);
6629
+ }
6630
+ function padded(bytes) {
6631
+ const out = new Uint8Array(Math.ceil(bytes.byteLength / BLOCK) * BLOCK);
6632
+ out.set(bytes);
6633
+ return out;
6634
+ }
6635
+ function concat(chunks) {
6636
+ const out = new Uint8Array(
6637
+ chunks.reduce((total, chunk) => total + chunk.byteLength, 0)
6638
+ );
6639
+ let offset = 0;
6640
+ for (const chunk of chunks) {
6641
+ out.set(chunk, offset);
6642
+ offset += chunk.byteLength;
6643
+ }
6644
+ return out;
6645
+ }
6646
+
6647
+ // src/submission-archive.ts
6648
+ function buildSubmissionArchive(files) {
6649
+ const sorted = [...files].sort(
6650
+ (left, right) => compareBytewise(left.path, right.path)
6651
+ );
6652
+ assertSubmissionFiles(sorted);
6653
+ const archive = gzipSync(writeTar(sorted), { level: 9 });
6654
+ return {
6655
+ archive: new Uint8Array(archive.buffer, archive.byteOffset, archive.length),
6656
+ ...describeFiles(sorted)
6657
+ };
6658
+ }
6659
+ function readSubmissionArchive(archive) {
6660
+ let tar;
6661
+ try {
6662
+ const inflated = gunzipSync(archive);
6663
+ tar = new Uint8Array(
6664
+ inflated.buffer,
6665
+ inflated.byteOffset,
6666
+ inflated.byteLength
6667
+ );
6668
+ } catch {
6669
+ reject("it is not a gzip stream");
6670
+ }
6671
+ let entries;
6672
+ try {
6673
+ entries = readTar(tar);
6674
+ } catch (error) {
6675
+ if (error instanceof TarFormatError) reject(error.message);
6676
+ throw error;
6677
+ }
6678
+ const files = entries.map((entry) => {
6679
+ if (entry.type !== "0") {
6680
+ reject(`"${entry.name}" is not a regular file entry`);
6681
+ }
6682
+ return { path: entry.name, bytes: entry.data };
6683
+ });
6684
+ files.sort((left, right) => compareBytewise(left.path, right.path));
6685
+ assertSubmissionFiles(files);
6686
+ return { files, ...describeFiles(files) };
6687
+ }
6688
+ function requireCurrentFormat(alg, bountyId, command20) {
6689
+ if (alg !== SUBMISSION_ENCRYPTION_TYPE_V1) {
6690
+ throw new Error(
6691
+ `Bounty ${bountyId} declares ${BOUNTY_CHALLENGE_PROFILE}, which this CLI does not handle. Use CLI 11 for it: npx @elgora/cli@11 ${command20}`
6692
+ );
6693
+ }
6694
+ }
6695
+ function readOpenedSubmission(bundle) {
6696
+ if (bundle.alg !== SUBMISSION_ENCRYPTION_TYPE_V1) {
6697
+ throw new Error("A _v0 Submission is opened with CLI 11.");
6698
+ }
6699
+ const { files, content_sha256 } = readSubmissionArchive(bundle.archive);
6700
+ return { files, content_sha256 };
6701
+ }
6702
+ function describeFiles(sorted) {
6703
+ const manifest = {
6704
+ kind: SUBMISSION_ENCRYPTED_BUNDLE_V1_KIND,
6705
+ artifacts: sorted.map((file) => ({
6706
+ path: file.path,
6707
+ size_bytes: file.bytes.byteLength,
6708
+ sha256: sha256Hex(file.bytes)
6709
+ }))
6710
+ };
6711
+ const manifestBytes = new TextEncoder().encode(
6712
+ canonicalizeJson(manifest)
6713
+ );
6714
+ return { manifest, content_sha256: sha256Hex(manifestBytes) };
6715
+ }
6716
+ function assertSubmissionFiles(files) {
6717
+ if (files.length === 0) {
6718
+ reject("a Submission must hold at least one file");
6719
+ }
6720
+ if (files.length > MAX_SOLVER_SUBMISSION_FILES_V1) {
6721
+ reject(
6722
+ `a Submission holds at most ${MAX_SOLVER_SUBMISSION_FILES_V1} files; this one holds ${files.length}`
6723
+ );
6724
+ }
6725
+ for (const file of files) {
6726
+ const issue = describeSolverSubmissionPathIssue(file.path);
6727
+ if (issue !== null) reject(issue);
6728
+ }
6729
+ const conflict = findSolverSubmissionPathConflict(
6730
+ files.map((file) => file.path)
6731
+ );
6732
+ if (conflict !== null) reject(describeSolverSubmissionPathConflict(conflict));
6733
+ const total = files.reduce((sum, file) => sum + file.bytes.byteLength, 0);
6734
+ if (total > MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES) {
6735
+ reject(
6736
+ `a Submission extracts to at most ${MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES} bytes; this one holds ${total}`
6737
+ );
6738
+ }
6739
+ }
6740
+ function sha256Hex(bytes) {
6741
+ return createHash2("sha256").update(bytes).digest("hex");
6742
+ }
6743
+ function compareBytewise(left, right) {
6744
+ if (left < right) return -1;
6745
+ if (left > right) return 1;
6746
+ return 0;
6747
+ }
6748
+ function reject(reason) {
6749
+ throw new Error(
6750
+ `Submission archive is not a valid Elgora tar.gz: ${reason.replace(/\.$/, "")}.`
6751
+ );
6752
+ }
6753
+
6754
+ // src/submission-check.ts
6755
+ function checkSubmissionArchive(input) {
6756
+ const { files, content_sha256 } = readSubmissionArchive(input.archive);
6757
+ return {
6758
+ content_sha256,
6759
+ ...checkOpenedSubmission({
6760
+ files,
6761
+ profile: input.profile,
6762
+ challenge: input.challenge
6763
+ })
6764
+ };
6765
+ }
6766
+ function checkOpenedSubmission(input) {
6767
+ const { files } = input;
6768
+ const constraints = readChallengeConstraints(
6769
+ parseBountyChallengeMarkdown(input.challenge).constraints
6770
+ );
6771
+ const budget = Math.min(
6772
+ input.profile.submission.maxExtractedBytes,
6773
+ constraints.maxExtractedBytes ?? Number.POSITIVE_INFINITY
6774
+ );
6775
+ const issues = [];
6776
+ const maxFiles = Math.min(
6777
+ input.profile.submission.maxFiles,
6778
+ constraints.maxFiles ?? Number.POSITIVE_INFINITY
6779
+ );
6780
+ if (files.length > maxFiles) {
6781
+ issues.push({
6782
+ outcome: "disqualifies",
6783
+ code: "max_files",
6784
+ message: `The Submission holds ${files.length} files; at most ${maxFiles} are allowed. An included archive counts as one file.`
6785
+ });
6786
+ }
6787
+ const paths = new Set(files.map((file) => file.path));
6788
+ for (const required2 of constraints.requiredFiles ?? []) {
6789
+ if (!paths.has(required2)) {
6790
+ issues.push({
6791
+ outcome: "fails_acceptance",
6792
+ code: "required_files",
6793
+ path: required2,
6794
+ message: `The challenge requires "${required2}", and the Submission has no file at that path.`
6795
+ });
6796
+ }
6797
+ }
6798
+ if (constraints.allowedExtensions !== void 0) {
6799
+ for (const file of files) {
6800
+ if (!hasAllowedExtension(file.path, constraints.allowedExtensions)) {
6801
+ issues.push({
6802
+ outcome: "fails_acceptance",
6803
+ code: "allowed_extensions",
6804
+ path: file.path,
6805
+ message: `"${file.path}" has an extension outside the challenge's allowed_extensions (${constraints.allowedExtensions.join(", ")}).`
6806
+ });
6807
+ }
6808
+ }
6809
+ }
6810
+ const extracted = files.reduce(
6811
+ (total, file) => total + file.bytes.byteLength,
6812
+ 0
6813
+ );
6814
+ if (extracted > budget) {
6815
+ issues.push({
6816
+ outcome: "disqualifies",
6817
+ code: "max_extracted_bytes",
6818
+ message: `The Submission's files total more than ${budget} bytes.`
6819
+ });
6820
+ }
6821
+ return {
6822
+ file_count: files.length,
6823
+ extracted_bytes: extracted,
6824
+ extracted_budget_bytes: budget,
6825
+ issues
6826
+ };
6827
+ }
6828
+
6829
+ // src/verified-challenge.ts
6830
+ async function readVerifiedChallenge(input) {
6831
+ const [{ challenge }, [parameters]] = await Promise.all([
6832
+ requestBountySubmissionDetail({
6833
+ apiBaseUrl: input.apiBaseUrl,
6834
+ bountyId: input.bountyId
6835
+ }),
6836
+ input.publicClient.readContract({
6837
+ address: input.hubAddress,
6838
+ abi: ELGORA_HUB_BOUNTY_READ_ABI,
6839
+ functionName: "getBounty",
6840
+ args: [BigInt(input.bountyId)],
6841
+ ...input.blockNumber !== void 0 && {
6842
+ blockNumber: input.blockNumber
6843
+ }
6844
+ })
6845
+ ]);
6846
+ const profile = verifyChallenge({
6847
+ bountyId: input.bountyId,
6848
+ challenge,
6849
+ specCommitment: parameters.specCommitment.toLowerCase()
6850
+ });
6851
+ return { challenge, profile };
6852
+ }
6853
+ function verifyChallenge(input) {
6854
+ if (input.challenge === null) {
6855
+ throw new Error(
6856
+ `Elgora API served no verified challenge for bounty ${input.bountyId}, so the Submission format it declares cannot be determined. Try again later.`
6857
+ );
6858
+ }
6859
+ if (deriveBountyChallengeContentCommitment(input.challenge).toLowerCase() !== input.specCommitment.toLowerCase()) {
6860
+ throw new Error(
6861
+ `Elgora API returned a challenge that does not match bounty ${input.bountyId}'s spec commitment in ElgoraHub`
6862
+ );
6863
+ }
6864
+ const { profile } = checkChallengeConformance(input.challenge);
6865
+ if (profile === null) {
6866
+ throw new Error(
6867
+ `Bounty ${input.bountyId} declares a protocol profile this CLI does not know. Upgrade @elgora/cli and submit again.`
6868
+ );
6869
+ }
6870
+ return profile;
6216
6871
  }
6217
6872
 
6218
6873
  // src/guardian-open.ts
6219
- import { mkdir, writeFile as writeFile2 } from "fs/promises";
6220
- import path2 from "path";
6221
6874
  var OPTIONS3 = {
6222
6875
  ...API_OPTIONS,
6223
6876
  ...ACCOUNT_OPTIONS
@@ -6255,7 +6908,7 @@ async function main3(env = process.env, argv = process.argv.slice(2)) {
6255
6908
  `Submission ${args.submissionCommitment} belongs to bounty_id ${content_record.bounty_id}, not ${args.bountyId}`
6256
6909
  );
6257
6910
  }
6258
- const envelope = await verifyPrivateArtifactEnvelope(
6911
+ const envelope = await verifySubmissionEnvelope(
6259
6912
  JSON.parse(content_record.canonical_json)
6260
6913
  );
6261
6914
  if (envelope.context.chain_id !== config.chainId || envelope.context.hub_address !== config.hubAddress || envelope.context.bounty_id !== args.bountyId || envelope.context.actor_address !== args.solver) {
@@ -6263,7 +6916,8 @@ async function main3(env = process.env, argv = process.argv.slice(2)) {
6263
6916
  `Submission envelope context does not match chain ${config.chainId}, ElgoraHub ${config.hubAddress}, bounty_id ${args.bountyId}, and Solver ${args.solver}`
6264
6917
  );
6265
6918
  }
6266
- const envelopeCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
6919
+ requireCurrentFormat(envelope.alg, args.bountyId, "guardian:open");
6920
+ const envelopeCommitment = deriveSubmissionEnvelopeCommitment(envelope);
6267
6921
  if (envelopeCommitment !== args.submissionCommitment) {
6268
6922
  throw new Error(
6269
6923
  `Elgora API returned an envelope whose commitment ${envelopeCommitment} does not match the requested submission_commitment ${args.submissionCommitment}`
@@ -6284,29 +6938,35 @@ async function main3(env = process.env, argv = process.argv.slice(2)) {
6284
6938
  `ElgoraHub does not record Solver ${args.solver} with submission_commitment ${args.submissionCommitment} for bounty_id ${args.bountyId}`
6285
6939
  );
6286
6940
  }
6941
+ const { challenge, profile } = await readVerifiedChallenge({
6942
+ publicClient,
6943
+ apiBaseUrl: config.apiBaseUrl,
6944
+ hubAddress: config.hubAddress,
6945
+ bountyId: args.bountyId,
6946
+ blockNumber: finalizedBlock.number
6947
+ });
6287
6948
  const ciphertext = await getBytes(ciphertext_download_url);
6288
- const opened = await openPrivateArtifactEnvelope({
6949
+ const opened = await openSubmissionEnvelope({
6289
6950
  envelope,
6290
6951
  ciphertext,
6291
6952
  guardian_private_keys: guardianPrivateKeys,
6292
- timelock_decrypt: createPrivateArtifactTimelockDecrypt()
6953
+ timelock_decrypt: createSubmissionTimelockDecrypt()
6954
+ });
6955
+ const { files, content_sha256 } = readOpenedSubmission(opened);
6956
+ await writeOpenedArtifacts(args.outputDir, files);
6957
+ const check = checkOpenedSubmission({
6958
+ files,
6959
+ profile,
6960
+ challenge
6293
6961
  });
6294
- await mkdir(args.outputDir, { recursive: true });
6295
- for (const artifact of opened.artifacts) {
6296
- const filePath = path2.join(args.outputDir, artifact.name);
6297
- await writeFile2(filePath, artifact.bytes);
6298
- }
6299
6962
  console.log(
6300
6963
  JSON.stringify({
6301
6964
  event: "guardian_open.completed",
6302
6965
  bounty_id: args.bountyId,
6303
6966
  solver: args.solver,
6304
6967
  submission_commitment: args.submissionCommitment,
6305
- artifact_count: opened.artifacts.length,
6306
- // The budget the Guardian has to vet these artifacts against before
6307
- // extracting any archive among them. Reported here so it comes from the
6308
- // running client rather than a possibly older copy of the skill.
6309
- max_extracted_bytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
6968
+ content_sha256,
6969
+ ...check
6310
6970
  })
6311
6971
  );
6312
6972
  }
@@ -6338,7 +6998,7 @@ function readGuardianOpenArgs(argv) {
6338
6998
  bountyId,
6339
6999
  solver: normalizedSolver,
6340
7000
  submissionCommitment: normalizedCommitment,
6341
- outputDir: outputDirOverride ?? path2.join(DEFAULT_OUTPUT_ROOT, bountyId, normalizedCommitment)
7001
+ outputDir: outputDirOverride ?? path3.join(DEFAULT_OUTPUT_ROOT, bountyId, normalizedCommitment)
6342
7002
  };
6343
7003
  }
6344
7004
  function readGuardianPrivateKeys(env) {
@@ -6510,6 +7170,8 @@ var BOUNTY_FIELDS = `
6510
7170
  submissionCount
6511
7171
  createdTxHash
6512
7172
  createdAt
7173
+ createdBlockNumber
7174
+ createdLogIndex
6513
7175
  consensusReachedAt
6514
7176
  consensusOutcome
6515
7177
  consensusWinner
@@ -6517,6 +7179,7 @@ var BOUNTY_FIELDS = `
6517
7179
  settledTxHash
6518
7180
  settledAt
6519
7181
  settledBlockNumber
7182
+ settledLogIndex
6520
7183
  updatedAt
6521
7184
  `;
6522
7185
  var LIST_BOUNTIES_QUERY = `
@@ -6543,6 +7206,7 @@ var SUBMISSION_FIELDS = `
6543
7206
  submissionCommitment
6544
7207
  recordedAt
6545
7208
  recordedBlockNumber
7209
+ recordedLogIndex
6546
7210
  recordedTxHash
6547
7211
  submissionCallCount
6548
7212
  `;
@@ -6555,8 +7219,18 @@ var VERDICT_FIELDS = `
6555
7219
  payoutData
6556
7220
  committedTxHash
6557
7221
  committedAt
7222
+ committedBlockNumber
7223
+ committedLogIndex
6558
7224
  updatedAt
6559
7225
  `;
7226
+ var CLAIM_FIELDS = `
7227
+ claimant
7228
+ amount
7229
+ claimedAt
7230
+ claimedBlockNumber
7231
+ claimedLogIndex
7232
+ claimedTxHash
7233
+ `;
6560
7234
  var GUARDIAN_ROSTER_SNAPSHOT_FIELDS = `
6561
7235
  id
6562
7236
  memberCount
@@ -6583,7 +7257,6 @@ var BOUNTY_DETAIL_QUERY = `
6583
7257
  reportCommitments
6584
7258
  awardedPayoutData
6585
7259
  timedOutRefundAmount
6586
- settledLogIndex
6587
7260
  claimsQueued(first: 1, orderBy: queuedLogIndex, orderDirection: desc) {
6588
7261
  recipient
6589
7262
  amount
@@ -6599,6 +7272,9 @@ var BOUNTY_DETAIL_QUERY = `
6599
7272
  verdicts {
6600
7273
  ${VERDICT_FIELDS}
6601
7274
  }
7275
+ claims(orderBy: claimedAt, orderDirection: asc) {
7276
+ ${CLAIM_FIELDS}
7277
+ }
6602
7278
  }
6603
7279
  }
6604
7280
  `;
@@ -6610,6 +7286,7 @@ var ANALYTICS_SNAPSHOT_QUERY = `
6610
7286
  $rosterSkip: Int!
6611
7287
  $submissionSkip: Int!
6612
7288
  $verdictSkip: Int!
7289
+ $claimSkip: Int!
6613
7290
  $block: Block_height
6614
7291
  ) {
6615
7292
  _meta(block: $block) {
@@ -6662,6 +7339,16 @@ var ANALYTICS_SNAPSHOT_QUERY = `
6662
7339
  bountyId
6663
7340
  ${VERDICT_FIELDS}
6664
7341
  }
7342
+ claims(
7343
+ first: $first
7344
+ skip: $claimSkip
7345
+ orderBy: id
7346
+ orderDirection: asc
7347
+ block: $block
7348
+ ) {
7349
+ bountyId
7350
+ ${CLAIM_FIELDS}
7351
+ }
6665
7352
  }
6666
7353
  `;
6667
7354
  var DEPLOYMENT_CONFIG_QUERY = `
@@ -6788,11 +7475,13 @@ function createElgoraSubgraphClient(options) {
6788
7475
  const rosterSnapshots = [];
6789
7476
  const submissions = [];
6790
7477
  const verdicts = [];
7478
+ const claims = [];
6791
7479
  let indexedBlockNumber = null;
6792
7480
  let bountySkip = 0;
6793
7481
  let rosterSkip = 0;
6794
7482
  let submissionSkip = 0;
6795
7483
  let verdictSkip = 0;
7484
+ let claimSkip = 0;
6796
7485
  for (; ; ) {
6797
7486
  const data = await execute(
6798
7487
  ANALYTICS_SNAPSHOT_QUERY,
@@ -6803,7 +7492,8 @@ function createElgoraSubgraphClient(options) {
6803
7492
  includeGuardianHistory,
6804
7493
  rosterSkip,
6805
7494
  submissionSkip,
6806
- verdictSkip
7495
+ verdictSkip,
7496
+ claimSkip
6807
7497
  }
6808
7498
  );
6809
7499
  const meta = readRecord(data._meta);
@@ -6822,23 +7512,27 @@ function createElgoraSubgraphClient(options) {
6822
7512
  const rosterPage = includeGuardianHistory ? asArray(data.guardianRosterSnapshots, "guardianRosterSnapshots") : [];
6823
7513
  const submissionPage = asArray(data.submissions, "submissions");
6824
7514
  const verdictPage = asArray(data.verdicts, "verdicts");
7515
+ const claimPage = asArray(data.claims, "claims");
6825
7516
  bounties.push(...bountyPage.map(mapAnalyticsBounty));
6826
7517
  rosterSnapshots.push(...rosterPage.map(mapAnalyticsGuardianRoster));
6827
7518
  submissions.push(...submissionPage.map(mapListedSubmission));
6828
7519
  verdicts.push(...verdictPage.map(mapListedVerdict));
6829
- if (bountyPage.length < BOUNTY_PAGE_SIZE && rosterPage.length < BOUNTY_PAGE_SIZE && submissionPage.length < BOUNTY_PAGE_SIZE && verdictPage.length < BOUNTY_PAGE_SIZE) {
7520
+ claims.push(...claimPage.map(mapListedClaim));
7521
+ if (bountyPage.length < BOUNTY_PAGE_SIZE && rosterPage.length < BOUNTY_PAGE_SIZE && submissionPage.length < BOUNTY_PAGE_SIZE && verdictPage.length < BOUNTY_PAGE_SIZE && claimPage.length < BOUNTY_PAGE_SIZE) {
6830
7522
  return {
6831
7523
  indexedBlockNumber,
6832
7524
  bounties,
6833
7525
  rosterSnapshots,
6834
7526
  submissions,
6835
- verdicts
7527
+ verdicts,
7528
+ claims
6836
7529
  };
6837
7530
  }
6838
7531
  bountySkip += bountyPage.length;
6839
7532
  rosterSkip += rosterPage.length;
6840
7533
  submissionSkip += submissionPage.length;
6841
7534
  verdictSkip += verdictPage.length;
7535
+ claimSkip += claimPage.length;
6842
7536
  }
6843
7537
  },
6844
7538
  async getBounty(bountyId) {
@@ -6927,6 +7621,11 @@ function mapBounty(row) {
6927
7621
  submissionCount: asNumber(record.submissionCount, "submissionCount"),
6928
7622
  createdTxHash: asHex(record.createdTxHash, "createdTxHash"),
6929
7623
  createdAt: asString(record.createdAt, "createdAt"),
7624
+ createdBlockNumber: asString(
7625
+ record.createdBlockNumber,
7626
+ "createdBlockNumber"
7627
+ ),
7628
+ createdLogIndex: asNumber(record.createdLogIndex, "createdLogIndex"),
6930
7629
  consensusReachedAt: asNullableString(
6931
7630
  record.consensusReachedAt,
6932
7631
  "consensusReachedAt"
@@ -6943,6 +7642,7 @@ function mapBounty(row) {
6943
7642
  record.settledBlockNumber,
6944
7643
  "settledBlockNumber"
6945
7644
  ),
7645
+ settledLogIndex: record.settledLogIndex == null ? null : asNumber(record.settledLogIndex, "settledLogIndex"),
6946
7646
  updatedAt: asString(record.updatedAt, "updatedAt")
6947
7647
  };
6948
7648
  }
@@ -7026,7 +7726,8 @@ function mapBountyDetail(row) {
7026
7726
  submissions: asArray(record.submissions, "submissions").map(
7027
7727
  mapSubmissionFields
7028
7728
  ),
7029
- verdicts: asArray(record.verdicts, "verdicts").map(mapVerdict)
7729
+ verdicts: asArray(record.verdicts, "verdicts").map(mapVerdict),
7730
+ claims: asArray(record.claims, "claims").map(mapClaim)
7030
7731
  };
7031
7732
  }
7032
7733
  function mapVerdict(row) {
@@ -7043,6 +7744,11 @@ function mapVerdict(row) {
7043
7744
  payoutData: asHex(record.payoutData, "payoutData"),
7044
7745
  committedTxHash: asHex(record.committedTxHash, "committedTxHash"),
7045
7746
  committedAt: asString(record.committedAt, "committedAt"),
7747
+ committedBlockNumber: asString(
7748
+ record.committedBlockNumber,
7749
+ "committedBlockNumber"
7750
+ ),
7751
+ committedLogIndex: asNumber(record.committedLogIndex, "committedLogIndex"),
7046
7752
  updatedAt: asString(record.updatedAt, "updatedAt")
7047
7753
  };
7048
7754
  }
@@ -7053,6 +7759,27 @@ function mapListedVerdict(row) {
7053
7759
  ...mapVerdict(record)
7054
7760
  };
7055
7761
  }
7762
+ function mapClaim(row) {
7763
+ const record = readRecord(row);
7764
+ return {
7765
+ claimant: asHex(record.claimant, "claimant"),
7766
+ amount: asString(record.amount, "amount"),
7767
+ claimedAt: asString(record.claimedAt, "claimedAt"),
7768
+ claimedBlockNumber: asString(
7769
+ record.claimedBlockNumber,
7770
+ "claimedBlockNumber"
7771
+ ),
7772
+ claimedLogIndex: asNumber(record.claimedLogIndex, "claimedLogIndex"),
7773
+ claimedTxHash: asHex(record.claimedTxHash, "claimedTxHash")
7774
+ };
7775
+ }
7776
+ function mapListedClaim(row) {
7777
+ const record = readRecord(row);
7778
+ return {
7779
+ bountyId: asString(record.bountyId, "bountyId"),
7780
+ ...mapClaim(record)
7781
+ };
7782
+ }
7056
7783
  function mapListedSubmission(row) {
7057
7784
  const record = readRecord(row);
7058
7785
  return {
@@ -7073,6 +7800,7 @@ function mapSubmissionFields(row) {
7073
7800
  record.recordedBlockNumber,
7074
7801
  "recordedBlockNumber"
7075
7802
  ),
7803
+ recordedLogIndex: asNumber(record.recordedLogIndex, "recordedLogIndex"),
7076
7804
  recordedTxHash: asHex(record.recordedTxHash, "recordedTxHash"),
7077
7805
  submissionCallCount: asNumber(
7078
7806
  record.submissionCallCount,
@@ -7367,11 +8095,11 @@ var OPTIONS6 = { ...HUB_OPTIONS, ...SIGNER_OPTIONS };
7367
8095
  var USAGE6 = "elgora-cli guardian:prove-roster-key <challenge_json_path|->";
7368
8096
  async function main6(env = process.env, argv = process.argv.slice(2)) {
7369
8097
  const parsed = parseArgs(argv, OPTIONS6);
7370
- const [path6] = parsed.positionals;
7371
- if (!path6 || parsed.positionals.length !== 1) {
8098
+ const [path7] = parsed.positionals;
8099
+ if (!path7 || parsed.positionals.length !== 1) {
7372
8100
  throw usageError(USAGE6);
7373
8101
  }
7374
- const challenge = parseChallenge(await readInput(path6));
8102
+ const challenge = parseChallenge(await readInput(path7));
7375
8103
  const envWithDefaults = deploymentEnv(env, parsed);
7376
8104
  const { chainId, hubAddress } = loadPublicDeploymentConfig(envWithDefaults);
7377
8105
  if (challenge.context.chain_id !== chainId || challenge.context.hub_address.toLowerCase() !== hubAddress.toLowerCase()) {
@@ -7697,7 +8425,7 @@ async function assertCheckedSubmission(input) {
7697
8425
  });
7698
8426
  let envelope;
7699
8427
  try {
7700
- envelope = await verifyPrivateArtifactEnvelope(
8428
+ envelope = await verifySubmissionEnvelope(
7701
8429
  JSON.parse(response.content_record.canonical_json)
7702
8430
  );
7703
8431
  } catch (error) {
@@ -7705,7 +8433,7 @@ async function assertCheckedSubmission(input) {
7705
8433
  cause: error
7706
8434
  });
7707
8435
  }
7708
- const matchesCheckedPair = response.content_record.bounty_id === bountyId && derivePrivateArtifactEnvelopeCommitment(envelope) === input.submissionCommitment && envelope.context.chain_id === input.chainId && envelope.context.hub_address === input.hubAddress.toLowerCase() && envelope.context.bounty_id === bountyId && envelope.context.actor_address === input.winner.toLowerCase();
8436
+ const matchesCheckedPair = response.content_record.bounty_id === bountyId && deriveSubmissionEnvelopeCommitment(envelope) === input.submissionCommitment && envelope.context.chain_id === input.chainId && envelope.context.hub_address === input.hubAddress.toLowerCase() && envelope.context.bounty_id === bountyId && envelope.context.actor_address === input.winner.toLowerCase();
7709
8437
  if (!matchesCheckedPair) {
7710
8438
  throw new Error(
7711
8439
  "Submission envelope does not match the checked Solver and Submission"
@@ -7788,7 +8516,7 @@ function resolveChainId(selector) {
7788
8516
  }
7789
8517
 
7790
8518
  // src/version.ts
7791
- var CLI_VERSION = true ? "11.0.0" : "0.0.0-dev";
8519
+ var CLI_VERSION = true ? "12.1.0" : "0.0.0-dev";
7792
8520
  var CLI_NODE_ENGINES = true ? "24.x || 26.x" : "24.x || 26.x";
7793
8521
 
7794
8522
  // src/node-engines.ts
@@ -7852,8 +8580,8 @@ function compareVersions(left, right) {
7852
8580
  }
7853
8581
 
7854
8582
  // src/poster-open-winning-submission.ts
7855
- import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
7856
- import path3 from "path";
8583
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
8584
+ import path4 from "path";
7857
8585
  var OPTIONS8 = { ...API_OPTIONS, ...ACCOUNT_OPTIONS };
7858
8586
  var USAGE8 = "elgora-cli poster:open-winning-submission <bounty_id> [output_dir]";
7859
8587
  var DEFAULT_OUTPUT_ROOT2 = "./winning-submission";
@@ -7917,47 +8645,34 @@ async function main8(env = process.env, argv = process.argv.slice(2)) {
7917
8645
  contentAuthorization
7918
8646
  )
7919
8647
  );
7920
- if (content_record.bounty_id !== args.bountyId) {
7921
- throw new Error(
7922
- `Submission ${retrieved.submission_commitment} belongs to bounty_id ${content_record.bounty_id}, not ${args.bountyId}`
7923
- );
7924
- }
7925
- const envelope = await verifyPrivateArtifactEnvelope(
8648
+ const envelope = parseSubmissionEnvelope(
7926
8649
  JSON.parse(content_record.canonical_json)
7927
8650
  );
7928
- if (envelope.context.chain_id !== config.chainId || envelope.context.hub_address !== config.hubAddress.toLowerCase() || envelope.context.bounty_id !== args.bountyId) {
7929
- throw new Error(
7930
- `Winning Submission envelope does not belong to chain ${config.chainId}, ElgoraHub ${config.hubAddress}, and bounty_id ${args.bountyId}`
7931
- );
7932
- }
7933
- const envelopeCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
7934
- if (envelopeCommitment !== retrieved.submission_commitment) {
7935
- throw new Error(
7936
- `Winning Submission envelope commitment ${envelopeCommitment} does not match the retrieved submission_commitment ${retrieved.submission_commitment}`
7937
- );
7938
- }
7939
- const ciphertext = await getBytes(ciphertext_download_url);
7940
- const opened = await openPrivateArtifactEnvelopeWithKnownAesKey({
8651
+ requireCurrentFormat(
8652
+ envelope.alg,
8653
+ args.bountyId,
8654
+ "poster:open-winning-submission"
8655
+ );
8656
+ const opened = await openSubmissionEnvelopeWithKnownAesKey({
7941
8657
  envelope,
7942
- ciphertext,
8658
+ ciphertext: await getBytes(ciphertext_download_url),
7943
8659
  rawAesKey
7944
8660
  });
7945
- const outputDir = args.outputDir ?? path3.join(
7946
- DEFAULT_OUTPUT_ROOT2,
7947
- args.bountyId,
7948
- retrieved.submission_commitment
8661
+ if (opened.alg !== SUBMISSION_ENCRYPTION_TYPE_V1) return;
8662
+ const outputDir = args.outputDir ?? DEFAULT_OUTPUT_ROOT2;
8663
+ const file = path4.join(
8664
+ outputDir,
8665
+ `elgora-${args.bountyId}-${retrieved.submission_commitment.slice(2, 12)}.tar.gz`
7949
8666
  );
7950
8667
  await mkdir2(outputDir, { recursive: true });
7951
- for (const artifact of opened.artifacts) {
7952
- await writeFile3(path3.join(outputDir, artifact.name), artifact.bytes);
7953
- }
8668
+ await writeFile2(file, opened.archive);
7954
8669
  console.log(
7955
8670
  JSON.stringify({
7956
8671
  event: "poster_open_winning_submission.completed",
7957
8672
  bounty_id: args.bountyId,
7958
8673
  submission_commitment: retrieved.submission_commitment,
7959
- artifact_count: opened.artifacts.length,
7960
- output_dir: outputDir
8674
+ file,
8675
+ byte_length: opened.archive.byteLength
7961
8676
  })
7962
8677
  );
7963
8678
  } finally {
@@ -8006,10 +8721,10 @@ var OPTIONS9 = {
8006
8721
  var USAGE9 = "elgora-cli poster:publish-fund [--prepare-only] <bounty_challenge_md_path|->";
8007
8722
  async function main9(env = process.env, argv = process.argv.slice(2)) {
8008
8723
  const args = parseArgs(argv, OPTIONS9);
8009
- const [path6] = args.positionals;
8010
- if (args.positionals.length !== 1 || !path6) throw usageError(USAGE9);
8724
+ const [path7] = args.positionals;
8725
+ if (args.positionals.length !== 1 || !path7) throw usageError(USAGE9);
8011
8726
  const config = loadApiDeployment(env, args);
8012
- const bountyChallengeMarkdown = await readInput(path6);
8727
+ const bountyChallengeMarkdown = await readInput(path7);
8013
8728
  const localSpecCommitment = deriveBountyChallengeContentCommitment(
8014
8729
  bountyChallengeMarkdown
8015
8730
  );
@@ -8387,7 +9102,7 @@ function parseRosterAddGuardianArgs(argv) {
8387
9102
  if (!name) throw usageError(USAGE11);
8388
9103
  if (!KEY_ID_RE.test(name)) {
8389
9104
  throw new Error(
8390
- // A Solver's CLI seals private artifacts against this exact string as
9105
+ // A Solver's CLI seals Submissions against this exact string as
8391
9106
  // the Guardian's key_id, so it must be non-empty, start with an
8392
9107
  // alphanumeric, and stay within printable ASCII (no control/formatting
8393
9108
  // characters, no leading whitespace).
@@ -8566,72 +9281,23 @@ function get(value, key) {
8566
9281
  }
8567
9282
  var command13 = { main: main13, OPTIONS: OPTIONS13, USAGE: USAGE13 };
8568
9283
 
8569
- // src/spec-commitment.ts
8570
- var OPTIONS14 = {};
8571
- var USAGE14 = "elgora-cli spec-commitment <bounty_challenge_md_path|->";
8572
- async function main14(_env = process.env, argv = process.argv.slice(2)) {
8573
- const markdown = await readBountyChallengeMarkdown2(
8574
- parseArgs(argv, OPTIONS14).positionals
8575
- );
8576
- const specCommitment = deriveBountyChallengeContentCommitment(markdown);
8577
- console.log(
8578
- JSON.stringify({
8579
- event: "spec_commitment.derived",
8580
- byte_length: Buffer.byteLength(markdown, "utf8"),
8581
- spec_commitment: specCommitment
8582
- })
8583
- );
8584
- }
8585
- function readBountyChallengeMarkdown2(argv) {
8586
- const path6 = argv[0];
8587
- if (!path6 || argv.length !== 1) throw usageError(USAGE14);
8588
- return readInput(path6);
8589
- }
8590
- var command14 = { main: main14, OPTIONS: OPTIONS14, USAGE: USAGE14 };
8591
-
8592
9284
  // src/submit.ts
8593
- import { readdir, readFile as readFile5, stat } from "fs/promises";
8594
- import path4 from "path";
8595
- var OPTIONS15 = {
9285
+ import { lstat as lstat2, readdir as readdir2, readFile as readFile5 } from "fs/promises";
9286
+ import path5 from "path";
9287
+ var OPTIONS14 = {
8596
9288
  ...API_OPTIONS,
8597
9289
  ...ACCOUNT_OPTIONS,
8598
9290
  ...PREPARE_ONLY_OPTIONS,
8599
9291
  ...REVEAL_DELAY_OPTIONS
8600
9292
  };
8601
- var USAGE15 = "elgora-cli solver:submit [--prepare-only] <bounty_id> <artifact_dir>";
9293
+ var USAGE14 = "elgora-cli solver:submit [--prepare-only] <bounty_id> <folder_or_file>";
8602
9294
  var PREPARE_SUBMISSION_ATTEMPTS = 5;
8603
9295
  var PREPARE_SUBMISSION_BASE_DELAY_MS = 1e3;
8604
9296
  var PREPARE_SUBMISSION_MAX_DELAY_MS = 4e3;
8605
9297
  var BOUNTY_PROJECTION_UNAVAILABLE_CODE = "bounty_projection_unavailable";
8606
9298
  var UNAUTHORIZED_CODE = "unauthorized";
8607
- var MEDIA_TYPE_BY_EXTENSION = {
8608
- txt: "text/plain",
8609
- md: "text/markdown",
8610
- json: "application/json",
8611
- csv: "text/csv",
8612
- pdf: "application/pdf",
8613
- zip: "application/zip",
8614
- gz: "application/gzip",
8615
- tar: "application/x-tar",
8616
- png: "image/png",
8617
- jpg: "image/jpeg",
8618
- jpeg: "image/jpeg",
8619
- gif: "image/gif",
8620
- svg: "image/svg+xml",
8621
- py: "text/x-python",
8622
- js: "text/javascript",
8623
- ts: "text/plain",
8624
- sol: "text/plain",
8625
- yaml: "application/yaml",
8626
- yml: "application/yaml",
8627
- toml: "application/toml",
8628
- log: "text/plain",
8629
- patch: "text/x-diff",
8630
- diff: "text/x-diff"
8631
- };
8632
- var DEFAULT_MEDIA_TYPE = "application/octet-stream";
8633
- async function main15(env = process.env, argv = process.argv.slice(2)) {
8634
- const parsed = parseArgs(argv, OPTIONS15);
9299
+ async function main14(env = process.env, argv = process.argv.slice(2)) {
9300
+ const parsed = parseArgs(argv, OPTIONS14);
8635
9301
  const config = {
8636
9302
  ...loadApiDeployment(env, parsed),
8637
9303
  timelockRevealDelaySeconds: readRevealDelaySeconds(parsed)
@@ -8651,10 +9317,11 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8651
9317
  address: solver,
8652
9318
  sign: approvals.signTypedData
8653
9319
  });
8654
- const rosterResponse = await requestBountyGuardianRoster({
9320
+ const detail = await requestBountySubmissionDetail({
8655
9321
  apiBaseUrl: config.apiBaseUrl,
8656
9322
  bountyId: bountyIdText
8657
9323
  });
9324
+ const rosterResponse = detail.bounty;
8658
9325
  const readBountySubmissionFacts = async () => {
8659
9326
  const [parameters, state] = await publicClient.readContract({
8660
9327
  address: config.hubAddress,
@@ -8664,6 +9331,7 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8664
9331
  });
8665
9332
  return parseBountySubmissionFacts({
8666
9333
  bountyId: bountyIdText,
9334
+ specCommitment: parameters.specCommitment,
8667
9335
  guardianRosterHash: parameters.guardianRosterHash,
8668
9336
  submissionDeadline: parameters.submissionDeadline,
8669
9337
  status: state.status
@@ -8675,12 +9343,29 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8675
9343
  response: rosterResponse,
8676
9344
  hubGuardianRosterHash: bountyFacts.guardianRosterHash
8677
9345
  });
8678
- const artifacts = await readArtifactDirectory(args.artifactDir);
9346
+ const profile = verifyChallenge({
9347
+ bountyId: bountyIdText,
9348
+ challenge: detail.challenge,
9349
+ specCommitment: bountyFacts.specCommitment
9350
+ });
9351
+ requireCurrentFormat(profile.encryption.type, bountyIdText, "solver:submit");
9352
+ const { plaintext, content_sha256, macos_metadata_skipped } = await readSubmissionPlaintext(args.inputPath, profile);
9353
+ const { issues } = checkSubmissionArchive({
9354
+ archive: plaintext.archive,
9355
+ profile,
9356
+ challenge: detail.challenge
9357
+ });
9358
+ if (issues.length > 0) {
9359
+ throw new Error(
9360
+ `The Submission does not meet bounty ${bountyIdText}'s rules, so nothing was submitted: ${issues.map((issue) => issue.message).join(" ")} Run solver:check for the full report.`
9361
+ );
9362
+ }
9363
+ const contentDigest = { content_sha256 };
8679
9364
  const { envelope, ciphertext } = await buildSolverSubmissionEnvelope({
8680
9365
  config,
8681
9366
  bountyId: args.bountyId,
8682
9367
  solver,
8683
- artifacts,
9368
+ plaintext,
8684
9369
  guardianRoster,
8685
9370
  submissionDeadline: bountyFacts.submissionDeadline
8686
9371
  });
@@ -8708,7 +9393,9 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8708
9393
  event: "submit.ciphertext_uploaded",
8709
9394
  bounty_id: bountyIdText,
8710
9395
  solver,
8711
- byte_length: ciphertext.byteLength
9396
+ byte_length: ciphertext.byteLength,
9397
+ ...contentDigest,
9398
+ ...macos_metadata_skipped.length > 0 && { macos_metadata_skipped }
8712
9399
  })
8713
9400
  );
8714
9401
  const prepared = await requestPrepareSubmission({
@@ -8718,7 +9405,7 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8718
9405
  envelope,
8719
9406
  authorizeRequest: authorizeSubmitRequest
8720
9407
  });
8721
- const localCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
9408
+ const localCommitment = deriveSubmissionEnvelopeCommitment(envelope);
8722
9409
  if (prepared.content_record.submission_commitment !== localCommitment) {
8723
9410
  throw new Error(
8724
9411
  "Elgora API returned a submission_commitment that does not match the locally sealed envelope"
@@ -8792,7 +9479,8 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8792
9479
  bounty_id: bountyIdText,
8793
9480
  solver,
8794
9481
  submission_commitment: localCommitment,
8795
- guardian_roster_hash: signingFacts.guardianRosterHash
9482
+ guardian_roster_hash: signingFacts.guardianRosterHash,
9483
+ ...contentDigest
8796
9484
  })
8797
9485
  );
8798
9486
  }
@@ -8824,10 +9512,28 @@ function parseBountySubmissionFacts(input) {
8824
9512
  }
8825
9513
  return {
8826
9514
  status: decodeBountyStatus(input.status),
9515
+ specCommitment: input.specCommitment.toLowerCase(),
8827
9516
  submissionDeadline: Number(input.submissionDeadline),
8828
9517
  guardianRosterHash: input.guardianRosterHash.toLowerCase()
8829
9518
  };
8830
9519
  }
9520
+ async function readSubmissionPlaintext(artifactPath, profile) {
9521
+ const { files, macos_metadata_skipped } = await readSubmissionFiles(
9522
+ artifactPath,
9523
+ profile
9524
+ );
9525
+ const { archive, content_sha256 } = buildSubmissionArchive(files);
9526
+ if (archive.byteLength + AES_GCM_TAG_BYTES > MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES) {
9527
+ throw new Error(
9528
+ `This Submission packs to ${archive.byteLength + AES_GCM_TAG_BYTES} bytes encrypted and exceeds Elgora's ${formatMebibytes(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)} encrypted submission limit. Reduce the files before submitting.`
9529
+ );
9530
+ }
9531
+ return {
9532
+ plaintext: { encryption_type: SUBMISSION_ENCRYPTION_TYPE_V1, archive },
9533
+ content_sha256,
9534
+ macos_metadata_skipped
9535
+ };
9536
+ }
8831
9537
  function verifyPinnedGuardianRoster(input) {
8832
9538
  const roster = input.response.guardian_roster.map((guardian) => ({
8833
9539
  name: guardian.name,
@@ -8895,7 +9601,7 @@ async function buildSolverSubmissionEnvelope(input) {
8895
9601
  const guardianRecipients = input.guardianRoster.map(
8896
9602
  (guardian) => ({
8897
9603
  key_id: guardian.name,
8898
- suite: PRIVATE_ARTIFACT_HPKE_SUITE,
9604
+ suite: SUBMISSION_HPKE_SUITE,
8899
9605
  public_key: guardian.encryptionPublicKey
8900
9606
  })
8901
9607
  );
@@ -8907,69 +9613,132 @@ async function buildSolverSubmissionEnvelope(input) {
8907
9613
  bounty_id: bountyId,
8908
9614
  actor_address: solver
8909
9615
  },
8910
- artifacts: input.artifacts,
9616
+ ...input.plaintext,
8911
9617
  guardian_recipients: guardianRecipients,
8912
- reveal_policy: buildPrivateArtifactRevealPolicyForDeadline({
9618
+ reveal_policy: buildSubmissionRevealPolicyForDeadline({
8913
9619
  submissionDeadline: input.submissionDeadline,
8914
9620
  revealDelaySeconds: input.config.timelockRevealDelaySeconds
8915
9621
  }),
8916
- timelock_encrypt: input.timelockEncrypt ?? createPrivateArtifactTimelockEncrypt()
9622
+ timelock_encrypt: input.timelockEncrypt ?? createSubmissionTimelockEncrypt()
8917
9623
  };
8918
9624
  if (input.randomBytes) {
8919
9625
  envelopeInput.random_bytes = input.randomBytes;
8920
9626
  }
8921
- return sealPrivateArtifactEnvelope(envelopeInput);
9627
+ return sealSubmissionEnvelope(envelopeInput);
8922
9628
  }
8923
9629
  function readSubmitArgs(argv) {
8924
- const [bountyIdText, artifactDir] = argv;
8925
- if (argv.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !artifactDir?.trim()) {
8926
- throw usageError(USAGE15);
9630
+ const [bountyIdText, inputPath] = argv;
9631
+ if (argv.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !inputPath?.trim()) {
9632
+ throw usageError(USAGE14);
9633
+ }
9634
+ return { bountyId: BigInt(bountyIdText), inputPath };
9635
+ }
9636
+ async function readSubmissionFiles(inputPath, profile) {
9637
+ const { maxFiles, maxExtractedBytes } = profile.submission;
9638
+ const root = await lstat2(inputPath);
9639
+ const found = [];
9640
+ const skipped = [];
9641
+ if (root.isFile()) {
9642
+ const issue = describeSolverSubmissionPathIssue(path5.basename(inputPath));
9643
+ if (issue !== null) throw new Error(issue);
9644
+ found.push({
9645
+ path: path5.basename(inputPath),
9646
+ absolute: inputPath,
9647
+ size: root.size
9648
+ });
9649
+ } else if (root.isDirectory()) {
9650
+ await walkSubmissionDirectory(inputPath, "", found, skipped, maxFiles);
9651
+ if (found.length === 0) {
9652
+ throw new Error(`No files found in folder: ${inputPath}`);
9653
+ }
9654
+ } else {
9655
+ throw new Error(
9656
+ `${inputPath} is ${describeEntryType(root)}; point solver:submit at a folder or a regular file.`
9657
+ );
8927
9658
  }
8928
- return { bountyId: BigInt(bountyIdText), artifactDir };
8929
- }
8930
- async function readArtifactDirectory(artifactDir) {
8931
- const entries = await readdir(artifactDir, { withFileTypes: true });
8932
- const unsupportedEntry = entries.find((entry) => !entry.isFile());
8933
- if (unsupportedEntry) {
9659
+ const conflict = findSolverSubmissionPathConflict(
9660
+ found.map((file) => file.path)
9661
+ );
9662
+ if (conflict !== null) {
9663
+ throw new Error(describeSolverSubmissionPathConflict(conflict));
9664
+ }
9665
+ const declaredBytes = found.reduce((total, file) => total + file.size, 0);
9666
+ if (declaredBytes > maxExtractedBytes) {
8934
9667
  throw new Error(
8935
- `Artifact directory must be flat and contain only regular files; found unsupported entry "${unsupportedEntry.name}". Remove it and submit only regular files directly inside ${artifactDir}.`
9668
+ `The Submission holds ${declaredBytes} bytes of files and exceeds Elgora's ${maxExtractedBytes}-byte limit on what one Submission extracts to.`
8936
9669
  );
8937
9670
  }
8938
- if (entries.length === 0) {
8939
- throw new Error(`No files found in artifact directory: ${artifactDir}`);
9671
+ const files = [];
9672
+ for (const file of found) {
9673
+ files.push({
9674
+ path: file.path,
9675
+ bytes: new Uint8Array(await readFile5(file.absolute))
9676
+ });
8940
9677
  }
8941
- if (entries.length > MAX_SOLVER_SUBMISSION_FILES) {
9678
+ return { files, macos_metadata_skipped: skipped };
9679
+ }
9680
+ function isMacosMetadataFile(entry) {
9681
+ return entry.isFile() && (entry.name === ".DS_Store" || entry.name.startsWith("._"));
9682
+ }
9683
+ async function walkSubmissionDirectory(directory, relative, found, skipped, maxFiles) {
9684
+ const entries = await readdir2(directory, { withFileTypes: true });
9685
+ if (entries.length === 0 && relative !== "") {
8942
9686
  throw new Error(
8943
- `Artifact directory holds ${entries.length} files and exceeds Elgora's maximum of ${MAX_SOLVER_SUBMISSION_FILES} files per Submission. Combine them into fewer files, if the challenge allows it, before submitting.`
9687
+ `Folder "${relative}" is empty. A Submission cannot carry an empty folder; remove it or add the files it should hold.`
8944
9688
  );
8945
9689
  }
8946
- let plaintextByteLength = 0;
9690
+ const foundBefore = found.length;
8947
9691
  for (const entry of entries) {
8948
- plaintextByteLength += (await stat(path4.join(artifactDir, entry.name))).size;
8949
- if (plaintextByteLength > MAX_SOLVER_SUBMISSION_ARTIFACT_BYTES) {
9692
+ const entryPath = relative === "" ? entry.name : `${relative}/${entry.name}`;
9693
+ const absolute = path5.join(directory, entry.name);
9694
+ if (isMacosMetadataFile(entry)) {
9695
+ skipped.push(entryPath);
9696
+ continue;
9697
+ }
9698
+ const issue = describeSolverSubmissionPathIssue(entryPath);
9699
+ if (issue !== null) throw new Error(issue);
9700
+ if (entry.isDirectory()) {
9701
+ await walkSubmissionDirectory(
9702
+ absolute,
9703
+ entryPath,
9704
+ found,
9705
+ skipped,
9706
+ maxFiles
9707
+ );
9708
+ continue;
9709
+ }
9710
+ if (!entry.isFile()) {
9711
+ throw new Error(
9712
+ `"${entryPath}" is ${describeEntryType(entry)}. A Submission carries regular files only; replace it with the file it stands for or remove it.`
9713
+ );
9714
+ }
9715
+ found.push({
9716
+ path: entryPath,
9717
+ absolute,
9718
+ size: (await lstat2(absolute)).size
9719
+ });
9720
+ if (found.length > maxFiles) {
8950
9721
  throw new Error(
8951
- `Artifact directory exceeds Elgora's ${formatMebibytes(MAX_SOLVER_SUBMISSION_ARTIFACT_BYTES)} of files per Submission, which is what fits the ${formatMebibytes(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)} encrypted submission limit once packaged. Reduce the final artifact package before submitting.`
9722
+ `The Submission holds more than ${maxFiles} files, Elgora's maximum per Submission.`
8952
9723
  );
8953
9724
  }
8954
9725
  }
8955
- return Promise.all(
8956
- entries.map(async (entry) => {
8957
- const bytes = await readFile5(path4.join(artifactDir, entry.name));
8958
- return {
8959
- name: entry.name,
8960
- media_type: guessMediaType(entry.name),
8961
- bytes: new Uint8Array(bytes)
8962
- };
8963
- })
8964
- );
9726
+ if (found.length === foundBefore && relative !== "") {
9727
+ throw new Error(
9728
+ `Folder "${relative}" holds only macOS metadata (.DS_Store or ._ files), which is left out, so it would be empty. A Submission cannot carry an empty folder; remove it or add the files it should hold.`
9729
+ );
9730
+ }
9731
+ }
9732
+ function describeEntryType(entry) {
9733
+ if (entry.isSymbolicLink()) return "a symbolic link";
9734
+ if (entry.isFIFO()) return "a FIFO";
9735
+ if (entry.isSocket()) return "a socket";
9736
+ if (entry.isBlockDevice() || entry.isCharacterDevice()) return "a device";
9737
+ return "not a regular file";
8965
9738
  }
8966
9739
  function formatMebibytes(byteLength) {
8967
9740
  return `${byteLength / 1024 / 1024} MiB`;
8968
9741
  }
8969
- function guessMediaType(fileName) {
8970
- const extension = path4.extname(fileName).slice(1).toLowerCase();
8971
- return MEDIA_TYPE_BY_EXTENSION[extension] ?? DEFAULT_MEDIA_TYPE;
8972
- }
8973
9742
  async function requestArtifactUploadUrl(input) {
8974
9743
  const requestUrl = new URL(
8975
9744
  `/api/bounties/${input.bountyId}/submissions/${input.solver}/artifact-upload-url`,
@@ -8993,7 +9762,7 @@ async function requestPrepareSubmission(input) {
8993
9762
  `/api/bounties/${input.bountyId}/submissions/${input.solver}/prepare`,
8994
9763
  input.apiBaseUrl
8995
9764
  );
8996
- const bodyText = serializePrivateArtifactEnvelope(input.envelope);
9765
+ const bodyText = serializeSubmissionEnvelope(input.envelope);
8997
9766
  for (let attempt = 1; ; attempt += 1) {
8998
9767
  try {
8999
9768
  const authorization = await input.authorizeRequest(requestUrl, bodyText);
@@ -9027,13 +9796,258 @@ async function requestPrepareSubmission(input) {
9027
9796
  }
9028
9797
  }
9029
9798
  }
9799
+ var command14 = { main: main14, OPTIONS: OPTIONS14, USAGE: USAGE14 };
9800
+
9801
+ // src/solver-check.ts
9802
+ var OPTIONS15 = { ...API_OPTIONS, ...RPC_OPTIONS, ...HUB_OPTIONS };
9803
+ var USAGE15 = "elgora-cli solver:check <bounty_id> <folder_or_file>";
9804
+ async function main15(env = process.env, argv = process.argv.slice(2)) {
9805
+ const parsed = parseArgs(argv, OPTIONS15);
9806
+ const [bountyIdText, inputPath] = parsed.positionals;
9807
+ if (parsed.positionals.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !inputPath?.trim()) {
9808
+ throw usageError(USAGE15);
9809
+ }
9810
+ const config = loadApiDeployment(env, parsed);
9811
+ const publicClient = createChainPublicClient(config.chainId, config.rpcUrl);
9812
+ await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
9813
+ const { challenge, profile } = await readVerifiedChallenge({
9814
+ publicClient,
9815
+ apiBaseUrl: config.apiBaseUrl,
9816
+ hubAddress: config.hubAddress,
9817
+ bountyId: bountyIdText
9818
+ });
9819
+ requireCurrentFormat(profile.encryption.type, bountyIdText, "solver:submit");
9820
+ const report = await checkSubmission({ inputPath, profile, challenge });
9821
+ const blocking = report.issues.length > 0;
9822
+ console.log(
9823
+ JSON.stringify({
9824
+ event: blocking ? "solver_check.issues" : "solver_check.passed",
9825
+ bounty_id: bountyIdText,
9826
+ // Named so a passing result is never read as "this Submission wins".
9827
+ checked: "format_and_constraints",
9828
+ ...report
9829
+ })
9830
+ );
9831
+ if (blocking) process.exitCode = 1;
9832
+ }
9833
+ async function checkSubmission(input) {
9834
+ const base4 = {
9835
+ profile: input.profile.profile,
9836
+ max_encrypted_bytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES
9837
+ };
9838
+ let archive;
9839
+ let skipped;
9840
+ try {
9841
+ const { plaintext, macos_metadata_skipped } = await readSubmissionPlaintext(
9842
+ input.inputPath,
9843
+ input.profile
9844
+ );
9845
+ archive = plaintext.archive;
9846
+ skipped = macos_metadata_skipped;
9847
+ } catch (error) {
9848
+ return {
9849
+ ...base4,
9850
+ issues: [
9851
+ {
9852
+ outcome: "refused",
9853
+ code: "submission_input",
9854
+ message: error instanceof Error ? error.message : String(error)
9855
+ }
9856
+ ]
9857
+ };
9858
+ }
9859
+ return {
9860
+ ...base4,
9861
+ encrypted_bytes: archive.byteLength + AES_GCM_TAG_BYTES,
9862
+ ...skipped.length > 0 && { macos_metadata_skipped: skipped },
9863
+ ...checkSubmissionArchive({
9864
+ archive,
9865
+ profile: input.profile,
9866
+ challenge: input.challenge
9867
+ })
9868
+ };
9869
+ }
9030
9870
  var command15 = { main: main15, OPTIONS: OPTIONS15, USAGE: USAGE15 };
9031
9871
 
9032
- // src/verification-record.ts
9033
- var OPTIONS16 = { ...API_OPTIONS, ...HUB_OPTIONS };
9034
- var USAGE16 = "elgora-cli verification-record <bounty_id>";
9872
+ // src/bounty-filter-flags.ts
9873
+ var BOUNTY_FILTER_OPTIONS = {
9874
+ "--min-amount": "value",
9875
+ "--max-amount": "value",
9876
+ "--min-time-left": "value",
9877
+ "--max-time-left": "value",
9878
+ "--keyword": "list"
9879
+ };
9880
+ var BOUNTY_FILTER_USAGE = "[--min-amount <units>] [--max-amount <units>]\n [--min-time-left <seconds>] [--max-time-left <seconds>]\n [--keyword <word>]...";
9881
+ var NUMERIC_FILTERS = [
9882
+ ["--min-amount", "min_amount"],
9883
+ ["--max-amount", "max_amount"],
9884
+ ["--min-time-left", "min_time_left"],
9885
+ ["--max-time-left", "max_time_left"]
9886
+ ];
9887
+ function readBountyFilterFlags(args) {
9888
+ const fields = {};
9889
+ for (const [flag, field] of NUMERIC_FILTERS) {
9890
+ const value = args.value(flag);
9891
+ if (value === void 0) continue;
9892
+ if (!/^(0|[1-9]\d*)$/.test(value)) {
9893
+ throw new Error(`${flag} must be a non-negative integer`);
9894
+ }
9895
+ fields[field] = value;
9896
+ }
9897
+ return fields;
9898
+ }
9899
+
9900
+ // src/solver-notifications.ts
9901
+ var OPTIONS16 = {
9902
+ "--webhook-url": "value",
9903
+ ...BOUNTY_FILTER_OPTIONS,
9904
+ ...API_OPTIONS,
9905
+ ...ACCOUNT_OPTIONS
9906
+ };
9907
+ var USAGE16 = `elgora-cli solver:notifications subscribe --webhook-url <https_url>
9908
+ ${BOUNTY_FILTER_USAGE}
9909
+ elgora-cli solver:notifications list
9910
+ elgora-cli solver:notifications unsubscribe <subscription_id>`;
9911
+ var PATH = "/api/notifications/subscriptions";
9035
9912
  async function main16(env = process.env, argv = process.argv.slice(2)) {
9036
9913
  const parsed = parseArgs(argv, OPTIONS16);
9914
+ const request = readNotificationRequest(parsed, env);
9915
+ const config = loadApiDeployment(env, parsed);
9916
+ const { signer } = await connectAccount(env, parsed, config);
9917
+ const url = new URL(PATH, config.apiBaseUrl);
9918
+ const body = JSON.stringify(request);
9919
+ const authorization = await authorizeRequest({
9920
+ requestUrl: url.toString(),
9921
+ method: "POST",
9922
+ contentType: "application/json",
9923
+ body,
9924
+ chainId: config.chainId,
9925
+ hubAddress: config.hubAddress,
9926
+ address: signer.address,
9927
+ sign: signer.approvals.signTypedData
9928
+ });
9929
+ const response = await postText(url, body, "application/json", authorization);
9930
+ process.stdout.write(
9931
+ `${JSON.stringify({ event: `notifications.${request.action}`, ...response })}
9932
+ `
9933
+ );
9934
+ }
9935
+ function readNotificationRequest(args, env) {
9936
+ const [action, ...rest] = args.positionals;
9937
+ if (action === "list" && rest.length === 0) return { action };
9938
+ if (action === "unsubscribe" && rest.length === 1) {
9939
+ return { action, subscription_id: rest[0] };
9940
+ }
9941
+ if (action !== "subscribe" || rest.length > 0) throw usageError(USAGE16);
9942
+ const url = args.value("--webhook-url");
9943
+ if (!url) throw usageError(USAGE16);
9944
+ const secret = env.ELGORA_WEBHOOK_SECRET?.trim();
9945
+ if (!secret) {
9946
+ throw new Error(
9947
+ "ELGORA_WEBHOOK_SECRET is required to subscribe: the shared secret your webhook verifies signatures with"
9948
+ );
9949
+ }
9950
+ return {
9951
+ action,
9952
+ event: "bounty_posted",
9953
+ filter: {
9954
+ ...readBountyFilterFlags(args),
9955
+ keywords: args.list("--keyword")
9956
+ },
9957
+ sink: { type: "webhook", url, secret }
9958
+ };
9959
+ }
9960
+ var command16 = { main: main16, OPTIONS: OPTIONS16, USAGE: USAGE16 };
9961
+
9962
+ // src/solver-solvable.ts
9963
+ import { isAddressEqual as isAddressEqual2 } from "viem";
9964
+ var OPTIONS17 = {
9965
+ ...BOUNTY_FILTER_OPTIONS,
9966
+ ...API_OPTIONS,
9967
+ ...HUB_OPTIONS
9968
+ };
9969
+ var USAGE17 = `elgora-cli solver:solvable ${BOUNTY_FILTER_USAGE}`;
9970
+ async function main17(env = process.env, argv = process.argv.slice(2)) {
9971
+ const parsed = parseArgs(argv, OPTIONS17);
9972
+ const params = readSolvableParams(parsed);
9973
+ const keywords = parsed.list("--keyword");
9974
+ const config = loadClientDeploymentConfig(deploymentEnv(env, parsed));
9975
+ const solvable = [];
9976
+ for (let skip = 0; ; skip += BOUNTY_LIST_MAX_FIRST) {
9977
+ params.set("skip", String(skip));
9978
+ const page = bountyListResponseSchema.parse(
9979
+ await getJson(config.apiBaseUrl, `/api/bounties?${params}`)
9980
+ );
9981
+ if (page.chain_id !== config.chainId || !isAddressEqual2(page.hub_address, config.hubAddress)) {
9982
+ throw new Error(
9983
+ `Elgora API lists bounties for chain ${page.chain_id} ElgoraHub ${page.hub_address}, not this deployment`
9984
+ );
9985
+ }
9986
+ for (const bounty of page.bounties) {
9987
+ if (keywords.length === 0 || await challengeMatches(config.apiBaseUrl, bounty, keywords)) {
9988
+ solvable.push(bounty);
9989
+ }
9990
+ }
9991
+ if (skip + BOUNTY_LIST_MAX_FIRST >= page.total) break;
9992
+ }
9993
+ process.stdout.write(`${JSON.stringify(solvable)}
9994
+ `);
9995
+ }
9996
+ function readSolvableParams(args) {
9997
+ if (args.positionals.length > 0) throw usageError(USAGE17);
9998
+ const params = new URLSearchParams({
9999
+ status: "open",
10000
+ first: String(BOUNTY_LIST_MAX_FIRST)
10001
+ });
10002
+ for (const [param, value] of Object.entries(readBountyFilterFlags(args))) {
10003
+ if (value !== void 0) params.set(param, value);
10004
+ }
10005
+ return params;
10006
+ }
10007
+ async function challengeMatches(apiBaseUrl, bounty, keywords) {
10008
+ const { challenge } = await requestBountySubmissionDetail({
10009
+ apiBaseUrl,
10010
+ bountyId: bounty.bounty_id
10011
+ });
10012
+ if (challenge === null || deriveBountyChallengeContentCommitment(challenge).toLowerCase() !== bounty.spec_commitment) {
10013
+ process.stderr.write(
10014
+ `Skipped bounty ${bounty.bounty_id}: the Elgora API served no challenge matching its spec_commitment
10015
+ `
10016
+ );
10017
+ return false;
10018
+ }
10019
+ return challengeMatchesKeywords(challenge, keywords);
10020
+ }
10021
+ var command17 = { main: main17, OPTIONS: OPTIONS17, USAGE: USAGE17 };
10022
+
10023
+ // src/spec-commitment.ts
10024
+ var OPTIONS18 = {};
10025
+ var USAGE18 = "elgora-cli spec-commitment <bounty_challenge_md_path|->";
10026
+ async function main18(_env = process.env, argv = process.argv.slice(2)) {
10027
+ const markdown = await readBountyChallengeMarkdown2(
10028
+ parseArgs(argv, OPTIONS18).positionals
10029
+ );
10030
+ const specCommitment = deriveBountyChallengeContentCommitment(markdown);
10031
+ console.log(
10032
+ JSON.stringify({
10033
+ event: "spec_commitment.derived",
10034
+ byte_length: Buffer.byteLength(markdown, "utf8"),
10035
+ spec_commitment: specCommitment
10036
+ })
10037
+ );
10038
+ }
10039
+ function readBountyChallengeMarkdown2(argv) {
10040
+ const path7 = argv[0];
10041
+ if (!path7 || argv.length !== 1) throw usageError(USAGE18);
10042
+ return readInput(path7);
10043
+ }
10044
+ var command18 = { main: main18, OPTIONS: OPTIONS18, USAGE: USAGE18 };
10045
+
10046
+ // src/verification-record.ts
10047
+ var OPTIONS19 = { ...API_OPTIONS, ...HUB_OPTIONS };
10048
+ var USAGE19 = "elgora-cli verification-record <bounty_id>";
10049
+ async function main19(env = process.env, argv = process.argv.slice(2)) {
10050
+ const parsed = parseArgs(argv, OPTIONS19);
9037
10051
  const args = readVerificationRecordArgs(parsed.positionals);
9038
10052
  const config = loadClientDeploymentConfig(deploymentEnv(env, parsed));
9039
10053
  const record = await requestVerificationRecord({
@@ -9085,15 +10099,15 @@ function readVerificationRecordArgs(argv) {
9085
10099
  const [bountyIdText] = argv;
9086
10100
  const bountyId = bountyIdText?.trim();
9087
10101
  if (argv.length !== 1 || !bountyId || !isBountyId(bountyId)) {
9088
- throw usageError(USAGE16);
10102
+ throw usageError(USAGE19);
9089
10103
  }
9090
10104
  return { bountyId: BigInt(bountyId) };
9091
10105
  }
9092
- var command16 = { main: main16, OPTIONS: OPTIONS16, USAGE: USAGE16 };
10106
+ var command19 = { main: main19, OPTIONS: OPTIONS19, USAGE: USAGE19 };
9093
10107
 
9094
10108
  // src/cli.ts
9095
10109
  if (process.env.DOTENV_CONFIG_PATH) {
9096
- const dotenvPath = path5.resolve(process.env.DOTENV_CONFIG_PATH);
10110
+ const dotenvPath = path6.resolve(process.env.DOTENV_CONFIG_PATH);
9097
10111
  const dotenvResult = loadDotenv({ path: dotenvPath, quiet: true });
9098
10112
  process.stderr.write(
9099
10113
  `Env: ${dotenvPath} (${dotenvResult.error ? `not found: ${dotenvResult.error.message}` : "loaded"})
@@ -9116,11 +10130,14 @@ var COMMANDS = {
9116
10130
  "roster:add-guardian": command11,
9117
10131
  "roster:prepare-guardian-proof": command12,
9118
10132
  "roster:verify-guardian-proof": command13,
9119
- "solver:submit": command15,
10133
+ "solver:solvable": command17,
10134
+ "solver:notifications": command16,
10135
+ "solver:check": command15,
10136
+ "solver:submit": command14,
9120
10137
  claim: command2,
9121
10138
  "check-challenge": command,
9122
- "spec-commitment": command14,
9123
- "verification-record": command16
10139
+ "spec-commitment": command18,
10140
+ "verification-record": command19
9124
10141
  };
9125
10142
  var OPTION_HELP = {
9126
10143
  "--api-base-url": "--api-base-url <url> Elgora API (else ELGORA_API_BASE_URL)",
@@ -9130,7 +10147,13 @@ var OPTION_HELP = {
9130
10147
  "--reveal-delay-seconds": "--reveal-delay-seconds <n> seconds after the Submission deadline before\n Guardian keys release (default 0)",
9131
10148
  "--prepare-only": "--prepare-only print the transactions instead of sending them",
9132
10149
  "--previous": "--previous <commitment> the previous challenge commitment, sent with\n the signed request",
9133
- "--guardian-address": "--guardian-address <address> the Guardian whose own Verdicts are reported"
10150
+ "--guardian-address": "--guardian-address <address> the Guardian whose own Verdicts are reported",
10151
+ "--min-amount": "--min-amount <units> smallest escrow, in token units (USDC: 1000000 = 1)",
10152
+ "--max-amount": "--max-amount <units> largest escrow, in token units",
10153
+ "--min-time-left": "--min-time-left <seconds> least time left before the Submission deadline",
10154
+ "--max-time-left": "--max-time-left <seconds> most time left before the Submission deadline",
10155
+ "--webhook-url": "--webhook-url <https_url> your webhook; it must verify Elgora's signature",
10156
+ "--keyword": "--keyword <word> must appear in the challenge, ignoring case;\n repeat it to require several"
9134
10157
  };
9135
10158
  var DEPLOYMENT_OPTIONS = [
9136
10159
  "--api-base-url",
@@ -9146,7 +10169,7 @@ var COMMAND_HELP = {
9146
10169
  },
9147
10170
  "guardian:open": {
9148
10171
  title: "Verify and decrypt one Solver Submission.",
9149
- description: "Check the exact Solver and Submission against ElgoraHub and the\n commitment-addressed envelope, then sign the protected content GET\n with this Guardian's account before downloading ciphertext. Then\n decrypt with this Guardian's retained X25519 keys and write the\n artifacts locally. Without output_dir, files go under\n guardian-open/<bounty_id>/<submission_commitment>/. A fetch or decrypt\n failure never becomes a Verdict.",
10172
+ description: "Verify, decrypt and extract one Submission into a new or empty\n output_dir (default guardian-open/<bounty_id>/<submission_commitment>/).\n Prints content_sha256 and the issues solver:check reports.",
9150
10173
  required: ["ELGORA_GUARDIAN_PRIVATE_KEYS_JSON"]
9151
10174
  },
9152
10175
  "guardian:prove-roster-key": {
@@ -9176,7 +10199,7 @@ var COMMAND_HELP = {
9176
10199
  },
9177
10200
  "poster:open-winning-submission": {
9178
10201
  title: "Retrieve and decrypt the finalized winning Submission.",
9179
- description: "Authenticate as the funding Poster, ask the existing delivery API for\n the finalized winner's key rewrapped to a fresh local key, verify the\n committed envelope, and decrypt the artifacts locally. Without\n output_dir, files go under\n winning-submission/<bounty_id>/<submission_commitment>/.",
10202
+ description: "Save the finalized winning Submission, decrypted, as one .tar.gz in\n output_dir (default winning-submission/).",
9180
10203
  required: []
9181
10204
  },
9182
10205
  "roster:add-guardian": {
@@ -9194,9 +10217,24 @@ var COMMAND_HELP = {
9194
10217
  description: "Takes the unmodified stdout of roster:prepare-guardian-proof and of\n the Guardian's guardian:prove-roster-key, reconstructs the signed\n payload from the fixture the admin generated (never sent to the\n Guardian), and checks through the chain that the signature is valid\n for the exact proposed account, which also covers contract and\n EIP-7702 accounts (ERC-1271). Exits non-zero on any mismatch.\n Advisory only \u2014 convinces\n the admin, proves nothing to ElgoraHub, and does not itself add the\n Guardian; run roster:add-guardian separately once satisfied.",
9195
10218
  required: []
9196
10219
  },
10220
+ "solver:solvable": {
10221
+ title: "List open bounties a Solver can still submit to.",
10222
+ description: "Read open bounties before their Submission deadline from the public\n Elgora API, newest first, filtered by escrow amount, time left and\n keywords. Keywords match the challenge text only after its bytes hash\n to the bounty's spec_commitment. Prints one JSON array. Advisory only:\n solver:submit checks ElgoraHub before it signs. No wallet or secret.",
10223
+ required: []
10224
+ },
10225
+ "solver:notifications": {
10226
+ title: "Get a signed webhook call for each new matching bounty.",
10227
+ description: "Subscribe this wallet's webhook to new bounties that match the filter,\n list its subscriptions, or unsubscribe one. Subscribing sends a signed\n webhook.verify call first and succeeds only on a 2xx answer. Every call\n carries X-Webhook-Signature (hex HMAC-SHA256 of the body) and Standard\n Webhooks headers, keyed with ELGORA_WEBHOOK_SECRET, which Elgora stores\n encrypted. A notification is advisory: read the bounty before acting.",
10228
+ required: []
10229
+ },
10230
+ "solver:check": {
10231
+ title: "Check a Submission the way a Guardian will, before submitting.",
10232
+ description: "Run a Guardian's format and constraint checks on a folder or file\n without submitting anything. Exits 1 on any issue.",
10233
+ required: []
10234
+ },
9197
10235
  "solver:submit": {
9198
- title: "Encrypt and submit a Solver's private artifacts.",
9199
- description: "Encrypt for the Guardian roster pinned to this bounty, upload the\n ciphertext, check ElgoraHub immediately before signing, and locally\n encode all four V2 submit values.",
10236
+ title: "Encrypt and submit a Solver's private files.",
10237
+ description: "Pack a folder or one file into a .tar.gz, encrypt it for the bounty's\n Guardians, upload it and submit it on ElgoraHub. Refuses it on any issue\n solver:check reports. Prints content_sha256, which stays on this machine.",
9200
10238
  required: []
9201
10239
  },
9202
10240
  claim: {
@@ -9241,8 +10279,8 @@ async function run(rawArgv) {
9241
10279
  printCommandHelpOrUsage(rest[0]);
9242
10280
  return;
9243
10281
  }
9244
- const command17 = COMMANDS[subcommand];
9245
- if (!command17) {
10282
+ const command20 = COMMANDS[subcommand];
10283
+ if (!command20) {
9246
10284
  process.stderr.write(`Unknown command: ${subcommand}
9247
10285
  `);
9248
10286
  printUsage();
@@ -9255,8 +10293,8 @@ async function run(rawArgv) {
9255
10293
  }
9256
10294
  let args;
9257
10295
  try {
9258
- args = parseArgs(rest, command17.OPTIONS);
9259
- if ("--signer" in command17.OPTIONS) readSignerOptions(args);
10296
+ args = parseArgs(rest, command20.OPTIONS);
10297
+ if ("--signer" in command20.OPTIONS) readSignerOptions(args);
9260
10298
  } catch (error) {
9261
10299
  process.stderr.write(
9262
10300
  `Error: ${error instanceof Error ? error.message : String(error)}
@@ -9273,10 +10311,10 @@ Run \`elgora-cli help ${subcommand}\` for its arguments and options.
9273
10311
  process.exitCode = 1;
9274
10312
  return;
9275
10313
  }
9276
- const networkNotice = usesDeployment(command17.OPTIONS) ? defaultNetworkNotice(env) : void 0;
10314
+ const networkNotice = usesDeployment(command20.OPTIONS) ? defaultNetworkNotice(env) : void 0;
9277
10315
  if (networkNotice) process.stderr.write(`${networkNotice}
9278
10316
  `);
9279
- const needsLocalKey = "--signer" in command17.OPTIONS && (args.value("--signer") ?? "local") === "local";
10317
+ const needsLocalKey = "--signer" in command20.OPTIONS && (args.value("--signer") ?? "local") === "local";
9280
10318
  const missing = missingRequiredEnv(
9281
10319
  [
9282
10320
  ...COMMAND_HELP[subcommand]?.required ?? [],
@@ -9303,7 +10341,7 @@ Run \`elgora-cli help ${subcommand}\` for its arguments and options.
9303
10341
  process.exitCode = 1;
9304
10342
  return;
9305
10343
  }
9306
- await command17.main(env, rest);
10344
+ await command20.main(env, rest);
9307
10345
  }
9308
10346
  function usesDeployment(options) {
9309
10347
  return DEPLOYMENT_OPTIONS.some((option) => option in options);
@@ -9344,14 +10382,14 @@ function printUsage() {
9344
10382
  }
9345
10383
  function printCommandHelpOrUsage(name) {
9346
10384
  const help = name ? COMMAND_HELP[name] : void 0;
9347
- const command17 = name ? COMMANDS[name] : void 0;
9348
- if (!help || !command17) {
10385
+ const command20 = name ? COMMANDS[name] : void 0;
10386
+ if (!help || !command20) {
9349
10387
  printUsage();
9350
10388
  process.exitCode = name ? 1 : 0;
9351
10389
  return;
9352
10390
  }
9353
- const options = command17.OPTIONS;
9354
- const lines = [`Usage: ${command17.USAGE}`, "", help.description];
10391
+ const options = command20.OPTIONS;
10392
+ const lines = [`Usage: ${command20.USAGE}`, "", help.description];
9355
10393
  const described = Object.keys(options).filter(
9356
10394
  (option) => option in OPTION_HELP
9357
10395
  );
@@ -9440,9 +10478,9 @@ function extractIssues(error) {
9440
10478
  if (!Array.isArray(issues)) return [];
9441
10479
  return issues.map((issue) => {
9442
10480
  if (issue && typeof issue === "object" && "message" in issue) {
9443
- const path6 = "path" in issue ? issue.path : void 0;
10481
+ const path7 = "path" in issue ? issue.path : void 0;
9444
10482
  return {
9445
- path: typeof path6 === "string" ? path6 : "",
10483
+ path: typeof path7 === "string" ? path7 : "",
9446
10484
  message: String(issue.message)
9447
10485
  };
9448
10486
  }