@elgora/cli 7.2.0 → 7.2.2
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 +650 -70
- 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
|
|
@@ -4787,6 +4890,16 @@ function readArgs(argv) {
|
|
|
4787
4890
|
};
|
|
4788
4891
|
}
|
|
4789
4892
|
|
|
4893
|
+
// ../../packages/subgraph-client/src/errors.ts
|
|
4894
|
+
var SubgraphReadError = class extends Error {
|
|
4895
|
+
reason;
|
|
4896
|
+
constructor(reason, message, options) {
|
|
4897
|
+
super(message, options);
|
|
4898
|
+
this.name = "SubgraphReadError";
|
|
4899
|
+
this.reason = reason;
|
|
4900
|
+
}
|
|
4901
|
+
};
|
|
4902
|
+
|
|
4790
4903
|
// ../../packages/subgraph-client/src/status.ts
|
|
4791
4904
|
function mapSubgraphStatus(value) {
|
|
4792
4905
|
switch (value) {
|
|
@@ -4862,6 +4975,7 @@ var SUBMISSION_FIELDS = `
|
|
|
4862
4975
|
recordedAt
|
|
4863
4976
|
recordedBlockNumber
|
|
4864
4977
|
recordedTxHash
|
|
4978
|
+
submissionCallCount
|
|
4865
4979
|
`;
|
|
4866
4980
|
var VERDICT_FIELDS = `
|
|
4867
4981
|
guardian
|
|
@@ -4877,6 +4991,7 @@ var GUARDIAN_ROSTER_SNAPSHOT_FIELDS = `
|
|
|
4877
4991
|
id
|
|
4878
4992
|
memberCount
|
|
4879
4993
|
members {
|
|
4994
|
+
position
|
|
4880
4995
|
account
|
|
4881
4996
|
name
|
|
4882
4997
|
encryptionPublicKey
|
|
@@ -5037,26 +5152,41 @@ function createElgoraSubgraphClient(options) {
|
|
|
5037
5152
|
return options.hubAddress;
|
|
5038
5153
|
}
|
|
5039
5154
|
async function execute(query, variables) {
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5155
|
+
let response;
|
|
5156
|
+
let raw;
|
|
5157
|
+
try {
|
|
5158
|
+
response = await fetchImpl(
|
|
5159
|
+
endpoint,
|
|
5160
|
+
JSON.stringify({ query, variables })
|
|
5161
|
+
);
|
|
5162
|
+
raw = await response.text();
|
|
5163
|
+
} catch (cause) {
|
|
5164
|
+
throw new SubgraphReadError(
|
|
5165
|
+
"transport_failed",
|
|
5166
|
+
"Subgraph request did not complete",
|
|
5167
|
+
{ cause }
|
|
5168
|
+
);
|
|
5169
|
+
}
|
|
5045
5170
|
if (!response.ok) {
|
|
5046
|
-
throw new
|
|
5171
|
+
throw new SubgraphReadError(
|
|
5172
|
+
"request_failed",
|
|
5047
5173
|
`Subgraph request failed with status ${response.status}: ${raw}`
|
|
5048
5174
|
);
|
|
5049
5175
|
}
|
|
5050
5176
|
const parsed = parseJson(raw);
|
|
5051
5177
|
const errors = readRecord(parsed).errors;
|
|
5052
5178
|
if (Array.isArray(errors) && errors.length > 0) {
|
|
5053
|
-
throw new
|
|
5179
|
+
throw new SubgraphReadError(
|
|
5180
|
+
"query_errors",
|
|
5054
5181
|
`Subgraph query returned errors: ${JSON.stringify(errors)}`
|
|
5055
5182
|
);
|
|
5056
5183
|
}
|
|
5057
5184
|
const data = readRecord(parsed).data;
|
|
5058
5185
|
if (data === void 0 || data === null) {
|
|
5059
|
-
throw new
|
|
5186
|
+
throw new SubgraphReadError(
|
|
5187
|
+
"malformed_response",
|
|
5188
|
+
"Subgraph response did not include a data object"
|
|
5189
|
+
);
|
|
5060
5190
|
}
|
|
5061
5191
|
return data;
|
|
5062
5192
|
}
|
|
@@ -5246,7 +5376,8 @@ function mapAnalyticsGuardianRoster(row) {
|
|
|
5246
5376
|
const members = asArray(record.members, "members");
|
|
5247
5377
|
const memberCount = asNumber(record.memberCount, "memberCount");
|
|
5248
5378
|
if (members.length !== memberCount) {
|
|
5249
|
-
throw new
|
|
5379
|
+
throw new SubgraphReadError(
|
|
5380
|
+
"unexpected_shape",
|
|
5250
5381
|
"Historical Guardian roster members do not match memberCount"
|
|
5251
5382
|
);
|
|
5252
5383
|
}
|
|
@@ -5343,12 +5474,17 @@ function mapSubmissionFields(row) {
|
|
|
5343
5474
|
record.recordedBlockNumber,
|
|
5344
5475
|
"recordedBlockNumber"
|
|
5345
5476
|
),
|
|
5346
|
-
recordedTxHash: asHex(record.recordedTxHash, "recordedTxHash")
|
|
5477
|
+
recordedTxHash: asHex(record.recordedTxHash, "recordedTxHash"),
|
|
5478
|
+
submissionCallCount: asNumber(
|
|
5479
|
+
record.submissionCallCount,
|
|
5480
|
+
"submissionCallCount"
|
|
5481
|
+
)
|
|
5347
5482
|
};
|
|
5348
5483
|
}
|
|
5349
5484
|
function mapGuardianRosterMember(row) {
|
|
5350
5485
|
const record = readRecord(row);
|
|
5351
5486
|
return {
|
|
5487
|
+
position: asSafeInteger(record.position, "members.position"),
|
|
5352
5488
|
account: asHex(record.account, "account"),
|
|
5353
5489
|
name: asString(record.name, "name"),
|
|
5354
5490
|
encryptionPublicKey: asString(
|
|
@@ -5359,10 +5495,19 @@ function mapGuardianRosterMember(row) {
|
|
|
5359
5495
|
}
|
|
5360
5496
|
function mapGuardianRosterSnapshot(row) {
|
|
5361
5497
|
const record = readRecord(row);
|
|
5498
|
+
const members = asArray(record.members, "members").map(mapGuardianRosterMember).sort((left, right) => left.position - right.position);
|
|
5499
|
+
members.forEach((member, index) => {
|
|
5500
|
+
if (member.position !== index) {
|
|
5501
|
+
throw new SubgraphReadError(
|
|
5502
|
+
"unexpected_shape",
|
|
5503
|
+
`Guardian roster snapshot positions are not 0..n-1 for '${asHex(record.id, "id")}'`
|
|
5504
|
+
);
|
|
5505
|
+
}
|
|
5506
|
+
});
|
|
5362
5507
|
return {
|
|
5363
5508
|
rosterHash: asHex(record.id, "id"),
|
|
5364
5509
|
memberCount: asNumber(record.memberCount, "memberCount"),
|
|
5365
|
-
members:
|
|
5510
|
+
members: members.map(({ position: _position, ...member }) => member),
|
|
5366
5511
|
recordedAt: asString(record.recordedAt, "recordedAt")
|
|
5367
5512
|
};
|
|
5368
5513
|
}
|
|
@@ -5441,7 +5586,10 @@ function parseJson(raw) {
|
|
|
5441
5586
|
try {
|
|
5442
5587
|
return JSON.parse(raw);
|
|
5443
5588
|
} catch {
|
|
5444
|
-
throw new
|
|
5589
|
+
throw new SubgraphReadError(
|
|
5590
|
+
"malformed_response",
|
|
5591
|
+
`Subgraph returned non-JSON response: ${raw}`
|
|
5592
|
+
);
|
|
5445
5593
|
}
|
|
5446
5594
|
}
|
|
5447
5595
|
function readRecord(value) {
|
|
@@ -5449,12 +5597,16 @@ function readRecord(value) {
|
|
|
5449
5597
|
}
|
|
5450
5598
|
function asString(value, field) {
|
|
5451
5599
|
if (typeof value === "string") return value;
|
|
5452
|
-
throw new
|
|
5600
|
+
throw new SubgraphReadError(
|
|
5601
|
+
"unexpected_shape",
|
|
5602
|
+
`Expected string for subgraph field '${field}'`
|
|
5603
|
+
);
|
|
5453
5604
|
}
|
|
5454
5605
|
function asUnsignedBigInt(value, field) {
|
|
5455
5606
|
const text = asString(value, field);
|
|
5456
5607
|
if (/^(?:0|[1-9][0-9]*)$/.test(text)) return BigInt(text);
|
|
5457
|
-
throw new
|
|
5608
|
+
throw new SubgraphReadError(
|
|
5609
|
+
"unexpected_shape",
|
|
5458
5610
|
`Expected non-negative integer string for subgraph field '${field}'`
|
|
5459
5611
|
);
|
|
5460
5612
|
}
|
|
@@ -5464,29 +5616,42 @@ function asNullableString(value, field) {
|
|
|
5464
5616
|
}
|
|
5465
5617
|
function asNumber(value, field) {
|
|
5466
5618
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
5467
|
-
throw new
|
|
5619
|
+
throw new SubgraphReadError(
|
|
5620
|
+
"unexpected_shape",
|
|
5621
|
+
`Expected number for subgraph field '${field}'`
|
|
5622
|
+
);
|
|
5468
5623
|
}
|
|
5469
5624
|
function asSafeInteger(value, field) {
|
|
5470
5625
|
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
|
|
5471
5626
|
return value;
|
|
5472
5627
|
}
|
|
5473
|
-
throw new
|
|
5628
|
+
throw new SubgraphReadError(
|
|
5629
|
+
"unexpected_shape",
|
|
5474
5630
|
`Expected non-negative integer for subgraph field '${field}'`
|
|
5475
5631
|
);
|
|
5476
5632
|
}
|
|
5477
5633
|
function asBoolean(value, field) {
|
|
5478
5634
|
if (typeof value === "boolean") return value;
|
|
5479
|
-
throw new
|
|
5635
|
+
throw new SubgraphReadError(
|
|
5636
|
+
"unexpected_shape",
|
|
5637
|
+
`Expected boolean for subgraph field '${field}'`
|
|
5638
|
+
);
|
|
5480
5639
|
}
|
|
5481
5640
|
function asArray(value, field) {
|
|
5482
5641
|
if (Array.isArray(value)) return value;
|
|
5483
|
-
throw new
|
|
5642
|
+
throw new SubgraphReadError(
|
|
5643
|
+
"unexpected_shape",
|
|
5644
|
+
`Expected array for subgraph field '${field}'`
|
|
5645
|
+
);
|
|
5484
5646
|
}
|
|
5485
5647
|
function asHex(value, field) {
|
|
5486
5648
|
if (typeof value === "string" && value.startsWith("0x")) {
|
|
5487
5649
|
return value.toLowerCase();
|
|
5488
5650
|
}
|
|
5489
|
-
throw new
|
|
5651
|
+
throw new SubgraphReadError(
|
|
5652
|
+
"unexpected_shape",
|
|
5653
|
+
`Expected hex string for subgraph field '${field}'`
|
|
5654
|
+
);
|
|
5490
5655
|
}
|
|
5491
5656
|
function asNullableHex(value, field) {
|
|
5492
5657
|
if (value === null || value === void 0) return null;
|
|
@@ -5548,7 +5713,10 @@ async function main4(env = process.env, argv = process.argv.slice(2)) {
|
|
|
5548
5713
|
},
|
|
5549
5714
|
submissions: submissions.map((submission) => ({
|
|
5550
5715
|
solver: submission.solver,
|
|
5551
|
-
submission_commitment: submission.submissionCommitment
|
|
5716
|
+
submission_commitment: submission.submissionCommitment,
|
|
5717
|
+
submitted_at: submission.recordedAt,
|
|
5718
|
+
submitted_at_block: submission.recordedBlockNumber,
|
|
5719
|
+
submission_call_count: submission.submissionCallCount
|
|
5552
5720
|
}))
|
|
5553
5721
|
});
|
|
5554
5722
|
}
|
|
@@ -5596,10 +5764,88 @@ function requiredUrl(env, name) {
|
|
|
5596
5764
|
}
|
|
5597
5765
|
}
|
|
5598
5766
|
|
|
5599
|
-
// src/guardian-
|
|
5767
|
+
// src/guardian-prove-roster-key.ts
|
|
5600
5768
|
import { readFile } from "fs/promises";
|
|
5601
|
-
import {
|
|
5769
|
+
import { text as readStdinText } from "stream/consumers";
|
|
5602
5770
|
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
5771
|
+
async function main5(env = process.env, argv = process.argv.slice(2)) {
|
|
5772
|
+
const [path5] = argv;
|
|
5773
|
+
if (!path5) {
|
|
5774
|
+
throw new Error(
|
|
5775
|
+
"Usage: elgora-cli guardian:prove-roster-key <challenge_json_path|->"
|
|
5776
|
+
);
|
|
5777
|
+
}
|
|
5778
|
+
const challenge = parseChallenge(
|
|
5779
|
+
path5 === "-" ? await readStdinText(process.stdin) : await readFile(path5, "utf8")
|
|
5780
|
+
);
|
|
5781
|
+
const envWithDefaults = withDeploymentDefaults(env);
|
|
5782
|
+
const { chainId, hubAddress } = loadPublicDeploymentConfig(envWithDefaults);
|
|
5783
|
+
if (challenge.context.chain_id !== chainId || challenge.context.hub_address.toLowerCase() !== hubAddress.toLowerCase()) {
|
|
5784
|
+
throw new Error(
|
|
5785
|
+
`Challenge names chain ${challenge.context.chain_id} / Hub ${challenge.context.hub_address}, not this deployment's chain ${chainId} / Hub ${hubAddress}`
|
|
5786
|
+
);
|
|
5787
|
+
}
|
|
5788
|
+
const guardianAccountPrivateKey = envWithDefaults.ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY?.trim().toLowerCase();
|
|
5789
|
+
if (!guardianAccountPrivateKey || !isHexBytes32(guardianAccountPrivateKey)) {
|
|
5790
|
+
throw new Error(
|
|
5791
|
+
"ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY must be a 32-byte hex private key"
|
|
5792
|
+
);
|
|
5793
|
+
}
|
|
5794
|
+
const account = privateKeyToAccount5(guardianAccountPrivateKey);
|
|
5795
|
+
if (account.address.toLowerCase() !== challenge.context.proposed_account) {
|
|
5796
|
+
throw new Error(
|
|
5797
|
+
`ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY resolves to ${account.address.toLowerCase()}, but this challenge names proposed_account ${challenge.context.proposed_account}`
|
|
5798
|
+
);
|
|
5799
|
+
}
|
|
5800
|
+
const guardianPrivateKeys = readGuardianPrivateKeys(env).map(
|
|
5801
|
+
(key) => key.private_key
|
|
5802
|
+
);
|
|
5803
|
+
const fixture = await openGuardianRosterKeyProofFixture({
|
|
5804
|
+
challenge,
|
|
5805
|
+
recipientPrivateKeys: guardianPrivateKeys
|
|
5806
|
+
});
|
|
5807
|
+
const signedPayload = buildGuardianRosterKeyProofSignedPayload(
|
|
5808
|
+
challenge.message,
|
|
5809
|
+
fixture
|
|
5810
|
+
);
|
|
5811
|
+
const signature = await account.signMessage({
|
|
5812
|
+
message: { raw: signedPayload }
|
|
5813
|
+
});
|
|
5814
|
+
console.log(
|
|
5815
|
+
JSON.stringify({
|
|
5816
|
+
event: "guardian_prove_roster_key.signed",
|
|
5817
|
+
guardian_account: account.address.toLowerCase(),
|
|
5818
|
+
proposed_encryption_public_key: challenge.context.proposed_encryption_public_key,
|
|
5819
|
+
signature
|
|
5820
|
+
})
|
|
5821
|
+
);
|
|
5822
|
+
}
|
|
5823
|
+
function parseChallenge(text) {
|
|
5824
|
+
let parsed;
|
|
5825
|
+
try {
|
|
5826
|
+
parsed = JSON.parse(text);
|
|
5827
|
+
} catch (error) {
|
|
5828
|
+
throw new Error("Challenge input must be valid JSON", { cause: error });
|
|
5829
|
+
}
|
|
5830
|
+
const challenge = parsed && typeof parsed === "object" && "send_to_guardian" in parsed ? parsed.send_to_guardian : parsed;
|
|
5831
|
+
if (!challenge || typeof challenge !== "object" || typeof challenge.message !== "string" || typeof challenge.enc !== "string" || typeof challenge.ciphertext !== "string") {
|
|
5832
|
+
throw new Error(
|
|
5833
|
+
"Challenge JSON must contain context, message, enc, and ciphertext (the object roster:prepare-guardian-proof printed as send_to_guardian)"
|
|
5834
|
+
);
|
|
5835
|
+
}
|
|
5836
|
+
const context = challenge.context;
|
|
5837
|
+
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") {
|
|
5838
|
+
throw new Error(
|
|
5839
|
+
"Challenge context must contain chain_id, hub_address, proposed_account, and proposed_encryption_public_key"
|
|
5840
|
+
);
|
|
5841
|
+
}
|
|
5842
|
+
return challenge;
|
|
5843
|
+
}
|
|
5844
|
+
|
|
5845
|
+
// src/guardian-verdict.ts
|
|
5846
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
5847
|
+
import { zeroAddress as zeroAddress4 } from "viem";
|
|
5848
|
+
import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
|
|
5603
5849
|
var OUTCOME = {
|
|
5604
5850
|
no_valid_submission: 0,
|
|
5605
5851
|
awarded: 1
|
|
@@ -5634,7 +5880,7 @@ function parseVerdictArgs(argv) {
|
|
|
5634
5880
|
}
|
|
5635
5881
|
throw usageError();
|
|
5636
5882
|
}
|
|
5637
|
-
async function
|
|
5883
|
+
async function main6(env = process.env, argv = process.argv.slice(2)) {
|
|
5638
5884
|
const { apiBaseUrl: apiBaseUrlOverride, argv: positionalArgs } = extractApiBaseUrlFlag(argv);
|
|
5639
5885
|
const args = parseVerdictArgs(positionalArgs);
|
|
5640
5886
|
const envWithDefaults = withDeploymentDefaults(env);
|
|
@@ -5650,11 +5896,11 @@ async function main5(env = process.env, argv = process.argv.slice(2)) {
|
|
|
5650
5896
|
"ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY must be a 32-byte hex private key"
|
|
5651
5897
|
);
|
|
5652
5898
|
}
|
|
5653
|
-
const account =
|
|
5899
|
+
const account = privateKeyToAccount6(guardianPrivateKey.toLowerCase());
|
|
5654
5900
|
const publicClient = createChainPublicClient(config.chainId, rpcUrl);
|
|
5655
5901
|
const walletClient = createChainWalletClient(config.chainId, rpcUrl, account);
|
|
5656
5902
|
await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
|
|
5657
|
-
const verdictBody = await
|
|
5903
|
+
const verdictBody = await readFile2(args.verdictBodyPath, "utf8");
|
|
5658
5904
|
const markdown = args.outcome === "awarded" ? buildWrittenVerdictMarkdown({
|
|
5659
5905
|
config,
|
|
5660
5906
|
bountyId: args.bountyId,
|
|
@@ -5921,9 +6167,9 @@ function resolveChainId(selector) {
|
|
|
5921
6167
|
// src/poster-open-winning-submission.ts
|
|
5922
6168
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
5923
6169
|
import path2 from "path";
|
|
5924
|
-
import { privateKeyToAccount as
|
|
6170
|
+
import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
|
|
5925
6171
|
var DEFAULT_OUTPUT_ROOT2 = "./winning-submission";
|
|
5926
|
-
async function
|
|
6172
|
+
async function main7(env = process.env, argv = process.argv.slice(2)) {
|
|
5927
6173
|
const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
|
|
5928
6174
|
const args = readPosterOpenWinningSubmissionArgs(remainingArgv);
|
|
5929
6175
|
const envWithDefaults = withDeploymentDefaults(env);
|
|
@@ -5938,7 +6184,7 @@ async function main6(env = process.env, argv = process.argv.slice(2)) {
|
|
|
5938
6184
|
"ELGORA_POSTER_PRIVATE_KEY must be a 32-byte hex private key"
|
|
5939
6185
|
);
|
|
5940
6186
|
}
|
|
5941
|
-
const account =
|
|
6187
|
+
const account = privateKeyToAccount7(posterPrivateKey.toLowerCase());
|
|
5942
6188
|
const publicClient = createChainPublicClient(config.chainId, rpcUrl);
|
|
5943
6189
|
await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
|
|
5944
6190
|
const ephemeral = await generateX25519KeyPair();
|
|
@@ -6062,26 +6308,26 @@ function required3(env, name) {
|
|
|
6062
6308
|
}
|
|
6063
6309
|
|
|
6064
6310
|
// src/publish-fund.ts
|
|
6065
|
-
import { readFile as
|
|
6066
|
-
import { text as
|
|
6311
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
6312
|
+
import { text as readStdinText2 } from "stream/consumers";
|
|
6067
6313
|
import {
|
|
6068
6314
|
parseAbi,
|
|
6069
6315
|
parseEventLogs
|
|
6070
6316
|
} from "viem";
|
|
6071
|
-
import { privateKeyToAccount as
|
|
6317
|
+
import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
|
|
6072
6318
|
var PUBLISH_FUND_USAGE = "Usage: elgora-cli poster:publish-fund <bounty_challenge_md_path>\n or: elgora-cli poster:publish-fund -";
|
|
6073
6319
|
var usdcAbi = parseAbi([
|
|
6074
6320
|
"function approve(address spender,uint256 value) returns (bool)",
|
|
6075
6321
|
"function balanceOf(address account) view returns (uint256)"
|
|
6076
6322
|
]);
|
|
6077
|
-
async function
|
|
6323
|
+
async function main8(env = process.env, argv = process.argv.slice(2)) {
|
|
6078
6324
|
const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
|
|
6079
6325
|
const config = loadPublishFundConfig(
|
|
6080
6326
|
withDeploymentDefaults(env),
|
|
6081
6327
|
apiBaseUrlOverride
|
|
6082
6328
|
);
|
|
6083
6329
|
const bountyChallengeMarkdown = await readBountyChallengeMarkdown(remainingArgv);
|
|
6084
|
-
const account =
|
|
6330
|
+
const account = privateKeyToAccount8(config.posterPrivateKey);
|
|
6085
6331
|
const publicClient = createChainPublicClient(config.chainId, config.rpcUrl);
|
|
6086
6332
|
const walletClient = createChainWalletClient(
|
|
6087
6333
|
config.chainId,
|
|
@@ -6238,9 +6484,9 @@ async function readBountyChallengeMarkdown(argv) {
|
|
|
6238
6484
|
throw new Error(PUBLISH_FUND_USAGE);
|
|
6239
6485
|
}
|
|
6240
6486
|
if (path5 === "-") {
|
|
6241
|
-
return
|
|
6487
|
+
return readStdinText2(process.stdin);
|
|
6242
6488
|
}
|
|
6243
|
-
return
|
|
6489
|
+
return readFile3(path5, "utf8");
|
|
6244
6490
|
}
|
|
6245
6491
|
function loadPublishFundConfig(env, apiBaseUrlOverride) {
|
|
6246
6492
|
const clientConfig = loadClientDeploymentConfig(env, apiBaseUrlOverride);
|
|
@@ -6268,11 +6514,311 @@ function loadPublishFundConfig(env, apiBaseUrlOverride) {
|
|
|
6268
6514
|
};
|
|
6269
6515
|
}
|
|
6270
6516
|
|
|
6517
|
+
// src/roster-add-guardian.ts
|
|
6518
|
+
import { isAddress as isAddress4 } from "viem";
|
|
6519
|
+
import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
|
|
6520
|
+
var ROSTER_ADMIN_ADDRESS_FLAG = "--roster-admin-address";
|
|
6521
|
+
var ENCRYPTION_PUBLIC_KEY_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
6522
|
+
async function main9(env = process.env, argv = process.argv.slice(2)) {
|
|
6523
|
+
const args = parseRosterAddGuardianArgs(argv);
|
|
6524
|
+
const config = loadRosterAddGuardianConfig(env, args.rosterAdminAddress);
|
|
6525
|
+
const account = config.rosterAdminPrivateKey ? privateKeyToAccount9(config.rosterAdminPrivateKey) : null;
|
|
6526
|
+
const rosterAdmin = account?.address ?? args.rosterAdminAddress;
|
|
6527
|
+
if (!rosterAdmin) throw new Error("Roster admin address is required");
|
|
6528
|
+
const normalizedRosterAdmin = rosterAdmin.toLowerCase();
|
|
6529
|
+
const publicClient = createChainPublicClient(config.chainId, config.rpcUrl);
|
|
6530
|
+
await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
|
|
6531
|
+
const liveRosterAdmin = await publicClient.readContract({
|
|
6532
|
+
address: config.hubAddress,
|
|
6533
|
+
abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
|
|
6534
|
+
functionName: "guardianRosterAdmin"
|
|
6535
|
+
});
|
|
6536
|
+
if (liveRosterAdmin.toLowerCase() !== normalizedRosterAdmin) {
|
|
6537
|
+
throw new Error(
|
|
6538
|
+
`${normalizedRosterAdmin} is not the live guardianRosterAdmin (${liveRosterAdmin.toLowerCase()})`
|
|
6539
|
+
);
|
|
6540
|
+
}
|
|
6541
|
+
const roster = await publicClient.readContract({
|
|
6542
|
+
address: config.hubAddress,
|
|
6543
|
+
abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
|
|
6544
|
+
functionName: "getGuardianRoster"
|
|
6545
|
+
});
|
|
6546
|
+
if (roster.some(
|
|
6547
|
+
(guardian) => guardian.account.toLowerCase() === args.guardianAccount
|
|
6548
|
+
)) {
|
|
6549
|
+
throw new Error(
|
|
6550
|
+
`${args.guardianAccount} is already on the Guardian roster`
|
|
6551
|
+
);
|
|
6552
|
+
}
|
|
6553
|
+
const contractArgs = [
|
|
6554
|
+
args.name,
|
|
6555
|
+
args.guardianAccount,
|
|
6556
|
+
args.encryptionPublicKey
|
|
6557
|
+
];
|
|
6558
|
+
if (!account) {
|
|
6559
|
+
await publicClient.simulateContract({
|
|
6560
|
+
account: normalizedRosterAdmin,
|
|
6561
|
+
address: config.hubAddress,
|
|
6562
|
+
abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
|
|
6563
|
+
functionName: "addGuardian",
|
|
6564
|
+
args: contractArgs
|
|
6565
|
+
});
|
|
6566
|
+
const prepared = encodeAddGuardianTransaction({
|
|
6567
|
+
chainId: config.chainId,
|
|
6568
|
+
hubAddress: config.hubAddress,
|
|
6569
|
+
name: args.name,
|
|
6570
|
+
account: args.guardianAccount,
|
|
6571
|
+
encryptionPublicKey: args.encryptionPublicKey
|
|
6572
|
+
});
|
|
6573
|
+
console.log(
|
|
6574
|
+
JSON.stringify({
|
|
6575
|
+
event: "roster_add_guardian.transaction_prepared",
|
|
6576
|
+
...prepared
|
|
6577
|
+
})
|
|
6578
|
+
);
|
|
6579
|
+
return;
|
|
6580
|
+
}
|
|
6581
|
+
const walletClient = createChainWalletClient(
|
|
6582
|
+
config.chainId,
|
|
6583
|
+
config.rpcUrl,
|
|
6584
|
+
account
|
|
6585
|
+
);
|
|
6586
|
+
const ethBalance = await publicClient.getBalance({
|
|
6587
|
+
address: account.address
|
|
6588
|
+
});
|
|
6589
|
+
if (ethBalance === 0n) {
|
|
6590
|
+
throw new Error(
|
|
6591
|
+
`Roster admin has no gas token balance: ${account.address}`
|
|
6592
|
+
);
|
|
6593
|
+
}
|
|
6594
|
+
await publicClient.simulateContract({
|
|
6595
|
+
account: account.address,
|
|
6596
|
+
address: config.hubAddress,
|
|
6597
|
+
abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
|
|
6598
|
+
functionName: "addGuardian",
|
|
6599
|
+
args: contractArgs
|
|
6600
|
+
});
|
|
6601
|
+
const transactionHash = await walletClient.writeContract({
|
|
6602
|
+
address: config.hubAddress,
|
|
6603
|
+
abi: ELGORA_HUB_ADD_GUARDIAN_ABI,
|
|
6604
|
+
functionName: "addGuardian",
|
|
6605
|
+
args: contractArgs
|
|
6606
|
+
});
|
|
6607
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
6608
|
+
hash: transactionHash
|
|
6609
|
+
});
|
|
6610
|
+
if (receipt.status !== "success") {
|
|
6611
|
+
throw new Error(`addGuardian transaction reverted: ${transactionHash}`);
|
|
6612
|
+
}
|
|
6613
|
+
console.log(
|
|
6614
|
+
JSON.stringify({
|
|
6615
|
+
event: "roster_add_guardian.recorded",
|
|
6616
|
+
tx_hash: transactionHash,
|
|
6617
|
+
block_number: receipt.blockNumber.toString(),
|
|
6618
|
+
name: args.name,
|
|
6619
|
+
account: args.guardianAccount,
|
|
6620
|
+
encryption_public_key: args.encryptionPublicKey
|
|
6621
|
+
})
|
|
6622
|
+
);
|
|
6623
|
+
}
|
|
6624
|
+
function parseRosterAddGuardianArgs(argv) {
|
|
6625
|
+
const usesRosterAdminAddress = argv[0] === ROSTER_ADMIN_ADDRESS_FLAG;
|
|
6626
|
+
const rosterAdminAddress = usesRosterAdminAddress ? argv[1]?.trim() : void 0;
|
|
6627
|
+
if (usesRosterAdminAddress && (!rosterAdminAddress || !isAddress4(rosterAdminAddress))) {
|
|
6628
|
+
throw usageError2();
|
|
6629
|
+
}
|
|
6630
|
+
const positional = usesRosterAdminAddress ? argv.slice(2) : argv;
|
|
6631
|
+
if (positional.length !== 3) throw usageError2();
|
|
6632
|
+
const name = positional[0]?.trim();
|
|
6633
|
+
const guardianAccount = positional[1]?.trim();
|
|
6634
|
+
const encryptionPublicKey = positional[2]?.trim();
|
|
6635
|
+
if (!name) throw usageError2();
|
|
6636
|
+
if (!guardianAccount || !isAddress4(guardianAccount)) {
|
|
6637
|
+
throw new Error("guardian_account must be a valid EVM address");
|
|
6638
|
+
}
|
|
6639
|
+
if (!encryptionPublicKey || !ENCRYPTION_PUBLIC_KEY_RE.test(encryptionPublicKey)) {
|
|
6640
|
+
throw new Error(
|
|
6641
|
+
"encryption_public_key must be an unpadded base64url X25519 public key (43 characters)"
|
|
6642
|
+
);
|
|
6643
|
+
}
|
|
6644
|
+
return {
|
|
6645
|
+
name,
|
|
6646
|
+
guardianAccount: guardianAccount.toLowerCase(),
|
|
6647
|
+
encryptionPublicKey,
|
|
6648
|
+
...rosterAdminAddress ? { rosterAdminAddress: rosterAdminAddress.toLowerCase() } : {}
|
|
6649
|
+
};
|
|
6650
|
+
}
|
|
6651
|
+
function loadRosterAddGuardianConfig(env, rosterAdminAddress) {
|
|
6652
|
+
const envWithDefaults = withDeploymentDefaults(env);
|
|
6653
|
+
const publicDeploymentConfig = loadPublicDeploymentConfig(envWithDefaults);
|
|
6654
|
+
const rpcUrl = envWithDefaults.ELGORA_RPC_URL?.trim();
|
|
6655
|
+
if (!rpcUrl) {
|
|
6656
|
+
throw new Error("ELGORA_RPC_URL is required");
|
|
6657
|
+
}
|
|
6658
|
+
const rosterAdminPrivateKey = envWithDefaults.ELGORA_GUARDIAN_ROSTER_ADMIN_PRIVATE_KEY?.trim();
|
|
6659
|
+
if (!rosterAdminAddress && (!rosterAdminPrivateKey || !isHexBytes32(rosterAdminPrivateKey))) {
|
|
6660
|
+
throw new Error(
|
|
6661
|
+
"ELGORA_GUARDIAN_ROSTER_ADMIN_PRIVATE_KEY must be a 32-byte hex private key unless --roster-admin-address is provided"
|
|
6662
|
+
);
|
|
6663
|
+
}
|
|
6664
|
+
return {
|
|
6665
|
+
...publicDeploymentConfig,
|
|
6666
|
+
rpcUrl,
|
|
6667
|
+
rosterAdminPrivateKey: rosterAdminAddress ? null : rosterAdminPrivateKey?.toLowerCase()
|
|
6668
|
+
};
|
|
6669
|
+
}
|
|
6670
|
+
function usageError2() {
|
|
6671
|
+
return new Error(
|
|
6672
|
+
"Usage: elgora-cli roster:add-guardian [--roster-admin-address <address>] <name> <guardian_account> <encryption_public_key>"
|
|
6673
|
+
);
|
|
6674
|
+
}
|
|
6675
|
+
|
|
6676
|
+
// src/roster-prepare-guardian-proof.ts
|
|
6677
|
+
import { isAddress as isAddress5 } from "viem";
|
|
6678
|
+
var ENCRYPTION_PUBLIC_KEY_RE2 = /^[A-Za-z0-9_-]{43}$/;
|
|
6679
|
+
async function main10(env = process.env, argv = process.argv.slice(2)) {
|
|
6680
|
+
const args = parseArgs(argv);
|
|
6681
|
+
const envWithDefaults = withDeploymentDefaults(env);
|
|
6682
|
+
const { chainId, hubAddress } = loadPublicDeploymentConfig(envWithDefaults);
|
|
6683
|
+
const { challenge, fixture } = await prepareGuardianRosterKeyProof({
|
|
6684
|
+
context: {
|
|
6685
|
+
chain_id: chainId,
|
|
6686
|
+
hub_address: hubAddress,
|
|
6687
|
+
proposed_account: args.guardianAccount,
|
|
6688
|
+
proposed_encryption_public_key: args.encryptionPublicKey
|
|
6689
|
+
}
|
|
6690
|
+
});
|
|
6691
|
+
console.log(
|
|
6692
|
+
JSON.stringify({
|
|
6693
|
+
event: "roster_guardian_proof.challenge_prepared",
|
|
6694
|
+
send_to_guardian: challenge,
|
|
6695
|
+
keep_local_do_not_share: {
|
|
6696
|
+
fixture: bytesToBase64Url(fixture),
|
|
6697
|
+
note: "Needed for roster:verify-guardian-proof once the Guardian replies. Never send this to the Guardian \u2014 sending it defeats the proof."
|
|
6698
|
+
}
|
|
6699
|
+
})
|
|
6700
|
+
);
|
|
6701
|
+
}
|
|
6702
|
+
function parseArgs(argv) {
|
|
6703
|
+
if (argv.length !== 2) throw usageError3();
|
|
6704
|
+
const guardianAccount = argv[0]?.trim();
|
|
6705
|
+
const encryptionPublicKey = argv[1]?.trim();
|
|
6706
|
+
if (!guardianAccount || !isAddress5(guardianAccount)) {
|
|
6707
|
+
throw new Error("guardian_account must be a valid EVM address");
|
|
6708
|
+
}
|
|
6709
|
+
if (!encryptionPublicKey || !ENCRYPTION_PUBLIC_KEY_RE2.test(encryptionPublicKey)) {
|
|
6710
|
+
throw new Error(
|
|
6711
|
+
"encryption_public_key must be an unpadded base64url X25519 public key (43 characters)"
|
|
6712
|
+
);
|
|
6713
|
+
}
|
|
6714
|
+
return {
|
|
6715
|
+
guardianAccount: guardianAccount.toLowerCase(),
|
|
6716
|
+
encryptionPublicKey
|
|
6717
|
+
};
|
|
6718
|
+
}
|
|
6719
|
+
function usageError3() {
|
|
6720
|
+
return new Error(
|
|
6721
|
+
"Usage: elgora-cli roster:prepare-guardian-proof <guardian_account> <encryption_public_key>"
|
|
6722
|
+
);
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
// src/roster-verify-guardian-proof.ts
|
|
6726
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
6727
|
+
import { isAddress as isAddress6, isHex as isHex5, recoverMessageAddress } from "viem";
|
|
6728
|
+
async function main11(_env = process.env, argv = process.argv.slice(2)) {
|
|
6729
|
+
const [preparedPath, responsePath] = argv;
|
|
6730
|
+
if (!preparedPath || !responsePath) {
|
|
6731
|
+
throw new Error(
|
|
6732
|
+
"Usage: elgora-cli roster:verify-guardian-proof <prepared_json_path> <response_json_path>"
|
|
6733
|
+
);
|
|
6734
|
+
}
|
|
6735
|
+
const prepared = parsePrepared(await readFile4(preparedPath, "utf8"));
|
|
6736
|
+
const response = parseResponse(await readFile4(responsePath, "utf8"));
|
|
6737
|
+
const fixture = base64UrlToBytes(prepared.fixture);
|
|
6738
|
+
const signedPayload = buildGuardianRosterKeyProofSignedPayload(
|
|
6739
|
+
prepared.message,
|
|
6740
|
+
fixture
|
|
6741
|
+
);
|
|
6742
|
+
const recoveredAddress = (await recoverMessageAddress({
|
|
6743
|
+
message: { raw: signedPayload },
|
|
6744
|
+
signature: response.signature
|
|
6745
|
+
})).toLowerCase();
|
|
6746
|
+
const proposedAccount = prepared.proposedAccount;
|
|
6747
|
+
const succeeded = recoveredAddress === proposedAccount && response.guardianAccount === proposedAccount;
|
|
6748
|
+
console.log(
|
|
6749
|
+
JSON.stringify({
|
|
6750
|
+
event: succeeded ? "roster_guardian_proof.verified" : "roster_guardian_proof.failed",
|
|
6751
|
+
proposed_account: proposedAccount,
|
|
6752
|
+
recovered_signer: recoveredAddress,
|
|
6753
|
+
response_guardian_account: response.guardianAccount,
|
|
6754
|
+
succeeded
|
|
6755
|
+
})
|
|
6756
|
+
);
|
|
6757
|
+
if (!succeeded) {
|
|
6758
|
+
throw new Error(
|
|
6759
|
+
`Guardian roster key proof FAILED for ${proposedAccount}: recovered signer ${recoveredAddress} does not match`
|
|
6760
|
+
);
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
function parsePrepared(text) {
|
|
6764
|
+
let parsed;
|
|
6765
|
+
try {
|
|
6766
|
+
parsed = JSON.parse(text);
|
|
6767
|
+
} catch (error) {
|
|
6768
|
+
throw new Error("Prepared proof JSON must be valid JSON", {
|
|
6769
|
+
cause: error
|
|
6770
|
+
});
|
|
6771
|
+
}
|
|
6772
|
+
const sendToGuardian = get(parsed, "send_to_guardian");
|
|
6773
|
+
const message = get(sendToGuardian, "message");
|
|
6774
|
+
const context = get(sendToGuardian, "context");
|
|
6775
|
+
const proposedAccount = get(context, "proposed_account");
|
|
6776
|
+
const keepLocal = get(parsed, "keep_local_do_not_share");
|
|
6777
|
+
const fixture = get(keepLocal, "fixture");
|
|
6778
|
+
if (typeof message !== "string" || typeof proposedAccount !== "string" || !isAddress6(proposedAccount) || typeof fixture !== "string") {
|
|
6779
|
+
throw new Error(
|
|
6780
|
+
"Prepared proof JSON must be the unmodified stdout of roster:prepare-guardian-proof"
|
|
6781
|
+
);
|
|
6782
|
+
}
|
|
6783
|
+
return {
|
|
6784
|
+
message,
|
|
6785
|
+
fixture,
|
|
6786
|
+
proposedAccount: proposedAccount.toLowerCase()
|
|
6787
|
+
};
|
|
6788
|
+
}
|
|
6789
|
+
function parseResponse(text) {
|
|
6790
|
+
let parsed;
|
|
6791
|
+
try {
|
|
6792
|
+
parsed = JSON.parse(text);
|
|
6793
|
+
} catch (error) {
|
|
6794
|
+
throw new Error("Guardian response JSON must be valid JSON", {
|
|
6795
|
+
cause: error
|
|
6796
|
+
});
|
|
6797
|
+
}
|
|
6798
|
+
const guardianAccount = get(parsed, "guardian_account");
|
|
6799
|
+
const signature = get(parsed, "signature");
|
|
6800
|
+
if (typeof guardianAccount !== "string" || !isAddress6(guardianAccount) || typeof signature !== "string" || !isHex5(signature) || signature.length !== 132) {
|
|
6801
|
+
throw new Error(
|
|
6802
|
+
"Guardian response JSON must be the unmodified stdout of guardian:prove-roster-key"
|
|
6803
|
+
);
|
|
6804
|
+
}
|
|
6805
|
+
return {
|
|
6806
|
+
guardianAccount: guardianAccount.toLowerCase(),
|
|
6807
|
+
signature
|
|
6808
|
+
};
|
|
6809
|
+
}
|
|
6810
|
+
function get(value, key) {
|
|
6811
|
+
if (!value || typeof value !== "object" || !(key in value)) {
|
|
6812
|
+
return void 0;
|
|
6813
|
+
}
|
|
6814
|
+
return value[key];
|
|
6815
|
+
}
|
|
6816
|
+
|
|
6271
6817
|
// src/spec-commitment.ts
|
|
6272
|
-
import { readFile as
|
|
6273
|
-
import { text as
|
|
6818
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
6819
|
+
import { text as readStdinText3 } from "stream/consumers";
|
|
6274
6820
|
var SPEC_COMMITMENT_USAGE = "Usage: elgora-cli spec-commitment <bounty_challenge_md_path>\n or: elgora-cli spec-commitment -";
|
|
6275
|
-
async function
|
|
6821
|
+
async function main12(_env = process.env, argv = process.argv.slice(2)) {
|
|
6276
6822
|
const markdown = await readBountyChallengeMarkdown2(argv);
|
|
6277
6823
|
const specCommitment = deriveBountyChallengeContentCommitment(markdown);
|
|
6278
6824
|
console.log(
|
|
@@ -6289,25 +6835,25 @@ async function readBountyChallengeMarkdown2(argv) {
|
|
|
6289
6835
|
throw new Error(SPEC_COMMITMENT_USAGE);
|
|
6290
6836
|
}
|
|
6291
6837
|
if (path5 === "-") {
|
|
6292
|
-
return
|
|
6838
|
+
return readStdinText3(process.stdin);
|
|
6293
6839
|
}
|
|
6294
|
-
return
|
|
6840
|
+
return readFile5(path5, "utf8");
|
|
6295
6841
|
}
|
|
6296
6842
|
|
|
6297
6843
|
// src/submit.ts
|
|
6298
|
-
import { readdir, readFile as
|
|
6844
|
+
import { readdir, readFile as readFile6, stat } from "fs/promises";
|
|
6299
6845
|
import path3 from "path";
|
|
6300
6846
|
import { createInterface } from "readline";
|
|
6301
|
-
import { isAddress as
|
|
6302
|
-
import { privateKeyToAccount as
|
|
6847
|
+
import { isAddress as isAddress7 } from "viem";
|
|
6848
|
+
import { privateKeyToAccount as privateKeyToAccount10 } from "viem/accounts";
|
|
6303
6849
|
|
|
6304
6850
|
// src/external-api-approval-signer.ts
|
|
6305
|
-
import { isHex as
|
|
6851
|
+
import { isHex as isHex6 } from "viem";
|
|
6306
6852
|
function createExternalApiApprovalSigner(input) {
|
|
6307
6853
|
return async (typedData) => {
|
|
6308
6854
|
input.writePrompt(input.renderPrompt(typedData));
|
|
6309
6855
|
const signature = (await input.readSignature())?.trim();
|
|
6310
|
-
if (!signature || signature === "0x" || !
|
|
6856
|
+
if (!signature || signature === "0x" || !isHex6(signature)) {
|
|
6311
6857
|
throw new Error(
|
|
6312
6858
|
"External signer must return one non-empty 0x-prefixed hex signature line on stdin"
|
|
6313
6859
|
);
|
|
@@ -6349,13 +6895,13 @@ var MEDIA_TYPE_BY_EXTENSION = {
|
|
|
6349
6895
|
diff: "text/x-diff"
|
|
6350
6896
|
};
|
|
6351
6897
|
var DEFAULT_MEDIA_TYPE = "application/octet-stream";
|
|
6352
|
-
async function
|
|
6898
|
+
async function main13(env = process.env, argv = process.argv.slice(2)) {
|
|
6353
6899
|
const { solverAddress, argv: argvWithoutSolverAddress } = extractSolverAddressFlag(argv);
|
|
6354
6900
|
const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argvWithoutSolverAddress);
|
|
6355
6901
|
const config = loadSubmitConfig(env, apiBaseUrlOverride, solverAddress);
|
|
6356
6902
|
const args = readSubmitArgs(remainingArgv);
|
|
6357
6903
|
const bountyIdText = args.bountyId.toString();
|
|
6358
|
-
const account = config.solverPrivateKey ?
|
|
6904
|
+
const account = config.solverPrivateKey ? privateKeyToAccount10(config.solverPrivateKey) : null;
|
|
6359
6905
|
const solverAddressFromConfig = account?.address ?? config.solverAddress;
|
|
6360
6906
|
if (!solverAddressFromConfig) throw new Error("Solver address is required");
|
|
6361
6907
|
const solver = solverAddressFromConfig.toLowerCase();
|
|
@@ -6694,7 +7240,7 @@ function extractSolverAddressFlag(argv) {
|
|
|
6694
7240
|
}
|
|
6695
7241
|
if (arg !== void 0) remaining.push(arg);
|
|
6696
7242
|
}
|
|
6697
|
-
if (solverAddress !== void 0 && !
|
|
7243
|
+
if (solverAddress !== void 0 && !isAddress7(solverAddress)) {
|
|
6698
7244
|
throw new Error("--solver-address must be a valid EVM address");
|
|
6699
7245
|
}
|
|
6700
7246
|
return {
|
|
@@ -6724,7 +7270,7 @@ async function readArtifactDirectory(artifactDir) {
|
|
|
6724
7270
|
}
|
|
6725
7271
|
return Promise.all(
|
|
6726
7272
|
entries.map(async (entry) => {
|
|
6727
|
-
const bytes = await
|
|
7273
|
+
const bytes = await readFile6(path3.join(artifactDir, entry.name));
|
|
6728
7274
|
return {
|
|
6729
7275
|
name: entry.name,
|
|
6730
7276
|
media_type: guessMediaType(entry.name),
|
|
@@ -6834,7 +7380,7 @@ function readTimelockRevealDelaySeconds(env) {
|
|
|
6834
7380
|
|
|
6835
7381
|
// src/verification-record.ts
|
|
6836
7382
|
var VERIFICATION_RECORD_USAGE = "Usage: elgora-cli verification-record <bounty_id>";
|
|
6837
|
-
async function
|
|
7383
|
+
async function main14(env = process.env, argv = process.argv.slice(2)) {
|
|
6838
7384
|
const { apiBaseUrl: apiBaseUrlOverride, argv: remainingArgv } = extractApiBaseUrlFlag(argv);
|
|
6839
7385
|
const args = readVerificationRecordArgs(remainingArgv);
|
|
6840
7386
|
const envWithDefaults = withDeploymentDefaults(env);
|
|
@@ -6891,11 +7437,11 @@ function readVerificationRecordArgs(argv) {
|
|
|
6891
7437
|
const [bountyIdText] = argv;
|
|
6892
7438
|
const bountyId = bountyIdText?.trim();
|
|
6893
7439
|
if (argv.length !== 1 || !bountyId || !isBountyId(bountyId)) {
|
|
6894
|
-
throw
|
|
7440
|
+
throw usageError4();
|
|
6895
7441
|
}
|
|
6896
7442
|
return { bountyId: BigInt(bountyId) };
|
|
6897
7443
|
}
|
|
6898
|
-
function
|
|
7444
|
+
function usageError4() {
|
|
6899
7445
|
return new Error(VERIFICATION_RECORD_USAGE);
|
|
6900
7446
|
}
|
|
6901
7447
|
|
|
@@ -6912,14 +7458,18 @@ process.stderr.write(
|
|
|
6912
7458
|
var COMMANDS = {
|
|
6913
7459
|
"guardian:judgeable": main4,
|
|
6914
7460
|
"guardian:open": main2,
|
|
6915
|
-
"guardian:
|
|
7461
|
+
"guardian:prove-roster-key": main5,
|
|
7462
|
+
"guardian:verdict": main6,
|
|
6916
7463
|
"guardian:deliver-key": main3,
|
|
6917
|
-
"poster:open-winning-submission":
|
|
6918
|
-
"poster:publish-fund":
|
|
6919
|
-
"
|
|
7464
|
+
"poster:open-winning-submission": main7,
|
|
7465
|
+
"poster:publish-fund": main8,
|
|
7466
|
+
"roster:add-guardian": main9,
|
|
7467
|
+
"roster:prepare-guardian-proof": main10,
|
|
7468
|
+
"roster:verify-guardian-proof": main11,
|
|
7469
|
+
"solver:submit": main13,
|
|
6920
7470
|
claim: main,
|
|
6921
|
-
"spec-commitment":
|
|
6922
|
-
"verification-record":
|
|
7471
|
+
"spec-commitment": main12,
|
|
7472
|
+
"verification-record": main14
|
|
6923
7473
|
};
|
|
6924
7474
|
var COMMAND_HELP = {
|
|
6925
7475
|
"guardian:judgeable": {
|
|
@@ -6949,6 +7499,16 @@ var COMMAND_HELP = {
|
|
|
6949
7499
|
],
|
|
6950
7500
|
apiBaseUrlFlag: true
|
|
6951
7501
|
},
|
|
7502
|
+
"guardian:prove-roster-key": {
|
|
7503
|
+
usage: "elgora-cli guardian:prove-roster-key <challenge_json_path|->",
|
|
7504
|
+
title: "Prove control of a proposed roster account and HPKE key.",
|
|
7505
|
+
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.",
|
|
7506
|
+
required: [
|
|
7507
|
+
"ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY",
|
|
7508
|
+
"ELGORA_GUARDIAN_PRIVATE_KEYS_JSON"
|
|
7509
|
+
],
|
|
7510
|
+
defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS"]
|
|
7511
|
+
},
|
|
6952
7512
|
"guardian:verdict": {
|
|
6953
7513
|
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
7514
|
title: "Publish and record this Guardian's Verdict.",
|
|
@@ -6964,8 +7524,8 @@ var COMMAND_HELP = {
|
|
|
6964
7524
|
},
|
|
6965
7525
|
"guardian:deliver-key": {
|
|
6966
7526
|
usage: "elgora-cli guardian:deliver-key <bounty_id> <solver> <submission_commitment>",
|
|
6967
|
-
title: "
|
|
6968
|
-
description: "
|
|
7527
|
+
title: "Deliver the finalized winner's content key for Poster retrieval.",
|
|
7528
|
+
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
7529
|
required: [
|
|
6970
7530
|
"ELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY",
|
|
6971
7531
|
"ELGORA_GUARDIAN_PRIVATE_KEYS_JSON"
|
|
@@ -7005,6 +7565,26 @@ var COMMAND_HELP = {
|
|
|
7005
7565
|
],
|
|
7006
7566
|
apiBaseUrlFlag: true
|
|
7007
7567
|
},
|
|
7568
|
+
"roster:add-guardian": {
|
|
7569
|
+
usage: "elgora-cli roster:add-guardian [--roster-admin-address <address>]\n <name> <guardian_account> <encryption_public_key>",
|
|
7570
|
+
title: "Append one Guardian to the live roster.",
|
|
7571
|
+
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.",
|
|
7572
|
+
required: [],
|
|
7573
|
+
defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS", "ELGORA_RPC_URL"]
|
|
7574
|
+
},
|
|
7575
|
+
"roster:prepare-guardian-proof": {
|
|
7576
|
+
usage: "elgora-cli roster:prepare-guardian-proof <guardian_account> <encryption_public_key>",
|
|
7577
|
+
title: "Start the key-custody proof for a proposed Guardian.",
|
|
7578
|
+
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.",
|
|
7579
|
+
required: [],
|
|
7580
|
+
defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS"]
|
|
7581
|
+
},
|
|
7582
|
+
"roster:verify-guardian-proof": {
|
|
7583
|
+
usage: "elgora-cli roster:verify-guardian-proof <prepared_json_path> <response_json_path>",
|
|
7584
|
+
title: "Check a Guardian's key-custody proof response.",
|
|
7585
|
+
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.",
|
|
7586
|
+
required: []
|
|
7587
|
+
},
|
|
7008
7588
|
"solver:submit": {
|
|
7009
7589
|
usage: "elgora-cli solver:submit [--api-base-url <url>] [--solver-address <address>] <bounty_id> <artifact_dir>",
|
|
7010
7590
|
title: "Encrypt and submit a Solver's private artifacts.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elgora/cli",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.2",
|
|
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",
|