@elgora/cli 7.2.0 → 7.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/dist/cli.js +575 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -35,8 +35,9 @@ npx @elgora/cli --help
35
35
  one exact Solver and Submission pair.
36
36
  - `guardian:verdict` publishes a written Verdict and records the same Verdict
37
37
  on ElgoraHub.
38
- - `guardian:deliver-key` repairs delivery for one exact Solver and Submission
39
- pair when ElgoraHub still permits it.
38
+ - `guardian:deliver-key` wraps one Solver–Submission content key for delivery
39
+ so the funding Poster can retrieve the winning work; idempotent, and accepted
40
+ only for the pair ElgoraHub currently records or the finalized winner.
40
41
  - `claim` prepares or sends the winning Solver's reserved award or the
41
42
  Poster's queued refund, as selected from finalized ElgoraHub state.
42
43
  - `verification-record` is a plain, unauthenticated GET that fetches or lazily
package/dist/cli.js CHANGED
@@ -196,7 +196,7 @@ function canonicalizeJson(value) {
196
196
  // ../../packages/core/src/submission-encryption.ts
197
197
  import { Aes256Gcm, CipherSuite, HkdfSha256 } from "@hpke/core";
198
198
  import { DhkemX25519HkdfSha256 } from "@hpke/dhkem-x25519";
199
- import { keccak256 as keccak2562, toBytes as toBytes2 } from "viem";
199
+ import { concatBytes, keccak256 as keccak2562, toBytes as toBytes2 } from "viem";
200
200
  import { z as z3 } from "zod";
201
201
 
202
202
  // ../../packages/core/src/base64.ts
@@ -474,6 +474,78 @@ function buildWinningSolutionDeliveryInfo(contextCommitment) {
474
474
  `${WINNING_SOLUTION_DELIVERY_HPKE_INFO_PREFIX}:${contextCommitment}`
475
475
  );
476
476
  }
477
+ var GUARDIAN_ROSTER_KEY_PROOF_INFO = "elgora/guardian-roster-key-proof/hpke-fixture/v0";
478
+ var GUARDIAN_ROSTER_KEY_PROOF_MESSAGE_TYPE = "elgora_guardian_roster_key_proof_v0";
479
+ var GUARDIAN_ROSTER_KEY_PROOF_FIXTURE_BYTES = 32;
480
+ var guardianRosterKeyProofContextSchema = z3.strictObject({
481
+ chain_id: chainIdSchema,
482
+ hub_address: lowerAddressSchema,
483
+ proposed_account: lowerAddressSchema,
484
+ proposed_encryption_public_key: x25519PublicKeySchema
485
+ });
486
+ async function prepareGuardianRosterKeyProof(input) {
487
+ const context = guardianRosterKeyProofContextSchema.parse({
488
+ ...input.context,
489
+ hub_address: input.context.hub_address.toLowerCase(),
490
+ proposed_account: input.context.proposed_account.toLowerCase()
491
+ });
492
+ const fixture = (input.random_bytes ?? secureRandomBytes)(
493
+ GUARDIAN_ROSTER_KEY_PROOF_FIXTURE_BYTES
494
+ );
495
+ const suite = createHpkeSuite();
496
+ const recipientPublicKey = await suite.kem.deserializePublicKey(
497
+ base64UrlToBytes(context.proposed_encryption_public_key)
498
+ );
499
+ const sealed = await suite.seal(
500
+ { recipientPublicKey, info: toBytes2(GUARDIAN_ROSTER_KEY_PROOF_INFO) },
501
+ fixture
502
+ );
503
+ const ciphertext = bytesToBase64Url(toUint8Array(sealed.ct));
504
+ const message = canonicalizeJson({
505
+ type: GUARDIAN_ROSTER_KEY_PROOF_MESSAGE_TYPE,
506
+ chain_id: context.chain_id,
507
+ hub_address: context.hub_address,
508
+ proposed_account: context.proposed_account,
509
+ proposed_encryption_public_key: context.proposed_encryption_public_key,
510
+ ciphertext_sha256: await computeSha256Hex(toUint8Array(sealed.ct))
511
+ });
512
+ return {
513
+ challenge: {
514
+ context,
515
+ message,
516
+ enc: bytesToBase64Url(toUint8Array(sealed.enc)),
517
+ ciphertext
518
+ },
519
+ fixture
520
+ };
521
+ }
522
+ async function openGuardianRosterKeyProofFixture(input) {
523
+ const suite = createHpkeSuite();
524
+ const info = toBytes2(GUARDIAN_ROSTER_KEY_PROOF_INFO);
525
+ const enc = base64UrlToBytes(input.challenge.enc);
526
+ const ciphertext = base64UrlToBytes(input.challenge.ciphertext);
527
+ for (const privateKey of input.recipientPrivateKeys) {
528
+ try {
529
+ const recipientKey = await suite.kem.deserializePrivateKey(
530
+ base64UrlToBytes(x25519PrivateKeySchema.parse(privateKey))
531
+ );
532
+ const fixture = await suite.open({ recipientKey, enc, info }, ciphertext);
533
+ return toUint8Array(fixture);
534
+ } catch {
535
+ }
536
+ }
537
+ throw new Error(
538
+ "none of the supplied private keys could open this guardian roster key proof fixture"
539
+ );
540
+ }
541
+ function buildGuardianRosterKeyProofSignedPayload(message, fixture) {
542
+ if (fixture.length !== GUARDIAN_ROSTER_KEY_PROOF_FIXTURE_BYTES) {
543
+ throw new Error(
544
+ `guardian roster key proof fixture must be ${GUARDIAN_ROSTER_KEY_PROOF_FIXTURE_BYTES} bytes`
545
+ );
546
+ }
547
+ return concatBytes([toBytes2(message), fixture]);
548
+ }
477
549
  async function sealPrivateArtifactEnvelope(input) {
478
550
  const context = privateArtifactContextSchema.parse(
479
551
  normalizeContext(input.context)
@@ -3424,6 +3496,11 @@ var ELGORA_HUB_BOUNTY_READ_ABI = [
3424
3496
  getAbiItem2({ abi: ELGORA_HUB_ABI, name: "activeSubmissionCommitment" }),
3425
3497
  ...ELGORA_HUB_ERRORS
3426
3498
  ];
3499
+ var ELGORA_HUB_OWNERSHIP_READ_ABI = [
3500
+ getAbiItem2({ abi: ELGORA_HUB_ABI, name: "owner" }),
3501
+ getAbiItem2({ abi: ELGORA_HUB_ABI, name: "pendingOwner" }),
3502
+ ...ELGORA_HUB_ERRORS
3503
+ ];
3427
3504
  var ELGORA_HUB_BOUNTY_CREATED_EVENT = getAbiItem2({
3428
3505
  abi: ELGORA_HUB_ABI,
3429
3506
  name: "BountyCreated"
@@ -3453,6 +3530,30 @@ var ELGORA_HUB_CLAIM_ABI = [
3453
3530
  getAbiItem2({ abi: ELGORA_HUB_ABI, name: "claim" }),
3454
3531
  ...ELGORA_HUB_ERRORS
3455
3532
  ];
3533
+ var ELGORA_HUB_ADD_GUARDIAN_ABI = [
3534
+ getAbiItem2({ abi: ELGORA_HUB_ABI, name: "guardianRosterAdmin" }),
3535
+ getAbiItem2({ abi: ELGORA_HUB_ABI, name: "getGuardianRoster" }),
3536
+ getAbiItem2({ abi: ELGORA_HUB_ABI, name: "addGuardian" }),
3537
+ ...ELGORA_HUB_ERRORS
3538
+ ];
3539
+ function encodeAddGuardianTransaction(input) {
3540
+ return {
3541
+ chain_id: input.chainId,
3542
+ to: input.hubAddress,
3543
+ value: "0",
3544
+ data: encodeFunctionData({
3545
+ abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
3546
+ functionName: "addGuardian",
3547
+ args: [input.name, input.account, input.encryptionPublicKey]
3548
+ }),
3549
+ function_name: "add_guardian",
3550
+ args: {
3551
+ name: input.name,
3552
+ account: input.account,
3553
+ encryption_public_key: input.encryptionPublicKey
3554
+ }
3555
+ };
3556
+ }
3456
3557
  function encodeSubmitTransaction(input) {
3457
3558
  return {
3458
3559
  chain_id: input.chainId,
@@ -3496,6 +3597,7 @@ var artifactUploadUrlRequestSchema = z4.object({
3496
3597
  }).strict();
3497
3598
 
3498
3599
  // ../../packages/core/src/api/config.ts
3600
+ import { createHash } from "crypto";
3499
3601
  import { z as z5 } from "zod";
3500
3602
  function withDeploymentEnvDefaults(env) {
3501
3603
  const merged = { ...env };
@@ -3559,7 +3661,6 @@ var envShape = {
3559
3661
  ELGORA_ARTIFACT_STORAGE_BUCKET: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_BUCKET is required"),
3560
3662
  ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID is required"),
3561
3663
  ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY is required"),
3562
- ELGORA_DELIVERY_KEY_ID: z5.string().min(1, "ELGORA_DELIVERY_KEY_ID is required").default("delivery_v0"),
3563
3664
  ELGORA_DELIVERY_PUBLIC_KEY: z5.string().min(1, "ELGORA_DELIVERY_PUBLIC_KEY is required"),
3564
3665
  ELGORA_DELIVERY_PRIVATE_KEY: z5.string().min(1, "ELGORA_DELIVERY_PRIVATE_KEY is required")
3565
3666
  };
@@ -3588,23 +3689,25 @@ var ELGORA_HUB_FEE_POLICY_ABI = [
3588
3689
  // ../../packages/core/src/api/orchestrator-readiness.ts
3589
3690
  import { z as z6 } from "zod";
3590
3691
  var MAX_ORCHESTRATOR_ISSUES = 20;
3591
- var boundedText = (maxLength) => z6.string().trim().min(1).transform((value) => value.slice(0, maxLength));
3692
+ var boundedText = (maxLength) => z6.string().trim().min(1).max(maxLength);
3592
3693
  var readinessIssueSchema = z6.object({
3593
3694
  path: boundedText(120),
3594
3695
  message: boundedText(500)
3595
3696
  }).strict();
3596
3697
  var readinessResponseSchema = z6.object({
3597
3698
  judgeable: z6.boolean(),
3598
- issues: z6.array(readinessIssueSchema).transform((issues) => issues.slice(0, MAX_ORCHESTRATOR_ISSUES))
3699
+ issues: z6.array(readinessIssueSchema).max(MAX_ORCHESTRATOR_ISSUES)
3599
3700
  }).strict();
3600
- var anthropicMessageSchema = z6.object({
3601
- content: z6.array(
3701
+ var providerMessageSchema = z6.object({
3702
+ id: z6.string().optional(),
3703
+ choices: z6.array(
3602
3704
  z6.object({
3603
- type: z6.string(),
3604
- text: z6.string().optional()
3705
+ message: z6.object({
3706
+ content: z6.string().nullable().optional()
3707
+ }).passthrough(),
3708
+ finish_reason: z6.string().nullable().optional()
3605
3709
  }).passthrough()
3606
- ),
3607
- stop_reason: z6.string().nullable().optional()
3710
+ ).min(1)
3608
3711
  }).passthrough();
3609
3712
 
3610
3713
  // ../../packages/core/src/api/solver-submission-responses.ts
@@ -4862,6 +4965,7 @@ var SUBMISSION_FIELDS = `
4862
4965
  recordedAt
4863
4966
  recordedBlockNumber
4864
4967
  recordedTxHash
4968
+ submissionCallCount
4865
4969
  `;
4866
4970
  var VERDICT_FIELDS = `
4867
4971
  guardian
@@ -5343,7 +5447,11 @@ function mapSubmissionFields(row) {
5343
5447
  record.recordedBlockNumber,
5344
5448
  "recordedBlockNumber"
5345
5449
  ),
5346
- recordedTxHash: asHex(record.recordedTxHash, "recordedTxHash")
5450
+ recordedTxHash: asHex(record.recordedTxHash, "recordedTxHash"),
5451
+ submissionCallCount: asNumber(
5452
+ record.submissionCallCount,
5453
+ "submissionCallCount"
5454
+ )
5347
5455
  };
5348
5456
  }
5349
5457
  function mapGuardianRosterMember(row) {
@@ -5548,7 +5656,10 @@ async function main4(env = process.env, argv = process.argv.slice(2)) {
5548
5656
  },
5549
5657
  submissions: submissions.map((submission) => ({
5550
5658
  solver: submission.solver,
5551
- submission_commitment: submission.submissionCommitment
5659
+ submission_commitment: submission.submissionCommitment,
5660
+ submitted_at: submission.recordedAt,
5661
+ submitted_at_block: submission.recordedBlockNumber,
5662
+ submission_call_count: submission.submissionCallCount
5552
5663
  }))
5553
5664
  });
5554
5665
  }
@@ -5596,10 +5707,88 @@ function requiredUrl(env, name) {
5596
5707
  }
5597
5708
  }
5598
5709
 
5599
- // src/guardian-verdict.ts
5710
+ // src/guardian-prove-roster-key.ts
5600
5711
  import { readFile } from "fs/promises";
5601
- import { zeroAddress as zeroAddress4 } from "viem";
5712
+ import { text as readStdinText } from "stream/consumers";
5602
5713
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
5714
+ async function main5(env = process.env, argv = process.argv.slice(2)) {
5715
+ const [path5] = argv;
5716
+ if (!path5) {
5717
+ throw new Error(
5718
+ "Usage: elgora-cli guardian:prove-roster-key <challenge_json_path|->"
5719
+ );
5720
+ }
5721
+ const challenge = parseChallenge(
5722
+ path5 === "-" ? await readStdinText(process.stdin) : await readFile(path5, "utf8")
5723
+ );
5724
+ const envWithDefaults = withDeploymentDefaults(env);
5725
+ const { chainId, hubAddress } = loadPublicDeploymentConfig(envWithDefaults);
5726
+ if (challenge.context.chain_id !== chainId || challenge.context.hub_address.toLowerCase() !== hubAddress.toLowerCase()) {
5727
+ throw new Error(
5728
+ `Challenge names chain ${challenge.context.chain_id} / Hub ${challenge.context.hub_address}, not this deployment's chain ${chainId} / Hub ${hubAddress}`
5729
+ );
5730
+ }
5731
+ const guardianAccountPrivateKey = envWithDefaults.ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY?.trim().toLowerCase();
5732
+ if (!guardianAccountPrivateKey || !isHexBytes32(guardianAccountPrivateKey)) {
5733
+ throw new Error(
5734
+ "ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY must be a 32-byte hex private key"
5735
+ );
5736
+ }
5737
+ const account = privateKeyToAccount5(guardianAccountPrivateKey);
5738
+ if (account.address.toLowerCase() !== challenge.context.proposed_account) {
5739
+ throw new Error(
5740
+ `ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY resolves to ${account.address.toLowerCase()}, but this challenge names proposed_account ${challenge.context.proposed_account}`
5741
+ );
5742
+ }
5743
+ const guardianPrivateKeys = readGuardianPrivateKeys(env).map(
5744
+ (key) => key.private_key
5745
+ );
5746
+ const fixture = await openGuardianRosterKeyProofFixture({
5747
+ challenge,
5748
+ recipientPrivateKeys: guardianPrivateKeys
5749
+ });
5750
+ const signedPayload = buildGuardianRosterKeyProofSignedPayload(
5751
+ challenge.message,
5752
+ fixture
5753
+ );
5754
+ const signature = await account.signMessage({
5755
+ message: { raw: signedPayload }
5756
+ });
5757
+ console.log(
5758
+ JSON.stringify({
5759
+ event: "guardian_prove_roster_key.signed",
5760
+ guardian_account: account.address.toLowerCase(),
5761
+ proposed_encryption_public_key: challenge.context.proposed_encryption_public_key,
5762
+ signature
5763
+ })
5764
+ );
5765
+ }
5766
+ function parseChallenge(text) {
5767
+ let parsed;
5768
+ try {
5769
+ parsed = JSON.parse(text);
5770
+ } catch (error) {
5771
+ throw new Error("Challenge input must be valid JSON", { cause: error });
5772
+ }
5773
+ const challenge = parsed && typeof parsed === "object" && "send_to_guardian" in parsed ? parsed.send_to_guardian : parsed;
5774
+ if (!challenge || typeof challenge !== "object" || typeof challenge.message !== "string" || typeof challenge.enc !== "string" || typeof challenge.ciphertext !== "string") {
5775
+ throw new Error(
5776
+ "Challenge JSON must contain context, message, enc, and ciphertext (the object roster:prepare-guardian-proof printed as send_to_guardian)"
5777
+ );
5778
+ }
5779
+ const context = challenge.context;
5780
+ if (!context || typeof context !== "object" || typeof context.chain_id !== "number" || typeof context.hub_address !== "string" || typeof context.proposed_account !== "string" || typeof context.proposed_encryption_public_key !== "string") {
5781
+ throw new Error(
5782
+ "Challenge context must contain chain_id, hub_address, proposed_account, and proposed_encryption_public_key"
5783
+ );
5784
+ }
5785
+ return challenge;
5786
+ }
5787
+
5788
+ // src/guardian-verdict.ts
5789
+ import { readFile as readFile2 } from "fs/promises";
5790
+ import { zeroAddress as zeroAddress4 } from "viem";
5791
+ import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
5603
5792
  var OUTCOME = {
5604
5793
  no_valid_submission: 0,
5605
5794
  awarded: 1
@@ -5634,7 +5823,7 @@ function parseVerdictArgs(argv) {
5634
5823
  }
5635
5824
  throw usageError();
5636
5825
  }
5637
- async function main5(env = process.env, argv = process.argv.slice(2)) {
5826
+ async function main6(env = process.env, argv = process.argv.slice(2)) {
5638
5827
  const { apiBaseUrl: apiBaseUrlOverride, argv: positionalArgs } = extractApiBaseUrlFlag(argv);
5639
5828
  const args = parseVerdictArgs(positionalArgs);
5640
5829
  const envWithDefaults = withDeploymentDefaults(env);
@@ -5650,11 +5839,11 @@ async function main5(env = process.env, argv = process.argv.slice(2)) {
5650
5839
  "ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY must be a 32-byte hex private key"
5651
5840
  );
5652
5841
  }
5653
- const account = privateKeyToAccount5(guardianPrivateKey.toLowerCase());
5842
+ const account = privateKeyToAccount6(guardianPrivateKey.toLowerCase());
5654
5843
  const publicClient = createChainPublicClient(config.chainId, rpcUrl);
5655
5844
  const walletClient = createChainWalletClient(config.chainId, rpcUrl, account);
5656
5845
  await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
5657
- const verdictBody = await readFile(args.verdictBodyPath, "utf8");
5846
+ const verdictBody = await readFile2(args.verdictBodyPath, "utf8");
5658
5847
  const markdown = args.outcome === "awarded" ? buildWrittenVerdictMarkdown({
5659
5848
  config,
5660
5849
  bountyId: args.bountyId,
@@ -5921,9 +6110,9 @@ function resolveChainId(selector) {
5921
6110
  // src/poster-open-winning-submission.ts
5922
6111
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
5923
6112
  import path2 from "path";
5924
- import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
6113
+ import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
5925
6114
  var DEFAULT_OUTPUT_ROOT2 = "./winning-submission";
5926
- async function main6(env = process.env, argv = process.argv.slice(2)) {
6115
+ async function main7(env = process.env, argv = process.argv.slice(2)) {
5927
6116
  const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
5928
6117
  const args = readPosterOpenWinningSubmissionArgs(remainingArgv);
5929
6118
  const envWithDefaults = withDeploymentDefaults(env);
@@ -5938,7 +6127,7 @@ async function main6(env = process.env, argv = process.argv.slice(2)) {
5938
6127
  "ELGORA_POSTER_PRIVATE_KEY must be a 32-byte hex private key"
5939
6128
  );
5940
6129
  }
5941
- const account = privateKeyToAccount6(posterPrivateKey.toLowerCase());
6130
+ const account = privateKeyToAccount7(posterPrivateKey.toLowerCase());
5942
6131
  const publicClient = createChainPublicClient(config.chainId, rpcUrl);
5943
6132
  await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
5944
6133
  const ephemeral = await generateX25519KeyPair();
@@ -6062,26 +6251,26 @@ function required3(env, name) {
6062
6251
  }
6063
6252
 
6064
6253
  // src/publish-fund.ts
6065
- import { readFile as readFile2 } from "fs/promises";
6066
- import { text as readStdinText } from "stream/consumers";
6254
+ import { readFile as readFile3 } from "fs/promises";
6255
+ import { text as readStdinText2 } from "stream/consumers";
6067
6256
  import {
6068
6257
  parseAbi,
6069
6258
  parseEventLogs
6070
6259
  } from "viem";
6071
- import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
6260
+ import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
6072
6261
  var PUBLISH_FUND_USAGE = "Usage: elgora-cli poster:publish-fund <bounty_challenge_md_path>\n or: elgora-cli poster:publish-fund -";
6073
6262
  var usdcAbi = parseAbi([
6074
6263
  "function approve(address spender,uint256 value) returns (bool)",
6075
6264
  "function balanceOf(address account) view returns (uint256)"
6076
6265
  ]);
6077
- async function main7(env = process.env, argv = process.argv.slice(2)) {
6266
+ async function main8(env = process.env, argv = process.argv.slice(2)) {
6078
6267
  const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
6079
6268
  const config = loadPublishFundConfig(
6080
6269
  withDeploymentDefaults(env),
6081
6270
  apiBaseUrlOverride
6082
6271
  );
6083
6272
  const bountyChallengeMarkdown = await readBountyChallengeMarkdown(remainingArgv);
6084
- const account = privateKeyToAccount7(config.posterPrivateKey);
6273
+ const account = privateKeyToAccount8(config.posterPrivateKey);
6085
6274
  const publicClient = createChainPublicClient(config.chainId, config.rpcUrl);
6086
6275
  const walletClient = createChainWalletClient(
6087
6276
  config.chainId,
@@ -6238,9 +6427,9 @@ async function readBountyChallengeMarkdown(argv) {
6238
6427
  throw new Error(PUBLISH_FUND_USAGE);
6239
6428
  }
6240
6429
  if (path5 === "-") {
6241
- return readStdinText(process.stdin);
6430
+ return readStdinText2(process.stdin);
6242
6431
  }
6243
- return readFile2(path5, "utf8");
6432
+ return readFile3(path5, "utf8");
6244
6433
  }
6245
6434
  function loadPublishFundConfig(env, apiBaseUrlOverride) {
6246
6435
  const clientConfig = loadClientDeploymentConfig(env, apiBaseUrlOverride);
@@ -6268,11 +6457,311 @@ function loadPublishFundConfig(env, apiBaseUrlOverride) {
6268
6457
  };
6269
6458
  }
6270
6459
 
6460
+ // src/roster-add-guardian.ts
6461
+ import { isAddress as isAddress4 } from "viem";
6462
+ import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
6463
+ var ROSTER_ADMIN_ADDRESS_FLAG = "--roster-admin-address";
6464
+ var ENCRYPTION_PUBLIC_KEY_RE = /^[A-Za-z0-9_-]{43}$/;
6465
+ async function main9(env = process.env, argv = process.argv.slice(2)) {
6466
+ const args = parseRosterAddGuardianArgs(argv);
6467
+ const config = loadRosterAddGuardianConfig(env, args.rosterAdminAddress);
6468
+ const account = config.rosterAdminPrivateKey ? privateKeyToAccount9(config.rosterAdminPrivateKey) : null;
6469
+ const rosterAdmin = account?.address ?? args.rosterAdminAddress;
6470
+ if (!rosterAdmin) throw new Error("Roster admin address is required");
6471
+ const normalizedRosterAdmin = rosterAdmin.toLowerCase();
6472
+ const publicClient = createChainPublicClient(config.chainId, config.rpcUrl);
6473
+ await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
6474
+ const liveRosterAdmin = await publicClient.readContract({
6475
+ address: config.hubAddress,
6476
+ abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
6477
+ functionName: "guardianRosterAdmin"
6478
+ });
6479
+ if (liveRosterAdmin.toLowerCase() !== normalizedRosterAdmin) {
6480
+ throw new Error(
6481
+ `${normalizedRosterAdmin} is not the live guardianRosterAdmin (${liveRosterAdmin.toLowerCase()})`
6482
+ );
6483
+ }
6484
+ const roster = await publicClient.readContract({
6485
+ address: config.hubAddress,
6486
+ abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
6487
+ functionName: "getGuardianRoster"
6488
+ });
6489
+ if (roster.some(
6490
+ (guardian) => guardian.account.toLowerCase() === args.guardianAccount
6491
+ )) {
6492
+ throw new Error(
6493
+ `${args.guardianAccount} is already on the Guardian roster`
6494
+ );
6495
+ }
6496
+ const contractArgs = [
6497
+ args.name,
6498
+ args.guardianAccount,
6499
+ args.encryptionPublicKey
6500
+ ];
6501
+ if (!account) {
6502
+ await publicClient.simulateContract({
6503
+ account: normalizedRosterAdmin,
6504
+ address: config.hubAddress,
6505
+ abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
6506
+ functionName: "addGuardian",
6507
+ args: contractArgs
6508
+ });
6509
+ const prepared = encodeAddGuardianTransaction({
6510
+ chainId: config.chainId,
6511
+ hubAddress: config.hubAddress,
6512
+ name: args.name,
6513
+ account: args.guardianAccount,
6514
+ encryptionPublicKey: args.encryptionPublicKey
6515
+ });
6516
+ console.log(
6517
+ JSON.stringify({
6518
+ event: "roster_add_guardian.transaction_prepared",
6519
+ ...prepared
6520
+ })
6521
+ );
6522
+ return;
6523
+ }
6524
+ const walletClient = createChainWalletClient(
6525
+ config.chainId,
6526
+ config.rpcUrl,
6527
+ account
6528
+ );
6529
+ const ethBalance = await publicClient.getBalance({
6530
+ address: account.address
6531
+ });
6532
+ if (ethBalance === 0n) {
6533
+ throw new Error(
6534
+ `Roster admin has no gas token balance: ${account.address}`
6535
+ );
6536
+ }
6537
+ await publicClient.simulateContract({
6538
+ account: account.address,
6539
+ address: config.hubAddress,
6540
+ abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
6541
+ functionName: "addGuardian",
6542
+ args: contractArgs
6543
+ });
6544
+ const transactionHash = await walletClient.writeContract({
6545
+ address: config.hubAddress,
6546
+ abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
6547
+ functionName: "addGuardian",
6548
+ args: contractArgs
6549
+ });
6550
+ const receipt = await publicClient.waitForTransactionReceipt({
6551
+ hash: transactionHash
6552
+ });
6553
+ if (receipt.status !== "success") {
6554
+ throw new Error(`addGuardian transaction reverted: ${transactionHash}`);
6555
+ }
6556
+ console.log(
6557
+ JSON.stringify({
6558
+ event: "roster_add_guardian.recorded",
6559
+ tx_hash: transactionHash,
6560
+ block_number: receipt.blockNumber.toString(),
6561
+ name: args.name,
6562
+ account: args.guardianAccount,
6563
+ encryption_public_key: args.encryptionPublicKey
6564
+ })
6565
+ );
6566
+ }
6567
+ function parseRosterAddGuardianArgs(argv) {
6568
+ const usesRosterAdminAddress = argv[0] === ROSTER_ADMIN_ADDRESS_FLAG;
6569
+ const rosterAdminAddress = usesRosterAdminAddress ? argv[1]?.trim() : void 0;
6570
+ if (usesRosterAdminAddress && (!rosterAdminAddress || !isAddress4(rosterAdminAddress))) {
6571
+ throw usageError2();
6572
+ }
6573
+ const positional = usesRosterAdminAddress ? argv.slice(2) : argv;
6574
+ if (positional.length !== 3) throw usageError2();
6575
+ const name = positional[0]?.trim();
6576
+ const guardianAccount = positional[1]?.trim();
6577
+ const encryptionPublicKey = positional[2]?.trim();
6578
+ if (!name) throw usageError2();
6579
+ if (!guardianAccount || !isAddress4(guardianAccount)) {
6580
+ throw new Error("guardian_account must be a valid EVM address");
6581
+ }
6582
+ if (!encryptionPublicKey || !ENCRYPTION_PUBLIC_KEY_RE.test(encryptionPublicKey)) {
6583
+ throw new Error(
6584
+ "encryption_public_key must be an unpadded base64url X25519 public key (43 characters)"
6585
+ );
6586
+ }
6587
+ return {
6588
+ name,
6589
+ guardianAccount: guardianAccount.toLowerCase(),
6590
+ encryptionPublicKey,
6591
+ ...rosterAdminAddress ? { rosterAdminAddress: rosterAdminAddress.toLowerCase() } : {}
6592
+ };
6593
+ }
6594
+ function loadRosterAddGuardianConfig(env, rosterAdminAddress) {
6595
+ const envWithDefaults = withDeploymentDefaults(env);
6596
+ const publicDeploymentConfig = loadPublicDeploymentConfig(envWithDefaults);
6597
+ const rpcUrl = envWithDefaults.ELGORA_RPC_URL?.trim();
6598
+ if (!rpcUrl) {
6599
+ throw new Error("ELGORA_RPC_URL is required");
6600
+ }
6601
+ const rosterAdminPrivateKey = envWithDefaults.ELGORA_GUARDIAN_ROSTER_ADMIN_PRIVATE_KEY?.trim();
6602
+ if (!rosterAdminAddress && (!rosterAdminPrivateKey || !isHexBytes32(rosterAdminPrivateKey))) {
6603
+ throw new Error(
6604
+ "ELGORA_GUARDIAN_ROSTER_ADMIN_PRIVATE_KEY must be a 32-byte hex private key unless --roster-admin-address is provided"
6605
+ );
6606
+ }
6607
+ return {
6608
+ ...publicDeploymentConfig,
6609
+ rpcUrl,
6610
+ rosterAdminPrivateKey: rosterAdminAddress ? null : rosterAdminPrivateKey?.toLowerCase()
6611
+ };
6612
+ }
6613
+ function usageError2() {
6614
+ return new Error(
6615
+ "Usage: elgora-cli roster:add-guardian [--roster-admin-address <address>] <name> <guardian_account> <encryption_public_key>"
6616
+ );
6617
+ }
6618
+
6619
+ // src/roster-prepare-guardian-proof.ts
6620
+ import { isAddress as isAddress5 } from "viem";
6621
+ var ENCRYPTION_PUBLIC_KEY_RE2 = /^[A-Za-z0-9_-]{43}$/;
6622
+ async function main10(env = process.env, argv = process.argv.slice(2)) {
6623
+ const args = parseArgs(argv);
6624
+ const envWithDefaults = withDeploymentDefaults(env);
6625
+ const { chainId, hubAddress } = loadPublicDeploymentConfig(envWithDefaults);
6626
+ const { challenge, fixture } = await prepareGuardianRosterKeyProof({
6627
+ context: {
6628
+ chain_id: chainId,
6629
+ hub_address: hubAddress,
6630
+ proposed_account: args.guardianAccount,
6631
+ proposed_encryption_public_key: args.encryptionPublicKey
6632
+ }
6633
+ });
6634
+ console.log(
6635
+ JSON.stringify({
6636
+ event: "roster_guardian_proof.challenge_prepared",
6637
+ send_to_guardian: challenge,
6638
+ keep_local_do_not_share: {
6639
+ fixture: bytesToBase64Url(fixture),
6640
+ note: "Needed for roster:verify-guardian-proof once the Guardian replies. Never send this to the Guardian \u2014 sending it defeats the proof."
6641
+ }
6642
+ })
6643
+ );
6644
+ }
6645
+ function parseArgs(argv) {
6646
+ if (argv.length !== 2) throw usageError3();
6647
+ const guardianAccount = argv[0]?.trim();
6648
+ const encryptionPublicKey = argv[1]?.trim();
6649
+ if (!guardianAccount || !isAddress5(guardianAccount)) {
6650
+ throw new Error("guardian_account must be a valid EVM address");
6651
+ }
6652
+ if (!encryptionPublicKey || !ENCRYPTION_PUBLIC_KEY_RE2.test(encryptionPublicKey)) {
6653
+ throw new Error(
6654
+ "encryption_public_key must be an unpadded base64url X25519 public key (43 characters)"
6655
+ );
6656
+ }
6657
+ return {
6658
+ guardianAccount: guardianAccount.toLowerCase(),
6659
+ encryptionPublicKey
6660
+ };
6661
+ }
6662
+ function usageError3() {
6663
+ return new Error(
6664
+ "Usage: elgora-cli roster:prepare-guardian-proof <guardian_account> <encryption_public_key>"
6665
+ );
6666
+ }
6667
+
6668
+ // src/roster-verify-guardian-proof.ts
6669
+ import { readFile as readFile4 } from "fs/promises";
6670
+ import { isAddress as isAddress6, isHex as isHex5, recoverMessageAddress } from "viem";
6671
+ async function main11(_env = process.env, argv = process.argv.slice(2)) {
6672
+ const [preparedPath, responsePath] = argv;
6673
+ if (!preparedPath || !responsePath) {
6674
+ throw new Error(
6675
+ "Usage: elgora-cli roster:verify-guardian-proof <prepared_json_path> <response_json_path>"
6676
+ );
6677
+ }
6678
+ const prepared = parsePrepared(await readFile4(preparedPath, "utf8"));
6679
+ const response = parseResponse(await readFile4(responsePath, "utf8"));
6680
+ const fixture = base64UrlToBytes(prepared.fixture);
6681
+ const signedPayload = buildGuardianRosterKeyProofSignedPayload(
6682
+ prepared.message,
6683
+ fixture
6684
+ );
6685
+ const recoveredAddress = (await recoverMessageAddress({
6686
+ message: { raw: signedPayload },
6687
+ signature: response.signature
6688
+ })).toLowerCase();
6689
+ const proposedAccount = prepared.proposedAccount;
6690
+ const succeeded = recoveredAddress === proposedAccount && response.guardianAccount === proposedAccount;
6691
+ console.log(
6692
+ JSON.stringify({
6693
+ event: succeeded ? "roster_guardian_proof.verified" : "roster_guardian_proof.failed",
6694
+ proposed_account: proposedAccount,
6695
+ recovered_signer: recoveredAddress,
6696
+ response_guardian_account: response.guardianAccount,
6697
+ succeeded
6698
+ })
6699
+ );
6700
+ if (!succeeded) {
6701
+ throw new Error(
6702
+ `Guardian roster key proof FAILED for ${proposedAccount}: recovered signer ${recoveredAddress} does not match`
6703
+ );
6704
+ }
6705
+ }
6706
+ function parsePrepared(text) {
6707
+ let parsed;
6708
+ try {
6709
+ parsed = JSON.parse(text);
6710
+ } catch (error) {
6711
+ throw new Error("Prepared proof JSON must be valid JSON", {
6712
+ cause: error
6713
+ });
6714
+ }
6715
+ const sendToGuardian = get(parsed, "send_to_guardian");
6716
+ const message = get(sendToGuardian, "message");
6717
+ const context = get(sendToGuardian, "context");
6718
+ const proposedAccount = get(context, "proposed_account");
6719
+ const keepLocal = get(parsed, "keep_local_do_not_share");
6720
+ const fixture = get(keepLocal, "fixture");
6721
+ if (typeof message !== "string" || typeof proposedAccount !== "string" || !isAddress6(proposedAccount) || typeof fixture !== "string") {
6722
+ throw new Error(
6723
+ "Prepared proof JSON must be the unmodified stdout of roster:prepare-guardian-proof"
6724
+ );
6725
+ }
6726
+ return {
6727
+ message,
6728
+ fixture,
6729
+ proposedAccount: proposedAccount.toLowerCase()
6730
+ };
6731
+ }
6732
+ function parseResponse(text) {
6733
+ let parsed;
6734
+ try {
6735
+ parsed = JSON.parse(text);
6736
+ } catch (error) {
6737
+ throw new Error("Guardian response JSON must be valid JSON", {
6738
+ cause: error
6739
+ });
6740
+ }
6741
+ const guardianAccount = get(parsed, "guardian_account");
6742
+ const signature = get(parsed, "signature");
6743
+ if (typeof guardianAccount !== "string" || !isAddress6(guardianAccount) || typeof signature !== "string" || !isHex5(signature) || signature.length !== 132) {
6744
+ throw new Error(
6745
+ "Guardian response JSON must be the unmodified stdout of guardian:prove-roster-key"
6746
+ );
6747
+ }
6748
+ return {
6749
+ guardianAccount: guardianAccount.toLowerCase(),
6750
+ signature
6751
+ };
6752
+ }
6753
+ function get(value, key) {
6754
+ if (!value || typeof value !== "object" || !(key in value)) {
6755
+ return void 0;
6756
+ }
6757
+ return value[key];
6758
+ }
6759
+
6271
6760
  // src/spec-commitment.ts
6272
- import { readFile as readFile3 } from "fs/promises";
6273
- import { text as readStdinText2 } from "stream/consumers";
6761
+ import { readFile as readFile5 } from "fs/promises";
6762
+ import { text as readStdinText3 } from "stream/consumers";
6274
6763
  var SPEC_COMMITMENT_USAGE = "Usage: elgora-cli spec-commitment <bounty_challenge_md_path>\n or: elgora-cli spec-commitment -";
6275
- async function main8(_env = process.env, argv = process.argv.slice(2)) {
6764
+ async function main12(_env = process.env, argv = process.argv.slice(2)) {
6276
6765
  const markdown = await readBountyChallengeMarkdown2(argv);
6277
6766
  const specCommitment = deriveBountyChallengeContentCommitment(markdown);
6278
6767
  console.log(
@@ -6289,25 +6778,25 @@ async function readBountyChallengeMarkdown2(argv) {
6289
6778
  throw new Error(SPEC_COMMITMENT_USAGE);
6290
6779
  }
6291
6780
  if (path5 === "-") {
6292
- return readStdinText2(process.stdin);
6781
+ return readStdinText3(process.stdin);
6293
6782
  }
6294
- return readFile3(path5, "utf8");
6783
+ return readFile5(path5, "utf8");
6295
6784
  }
6296
6785
 
6297
6786
  // src/submit.ts
6298
- import { readdir, readFile as readFile4, stat } from "fs/promises";
6787
+ import { readdir, readFile as readFile6, stat } from "fs/promises";
6299
6788
  import path3 from "path";
6300
6789
  import { createInterface } from "readline";
6301
- import { isAddress as isAddress4 } from "viem";
6302
- import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
6790
+ import { isAddress as isAddress7 } from "viem";
6791
+ import { privateKeyToAccount as privateKeyToAccount10 } from "viem/accounts";
6303
6792
 
6304
6793
  // src/external-api-approval-signer.ts
6305
- import { isHex as isHex5 } from "viem";
6794
+ import { isHex as isHex6 } from "viem";
6306
6795
  function createExternalApiApprovalSigner(input) {
6307
6796
  return async (typedData) => {
6308
6797
  input.writePrompt(input.renderPrompt(typedData));
6309
6798
  const signature = (await input.readSignature())?.trim();
6310
- if (!signature || signature === "0x" || !isHex5(signature)) {
6799
+ if (!signature || signature === "0x" || !isHex6(signature)) {
6311
6800
  throw new Error(
6312
6801
  "External signer must return one non-empty 0x-prefixed hex signature line on stdin"
6313
6802
  );
@@ -6349,13 +6838,13 @@ var MEDIA_TYPE_BY_EXTENSION = {
6349
6838
  diff: "text/x-diff"
6350
6839
  };
6351
6840
  var DEFAULT_MEDIA_TYPE = "application/octet-stream";
6352
- async function main9(env = process.env, argv = process.argv.slice(2)) {
6841
+ async function main13(env = process.env, argv = process.argv.slice(2)) {
6353
6842
  const { solverAddress, argv: argvWithoutSolverAddress } = extractSolverAddressFlag(argv);
6354
6843
  const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argvWithoutSolverAddress);
6355
6844
  const config = loadSubmitConfig(env, apiBaseUrlOverride, solverAddress);
6356
6845
  const args = readSubmitArgs(remainingArgv);
6357
6846
  const bountyIdText = args.bountyId.toString();
6358
- const account = config.solverPrivateKey ? privateKeyToAccount8(config.solverPrivateKey) : null;
6847
+ const account = config.solverPrivateKey ? privateKeyToAccount10(config.solverPrivateKey) : null;
6359
6848
  const solverAddressFromConfig = account?.address ?? config.solverAddress;
6360
6849
  if (!solverAddressFromConfig) throw new Error("Solver address is required");
6361
6850
  const solver = solverAddressFromConfig.toLowerCase();
@@ -6694,7 +7183,7 @@ function extractSolverAddressFlag(argv) {
6694
7183
  }
6695
7184
  if (arg !== void 0) remaining.push(arg);
6696
7185
  }
6697
- if (solverAddress !== void 0 && !isAddress4(solverAddress)) {
7186
+ if (solverAddress !== void 0 && !isAddress7(solverAddress)) {
6698
7187
  throw new Error("--solver-address must be a valid EVM address");
6699
7188
  }
6700
7189
  return {
@@ -6724,7 +7213,7 @@ async function readArtifactDirectory(artifactDir) {
6724
7213
  }
6725
7214
  return Promise.all(
6726
7215
  entries.map(async (entry) => {
6727
- const bytes = await readFile4(path3.join(artifactDir, entry.name));
7216
+ const bytes = await readFile6(path3.join(artifactDir, entry.name));
6728
7217
  return {
6729
7218
  name: entry.name,
6730
7219
  media_type: guessMediaType(entry.name),
@@ -6834,7 +7323,7 @@ function readTimelockRevealDelaySeconds(env) {
6834
7323
 
6835
7324
  // src/verification-record.ts
6836
7325
  var VERIFICATION_RECORD_USAGE = "Usage: elgora-cli verification-record <bounty_id>";
6837
- async function main10(env = process.env, argv = process.argv.slice(2)) {
7326
+ async function main14(env = process.env, argv = process.argv.slice(2)) {
6838
7327
  const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
6839
7328
  const args = readVerificationRecordArgs(remainingArgv);
6840
7329
  const envWithDefaults = withDeploymentDefaults(env);
@@ -6891,11 +7380,11 @@ function readVerificationRecordArgs(argv) {
6891
7380
  const [bountyIdText] = argv;
6892
7381
  const bountyId = bountyIdText?.trim();
6893
7382
  if (argv.length !== 1 || !bountyId || !isBountyId(bountyId)) {
6894
- throw usageError2();
7383
+ throw usageError4();
6895
7384
  }
6896
7385
  return { bountyId: BigInt(bountyId) };
6897
7386
  }
6898
- function usageError2() {
7387
+ function usageError4() {
6899
7388
  return new Error(VERIFICATION_RECORD_USAGE);
6900
7389
  }
6901
7390
 
@@ -6912,14 +7401,18 @@ process.stderr.write(
6912
7401
  var COMMANDS = {
6913
7402
  "guardian:judgeable": main4,
6914
7403
  "guardian:open": main2,
6915
- "guardian:verdict": main5,
7404
+ "guardian:prove-roster-key": main5,
7405
+ "guardian:verdict": main6,
6916
7406
  "guardian:deliver-key": main3,
6917
- "poster:open-winning-submission": main6,
6918
- "poster:publish-fund": main7,
6919
- "solver:submit": main9,
7407
+ "poster:open-winning-submission": main7,
7408
+ "poster:publish-fund": main8,
7409
+ "roster:add-guardian": main9,
7410
+ "roster:prepare-guardian-proof": main10,
7411
+ "roster:verify-guardian-proof": main11,
7412
+ "solver:submit": main13,
6920
7413
  claim: main,
6921
- "spec-commitment": main8,
6922
- "verification-record": main10
7414
+ "spec-commitment": main12,
7415
+ "verification-record": main14
6923
7416
  };
6924
7417
  var COMMAND_HELP = {
6925
7418
  "guardian:judgeable": {
@@ -6949,6 +7442,16 @@ var COMMAND_HELP = {
6949
7442
  ],
6950
7443
  apiBaseUrlFlag: true
6951
7444
  },
7445
+ "guardian:prove-roster-key": {
7446
+ usage: "elgora-cli guardian:prove-roster-key <challenge_json_path|->",
7447
+ title: "Prove control of a proposed roster account and HPKE key.",
7448
+ description: "Run asynchronously, independent of anything on chain: open the sealed\n fixture from roster:prepare-guardian-proof's send_to_guardian output\n (pass the whole prepared JSON, or just its send_to_guardian object;\n '-' reads stdin), then EIP-191 personal_sign proof that this account\n recovered it. Refuses to sign if the challenge names a different\n chain, Hub, or proposed_account than this key resolves to. Send the\n printed response back to the roster admin for\n roster:verify-guardian-proof. This is advisory only \u2014 it is never\n submitted to ElgoraHub and enforces nothing there.",
7449
+ required: [
7450
+ "ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY",
7451
+ "ELGORA_GUARDIAN_PRIVATE_KEYS_JSON"
7452
+ ],
7453
+ defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS"]
7454
+ },
6952
7455
  "guardian:verdict": {
6953
7456
  usage: "elgora-cli guardian:verdict <bounty_id> awarded <solver> <submission_commitment> <verdict_body_path>\n or: elgora-cli guardian:verdict <bounty_id> no_valid_submission <verdict_body_path>",
6954
7457
  title: "Publish and record this Guardian's Verdict.",
@@ -6964,8 +7467,8 @@ var COMMAND_HELP = {
6964
7467
  },
6965
7468
  "guardian:deliver-key": {
6966
7469
  usage: "elgora-cli guardian:deliver-key <bounty_id> <solver> <submission_commitment>",
6967
- title: "Repair delivery for one exact Submission.",
6968
- description: "Ask the API to wrap the key for the explicit Solver and Submission.\n ElgoraHub decides whether the pair is still active or is the finalized\n winner; written Verdicts and local state do not authorize delivery.",
7470
+ title: "Deliver the finalized winner's content key for Poster retrieval.",
7471
+ description: "Required after a bounty finalizes `awarded`: wrap the winning\n Submission's key so the funding Poster can retrieve the work. Read the\n winning Solver and Submission from ElgoraHub's finalized state (the\n subgraph), not from a local Verdict, and pass exactly that pair. The\n API rechecks against ElgoraHub and accepts only the current active or\n finalized winner; written Verdicts and local state authorize nothing.\n Idempotent \u2014 safe to re-run and safe if another Guardian already did it.",
6969
7472
  required: [
6970
7473
  "ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY",
6971
7474
  "ELGORA_GUARDIAN_PRIVATE_KEYS_JSON"
@@ -7005,6 +7508,26 @@ var COMMAND_HELP = {
7005
7508
  ],
7006
7509
  apiBaseUrlFlag: true
7007
7510
  },
7511
+ "roster:add-guardian": {
7512
+ usage: "elgora-cli roster:add-guardian [--roster-admin-address <address>]\n <name> <guardian_account> <encryption_public_key>",
7513
+ title: "Append one Guardian to the live roster.",
7514
+ description: "Call ElgoraHub's guardianRosterAdmin-only addGuardian directly. Only\n future bounties see the new roster; open bounties keep judging\n against the hash pinned when they were created. With\n --roster-admin-address, the transaction is simulated and printed\n unsigned (to, data, value) for an external signer such as a Safe,\n instead of being sent. Run roster:prepare-guardian-proof,\n guardian:prove-roster-key, and roster:verify-guardian-proof against\n the proposed Guardian before ever calling this.",
7515
+ required: [],
7516
+ defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS", "ELGORA_RPC_URL"]
7517
+ },
7518
+ "roster:prepare-guardian-proof": {
7519
+ usage: "elgora-cli roster:prepare-guardian-proof <guardian_account> <encryption_public_key>",
7520
+ title: "Start the key-custody proof for a proposed Guardian.",
7521
+ description: "Picks a fresh random secret, seals it only to the proposed HPKE\n public key, and builds the message the Guardian must sign alongside\n the recovered bytes. Send only the printed send_to_guardian object to\n the Guardian, over whatever channel you already trust them on \u2014 never\n keep_local_do_not_share; that half stays with the roster admin and\n feeds roster:verify-guardian-proof once the Guardian responds with\n guardian:prove-roster-key. Pure local computation: no chain read, no\n wallet, no network call, nothing enforced on ElgoraHub.",
7522
+ required: [],
7523
+ defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS"]
7524
+ },
7525
+ "roster:verify-guardian-proof": {
7526
+ usage: "elgora-cli roster:verify-guardian-proof <prepared_json_path> <response_json_path>",
7527
+ title: "Check a Guardian's key-custody proof response.",
7528
+ 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 the recovered signer is the exact proposed\n account. Exits non-zero on any mismatch. 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.",
7529
+ required: []
7530
+ },
7008
7531
  "solver:submit": {
7009
7532
  usage: "elgora-cli solver:submit [--api-base-url <url>] [--solver-address <address>] <bounty_id> <artifact_dir>",
7010
7533
  title: "Encrypt and submit a Solver's private artifacts.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elgora/cli",
3
- "version": "7.2.0",
3
+ "version": "7.2.1",
4
4
  "description": "Command-line client for Elgora, a decentralized bounty marketplace for science. Lets Posters, Solvers, and Guardians interact with Elgora's public API and on-chain contracts.",
5
5
  "type": "module",
6
6
  "license": "MIT",