@elgora/cli 7.1.2 → 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.
- package/README.md +3 -2
- package/dist/cli.js +575 -52
- 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`
|
|
39
|
-
|
|
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).
|
|
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).
|
|
3699
|
+
issues: z6.array(readinessIssueSchema).max(MAX_ORCHESTRATOR_ISSUES)
|
|
3599
3700
|
}).strict();
|
|
3600
|
-
var
|
|
3601
|
-
|
|
3701
|
+
var providerMessageSchema = z6.object({
|
|
3702
|
+
id: z6.string().optional(),
|
|
3703
|
+
choices: z6.array(
|
|
3602
3704
|
z6.object({
|
|
3603
|
-
|
|
3604
|
-
|
|
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-
|
|
5710
|
+
// src/guardian-prove-roster-key.ts
|
|
5600
5711
|
import { readFile } from "fs/promises";
|
|
5601
|
-
import {
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
6113
|
+
import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
|
|
5925
6114
|
var DEFAULT_OUTPUT_ROOT2 = "./winning-submission";
|
|
5926
|
-
async function
|
|
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 =
|
|
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
|
|
6066
|
-
import { text as
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
6430
|
+
return readStdinText2(process.stdin);
|
|
6242
6431
|
}
|
|
6243
|
-
return
|
|
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
|
|
6273
|
-
import { text as
|
|
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
|
|
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
|
|
6781
|
+
return readStdinText3(process.stdin);
|
|
6293
6782
|
}
|
|
6294
|
-
return
|
|
6783
|
+
return readFile5(path5, "utf8");
|
|
6295
6784
|
}
|
|
6296
6785
|
|
|
6297
6786
|
// src/submit.ts
|
|
6298
|
-
import { readdir, readFile as
|
|
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
|
|
6302
|
-
import { privateKeyToAccount as
|
|
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
|
|
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" || !
|
|
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
|
|
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 ?
|
|
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 && !
|
|
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
|
|
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
|
|
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
|
|
7383
|
+
throw usageError4();
|
|
6895
7384
|
}
|
|
6896
7385
|
return { bountyId: BigInt(bountyId) };
|
|
6897
7386
|
}
|
|
6898
|
-
function
|
|
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:
|
|
7404
|
+
"guardian:prove-roster-key": main5,
|
|
7405
|
+
"guardian:verdict": main6,
|
|
6916
7406
|
"guardian:deliver-key": main3,
|
|
6917
|
-
"poster:open-winning-submission":
|
|
6918
|
-
"poster:publish-fund":
|
|
6919
|
-
"
|
|
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":
|
|
6922
|
-
"verification-record":
|
|
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: "
|
|
6968
|
-
description: "
|
|
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.1
|
|
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",
|