Elgora docs

Submitting, step by step

What one solver:submit run does under the hood, how to sign with an external wallet, and every way it can stop.

elgora-cli solver:submit --solver-address 0xYourWallet <bounty_id> ./artifacts

The command is one line. Underneath it are nine steps, each of which can refuse to continue. Every check exists so that neither Elgora's API nor a stale read can get your wallet to sign something you did not mean.

What happens

Resolve the pinned Guardian roster

The client asks the API for the bounty, then reads ElgoraHub directly for the bounty's own pinned guardianRosterHash. It re-derives the hash from the roster it was given — names, addresses, and encryption keys, in order — and refuses to continue unless it matches the contract's pinned value exactly.

The API supplies the roster's contents; the contract decides whether they are the right contents. See The Guardian roster.

Check the bounty is submittable

Status must be open and the submission deadline must still be ahead. This runs before any encryption work, so a doomed submission fails in a second rather than after sealing 40 MB.

Seal the envelope locally

Your files are read, packaged, and encrypted with a fresh random AES-256 key. That key is wrapped once for each Guardian's public key, and the whole bag of wrapped keys is then time-locked to a public randomness beacon round that does not exist until your bounty's deadline has passed.

Nobody can open your Submission before then — not a Guardian, not Elgora, not the Poster. The mechanism is on Submission privacy.

Check the size

If the ciphertext exceeds 50 MiB the run stops here, before anything is uploaded. An oversized directory is caught earlier still, against the 37 MiB plaintext allowance, before any encryption work.

Get an upload target

A signed request exchanges the ciphertext's SHA-256 and byte length for a one-time upload URL. The client checks that the storage locator the API returned equals the one it derived itself; a mismatch means your configuration and the API disagree about the deployment, and it stops.

Upload ciphertext

Only ciphertext is uploaded. No plaintext artifact and no key ever leaves your machine.

Emits submit.ciphertext_uploaded.

Prepare the Submission

A second signed request sends the canonical envelope JSON. The API re-verifies it: internal digests, the roster-recipients commitment against the bounty's pinned roster, the reveal policy against the deadline, the storage locator, the ciphertext's integrity against the hash inside the envelope, and that the bounty is still open. Then it stores the record and returns the exact submit call it believes you should send.

The client derives the submission_commitment itself from the envelope it sealed, and stops if the API's differs.

Emits submit.submission_prepared.

Re-read the chain, then encode the transaction yourself

ElgoraHub is read again after preparation — status, deadline, pinned roster hash — so a stale API response can never reach your wallet. The client then encodes the submit calldata itself, from values it verified, against its own copy of the contract interface, and compares field by field with what the API returned. It signs its own encoding, never the server's bytes.

Record the commitment on-chain

submit(bounty_id, submission_commitment, guardian_roster_hash, 0x) from your wallet. In external-wallet mode the client stops here and prints the exact transaction instead.

Emits submit.submission_recorded with the transaction hash and block.

Signing with an external wallet

With --solver-address, no key enters the CLI process. You get two kinds of prompt on stdout.

Request approvals, one per protected API call:

{"event":"submit.approval_required","solver":"0x…","typed_data":{"domain":{…},"types":{…},"primaryType":"ApiRequestApproval","message":{…}}}

Sign that exact typed_data with the wallet at --solver-address and return only the hex signature, on one line, on stdin. The scheme is documented in Request authorization — read it before you automate the signing.

The transaction:

{"event":"submit.transaction_prepared","chain_id":8453,"to":"0x…","value":"0","data":"0x…","function_name":"submit","args":{…}}

Confirm your wallet is on the displayed chain and send that exact to, value, and data. Do not substitute a different Solver, commitment, roster hash, or extra_data.

Slow signers are handled

A hardware wallet or a multisig co-signer can take longer than the freshness window on a signed request. The client retries automatically with a freshly signed approval when the server reports the request has expired, so a slow approval is an inconvenience, not a failure.

Local signing — omit --solver-address, supply ELGORA_SOLVER_PRIVATE_KEY through your host's secret manager — performs the identical checks and sends the transaction for you. It is meant for controlled testing.

Where it can stop, and what it means

MessageMeaningWhat to do
Bounty N is not open for submission (status: …)The bounty already settled or timed outNothing to do
Bounty N submission deadline has passedToo lateNothing to do
Elgora API Guardian roster does not match its guardian_roster_hashThe roster the API returned is internally inconsistentStop and report it; do not encrypt to it
…does not match this bounty's guardianRosterHash in ElgoraHubThe API's roster is not the one the contract pinnedStop. Encrypting to it would produce a Submission no Guardian can open
Artifact directory must be flat… / No files found…Package rules — see Build the packageFlatten the directory; a challenge asking for a source/ folder does not change this
Artifact directory holds N files and exceeds Elgora's maximum of 500 filesToo many filesCombine them into fewer files, if the challenge allows it
Artifact directory exceeds Elgora's 37 MiB of files per SubmissionToo big before sealingReduce the package
Submission exceeds the maximum of 500 files.A package built by a client that skipped the checkRebuild within the limit; Guardians disqualify only this Submission
Encrypted submission is N bytes and exceeds Elgora's 50 MiB limitToo big after sealingReduce the package
Disqualified by a Guardian for extracted sizeThe Submission unpacks past 250 MB, or past a lower budget the challenge setNothing before decryption checks this — size the extracted content, not just the upload
…returned a ciphertext locator that does not match the locally derived oneYour chain/Hub configuration does not match the API's deploymentCheck --network and --api-base-url agree
…returned a submission_commitment that does not match the locally sealed envelopeThe API is describing a different SubmissionStop. Do not sign
…returned a submit_transaction that does not match this submissionThe prepared call disagrees with the verified valuesStop. Do not sign
bounty_projection_unavailableThe read model has not caught up with a very recent on-chain changeThe client retries with backoff automatically
unauthorizedThe signed request went stale, or the signature did not verifyThe client re-signs and retries; if it persists, check your signer returns the signature for the exact typed data shown
403 forbiddenThe signing wallet is not the Solver named in the pathSign with the wallet you passed to --solver-address
Solver has no ETH on chain NNo gasFund the wallet
Revert StaleGuardianRosterThe hash you submitted is not the one this bounty pinned — a bounty's pinned hash never changes, so this means the wrong bounty, API, or deploymentCheck --network and --api-base-url against the bounty you meant, then re-run
Revert DeadlinePassed / InvalidStatusThe window closed while you were signingNothing to do
Revert SubmissionRejectedThe deployment's submission guard declined the call. On Base mainnet each bounty admits up to 10 Solver accounts, so this bounty already has its full complement. Revising a submission you already made never hits thisDeployment policy; ask the operator
Revert ContractPausedThe protocol owner has paused the HubWait — see Limits and control

An operational failure is never a protocol result

If upload, verification, or decryption fails somewhere, that is a blocker to resolve — never evidence about a Submission's quality. Guardians are held to the same rule from the other side: a Submission they cannot open does not become a Verdict against it.

Replacing a Submission

Run the command again before the deadline. The new commitment overwrites your old one, on-chain and in Elgora's storage. There is no second slot, and the replaced Submission stops being eligible immediately.

Re-read the challenge before replacing — and keep a record of the final submission_commitment, because that is the value a Verdict and a delivery will name.

Read this page as Markdown

On this page