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