@elgora/cli 7.4.1 → 7.5.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 (2) hide show
  1. package/dist/cli.js +76 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -532,7 +532,7 @@ var AES_GCM_TAG_BITS = 128;
532
532
  var BASE64URL_RE = /^[A-Za-z0-9_-]+$/;
533
533
  var SOLVER_SUBMISSION_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
534
534
  var MEDIA_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/;
535
- var KEY_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
535
+ var KEY_ID_RE = /^[A-Za-z0-9][ -~]*$/;
536
536
  var base64UrlSchema = z3.string().regex(BASE64URL_RE);
537
537
  var lowerAddressSchema = z3.string().refine(isLowercaseHexAddress);
538
538
  var bytes32Schema2 = z3.string().refine(isLowercaseBytes32);
@@ -1142,8 +1142,12 @@ async function openAesKeyForRecipient(input) {
1142
1142
  }
1143
1143
  }
1144
1144
  if (!matched) {
1145
+ const wanted = [...new Set(wrappedKeys.map((key) => key.key_id))].sort();
1146
+ const configured = [
1147
+ ...new Set(privateKeys.map((key) => key.key_id))
1148
+ ].sort();
1145
1149
  throw new Error(
1146
- "no matching Guardian private key for private artifact envelope"
1150
+ `no matching Guardian private key for private artifact envelope: this envelope was sealed for key_id ${JSON.stringify(wanted)}, but ELGORA_GUARDIAN_PRIVATE_KEYS_JSON configures key_id ${JSON.stringify(configured)}. key_id must be this Guardian's own registered roster name (the name passed to roster:add-guardian), not its account address.`
1147
1151
  );
1148
1152
  }
1149
1153
  throw new Error("matching Guardian private key could not open wrapped key", {
@@ -6903,7 +6907,7 @@ function resolveChainId(selector) {
6903
6907
  }
6904
6908
 
6905
6909
  // src/version.ts
6906
- var CLI_VERSION = true ? "7.4.1" : "0.0.0-dev";
6910
+ var CLI_VERSION = true ? "7.5.1" : "0.0.0-dev";
6907
6911
  var CLI_NODE_ENGINES = true ? ">=24 <25" : ">=24 <25";
6908
6912
 
6909
6913
  // src/node-engines.ts
@@ -7340,6 +7344,11 @@ function loadPublishFundConfig(env, apiBaseUrlOverride) {
7340
7344
  };
7341
7345
  }
7342
7346
 
7347
+ // src/required-env.ts
7348
+ function missingRequiredEnv(required4, env) {
7349
+ return required4.filter((envName) => !env[envName]?.trim());
7350
+ }
7351
+
7343
7352
  // src/roster-add-guardian.ts
7344
7353
  import { isAddress as isAddress4 } from "viem";
7345
7354
  import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
@@ -7376,6 +7385,11 @@ async function main10(env = process.env, argv = process.argv.slice(2)) {
7376
7385
  `${args.guardianAccount} is already on the Guardian roster`
7377
7386
  );
7378
7387
  }
7388
+ if (roster.some((guardian) => guardian.name === args.name)) {
7389
+ throw new Error(
7390
+ `"${args.name}" is already the name of a Guardian on the roster; names must be unique`
7391
+ );
7392
+ }
7379
7393
  const contractArgs = [
7380
7394
  args.name,
7381
7395
  args.guardianAccount,
@@ -7459,6 +7473,15 @@ function parseRosterAddGuardianArgs(argv) {
7459
7473
  const guardianAccount = positional[1]?.trim();
7460
7474
  const encryptionPublicKey = positional[2]?.trim();
7461
7475
  if (!name) throw usageError2();
7476
+ if (!KEY_ID_RE.test(name)) {
7477
+ throw new Error(
7478
+ // A Solver's CLI seals private artifacts against this exact string as
7479
+ // the Guardian's key_id, so it must be non-empty, start with an
7480
+ // alphanumeric, and stay within printable ASCII (no control/formatting
7481
+ // characters, no leading whitespace).
7482
+ `name must match ${KEY_ID_RE.source} (it becomes this Guardian's private-artifact key_id)`
7483
+ );
7484
+ }
7462
7485
  if (!guardianAccount || !isAddress4(guardianAccount)) {
7463
7486
  throw new Error("guardian_account must be a valid EVM address");
7464
7487
  }
@@ -7992,6 +8015,23 @@ function requirePinnedGuardianRosterHash(hubGuardianRosterHash, apiGuardianRoste
7992
8015
  );
7993
8016
  }
7994
8017
  }
8018
+ function requireUsableGuardianNames(roster) {
8019
+ const seen = /* @__PURE__ */ new Map();
8020
+ for (const guardian of roster) {
8021
+ if (!KEY_ID_RE.test(guardian.name)) {
8022
+ throw new Error(
8023
+ `Guardian ${guardian.account} has roster name ${JSON.stringify(guardian.name)}, which cannot be used as a private-artifact key_id (must match ${KEY_ID_RE.source}). This roster cannot receive private submissions until its admin corrects the name.`
8024
+ );
8025
+ }
8026
+ const collidingAccount = seen.get(guardian.name);
8027
+ if (collidingAccount) {
8028
+ throw new Error(
8029
+ `Guardians ${collidingAccount} and ${guardian.account} share the roster name ${JSON.stringify(guardian.name)}. Names must be unique on the pinned roster so each Guardian's key_id resolves to exactly one recipient.`
8030
+ );
8031
+ }
8032
+ seen.set(guardian.name, guardian.account);
8033
+ }
8034
+ }
7995
8035
  function requireMatchingSubmitTransaction(actual, expected) {
7996
8036
  if (actual.chain_id !== expected.chain_id || actual.to.toLowerCase() !== expected.to.toLowerCase() || actual.value !== expected.value || actual.data.toLowerCase() !== expected.data.toLowerCase() || actual.function_name !== expected.function_name || actual.args.bounty_id !== expected.args.bounty_id || actual.args.submission_commitment.toLowerCase() !== expected.args.submission_commitment.toLowerCase() || actual.args.guardian_roster_hash.toLowerCase() !== expected.args.guardian_roster_hash.toLowerCase() || actual.args.extra_data !== expected.args.extra_data) {
7997
8037
  throw new Error(
@@ -8013,9 +8053,10 @@ function requireOpenSubmittableBounty(bountyFacts, bountyId) {
8013
8053
  async function buildSolverSubmissionEnvelope(input) {
8014
8054
  const solver = input.solver.toLowerCase();
8015
8055
  const bountyId = input.bountyId.toString();
8056
+ requireUsableGuardianNames(input.guardianRoster);
8016
8057
  const guardianRecipients = input.guardianRoster.map(
8017
8058
  (guardian) => ({
8018
- key_id: guardian.account.toLowerCase(),
8059
+ key_id: guardian.name,
8019
8060
  suite: PRIVATE_ARTIFACT_HPKE_SUITE,
8020
8061
  public_key: guardian.encryptionPublicKey
8021
8062
  })
@@ -8280,15 +8321,18 @@ function usageError4() {
8280
8321
  }
8281
8322
 
8282
8323
  // src/cli.ts
8283
- var dotenvPath = path4.resolve(process.env.DOTENV_CONFIG_PATH ?? ".env");
8284
- var dotenvResult = loadDotenv({
8285
- quiet: true,
8286
- ...process.env.DOTENV_CONFIG_PATH ? { path: process.env.DOTENV_CONFIG_PATH } : void 0
8287
- });
8288
- process.stderr.write(
8289
- `Env: ${dotenvPath} (${dotenvResult.error ? "not found, using process env only" : "loaded"})
8324
+ if (process.env.DOTENV_CONFIG_PATH) {
8325
+ const dotenvPath = path4.resolve(process.env.DOTENV_CONFIG_PATH);
8326
+ const dotenvResult = loadDotenv({ path: dotenvPath, quiet: true });
8327
+ process.stderr.write(
8328
+ `Env: ${dotenvPath} (${dotenvResult.error ? `not found: ${dotenvResult.error.message}` : "loaded"})
8290
8329
  `
8291
- );
8330
+ );
8331
+ } else {
8332
+ process.stderr.write(
8333
+ "Env: DOTENV_CONFIG_PATH not set; reading process.env only\n"
8334
+ );
8335
+ }
8292
8336
  var COMMANDS = {
8293
8337
  "guardian:judgeable": main5,
8294
8338
  "guardian:open": main3,
@@ -8403,7 +8447,7 @@ var COMMAND_HELP = {
8403
8447
  "roster:add-guardian": {
8404
8448
  usage: "elgora-cli roster:add-guardian [--roster-admin-address <address>]\n <name> <guardian_account> <encryption_public_key>",
8405
8449
  title: "Append one Guardian to the live roster.",
8406
- 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.",
8450
+ 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. name becomes this\n Guardian's key_id for every private submission a Solver seals\n afterward \u2014 it must be unique on the roster and is what this\n Guardian's own ELGORA_GUARDIAN_PRIVATE_KEYS_JSON entries must use as\n their key_id, not the account address.",
8407
8451
  required: [],
8408
8452
  defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS", "ELGORA_RPC_URL"]
8409
8453
  },
@@ -8502,6 +8546,25 @@ async function run(rawArgv) {
8502
8546
  process.exitCode = 1;
8503
8547
  return;
8504
8548
  }
8549
+ const missing = missingRequiredEnv(
8550
+ COMMAND_HELP[subcommand]?.required ?? [],
8551
+ env
8552
+ );
8553
+ if (missing.length > 0) {
8554
+ process.stderr.write(
8555
+ `${[
8556
+ `${subcommand} is missing required environment variable${missing.length > 1 ? "s" : ""}:`,
8557
+ ...missing.map((envName) => ` ${envName}`),
8558
+ "",
8559
+ "This CLI reads only process.env (plus DOTENV_CONFIG_PATH if you set it)",
8560
+ "\u2014 it never assumes a secrets location. Provide these before invoking",
8561
+ `elgora-cli, or run \`elgora-cli help ${subcommand}\` for the full list.`
8562
+ ].join("\n")}
8563
+ `
8564
+ );
8565
+ process.exitCode = 1;
8566
+ return;
8567
+ }
8505
8568
  await command(env, rest);
8506
8569
  }
8507
8570
  function printUsage() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elgora/cli",
3
- "version": "7.4.1",
3
+ "version": "7.5.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",