@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.
- package/README.md +26 -6
- package/dist/cli.js +1744 -706
- 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
|
|
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
|
|
44
|
-
return base64ToBytes(
|
|
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
|
|
133
|
-
var
|
|
134
|
-
var
|
|
135
|
-
var
|
|
136
|
-
var
|
|
137
|
-
var
|
|
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
|
|
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
|
|
147
|
-
var
|
|
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 =
|
|
153
|
-
var lowerAddressSchema =
|
|
154
|
-
var bytes32Schema =
|
|
155
|
-
var sha256HexSchema =
|
|
156
|
-
var keyIdSchema =
|
|
157
|
-
var
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
).
|
|
161
|
-
var
|
|
162
|
-
|
|
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 =
|
|
247
|
+
var guardianRecipientSchema = z2.strictObject({
|
|
172
248
|
key_id: keyIdSchema,
|
|
173
|
-
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 =
|
|
255
|
+
var guardianRecipientPrivateKeySchema = z2.strictObject({
|
|
180
256
|
key_id: keyIdSchema,
|
|
181
|
-
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 =
|
|
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 =
|
|
201
|
-
var
|
|
202
|
-
name:
|
|
276
|
+
var guardianRecipientPrivateKeysSchema = z2.array(guardianRecipientPrivateKeySchema).min(1);
|
|
277
|
+
var bundleFileEntrySchema = z2.strictObject({
|
|
278
|
+
name: fileNameSchema,
|
|
203
279
|
media_type: mediaTypeSchema,
|
|
204
|
-
size_bytes:
|
|
280
|
+
size_bytes: z2.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER),
|
|
205
281
|
sha256: sha256HexSchema
|
|
206
282
|
});
|
|
207
|
-
var
|
|
208
|
-
kind:
|
|
209
|
-
context:
|
|
283
|
+
var submissionPublicManifestSchema = z2.strictObject({
|
|
284
|
+
kind: z2.literal(SUBMISSION_PUBLIC_MANIFEST_KIND),
|
|
285
|
+
context: submissionContextSchema
|
|
210
286
|
});
|
|
211
|
-
var
|
|
287
|
+
var encryptedBundleFileSchema = bundleFileEntrySchema.extend({
|
|
212
288
|
bytes: base64UrlSchema
|
|
213
289
|
});
|
|
214
|
-
var
|
|
215
|
-
kind:
|
|
216
|
-
context:
|
|
217
|
-
artifacts:
|
|
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
|
-
(
|
|
222
|
-
"
|
|
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
|
|
226
|
-
kind:
|
|
227
|
-
network:
|
|
228
|
-
chain_hash:
|
|
229
|
-
round:
|
|
230
|
-
not_before:
|
|
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 =
|
|
308
|
+
var wrappedRecipientKeySchema = z2.strictObject({
|
|
233
309
|
key_id: keyIdSchema,
|
|
234
|
-
suite:
|
|
310
|
+
suite: z2.literal(SUBMISSION_HPKE_SUITE),
|
|
235
311
|
enc: base64UrlSchema,
|
|
236
312
|
ciphertext: base64UrlSchema
|
|
237
313
|
});
|
|
238
|
-
var wrappedKeyBagSchema =
|
|
239
|
-
kind:
|
|
240
|
-
keys:
|
|
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
|
|
243
|
-
kind:
|
|
244
|
-
alg:
|
|
245
|
-
context:
|
|
246
|
-
public_manifest:
|
|
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:
|
|
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:
|
|
335
|
+
ciphertext_locator: z2.string().min(1)
|
|
260
336
|
});
|
|
261
|
-
function
|
|
262
|
-
return
|
|
337
|
+
function parseSubmissionEnvelope(input) {
|
|
338
|
+
return submissionEnvelopeSchema.parse(input);
|
|
263
339
|
}
|
|
264
|
-
function
|
|
340
|
+
function serializeSubmissionEnvelope(envelope) {
|
|
265
341
|
return canonicalizeJson(
|
|
266
|
-
|
|
342
|
+
submissionEnvelopeSchema.parse(envelope)
|
|
267
343
|
);
|
|
268
344
|
}
|
|
269
|
-
function
|
|
270
|
-
return keccak256(toBytes(
|
|
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
|
|
277
|
-
const envelope =
|
|
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 =
|
|
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 =
|
|
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
|
|
469
|
-
const context =
|
|
470
|
-
|
|
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
|
|
478
|
-
|
|
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
|
|
483
|
-
|
|
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(
|
|
563
|
+
const envelopeNonce = randomBytes(SUBMISSION_NONCE_BYTES);
|
|
500
564
|
const iv = randomBytes(AES_GCM_IV_BYTES);
|
|
501
565
|
const aadInput = {
|
|
502
|
-
kind:
|
|
503
|
-
alg
|
|
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:
|
|
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:
|
|
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 =
|
|
536
|
-
kind:
|
|
537
|
-
alg
|
|
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
|
|
554
|
-
const opened = await
|
|
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
|
|
562
|
-
const
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
658
|
+
...bundle,
|
|
597
659
|
rawAesKey: openResult.rawAesKey
|
|
598
660
|
};
|
|
599
661
|
}
|
|
600
|
-
async function
|
|
601
|
-
const envelope = await
|
|
662
|
+
async function openSubmissionEnvelopeWithKnownAesKey(input) {
|
|
663
|
+
const envelope = await verifySubmissionEnvelope(input.envelope);
|
|
602
664
|
await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
|
|
603
|
-
const
|
|
665
|
+
const bundle = await decryptAndVerifyBundle(envelope, input.ciphertext, {
|
|
604
666
|
rawAesKey: input.rawAesKey
|
|
605
667
|
});
|
|
606
|
-
return { envelope,
|
|
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:
|
|
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
|
-
|
|
631
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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("
|
|
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
|
-
`${
|
|
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
|
|
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
|
|
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
|
|
914
|
+
var SubmissionTimelockOpenError = class extends Error {
|
|
896
915
|
code;
|
|
897
916
|
constructor(code, message, options) {
|
|
898
917
|
super(message, options);
|
|
899
|
-
this.name = "
|
|
918
|
+
this.name = "SubmissionTimelockOpenError";
|
|
900
919
|
this.code = code;
|
|
901
920
|
}
|
|
902
921
|
};
|
|
903
|
-
function
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
966
|
+
assertSubmissionTimelockCiphertextMatchesPolicy({
|
|
948
967
|
ciphertext,
|
|
949
968
|
revealPolicy: reveal_policy
|
|
950
969
|
});
|
|
951
|
-
const releaseState =
|
|
970
|
+
const releaseState = getSubmissionTimelockReleaseState({
|
|
952
971
|
revealPolicy: reveal_policy,
|
|
953
972
|
nowSeconds: nowSeconds()
|
|
954
973
|
});
|
|
955
974
|
if (releaseState.status !== "released") {
|
|
956
|
-
throw new
|
|
975
|
+
throw new SubmissionTimelockOpenError(
|
|
957
976
|
"timelock_not_released",
|
|
958
|
-
`
|
|
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
|
|
986
|
+
if (error instanceof SubmissionTimelockOpenError) {
|
|
968
987
|
throw error;
|
|
969
988
|
}
|
|
970
|
-
throw new
|
|
989
|
+
throw new SubmissionTimelockOpenError(
|
|
971
990
|
"timelock_open_failed",
|
|
972
|
-
"
|
|
991
|
+
"Submission timelock ciphertext could not be opened",
|
|
973
992
|
{ cause: error }
|
|
974
993
|
);
|
|
975
994
|
}
|
|
976
995
|
};
|
|
977
996
|
}
|
|
978
|
-
function
|
|
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
|
|
1005
|
-
const
|
|
1006
|
-
if (
|
|
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 (
|
|
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 !==
|
|
1053
|
-
throw new Error("unsupported
|
|
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
|
|
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
|
|
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
|
|
1081
|
-
return safeDecodeUtf8(
|
|
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
|
|
1135
|
+
throw new SubmissionTimelockOpenError(
|
|
1117
1136
|
"timelock_beacon_unavailable",
|
|
1118
|
-
"drand beacon is unavailable for
|
|
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(
|
|
1189
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
|
1386
|
-
if (!
|
|
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
|
|
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
|
|
1565
|
+
if (profile !== void 0 && !isKnownProtocolProfile(profile)) {
|
|
1443
1566
|
issues.push({
|
|
1444
1567
|
path: "frontmatter.profile",
|
|
1445
|
-
message: `Must
|
|
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
|
|
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,
|
|
1598
|
+
function normalizeFrontmatterScalar(rawValue, path7, issues) {
|
|
1476
1599
|
const value = rawValue.trim();
|
|
1477
1600
|
if (value.length === 0) {
|
|
1478
|
-
issues.push({ path:
|
|
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:
|
|
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,
|
|
1615
|
+
function parsePositiveSafeIntegerFrontmatter(value, path7, issues) {
|
|
1493
1616
|
if (value === void 0) return null;
|
|
1494
1617
|
if (!isPositiveInteger(value)) {
|
|
1495
|
-
issues.push({ path:
|
|
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:
|
|
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,
|
|
1628
|
+
function parsePositiveIntegerStringFrontmatter(value, path7, issues) {
|
|
1506
1629
|
if (value === void 0) return null;
|
|
1507
1630
|
if (!isPositiveInteger(value)) {
|
|
1508
|
-
issues.push({ path:
|
|
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
|
|
1533
|
-
var addressSchema =
|
|
1534
|
-
var bytes32Schema2 =
|
|
1535
|
-
var hexBytesSchema =
|
|
1536
|
-
var bountyChallengeContentRecordSchema =
|
|
1537
|
-
content_kind:
|
|
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:
|
|
1662
|
+
byte_length: z4.number().int().positive(),
|
|
1540
1663
|
token_address: addressSchema,
|
|
1541
|
-
created_at:
|
|
1664
|
+
created_at: z4.string().datetime({ offset: true })
|
|
1542
1665
|
}).strict();
|
|
1543
|
-
var bountyPublicationResponseSchema =
|
|
1666
|
+
var bountyPublicationResponseSchema = z4.object({
|
|
1544
1667
|
content_record: bountyChallengeContentRecordSchema,
|
|
1545
|
-
create_bounty_transaction:
|
|
1546
|
-
chain_id:
|
|
1668
|
+
create_bounty_transaction: z4.object({
|
|
1669
|
+
chain_id: z4.number().int().positive(),
|
|
1547
1670
|
to: addressSchema,
|
|
1548
|
-
value:
|
|
1671
|
+
value: z4.literal("0"),
|
|
1549
1672
|
data: hexBytesSchema,
|
|
1550
|
-
function_name:
|
|
1551
|
-
args:
|
|
1673
|
+
function_name: z4.literal("create_bounty"),
|
|
1674
|
+
args: z4.object({
|
|
1552
1675
|
spec_commitment: bytes32Schema2,
|
|
1553
|
-
escrow_amount:
|
|
1554
|
-
submission_deadline:
|
|
1676
|
+
escrow_amount: z4.string().refine(isPositiveInteger),
|
|
1677
|
+
submission_deadline: z4.number().int().positive()
|
|
1555
1678
|
}).strict()
|
|
1556
1679
|
}).strict(),
|
|
1557
|
-
protocol_fee_policy:
|
|
1680
|
+
protocol_fee_policy: z4.object({
|
|
1558
1681
|
treasury_recipient: addressSchema,
|
|
1559
|
-
treasury_fee_bps:
|
|
1682
|
+
treasury_fee_bps: z4.number().int().min(0).max(1e4),
|
|
1560
1683
|
guardian_fee_recipient: addressSchema,
|
|
1561
|
-
guardian_fee_bps:
|
|
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 =
|
|
1573
|
-
issue_index:
|
|
1574
|
-
message:
|
|
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:
|
|
1700
|
+
status: z4.enum(["resolved", "open"])
|
|
1578
1701
|
}).strict();
|
|
1579
|
-
var readinessFindingSchema =
|
|
1580
|
-
path:
|
|
1581
|
-
message:
|
|
1582
|
-
code:
|
|
1583
|
-
anchor:
|
|
1584
|
-
hazard:
|
|
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 =
|
|
1710
|
+
var bountyReviewResponseSchema = z4.object({
|
|
1588
1711
|
content_record: bountyChallengeContentRecordSchema.omit({ created_at: true }),
|
|
1589
|
-
readiness:
|
|
1590
|
-
decision:
|
|
1712
|
+
readiness: z4.object({
|
|
1713
|
+
decision: z4.literal("ready"),
|
|
1591
1714
|
// Publication reuses the approval of these exact bytes until then.
|
|
1592
|
-
approved_until:
|
|
1715
|
+
approved_until: z4.string().datetime({ offset: true }),
|
|
1593
1716
|
// The dissenting pass's findings on a ready check; never blocking.
|
|
1594
|
-
warnings:
|
|
1595
|
-
prior_issues_resolved:
|
|
1596
|
-
prior_issues_open:
|
|
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
|
|
1684
|
-
if (parsed !== null &&
|
|
1685
|
-
checkTightenings(parsed.constraints,
|
|
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:
|
|
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
|
|
1707
|
-
if (
|
|
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
|
|
1774
|
+
message: `Unknown protocol profile "${declared}". A bounty can only declare a published profile version.`
|
|
1713
1775
|
});
|
|
1714
1776
|
}
|
|
1715
|
-
return
|
|
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
|
|
1898
|
-
if (!
|
|
1899
|
-
return readInput(
|
|
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
|
|
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 =
|
|
4405
|
-
ciphertext_sha256:
|
|
4406
|
-
ciphertext_byte_length:
|
|
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
|
|
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 =
|
|
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 =
|
|
4436
|
-
var publicDeploymentEnvSchema =
|
|
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:
|
|
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:
|
|
4457
|
-
ELGORA_RPC_URL:
|
|
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:
|
|
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:
|
|
4468
|
-
ELGORA_SUBGRAPH_ENDPOINT:
|
|
4469
|
-
ELGORA_ARTIFACT_STORAGE_ENDPOINT:
|
|
4470
|
-
ELGORA_ARTIFACT_STORAGE_REGION:
|
|
4471
|
-
ELGORA_ARTIFACT_STORAGE_BUCKET:
|
|
4472
|
-
ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID:
|
|
4473
|
-
ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY:
|
|
4474
|
-
ELGORA_DELIVERY_PUBLIC_KEY:
|
|
4475
|
-
ELGORA_DELIVERY_PRIVATE_KEY:
|
|
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:
|
|
4484
|
-
ELGORA_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 =
|
|
4487
|
-
var bountyPublicationEnvSchema =
|
|
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
|
|
4492
|
-
var hexAddressSchema =
|
|
4493
|
-
var hexBytes32Schema =
|
|
4494
|
-
var hexDataSchema =
|
|
4495
|
-
var artifactUploadUrlResponseSchema =
|
|
4496
|
-
locator:
|
|
4497
|
-
upload_url:
|
|
4498
|
-
method:
|
|
4499
|
-
expires_at:
|
|
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 =
|
|
4502
|
-
content_record:
|
|
4503
|
-
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:
|
|
4506
|
-
created_at:
|
|
4702
|
+
byte_length: z8.number().int().nonnegative(),
|
|
4703
|
+
created_at: z8.string().min(1)
|
|
4507
4704
|
}),
|
|
4508
|
-
submit_transaction:
|
|
4509
|
-
chain_id:
|
|
4705
|
+
submit_transaction: z8.object({
|
|
4706
|
+
chain_id: z8.number().int().positive(),
|
|
4510
4707
|
to: hexAddressSchema,
|
|
4511
|
-
value:
|
|
4708
|
+
value: z8.literal("0"),
|
|
4512
4709
|
data: hexDataSchema,
|
|
4513
|
-
function_name:
|
|
4514
|
-
args:
|
|
4515
|
-
bounty_id:
|
|
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:
|
|
4715
|
+
extra_data: z8.literal("0x")
|
|
4519
4716
|
})
|
|
4520
4717
|
})
|
|
4521
4718
|
});
|
|
4522
|
-
var solverSubmissionContentResponseSchema =
|
|
4523
|
-
content_record:
|
|
4524
|
-
bounty_id:
|
|
4525
|
-
canonical_json:
|
|
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:
|
|
4528
|
-
expires_at:
|
|
4724
|
+
ciphertext_download_url: z8.string().min(1),
|
|
4725
|
+
expires_at: z8.string().min(1)
|
|
4529
4726
|
});
|
|
4530
|
-
var
|
|
4531
|
-
bounty:
|
|
4727
|
+
var bountySubmissionDetailResponseSchema = z8.object({
|
|
4728
|
+
bounty: z8.object({
|
|
4532
4729
|
guardian_roster_hash: hexBytes32Schema,
|
|
4533
|
-
guardian_roster:
|
|
4534
|
-
|
|
4535
|
-
name:
|
|
4730
|
+
guardian_roster: z8.array(
|
|
4731
|
+
z8.object({
|
|
4732
|
+
name: z8.string(),
|
|
4536
4733
|
account: hexAddressSchema,
|
|
4537
|
-
encryption_public_key:
|
|
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
|
|
4545
|
-
var hexAddressSchema2 =
|
|
4546
|
-
var hexBytes32Schema2 =
|
|
4547
|
-
var integerStringSchema =
|
|
4548
|
-
var verdictEntrySchema =
|
|
4549
|
-
name:
|
|
4550
|
-
outcome:
|
|
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:
|
|
4554
|
-
supporting:
|
|
4751
|
+
committed_at: z9.string().datetime({ offset: true }).nullable(),
|
|
4752
|
+
supporting: z9.boolean()
|
|
4555
4753
|
}).strict();
|
|
4556
|
-
var formattedVerificationRecordSchema =
|
|
4557
|
-
chain_id:
|
|
4754
|
+
var formattedVerificationRecordSchema = z9.object({
|
|
4755
|
+
chain_id: z9.number().int().positive(),
|
|
4558
4756
|
hub_address: hexAddressSchema2,
|
|
4559
4757
|
bounty_id: integerStringSchema,
|
|
4560
|
-
status:
|
|
4758
|
+
status: z9.enum(["awarded", "no_valid_submission", "timed_out"]),
|
|
4561
4759
|
spec_commitment: hexBytes32Schema2,
|
|
4562
4760
|
winner: hexAddressSchema2.nullable(),
|
|
4563
|
-
verdicts:
|
|
4564
|
-
settlement:
|
|
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:
|
|
4576
|
-
spec_content_byte_length:
|
|
4773
|
+
content_record: z9.object({
|
|
4774
|
+
spec_content_byte_length: z9.number().int().positive()
|
|
4577
4775
|
}).strict(),
|
|
4578
|
-
verified_at:
|
|
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
|
|
4601
|
-
var hexBytes32Schema3 =
|
|
4602
|
-
var hexAddressSchema3 =
|
|
4603
|
-
var deliveryKeyIdSchema =
|
|
4604
|
-
var winningSolutionWrappedKeySchema =
|
|
4605
|
-
enc:
|
|
4606
|
-
ciphertext:
|
|
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 =
|
|
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 =
|
|
4614
|
-
bounty_id:
|
|
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:
|
|
4817
|
+
created_at: z10.string().min(1)
|
|
4620
4818
|
});
|
|
4621
|
-
var deliveryWrapResponseSchema =
|
|
4622
|
-
status:
|
|
4819
|
+
var deliveryWrapResponseSchema = z10.strictObject({
|
|
4820
|
+
status: z10.literal("stored"),
|
|
4623
4821
|
wrap: deliveryWrapRecordSchema
|
|
4624
4822
|
});
|
|
4625
|
-
var deliveryRetrieveRequestSchema =
|
|
4823
|
+
var deliveryRetrieveRequestSchema = z10.strictObject({
|
|
4626
4824
|
poster_ephemeral_public_key: x25519PublicKeySchema
|
|
4627
4825
|
});
|
|
4628
|
-
var deliveryRetrieveResponseSchema =
|
|
4826
|
+
var deliveryRetrieveResponseSchema = z10.strictObject({
|
|
4629
4827
|
submission_commitment: hexBytes32Schema3,
|
|
4630
4828
|
wrapped_key: winningSolutionWrappedKeySchema
|
|
4631
4829
|
});
|
|
4632
|
-
var deliveryStatusResponseSchema =
|
|
4633
|
-
exists:
|
|
4830
|
+
var deliveryStatusResponseSchema = z10.strictObject({
|
|
4831
|
+
exists: z10.boolean()
|
|
4634
4832
|
});
|
|
4635
|
-
var deliveryKeyConfigResponseSchema =
|
|
4833
|
+
var deliveryKeyConfigResponseSchema = z10.strictObject({
|
|
4636
4834
|
delivery_key_id: deliveryKeyIdSchema,
|
|
4637
|
-
public_key:
|
|
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
|
|
4840
|
+
import { z as z11 } from "zod";
|
|
4643
4841
|
var WRITTEN_VERDICT_FORMAT_VERSION = 0;
|
|
4644
4842
|
var FRONTMATTER_DELIMITER2 = "---";
|
|
4645
|
-
var decimalSchema =
|
|
4646
|
-
var bountyIdSchema2 =
|
|
4647
|
-
var addressSchema2 =
|
|
4648
|
-
var bytes32Schema3 =
|
|
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:
|
|
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:
|
|
4852
|
+
verdict_body: z11.string().refine((value) => value.trim().length > 0, "Must not be empty")
|
|
4655
4853
|
};
|
|
4656
|
-
var writtenVerdictSchema =
|
|
4657
|
-
|
|
4854
|
+
var writtenVerdictSchema = z11.discriminatedUnion("outcome", [
|
|
4855
|
+
z11.object({
|
|
4658
4856
|
...commonFields,
|
|
4659
|
-
outcome:
|
|
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
|
-
|
|
4867
|
+
z11.object({
|
|
4670
4868
|
...commonFields,
|
|
4671
|
-
outcome:
|
|
4672
|
-
winner:
|
|
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(
|
|
4952
|
+
function invalidWrittenVerdict(path7, message) {
|
|
4755
4953
|
return new BoundaryValidationError(
|
|
4756
4954
|
"Invalid written Verdict",
|
|
4757
|
-
[{ path:
|
|
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
|
|
4768
|
-
return { apiBaseUrl: resolveApiBaseUrl(env
|
|
4965
|
+
function loadApiClientConfig(env) {
|
|
4966
|
+
return { apiBaseUrl: resolveApiBaseUrl(env) };
|
|
4769
4967
|
}
|
|
4770
|
-
function loadClientDeploymentConfig(env
|
|
4968
|
+
function loadClientDeploymentConfig(env) {
|
|
4771
4969
|
return {
|
|
4772
4970
|
...loadPublicDeploymentConfig(env),
|
|
4773
|
-
...loadApiClientConfig(env
|
|
4971
|
+
...loadApiClientConfig(env)
|
|
4774
4972
|
};
|
|
4775
4973
|
}
|
|
4776
|
-
function resolveApiBaseUrl(env
|
|
4777
|
-
const value =
|
|
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
|
|
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
|
|
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,
|
|
5355
|
-
const url = `${PRIVY_API_URL}${
|
|
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} ${
|
|
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} ${
|
|
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
|
|
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 =
|
|
6116
|
-
error:
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
6949
|
+
const opened = await openSubmissionEnvelope({
|
|
6289
6950
|
envelope,
|
|
6290
6951
|
ciphertext,
|
|
6291
6952
|
guardian_private_keys: guardianPrivateKeys,
|
|
6292
|
-
timelock_decrypt:
|
|
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
|
-
|
|
6306
|
-
|
|
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 ??
|
|
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
|
-
|
|
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 [
|
|
7371
|
-
if (!
|
|
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(
|
|
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
|
|
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 &&
|
|
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 ? "
|
|
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
|
|
7856
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
const
|
|
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
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7960
|
-
|
|
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 [
|
|
8010
|
-
if (args.positionals.length !== 1 || !
|
|
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(
|
|
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
|
|
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
|
|
8594
|
-
import
|
|
8595
|
-
var
|
|
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
|
|
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
|
-
|
|
8608
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
9616
|
+
...input.plaintext,
|
|
8911
9617
|
guardian_recipients: guardianRecipients,
|
|
8912
|
-
reveal_policy:
|
|
9618
|
+
reveal_policy: buildSubmissionRevealPolicyForDeadline({
|
|
8913
9619
|
submissionDeadline: input.submissionDeadline,
|
|
8914
9620
|
revealDelaySeconds: input.config.timelockRevealDelaySeconds
|
|
8915
9621
|
}),
|
|
8916
|
-
timelock_encrypt: input.timelockEncrypt ??
|
|
9622
|
+
timelock_encrypt: input.timelockEncrypt ?? createSubmissionTimelockEncrypt()
|
|
8917
9623
|
};
|
|
8918
9624
|
if (input.randomBytes) {
|
|
8919
9625
|
envelopeInput.random_bytes = input.randomBytes;
|
|
8920
9626
|
}
|
|
8921
|
-
return
|
|
9627
|
+
return sealSubmissionEnvelope(envelopeInput);
|
|
8922
9628
|
}
|
|
8923
9629
|
function readSubmitArgs(argv) {
|
|
8924
|
-
const [bountyIdText,
|
|
8925
|
-
if (argv.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !
|
|
8926
|
-
throw usageError(
|
|
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
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
8939
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
9690
|
+
const foundBefore = found.length;
|
|
8947
9691
|
for (const entry of entries) {
|
|
8948
|
-
|
|
8949
|
-
|
|
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
|
-
`
|
|
9722
|
+
`The Submission holds more than ${maxFiles} files, Elgora's maximum per Submission.`
|
|
8952
9723
|
);
|
|
8953
9724
|
}
|
|
8954
9725
|
}
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
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 =
|
|
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/
|
|
9033
|
-
var
|
|
9034
|
-
|
|
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(
|
|
10102
|
+
throw usageError(USAGE19);
|
|
9089
10103
|
}
|
|
9090
10104
|
return { bountyId: BigInt(bountyId) };
|
|
9091
10105
|
}
|
|
9092
|
-
var
|
|
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 =
|
|
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:
|
|
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":
|
|
9123
|
-
"verification-record":
|
|
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: "
|
|
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: "
|
|
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
|
|
9199
|
-
description: "
|
|
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
|
|
9245
|
-
if (!
|
|
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,
|
|
9259
|
-
if ("--signer" in
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
9348
|
-
if (!help || !
|
|
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 =
|
|
9354
|
-
const lines = [`Usage: ${
|
|
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
|
|
10481
|
+
const path7 = "path" in issue ? issue.path : void 0;
|
|
9444
10482
|
return {
|
|
9445
|
-
path: typeof
|
|
10483
|
+
path: typeof path7 === "string" ? path7 : "",
|
|
9446
10484
|
message: String(issue.message)
|
|
9447
10485
|
};
|
|
9448
10486
|
}
|