@elgora/cli 10.0.0 → 10.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +12 -9
  2. package/dist/cli.js +163 -179
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -34,7 +34,7 @@ answer.
34
34
  another AI review, or reviews them first.
35
35
  - `poster:open-winning-submission` retrieves the finalized winning Submission
36
36
  for the funding Poster and decrypts it locally.
37
- - `check-page <bounty_challenge_md_path|->` checks structural conformance offline,
37
+ - `check-challenge <bounty_challenge_md_path|->` checks structural conformance offline,
38
38
  returns an issue list and exits non-zero on failure. It needs no wallet or
39
39
  network and does not replace hosted semantic readiness review.
40
40
  - `spec-commitment` reproduces the challenge's `spec_commitment` offline.
@@ -65,8 +65,11 @@ time, so it stays correct however the CLI was installed.
65
65
  Posters use `ELGORA_POSTER_PRIVATE_KEY`. Solvers should normally use
66
66
  `--solver-address` and sign the emitted EIP-712 requests and prepared
67
67
  transaction with an external wallet; `ELGORA_SOLVER_PRIVATE_KEY` is an optional
68
- local testing convenience. Claimants can use `--claimant-address`, or
69
- `ELGORA_CLAIMANT_PRIVATE_KEY` for a local test wallet.
68
+ local testing convenience. Each emitted `typed_data` is in the
69
+ `eth_signTypedData_v4` shape, with `EIP712Domain` in its types; for ethers v6,
70
+ delete `types.EIP712Domain` before passing it to `signTypedData`. Claimants can
71
+ use `--claimant-address`, or `ELGORA_CLAIMANT_PRIVATE_KEY` for a local test
72
+ wallet.
70
73
 
71
74
  Guardian commands use two different kinds of secret:
72
75
 
@@ -86,9 +89,9 @@ needs.
86
89
 
87
90
  ## Deployment configuration
88
91
 
89
- Since 8.0.0 the default deployment is **Base mainnet** (`base`, chain `8453`),
90
- which escrows real USDC. Elgora's staging deployment runs on **Base Sepolia**
91
- (`base-sepolia`, chain `84532`) behind `https://staging.elgora.ai`. A run that
92
+ The default deployment is **Base mainnet** (`base`, chain `8453`), which
93
+ escrows real USDC. Elgora's testnet deployment runs on **Base Sepolia**
94
+ (`base-sepolia`, chain `84532`) behind `https://testnet.elgora.ai`. A run that
92
95
  selects no network prints one stderr line saying it used the default.
93
96
 
94
97
  Every command accepts `--network <name|chain id>`, which selects the deployment
@@ -99,9 +102,9 @@ passed — for that run only:
99
102
 
100
103
  ```sh
101
104
  elgora-cli guardian:judgeable # Base mainnet, the default
102
- elgora-cli claim --network base-sepolia 7 # staging, by name
105
+ elgora-cli claim --network base-sepolia 7 # testnet, by name
103
106
  elgora-cli claim --network 84532 7 # the same, by chain id
104
- export ELGORA_CHAIN_ID=84532 # staging for every run in this shell
107
+ export ELGORA_CHAIN_ID=84532 # testnet for every run in this shell
105
108
  ```
106
109
 
107
110
  The chain's public RPC, the ElgoraHub address, the escrow token, and the subgraph
@@ -115,7 +118,7 @@ targeting a deployment this CLI release does not know about, and do not mix
115
118
  addresses or endpoints from different deployments.
116
119
 
117
120
  The API follows the chain too: `https://elgora.ai` for `base` and
118
- `https://staging.elgora.ai` for `base-sepolia`. Commands that use the public API
121
+ `https://testnet.elgora.ai` for `base-sepolia`. Commands that use the public API
119
122
  accept `--api-base-url <url>`, which wins over `ELGORA_API_BASE_URL` and that
120
123
  default for one invocation — for example to reach a Vercel preview. Point it at
121
124
  an API serving the deployment you selected.
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import path4 from "path";
5
5
  import { config as loadDotenv } from "dotenv";
6
6
  import { ZodError as ZodError2 } from "zod";
7
7
 
8
- // src/check-page.ts
8
+ // src/check-challenge.ts
9
9
  import { readFile } from "fs/promises";
10
10
  import { text as readStdinText } from "stream/consumers";
11
11
 
@@ -72,6 +72,7 @@ var WINNER_TAKE_ALL_PAYOUT_POLICY = "winner_take_all";
72
72
  var SUBMISSION_ENCRYPTION_TYPE = "guardian_timelock_hpke_aes_256_gcm_v0";
73
73
  var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
74
74
  var READINESS_APPROVAL_VALIDITY_SECONDS = 7 * 24 * 60 * 60;
75
+ var READINESS_TIMEOUT_MS = 12e4;
75
76
 
76
77
  // ../../packages/core/src/bounty-challenge-markdown.ts
77
78
  var FRONTMATTER_DELIMITER = "---";
@@ -1532,7 +1533,7 @@ var DEPLOYMENTS = {
1532
1533
  ELGORA_ESCROW_TOKEN_ADDRESS: "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
1533
1534
  payoutSchemeAddress: "0x752d4305b8567b777d479dfa9847dc4f5ffb5750",
1534
1535
  ELGORA_SUBGRAPH_ENDPOINT: "https://api.goldsky.com/api/public/project_cmtu7wslg04s501ovc2je3beh/subgraphs/elgora-elgorahub/testnet-production/gn",
1535
- ELGORA_API_BASE_URL: "https://staging.elgora.ai",
1536
+ ELGORA_API_BASE_URL: "https://testnet.elgora.ai",
1536
1537
  deployBlock: 46353511,
1537
1538
  deployTransaction: "0xed3d3c8af3c69f9f8d4ec47da7179fe4b89bd4a4cd8d8232dbbd3d3f3d38e74b",
1538
1539
  sourceCommit: "1377da5c38dc5c90d8608ffa3cb15ad6f8567a01"
@@ -1543,16 +1544,16 @@ function getDeployment(chainId) {
1543
1544
  return DEPLOYMENTS[chainId];
1544
1545
  }
1545
1546
 
1546
- // src/check-page.ts
1547
- var CHECK_PAGE_USAGE = "Usage: elgora-cli check-page <bounty_challenge_md_path>\n or: elgora-cli check-page -";
1547
+ // src/check-challenge.ts
1548
+ var CHECK_CHALLENGE_USAGE = "Usage: elgora-cli check-challenge <bounty_challenge_md_path>\n or: elgora-cli check-challenge -";
1548
1549
  async function main(_env = process.env, argv = process.argv.slice(2)) {
1549
1550
  const markdown = await readBountyChallengeMarkdown(argv);
1550
1551
  const { issues, profile } = checkChallengeConformance(markdown);
1551
1552
  console.log(
1552
1553
  JSON.stringify({
1553
- event: issues.length === 0 ? "check_page.structure_conforming" : "check_page.issues",
1554
+ event: issues.length === 0 ? "check_challenge.structure_conforming" : "check_challenge.issues",
1554
1555
  profile: profile?.profile ?? null,
1555
- // Named so a caller cannot read a clean result as "this page is safe".
1556
+ // Named so a caller cannot read a clean result as "this challenge is safe".
1556
1557
  checked: "structure",
1557
1558
  issues: issues.map((issue) => ({
1558
1559
  code: issue.code,
@@ -1568,7 +1569,7 @@ async function main(_env = process.env, argv = process.argv.slice(2)) {
1568
1569
  async function readBountyChallengeMarkdown(argv) {
1569
1570
  const path5 = argv[0];
1570
1571
  if (!path5) {
1571
- throw new Error(CHECK_PAGE_USAGE);
1572
+ throw new Error(CHECK_CHALLENGE_USAGE);
1572
1573
  }
1573
1574
  if (path5 === "-") {
1574
1575
  return readStdinText(process.stdin);
@@ -4414,14 +4415,6 @@ var envShape = {
4414
4415
  ELGORA_DELIVERY_PUBLIC_KEY: z5.string().min(1, "ELGORA_DELIVERY_PUBLIC_KEY is required"),
4415
4416
  ELGORA_DELIVERY_PRIVATE_KEY: z5.string().min(1, "ELGORA_DELIVERY_PRIVATE_KEY is required")
4416
4417
  };
4417
- var readinessAudienceSchema = z5.string().trim().toLowerCase().min(1).max(512).refine((host) => {
4418
- try {
4419
- const url = new URL(`https://${host}`);
4420
- return url.host === host && url.pathname === "/" && !url.username && !url.password && !url.search && !url.hash;
4421
- } catch {
4422
- return false;
4423
- }
4424
- }, "Must be a canonical hostname with optional port, without a URL path");
4425
4418
  var publicationEnvShape = {
4426
4419
  ELGORA_CHAIN_ID: envShape.ELGORA_CHAIN_ID,
4427
4420
  ELGORA_HUB_ADDRESS: envShape.ELGORA_HUB_ADDRESS,
@@ -4429,7 +4422,7 @@ var publicationEnvShape = {
4429
4422
  ELGORA_DATABASE_URL: envShape.ELGORA_DATABASE_URL,
4430
4423
  ELGORA_SUBGRAPH_ENDPOINT: envShape.ELGORA_SUBGRAPH_ENDPOINT,
4431
4424
  ELGORA_PUBLICATION_REVIEW_ENABLED: z5.string().trim().toLowerCase().pipe(z5.enum(["true", "false"])).default("true").transform((value) => value === "true"),
4432
- ELGORA_READINESS_TIMEOUT_MS: z5.coerce.number().int().min(1).max(12e4).default(12e4)
4425
+ ELGORA_READINESS_TIMEOUT_MS: z5.coerce.number().int().min(1).max(READINESS_TIMEOUT_MS).default(READINESS_TIMEOUT_MS)
4433
4426
  };
4434
4427
  var envSchema = z5.object(envShape);
4435
4428
  var bountyPublicationEnvSchema = z5.object(publicationEnvShape);
@@ -4726,23 +4719,6 @@ import {
4726
4719
  fallback,
4727
4720
  http
4728
4721
  } from "viem";
4729
-
4730
- // ../../packages/core/src/auth/freshness.ts
4731
- var API_REQUEST_APPROVAL_ANCHOR_LAG_SECONDS = 6;
4732
- var FALLBACK_BLOCK_TIME_MS = 2e3;
4733
- function blockTimeMsFor(chainId) {
4734
- const chain = chainId === void 0 ? void 0 : SUPPORTED_CHAINS[chainId];
4735
- return chain?.blockTime ?? FALLBACK_BLOCK_TIME_MS;
4736
- }
4737
- function blocksForSeconds(seconds, chainId) {
4738
- const blocks = Math.ceil(seconds * 1e3 / blockTimeMsFor(chainId));
4739
- return BigInt(Math.max(1, blocks));
4740
- }
4741
- function apiRequestApprovalAnchorLagBlocks(chainId) {
4742
- return blocksForSeconds(API_REQUEST_APPROVAL_ANCHOR_LAG_SECONDS, chainId);
4743
- }
4744
-
4745
- // ../../packages/core/src/chain-client.ts
4746
4722
  var RPC_RETRY_COUNT = 3;
4747
4723
  function createChainTransport(chainId, rpcUrl) {
4748
4724
  const publicRpcUrl = resolveSupportedChain(chainId).rpcUrls.default.http[0];
@@ -4776,13 +4752,6 @@ function assertChainId(actual, expected) {
4776
4752
  throw new Error(`RPC returned chain_id ${actual}, expected ${expected}`);
4777
4753
  }
4778
4754
  }
4779
- async function fetchSignableBlock(client) {
4780
- const offset = apiRequestApprovalAnchorLagBlocks(client.chain?.id);
4781
- const head = await client.getBlockNumber();
4782
- const anchorNumber = head > offset ? head - offset : 0n;
4783
- const anchor = await client.getBlock({ blockNumber: anchorNumber });
4784
- return { number: anchor.number, hash: anchor.hash };
4785
- }
4786
4755
  async function assertHubDeployment(client, chainId, hubAddress) {
4787
4756
  const [actualChainId, hubCode] = await Promise.all([
4788
4757
  client.getChainId(),
@@ -5363,22 +5332,37 @@ import {
5363
5332
  recoverTypedDataAddress as recoverTypedDataAddress2
5364
5333
  } from "viem";
5365
5334
 
5335
+ // ../../packages/core/src/auth/request-approval.ts
5336
+ function typedDataForRequest(request, block) {
5337
+ const bodyBytes = typeof request.body === "string" ? toBytes4(request.body) : request.body;
5338
+ return buildApiRequestApprovalTypedData({
5339
+ chainId: request.domain.chainId,
5340
+ hubAddress: request.domain.hubAddress,
5341
+ message: {
5342
+ endpointId: request.endpointId,
5343
+ audience: audienceFor(request.host),
5344
+ bodyHash: keccak2565(bodyBytes),
5345
+ query: queryFor(request.query),
5346
+ blockNumber: block.number,
5347
+ blockHash: block.hash
5348
+ }
5349
+ });
5350
+ }
5351
+
5366
5352
  // ../../packages/core/src/auth/request-approval-header.ts
5367
- import { keccak256 as keccak2566, toBytes as toBytes5 } from "viem";
5353
+ import { z as z10 } from "zod";
5368
5354
  async function buildAuthorizationHeader(params) {
5369
5355
  const url = new URL(params.requestUrl);
5370
- const typedData = buildApiRequestApprovalTypedData({
5371
- chainId: params.chainId,
5372
- hubAddress: params.hubAddress,
5373
- message: {
5356
+ const typedData = typedDataForRequest(
5357
+ {
5374
5358
  endpointId: endpointIdFor({ method: params.method, path: url.pathname }),
5375
- audience: audienceFor(url.host),
5376
- bodyHash: keccak2566(toBytes5(params.body ?? "")),
5377
- query: queryFor(url.search),
5378
- blockNumber: params.block.number,
5379
- blockHash: params.block.hash
5380
- }
5381
- });
5359
+ host: url.host,
5360
+ query: url.search,
5361
+ body: params.body ?? "",
5362
+ domain: { chainId: params.chainId, hubAddress: params.hubAddress }
5363
+ },
5364
+ params.block
5365
+ );
5382
5366
  const signature = await params.sign(typedData);
5383
5367
  return encodeApiRequestApprovalHeader({
5384
5368
  address: params.address,
@@ -5387,6 +5371,31 @@ async function buildAuthorizationHeader(params) {
5387
5371
  blockHash: params.block.hash
5388
5372
  });
5389
5373
  }
5374
+ async function authorizeRequest(params) {
5375
+ return buildAuthorizationHeader({
5376
+ ...params,
5377
+ block: await readChallengeBlock(params)
5378
+ });
5379
+ }
5380
+ var challengeBlockSchema = z10.object({
5381
+ error: z10.object({ block_number: z10.string(), block_hash: z10.string() })
5382
+ });
5383
+ async function readChallengeBlock(params) {
5384
+ const response = await fetch(params.requestUrl, {
5385
+ method: params.method,
5386
+ headers: params.contentType ? { "content-type": params.contentType } : {},
5387
+ body: params.method.toUpperCase() === "GET" ? null : params.body ?? ""
5388
+ });
5389
+ if (response.status !== 401) {
5390
+ throw new Error(
5391
+ `Expected an approval challenge (401) from ${params.requestUrl}, got ${response.status}.`
5392
+ );
5393
+ }
5394
+ const { block_number, block_hash } = challengeBlockSchema.parse(
5395
+ await response.json()
5396
+ ).error;
5397
+ return { number: BigInt(block_number), hash: block_hash };
5398
+ }
5390
5399
 
5391
5400
  // ../../packages/core/src/winning-solution-delivery.ts
5392
5401
  async function wrapWinningSolutionKey(input) {
@@ -5395,14 +5404,13 @@ async function wrapWinningSolutionKey(input) {
5395
5404
  apiBaseUrl: input.config.apiBaseUrl
5396
5405
  });
5397
5406
  const contentPathname = `/api/bounties/${input.bountyId}/submissions/${input.submissionCommitment}/content`;
5398
- const contentAuthorization = await buildAuthorizationHeader({
5407
+ const contentAuthorization = await authorizeRequest({
5399
5408
  requestUrl: new URL(contentPathname, input.config.apiBaseUrl).toString(),
5400
5409
  method: "GET",
5401
5410
  body: "",
5402
5411
  chainId: input.config.chainId,
5403
5412
  hubAddress: input.config.hubAddress,
5404
5413
  address: input.owner.address,
5405
- block: await fetchSignableBlock(input.publicClient),
5406
5414
  sign: (typedData) => account.signTypedData(typedData)
5407
5415
  });
5408
5416
  const { content_record, ciphertext_download_url } = solverSubmissionContentResponseSchema.parse(
@@ -5454,14 +5462,14 @@ async function wrapWinningSolutionKey(input) {
5454
5462
  submission_commitment: input.submissionCommitment,
5455
5463
  wrapped_key: opened.wrapped_key
5456
5464
  });
5457
- const authorization = await buildAuthorizationHeader({
5465
+ const authorization = await authorizeRequest({
5458
5466
  requestUrl: requestUrl.toString(),
5459
5467
  method: "POST",
5468
+ contentType: "application/json",
5460
5469
  body,
5461
5470
  chainId: input.config.chainId,
5462
5471
  hubAddress: input.config.hubAddress,
5463
5472
  address: input.owner.address,
5464
- block: await fetchSignableBlock(input.publicClient),
5465
5473
  sign: (typedData) => account.signTypedData(typedData)
5466
5474
  });
5467
5475
  const responseBody = await postText(
@@ -5504,14 +5512,13 @@ async function main3(env = process.env, argv = process.argv.slice(2)) {
5504
5512
  bountyId: args.bountyId,
5505
5513
  submissionCommitment: args.submissionCommitment
5506
5514
  });
5507
- const authorization = await buildAuthorizationHeader({
5515
+ const authorization = await authorizeRequest({
5508
5516
  requestUrl: new URL(pathname, config.apiBaseUrl).toString(),
5509
5517
  method: "GET",
5510
5518
  body: "",
5511
5519
  chainId: config.chainId,
5512
5520
  hubAddress: config.hubAddress,
5513
5521
  address: account.address,
5514
- block: await fetchSignableBlock(publicClient),
5515
5522
  sign: (typedData) => account.signTypedData(typedData)
5516
5523
  });
5517
5524
  const { content_record, ciphertext_download_url } = await requestSubmissionContent({
@@ -5693,7 +5700,6 @@ async function main4(env = process.env, argv = process.argv.slice(2)) {
5693
5700
  privateKey: guardianAccountPrivateKey,
5694
5701
  address: account.address
5695
5702
  },
5696
- publicClient,
5697
5703
  guardianPrivateKeys
5698
5704
  });
5699
5705
  console.log(
@@ -6854,14 +6860,13 @@ async function main7(env = process.env, argv = process.argv.slice(2)) {
6854
6860
  }
6855
6861
  if (args.outcome === "awarded") {
6856
6862
  const contentPathname = `/api/bounties/${args.bountyId.toString()}/submissions/${args.awardedSubmissionCommitment}/content`;
6857
- const contentAuthorization = await buildAuthorizationHeader({
6863
+ const contentAuthorization = await authorizeRequest({
6858
6864
  requestUrl: new URL(contentPathname, config.apiBaseUrl).toString(),
6859
6865
  method: "GET",
6860
6866
  body: "",
6861
6867
  chainId: config.chainId,
6862
6868
  hubAddress: config.hubAddress,
6863
6869
  address: account.address,
6864
- block: await fetchSignableBlock(publicClient),
6865
6870
  sign: (typedData) => account.signTypedData(typedData)
6866
6871
  });
6867
6872
  await assertCheckedSubmission({
@@ -6908,14 +6913,14 @@ async function main7(env = process.env, argv = process.argv.slice(2)) {
6908
6913
  }
6909
6914
  const pathname = `/api/written-verdicts/${reportCommitment}`;
6910
6915
  const requestUrl = new URL(pathname, config.apiBaseUrl);
6911
- const authorization = await buildAuthorizationHeader({
6916
+ const authorization = await authorizeRequest({
6912
6917
  requestUrl: requestUrl.toString(),
6913
6918
  method: "POST",
6919
+ contentType: MARKDOWN_CONTENT_TYPE,
6914
6920
  body: markdown,
6915
6921
  chainId: config.chainId,
6916
6922
  hubAddress: config.hubAddress,
6917
6923
  address: account.address,
6918
- block: await fetchSignableBlock(publicClient),
6919
6924
  sign: (typedData) => account.signTypedData(typedData)
6920
6925
  });
6921
6926
  await postText(requestUrl, markdown, MARKDOWN_CONTENT_TYPE, authorization);
@@ -7070,14 +7075,14 @@ var NETWORKS = [
7070
7075
  {
7071
7076
  name: "base-sepolia",
7072
7077
  chainId: baseSepolia3.id,
7073
- label: "Base Sepolia testnet, Elgora's staging deployment"
7078
+ label: "Base Sepolia, test USDC"
7074
7079
  },
7075
7080
  { name: "local", chainId: foundry3.id, label: "a local Anvil stack" }
7076
7081
  ];
7077
7082
  var NETWORK_CHAIN_IDS = Object.fromEntries(
7078
7083
  NETWORKS.map(({ name, chainId }) => [name, chainId])
7079
7084
  );
7080
- var STAGING_NETWORK = NETWORKS.find(
7085
+ var TESTNET_NETWORK = NETWORKS.find(
7081
7086
  ({ chainId }) => chainId === baseSepolia3.id
7082
7087
  );
7083
7088
  var NETWORK_NAMES_HINT = NETWORKS.map(
@@ -7087,15 +7092,15 @@ function describeChain(chainId) {
7087
7092
  const network = NETWORKS.find((candidate) => candidate.chainId === chainId);
7088
7093
  return network ? `${network.name} (${network.label})` : `chain ${chainId}`;
7089
7094
  }
7090
- var STAGING_HINT = `add ${NETWORK_FLAG} ${STAGING_NETWORK.name} or set ELGORA_CHAIN_ID=${STAGING_NETWORK.chainId}`;
7095
+ var TESTNET_HINT = `add ${NETWORK_FLAG} ${TESTNET_NETWORK.name} or set ELGORA_CHAIN_ID=${TESTNET_NETWORK.chainId}`;
7091
7096
  var DEFAULT_NETWORK_HELP_LINES = [
7092
7097
  `Default: ${describeChain(DEFAULT_CHAIN_ID)}.`,
7093
- `Staging: ${describeChain(STAGING_NETWORK.chainId)};`,
7094
- ` ${STAGING_HINT}.`
7098
+ `Testnet: ${describeChain(TESTNET_NETWORK.chainId)};`,
7099
+ ` ${TESTNET_HINT}.`
7095
7100
  ];
7096
7101
  function defaultNetworkNotice(env) {
7097
7102
  if (env.ELGORA_CHAIN_ID?.trim()) return void 0;
7098
- return `elgora-cli: using the default network, ${describeChain(DEFAULT_CHAIN_ID)}. For staging, ${STAGING_HINT}.`;
7103
+ return `elgora-cli: using the default network, ${describeChain(DEFAULT_CHAIN_ID)}. For testnet, ${TESTNET_HINT}.`;
7099
7104
  }
7100
7105
  function applyNetworkFlag(env, argv) {
7101
7106
  const present = argv.some(
@@ -7122,8 +7127,8 @@ function resolveChainId(selector) {
7122
7127
  }
7123
7128
 
7124
7129
  // src/version.ts
7125
- var CLI_VERSION = true ? "10.0.0" : "0.0.0-dev";
7126
- var CLI_NODE_ENGINES = true ? ">=24 <25" : ">=24 <25";
7130
+ var CLI_VERSION = true ? "10.2.0" : "0.0.0-dev";
7131
+ var CLI_NODE_ENGINES = true ? "24.x || 26.x" : "24.x || 26.x";
7127
7132
 
7128
7133
  // src/node-engines.ts
7129
7134
  function unsupportedNodeMessage(running = process.versions.node, range = CLI_NODE_ENGINES) {
@@ -7189,24 +7194,51 @@ function compareVersions(left, right) {
7189
7194
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
7190
7195
  import path2 from "path";
7191
7196
  import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
7192
- var DEFAULT_OUTPUT_ROOT2 = "./winning-submission";
7193
- async function main8(env = process.env, argv = process.argv.slice(2)) {
7194
- const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
7195
- const args = readPosterOpenWinningSubmissionArgs(remainingArgv);
7196
- const envWithDefaults = withDeploymentDefaults(env);
7197
- const config = loadClientDeploymentConfig(
7198
- envWithDefaults,
7199
- apiBaseUrlOverride
7200
- );
7201
- const rpcUrl = required3(envWithDefaults, "ELGORA_RPC_URL");
7202
- const posterPrivateKey = envWithDefaults.ELGORA_POSTER_PRIVATE_KEY?.trim();
7203
- if (!posterPrivateKey || !isHexBytes32(posterPrivateKey)) {
7197
+
7198
+ // src/poster-request.ts
7199
+ import { readFile as readFile4 } from "fs/promises";
7200
+ import { text as readStdinText3 } from "stream/consumers";
7201
+ function loadPosterConfig(env, apiBaseUrlOverride) {
7202
+ const defaults = withDeploymentDefaults(env);
7203
+ const clientConfig = loadClientDeploymentConfig(defaults, apiBaseUrlOverride);
7204
+ const rpcUrl = defaults.ELGORA_RPC_URL?.trim();
7205
+ if (!rpcUrl) throw new Error("ELGORA_RPC_URL is required");
7206
+ const privateKey = env.ELGORA_POSTER_PRIVATE_KEY?.trim();
7207
+ if (!privateKey || !isHexBytes32(privateKey)) {
7204
7208
  throw new Error(
7205
7209
  "ELGORA_POSTER_PRIVATE_KEY must be a 32-byte hex private key"
7206
7210
  );
7207
7211
  }
7208
- const account = privateKeyToAccount7(posterPrivateKey.toLowerCase());
7209
- const publicClient = createChainPublicClient(config.chainId, rpcUrl);
7212
+ return {
7213
+ ...clientConfig,
7214
+ rpcUrl,
7215
+ posterPrivateKey: privateKey
7216
+ };
7217
+ }
7218
+ async function readBountyChallengeMarkdown2(path5) {
7219
+ return path5 === "-" ? readStdinText3(process.stdin) : readFile4(path5, "utf8");
7220
+ }
7221
+ async function signPosterRequest(input) {
7222
+ return authorizeRequest({
7223
+ requestUrl: input.requestUrl.toString(),
7224
+ method: "POST",
7225
+ contentType: "text/markdown",
7226
+ body: input.markdown,
7227
+ chainId: input.config.chainId,
7228
+ hubAddress: input.config.hubAddress,
7229
+ address: input.account.address,
7230
+ sign: (typedData) => input.account.signTypedData(typedData)
7231
+ });
7232
+ }
7233
+
7234
+ // src/poster-open-winning-submission.ts
7235
+ var DEFAULT_OUTPUT_ROOT2 = "./winning-submission";
7236
+ async function main8(env = process.env, argv = process.argv.slice(2)) {
7237
+ const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
7238
+ const args = readPosterOpenWinningSubmissionArgs(remainingArgv);
7239
+ const config = loadPosterConfig(env, apiBaseUrlOverride);
7240
+ const account = privateKeyToAccount7(config.posterPrivateKey);
7241
+ const publicClient = createChainPublicClient(config.chainId, config.rpcUrl);
7210
7242
  await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
7211
7243
  const ephemeral = await generateX25519KeyPair();
7212
7244
  const retrieveUrl = new URL(
@@ -7216,14 +7248,14 @@ async function main8(env = process.env, argv = process.argv.slice(2)) {
7216
7248
  const retrieveBody = JSON.stringify({
7217
7249
  poster_ephemeral_public_key: ephemeral.public_key
7218
7250
  });
7219
- const retrieveAuthorization = await buildAuthorizationHeader({
7251
+ const retrieveAuthorization = await authorizeRequest({
7220
7252
  requestUrl: retrieveUrl.toString(),
7221
7253
  method: "POST",
7254
+ contentType: "application/json",
7222
7255
  body: retrieveBody,
7223
7256
  chainId: config.chainId,
7224
7257
  hubAddress: config.hubAddress,
7225
7258
  address: account.address,
7226
- block: await fetchSignableBlock(publicClient),
7227
7259
  sign: (typedData) => account.signTypedData(typedData)
7228
7260
  });
7229
7261
  const retrieved = deliveryRetrieveResponseSchema.parse(
@@ -7246,14 +7278,13 @@ async function main8(env = process.env, argv = process.argv.slice(2)) {
7246
7278
  });
7247
7279
  try {
7248
7280
  const contentPathname = `/api/bounties/${args.bountyId}/submissions/${retrieved.submission_commitment}/content`;
7249
- const contentAuthorization = await buildAuthorizationHeader({
7281
+ const contentAuthorization = await authorizeRequest({
7250
7282
  requestUrl: new URL(contentPathname, config.apiBaseUrl).toString(),
7251
7283
  method: "GET",
7252
7284
  body: "",
7253
7285
  chainId: config.chainId,
7254
7286
  hubAddress: config.hubAddress,
7255
7287
  address: account.address,
7256
- block: await fetchSignableBlock(publicClient),
7257
7288
  sign: (typedData) => account.signTypedData(typedData)
7258
7289
  });
7259
7290
  const { content_record, ciphertext_download_url } = solverSubmissionContentResponseSchema.parse(
@@ -7322,62 +7353,15 @@ function readPosterOpenWinningSubmissionArgs(argv) {
7322
7353
  ...outputDir ? { outputDir } : {}
7323
7354
  };
7324
7355
  }
7325
- function required3(env, name) {
7326
- const value = env[name]?.trim();
7327
- if (!value) throw new Error(`${name} is required`);
7328
- return value;
7329
- }
7330
7356
 
7331
7357
  // src/publish-fund.ts
7332
7358
  import {
7333
7359
  decodeFunctionData,
7334
- parseAbi as parseAbi2,
7360
+ erc20Abi,
7335
7361
  parseEventLogs
7336
7362
  } from "viem";
7337
7363
  import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
7338
-
7339
- // src/poster-request.ts
7340
- import { readFile as readFile4 } from "fs/promises";
7341
- import { text as readStdinText3 } from "stream/consumers";
7342
- function loadPosterConfig(env, apiBaseUrlOverride) {
7343
- const defaults = withDeploymentDefaults(env);
7344
- const clientConfig = loadClientDeploymentConfig(defaults, apiBaseUrlOverride);
7345
- const rpcUrl = defaults.ELGORA_RPC_URL?.trim();
7346
- if (!rpcUrl) throw new Error("ELGORA_RPC_URL is required");
7347
- const privateKey = env.ELGORA_POSTER_PRIVATE_KEY?.trim();
7348
- if (!privateKey || !isHexBytes32(privateKey)) {
7349
- throw new Error(
7350
- "ELGORA_POSTER_PRIVATE_KEY must be a 32-byte hex private key"
7351
- );
7352
- }
7353
- return {
7354
- ...clientConfig,
7355
- rpcUrl,
7356
- posterPrivateKey: privateKey
7357
- };
7358
- }
7359
- async function readBountyChallengeMarkdown2(path5) {
7360
- return path5 === "-" ? readStdinText3(process.stdin) : readFile4(path5, "utf8");
7361
- }
7362
- async function signPosterRequest(input) {
7363
- return buildAuthorizationHeader({
7364
- requestUrl: input.requestUrl.toString(),
7365
- method: "POST",
7366
- body: input.markdown,
7367
- chainId: input.config.chainId,
7368
- hubAddress: input.config.hubAddress,
7369
- address: input.account.address,
7370
- block: await fetchSignableBlock(input.publicClient),
7371
- sign: (typedData) => input.account.signTypedData(typedData)
7372
- });
7373
- }
7374
-
7375
- // src/publish-fund.ts
7376
7364
  var PUBLISH_FUND_USAGE = "Usage: elgora-cli poster:publish-fund <bounty_challenge_md_path|->";
7377
- var usdcAbi = parseAbi2([
7378
- "function approve(address spender,uint256 value) returns (bool)",
7379
- "function balanceOf(address account) view returns (uint256)"
7380
- ]);
7381
7365
  async function main9(env = process.env, argv = process.argv.slice(2)) {
7382
7366
  const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
7383
7367
  if (remainingArgv.length !== 1 || !remainingArgv[0])
@@ -7413,8 +7397,7 @@ async function main9(env = process.env, argv = process.argv.slice(2)) {
7413
7397
  requestUrl,
7414
7398
  markdown: bountyChallengeMarkdown,
7415
7399
  config,
7416
- account,
7417
- publicClient
7400
+ account
7418
7401
  });
7419
7402
  const prepared = await requestPreparePublication({
7420
7403
  requestUrl: requestUrl.toString(),
@@ -7449,7 +7432,7 @@ async function main9(env = process.env, argv = process.argv.slice(2)) {
7449
7432
  publicClient.getBalance({ address: account.address }),
7450
7433
  publicClient.readContract({
7451
7434
  address: tokenAddress,
7452
- abi: usdcAbi,
7435
+ abi: erc20Abi,
7453
7436
  functionName: "balanceOf",
7454
7437
  args: [account.address]
7455
7438
  })
@@ -7486,7 +7469,7 @@ async function main9(env = process.env, argv = process.argv.slice(2)) {
7486
7469
  } else {
7487
7470
  const approveHash = await walletClient.writeContract({
7488
7471
  address: tokenAddress,
7489
- abi: usdcAbi,
7472
+ abi: erc20Abi,
7490
7473
  functionName: "approve",
7491
7474
  args: [config.hubAddress, escrowAmount]
7492
7475
  });
@@ -7565,8 +7548,8 @@ async function requestPreparePublication(input) {
7565
7548
  }
7566
7549
 
7567
7550
  // src/required-env.ts
7568
- function missingRequiredEnv(required4, env) {
7569
- return required4.filter((envName) => !env[envName]?.trim());
7551
+ function missingRequiredEnv(required3, env) {
7552
+ return required3.filter((envName) => !env[envName]?.trim());
7570
7553
  }
7571
7554
 
7572
7555
  // src/review.ts
@@ -7586,10 +7569,10 @@ async function main10(env = process.env, argv = process.argv.slice(2)) {
7586
7569
  const { value: previous, argv: paths } = takeFlag(
7587
7570
  args,
7588
7571
  "--previous",
7589
- "--previous must be one bytes32 page commitment"
7572
+ "--previous must be one bytes32 challenge commitment"
7590
7573
  );
7591
7574
  if (previous && !isHexBytes32(previous))
7592
- throw new Error("--previous must be one bytes32 page commitment");
7575
+ throw new Error("--previous must be one bytes32 challenge commitment");
7593
7576
  if (paths.length !== 1 || !paths[0] || paths[0].startsWith("--"))
7594
7577
  throw new Error(USAGE2);
7595
7578
  const config = loadPosterConfig(env, apiBaseUrl);
@@ -7612,8 +7595,7 @@ async function main10(env = process.env, argv = process.argv.slice(2)) {
7612
7595
  requestUrl,
7613
7596
  markdown,
7614
7597
  config,
7615
- account,
7616
- publicClient
7598
+ account
7617
7599
  });
7618
7600
  let response;
7619
7601
  try {
@@ -7996,7 +7978,7 @@ async function readBountyChallengeMarkdown3(argv) {
7996
7978
  import { readdir, readFile as readFile7, stat } from "fs/promises";
7997
7979
  import path3 from "path";
7998
7980
  import { createInterface } from "readline";
7999
- import { isAddress as isAddress7 } from "viem";
7981
+ import { getTypesForEIP712Domain, isAddress as isAddress7, stringify } from "viem";
8000
7982
  import { privateKeyToAccount as privateKeyToAccount11 } from "viem/accounts";
8001
7983
 
8002
7984
  // src/external-api-approval-signer.ts
@@ -8076,19 +8058,16 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8076
8058
  return next.done ? void 0 : next.value;
8077
8059
  }
8078
8060
  });
8079
- const authorizeRequest = async (requestUrl, body) => {
8080
- const block = await fetchSignableBlock(publicClient);
8081
- return buildAuthorizationHeader({
8082
- requestUrl: requestUrl.toString(),
8083
- method: "POST",
8084
- body,
8085
- chainId: config.chainId,
8086
- hubAddress: config.hubAddress,
8087
- address: solver,
8088
- block,
8089
- sign: signApproval
8090
- });
8091
- };
8061
+ const authorizeSubmitRequest = (requestUrl, body) => authorizeRequest({
8062
+ requestUrl: requestUrl.toString(),
8063
+ method: "POST",
8064
+ body,
8065
+ contentType: "application/json",
8066
+ chainId: config.chainId,
8067
+ hubAddress: config.hubAddress,
8068
+ address: solver,
8069
+ sign: signApproval
8070
+ });
8092
8071
  const rosterResponse = await requestBountyGuardianRoster({
8093
8072
  apiBaseUrl: config.apiBaseUrl,
8094
8073
  bountyId: bountyIdText
@@ -8135,7 +8114,7 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8135
8114
  solver,
8136
8115
  ciphertextSha256: envelope.ciphertext_sha256,
8137
8116
  ciphertextByteLength: ciphertext.byteLength,
8138
- authorizeRequest
8117
+ authorizeRequest: authorizeSubmitRequest
8139
8118
  });
8140
8119
  if (uploadUrl.locator !== envelope.ciphertext_locator) {
8141
8120
  throw new Error(
@@ -8156,7 +8135,7 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8156
8135
  bountyId: bountyIdText,
8157
8136
  solver,
8158
8137
  envelope,
8159
- authorizeRequest
8138
+ authorizeRequest: authorizeSubmitRequest
8160
8139
  });
8161
8140
  } finally {
8162
8141
  externalApprovalInput?.close();
@@ -8255,14 +8234,19 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8255
8234
  }
8256
8235
  function createExternalApprovalSigner(input) {
8257
8236
  return createExternalApiApprovalSigner({
8258
- renderPrompt: (typedData) => JSON.stringify(
8259
- {
8260
- event: "submit.approval_required",
8261
- solver: input.solver.toLowerCase(),
8262
- typed_data: typedData
8263
- },
8264
- (_key, value) => typeof value === "bigint" ? value.toString() : value
8265
- ),
8237
+ // The eth_signTypedData_v4 shape: its types carry EIP712Domain, which
8238
+ // MetaMask-style signers need to hash the domain. The signed hash is the same.
8239
+ renderPrompt: (typedData) => stringify({
8240
+ event: "submit.approval_required",
8241
+ solver: input.solver.toLowerCase(),
8242
+ typed_data: {
8243
+ ...typedData,
8244
+ types: {
8245
+ EIP712Domain: getTypesForEIP712Domain({ domain: typedData.domain }),
8246
+ ...typedData.types
8247
+ }
8248
+ }
8249
+ }),
8266
8250
  writePrompt: input.writePrompt,
8267
8251
  readSignature: input.readSignature
8268
8252
  });
@@ -8400,7 +8384,7 @@ function extractSolverAddressFlag(argv) {
8400
8384
  for (let i = 0; i < argv.length; i++) {
8401
8385
  const arg = argv[i];
8402
8386
  if (arg === SOLVER_ADDRESS_FLAG) {
8403
- solverAddress = argv[i + 1];
8387
+ solverAddress = argv[i + 1] ?? "";
8404
8388
  i++;
8405
8389
  continue;
8406
8390
  }
@@ -8650,7 +8634,7 @@ var COMMANDS = {
8650
8634
  "roster:verify-guardian-proof": main13,
8651
8635
  "solver:submit": main15,
8652
8636
  claim: main2,
8653
- "check-page": main,
8637
+ "check-challenge": main,
8654
8638
  "spec-commitment": main14,
8655
8639
  "verification-record": main16
8656
8640
  };
@@ -8723,8 +8707,8 @@ var COMMAND_HELP = {
8723
8707
  },
8724
8708
  "poster:review": {
8725
8709
  usage: "elgora-cli poster:review <path|-> [--previous <commitment>]",
8726
- title: "Review a bounty page before publishing it.",
8727
- description: "Sign and send the exact Markdown for a readiness review. Ready prints\n review.ready; Elgora keeps the approval for these exact bytes.\n Not-ready prints all issues and exits 2; other failures exit 1.\n --previous sends the previous page commitment with the signed request.",
8710
+ title: "Review a challenge before publishing it.",
8711
+ description: "Sign and send the exact Markdown for a readiness review. Ready prints\n review.ready; Elgora keeps the approval for these exact bytes.\n Not-ready prints all issues and exits 2; other failures exit 1.\n --previous sends the previous challenge commitment with the signed request.",
8728
8712
  required: ["ELGORA_POSTER_PRIVATE_KEY"],
8729
8713
  defaulted: [
8730
8714
  "ELGORA_CHAIN_ID",
@@ -8802,10 +8786,10 @@ var COMMAND_HELP = {
8802
8786
  required: [],
8803
8787
  defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS", "ELGORA_RPC_URL"]
8804
8788
  },
8805
- "check-page": {
8806
- usage: "elgora-cli check-page <bounty_challenge_md_path|->",
8807
- title: "Check a challenge page's structure against Elgora's rules.",
8808
- description: "Run the same structural module the API runs before its readiness\n review: frontmatter, a known profile, and every declared tightening.\n Reports each issue with a stable code and the field that fixes it,\n and exits non-zero when any is found. A Guardian runs this against a\n bounty's committed bytes before judging, because a page can reach\n chain without passing publication. It checks structure only: whether\n the page tries to instruct you, or carries unsafe content, is a\n reading you make yourself. Pass '-' to read from stdin. Makes no\n network call and needs no configuration or secret.",
8789
+ "check-challenge": {
8790
+ usage: "elgora-cli check-challenge <bounty_challenge_md_path|->",
8791
+ title: "Check a challenge's structure against Elgora's rules.",
8792
+ description: "Run the same structural module the API runs before its readiness\n review: frontmatter, a known profile, and every declared tightening.\n Reports each issue with a stable code and the field that fixes it,\n and exits non-zero when any is found. A Guardian runs this against a\n bounty's committed bytes before judging, because a challenge can reach\n chain without passing publication. It checks structure only: whether\n the challenge tries to instruct you, or carries unsafe content, is a\n reading you make yourself. Pass '-' to read from stdin. Makes no\n network call and needs no configuration or secret.",
8809
8793
  required: []
8810
8794
  },
8811
8795
  "spec-commitment": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elgora/cli",
3
- "version": "10.0.0",
3
+ "version": "10.2.0",
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",
@@ -19,7 +19,7 @@
19
19
  "access": "public"
20
20
  },
21
21
  "engines": {
22
- "node": ">=24 <25"
22
+ "node": "24.x || 26.x"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup && chmod +x dist/cli.js",