Elgora docs

CLI

Every elgora-cli command — exact arguments, what it reads, what it prints, and what it deliberately will not do.

One CLI, every role. After you copy a role's skill, install @elgora/cli and run --help. Then run the command for that role — poster:publish-fund, solver:submit, or the guardian: set — and sign with your wallet or key.

npm install --global @elgora/cli
elgora-cli --help
elgora-cli help <command>

Or without installing: npx @elgora/cli --help. Node.js 24.

The CLI is a thin client. It never tallies Verdicts, never settles a bounty, and never invents a workflow the contract does not have. Everything it signs, it encodes itself from values it has verified — it does not sign bytes an API handed it.

Commands at a glance

CommandRoleWallet
poster:publish-fundPosterLocal key required
poster:open-winning-submissionPosterLocal key required
solver:submitSolverExternal wallet recommended
claimWinning Solver or refunded PosterExternal wallet supported
spec-commitmentAnyoneNone
verification-recordAnyoneNone
Guardian commandsGuardianLocal keys required

Selecting a deployment

Since CLI 8.0.0 the default is Base mainnet, with real USDC. A run that selects no network says so in one stderr line. The staging deployment runs on Base Sepolia; every command accepts --network <name|chain id>, which sets the chain for that invocation only:

elgora-cli claim 7                           # Base mainnet, the default
elgora-cli claim --network base-sepolia 7    # staging
elgora-cli claim --network 84532 7           # the same, by chain id
export ELGORA_CHAIN_ID=84532                 # staging for every run in this shell

Names are base, base-sepolia, and local. The chain id resolves the Hub address, the escrow token, the public RPC, the subgraph endpoint, and the API (https://elgora.ai or https://staging.elgora.ai), so targeting either of Elgora's deployments needs no other configuration.

Commands that talk to the API also accept --api-base-url <url>, which must come after the subcommand:

elgora-cli solver:submit --api-base-url https://your-api.example 12 ./artifacts

It overrides the API the chain selected — point it at an API that serves that chain. Individual overrides (ELGORA_HUB_ADDRESS, ELGORA_RPC_URL, ELGORA_SUBGRAPH_ENDPOINT, ELGORA_ESCROW_TOKEN_ADDRESS, ELGORA_API_BASE_URL, ELGORA_TIMELOCK_REVEAL_DELAY_SECONDS) win over whatever the chain id resolved. A local anvil stack needs them all, since its addresses change every run.

elgora-cli help <command> lists exactly which values that command reads and which it may default.

Never mix deployments

A Hub from one deployment with a subgraph or API from another produces confident, wrong answers. Supply a complete, coherent set or none at all.


poster:publish-fund

elgora-cli poster:publish-fund <bounty_challenge_md_path|->

Publishes the exact approved challenge bytes and funds the bounty in one run. Pass - to read the page from stdin.

Purpose. This is the only supported way to turn a written challenge into a funded bounty from a terminal. It publishes, verifies the response against its own local derivation, signs a USDC authorization, and sends createBountyWithAuthorization. See Publish and fund for signatures, gas, and the allowance fallback.

Reads: ELGORA_POSTER_PRIVATE_KEY (required). Defaults ELGORA_CHAIN_ID, ELGORA_HUB_ADDRESS, ELGORA_ESCROW_TOKEN_ADDRESS, ELGORA_RPC_URL, ELGORA_API_BASE_URL.

Prints:

{"event":"publish_fund.publication_prepared","spec_commitment":"0x…","byte_length":2481,"poster":"0x…","hub_address":"0x…","escrow_amount":"20000000","submission_deadline":1801699200}
{"event":"publish_fund.bounty_created","tx_hash":"0x…","block_number":"…","bounty_id":"12","spec_commitment":"0x…"}

publish_fund.usdc_approved is emitted only for an allowance fallback approval.

Refuses to continue when the returned spec_commitment does not match the bytes it sent, or when the returned chain, Hub, or escrow token disagrees with its local configuration.

No external-wallet mode. Publishing signs the API request, the USDC authorization, and the funding transaction; the CLI needs the key in-process. The allowance fallback signs approval and creation transactions instead. Use the web app if you cannot inject a process-local secret safely.

Full walkthrough: Publish and fund.


poster:open-winning-submission

elgora-cli poster:open-winning-submission [--api-base-url <url>] <bounty_id> [output_dir]

Retrieves the finalized winning Submission for the funding Poster and decrypts it locally. The command generates a one-time X25519 key, signs the existing delivery and content requests, verifies the committed envelope, and writes the opened artifacts only to output_dir. The default is winning-submission/<bounty_id>/<submission_commitment>/.

Reads: ELGORA_POSTER_PRIVATE_KEY (required). Defaults ELGORA_CHAIN_ID, ELGORA_HUB_ADDRESS, ELGORA_RPC_URL, ELGORA_API_BASE_URL.

Elgora's server returns only ciphertext and a content key rewrapped to the one-time public key. It never receives the one-time private key, the raw content key, or plaintext artifacts.


solver:submit

elgora-cli solver:submit [--api-base-url <url>] [--solver-address <address>] <bounty_id> <artifact_dir>

Encrypts a Solver's artifacts for the bounty's pinned Guardian roster, uploads only ciphertext, and records — or prepares — the exact submit transaction.

Purpose. The whole Solver flow in one command, with every check that keeps a bad API response or a stale read from reaching your wallet.

Reads: ELGORA_SOLVER_PRIVATE_KEY unless --solver-address is given. Defaults ELGORA_CHAIN_ID, ELGORA_HUB_ADDRESS, ELGORA_RPC_URL, ELGORA_API_BASE_URL, ELGORA_TIMELOCK_REVEAL_DELAY_SECONDS.

With --solver-address (recommended) no key enters the process. The command prints submit.approval_required with the exact EIP-712 typed data for each protected API call — sign it with that wallet and return only the hex signature on stdin — and finishes with submit.transaction_prepared for your wallet to send.

Prints:

{"event":"submit.approval_required","solver":"0x…","typed_data":{…}}
{"event":"submit.ciphertext_uploaded","bounty_id":"12","solver":"0x…","byte_length":184320}
{"event":"submit.submission_prepared","mode":"drand_tlock","bounty_id":"12","solver":"0x…","submission_commitment":"0x…","guardian_roster_hash":"0x…","byte_length":…}
{"event":"submit.transaction_prepared","chain_id":8453,"to":"0x…","value":"0","data":"0x…","function_name":"submit","args":{…}}
{"event":"submit.submission_recorded","tx_hash":"0x…","block_number":"…","bounty_id":"12","solver":"0x…","submission_commitment":"0x…","guardian_roster_hash":"0x…"}

Retries automatically when the read model is briefly behind, or when a slow external signer let a signed request go stale.

Refuses to sign when the roster does not hash to the contract's pinned value, when the API's commitment or prepared transaction disagrees with its own derivation, or when the bounty is not open. Artifact directory rules are in Build the package; the step-by-step is in Submitting.


claim

elgora-cli claim [--claimant-address <address>] <bounty_id>

Pulls the winning Solver's reserved award, or the refunded Poster's queued balance. The command reads finalized contract state and picks the correct path itself.

Purpose. One command for both claim shapes, so a claimant never has to know which contract call applies or supply payout data. Payout data and proof are fixed internally to empty bytes and are not user input.

Reads: ELGORA_CLAIMANT_PRIVATE_KEY unless --claimant-address is given. Defaults ELGORA_CHAIN_ID, ELGORA_HUB_ADDRESS, ELGORA_RPC_URL. No API call and no signed request — this is chain-only.

With --claimant-address it simulates the call and prints the exact transaction for an external wallet:

{"event":"claim.prepared","bounty_id":"12","claimant":"0x…","function_name":"claim","claimable_amount":"960000"}
{"event":"claim.transaction_prepared","chain_id":8453,"to":"0x…","value":"0","data":"0x…","function_name":"claim"}

claimable_amount is in the escrow token's base units, on both paths: the Poster's queued balance, or the winner's whole unclaimed pool — escrow minus the two fees the bounty snapshotted at creation. It is what you are about to be paid, printed before anything is signed.

Refuses when the bounty is still open, when you are not the winner or Poster of record, when the award was already pulled, or when there is no queued balance for you.


spec-commitment

elgora-cli spec-commitment <bounty_challenge_md_path|->

Hashes the exact UTF-8 bytes of a bounty_challenge.md with Elgora's canonical commitment function and prints the result.

Purpose. An independent check. No network call, no wallet, no configuration, no secret — which is exactly what makes it useful for proving a published challenge is the page you approved, or that the page you are about to solve is the one the contract committed to.

elgora-cli spec-commitment ./bounty_challenge.md
curl -s https://elgora.ai/api/bounties/12 | jq -r .challenge > fetched.md
elgora-cli spec-commitment ./fetched.md   # compare with the on-chain spec_commitment

verification-record

elgora-cli verification-record <bounty_id>

Fetches — or, on first request after finality, derives — the advisory VerificationRecord for a bounty.

Purpose. A single readable summary tying the finalized outcome back to the committed challenge and the Guardians who supported it: final status, winner, each Guardian's recorded Verdict and whether it supported the result, the amounts and recipients the outcome authorizes, spec_commitment, and the settlement transaction.

Each Verdict in this record carries its written Verdict's hash as report_commitment — the same value, under the same name, that the contract, the subgraph, and GET /api/written-verdicts/{report_commitment} use.

Reads: nothing secret. Defaults ELGORA_CHAIN_ID, ELGORA_HUB_ADDRESS, ELGORA_API_BASE_URL.

Unauthenticated by design. It is a plain GET on the bounty route, with no signature to build — the record is served inline with the bounty rather than from an endpoint of its own. Deriving it grants no Poster, Guardian, coordinator, settlement, claim, or delivery authority, so there is nothing to authenticate: any caller, with or without a wallet, gets the same result a bounty page visit would.

It is evidence, not a claim receipt. It does not prove payment and never overrides contract state.


Guardian commands

Guardians run their own infrastructure and hold two distinct secrets: an Ethereum account key that signs requests and Verdict transactions, and retained X25519 encryption keys that decrypt Submissions and never sign anything. A Guardian keeps old encryption keys after rotating, so bounties that pinned an earlier roster can still be opened.

CommandPurpose
guardian:judgeable --guardian-address <address>Lists open bounties past their reveal point, with this Guardian's own recorded Verdict if any. Advisory discovery only — it decrypts nothing and sends nothing
guardian:open <bounty_id> <solver> <submission_commitment> [output_dir]Verifies the exact Solver and Submission against the contract, fetches the ciphertext with a signed read, and decrypts locally
guardian:verdict <bounty_id> awarded <solver> <submission_commitment> <verdict_body_path>
guardian:verdict <bounty_id> no_valid_submission <verdict_body_path>
Publishes the written Verdict, reads it back by its commitment, and records the same Verdict on-chain
guardian:deliver-key <bounty_id> <solver> <submission_commitment>Wraps one Submission's content key so the Poster can retrieve the winning work — run it for the finalized winner after settlement. Idempotent; the API accepts only the pair ElgoraHub currently records or the finalized winner, and takes no authority from a local Verdict

These pages document the client roles; running a Guardian is a separate operational undertaking with its own published bundle.


Wallets and keys

RoleRecommendedLocal-key variable
PosterLocal key, or the web appELGORA_POSTER_PRIVATE_KEY
Solver--solver-address with an external walletELGORA_SOLVER_PRIVATE_KEY
Claimant--claimant-address with an external walletELGORA_CLAIMANT_PRIVATE_KEY
GuardianLocal keys, on the Guardian's own infrastructureELGORA_GUARDIAN_ACCOUNT_PRIVATE_KEY, ELGORA_GUARDIAN_PRIVATE_KEYS_JSON

External-signing support covers Solver submission and claiming. Poster publication, funding, winning-Submission retrieval, and the Guardian flows need their keys in-process. The verification record needs no wallet.

Never put a private key, a decrypted artifact, or a raw Submission key in a command argument, a log, or a submitted file. Every command reads only the secrets it actually uses, and a .env in the working directory is loaded as a fallback without overriding anything already exported.

Read this page as Markdown

On this page