# Submitting, step by step

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

```sh
elgora-cli solver:submit <bounty_id> ./solution
```

Point it at a folder, whose contents and subfolders become the Submission, or
at a single file. Underneath the one line 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

<Steps>
  <Step>
    ### 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](/docs/how-it-works/guardian-roster).
  </Step>

  <Step>
    ### 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 packing and sealing a large Submission.
  </Step>

  <Step>
    ### Pack and seal locally

    The client reads the bounty's challenge, checks it against the on-chain
    `spec_commitment`, and reads the protocol profile it declares: that decides the
    Submission format. It then checks every path, file type, count and size
    against the [package rules](/docs/solver/package), builds one `.tar.gz` of your
    files, and encrypts the archive's raw bytes 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](/docs/how-it-works/encryption).
  </Step>

  <Step>
    ### Check the size

    The encrypted object is the archive plus a 16-byte authentication tag, so the
    client knows its size as soon as the archive is built. If it would exceed
    50 MiB the run stops before any encryption work, and nothing is uploaded. Your
    files are also checked against the 250 MB extracted ceiling before they are
    read.
  </Step>

  <Step>
    ### 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.
  </Step>

  <Step>
    ### Upload ciphertext

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

    Emits `submit.ciphertext_uploaded` with `content_sha256`, a private digest of
    your files that goes nowhere else. The Guardians and, if you win, the Poster
    see the same value when they open your Submission.
  </Step>

  <Step>
    ### 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`.
  </Step>

  <Step>
    ### 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.
  </Step>

  <Step>
    ### Record the commitment on-chain

    `submit(bounty_id, submission_commitment, guardian_roster_hash, 0x)` from your
    wallet. With `--prepare-only` the client stops here and prints the exact
    transaction instead.

    Emits `submit.submission_recorded` with the transaction hash, the block, and
    `content_sha256` again.
  </Step>
</Steps>

## Signing with your wallet

The command signs two API requests and sends one transaction from your Solver
account. With `--signer manual --signer-address 0xYourWallet`, no key enters
the CLI process: it writes each request to a file and your own wallet tooling
answers it. A local key or a Privy server wallet works too; see
[Wallets and signers](/docs/reference/cli#wallets-and-signers).
Each request is signed just before it is sent; the scheme is documented in
[Request authorization](/docs/reference/authorization).

To send the transaction yourself, add `--prepare-only`:

```json
{"event":"submit.calls_prepared","chain_id":8453,"from":"0x…","calls":[{"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` from `from`. Do not substitute a different Solver,
commitment, roster hash, or `extra_data`.

<Callout title="Slow signers are handled">
  A signed request that goes stale before it reaches Elgora is signed again: the
  client retries automatically with a fresh approval when the server reports the
  request has expired.
</Callout>

## Where it can stop, and what it means

| Message                                                                                                | Meaning                                                                                                                                                                                                                  | What to do                                                                                                              |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `Bounty N is not open for submission (status: …)`                                                      | The bounty already settled or timed out                                                                                                                                                                                  | Nothing to do                                                                                                           |
| `Bounty N submission deadline has passed`                                                              | Too late                                                                                                                                                                                                                 | Nothing to do                                                                                                           |
| `Elgora API Guardian roster does not match its guardian_roster_hash`                                   | The roster the API returned is internally inconsistent                                                                                                                                                                   | Stop and report it; do not encrypt to it                                                                                |
| `…does not match this bounty's guardianRosterHash in ElgoraHub`                                        | The API's roster is not the one the contract pinned                                                                                                                                                                      | Stop. Encrypting to it would produce a Submission no Guardian can open                                                  |
| `Elgora API served no verified challenge…` / `…does not match bounty N's spec commitment in ElgoraHub` | The challenge the API served is missing or not the one the bounty committed to                                                                                                                                           | Try again later; never submit against unverified bytes                                                                  |
| `Bounty N declares a protocol profile this CLI does not know`                                          | The bounty is newer than your client                                                                                                                                                                                     | Upgrade `@elgora/cli` and run again                                                                                     |
| `…is a symbolic link` / `…is a FIFO` / `Folder "…" is empty` / `No files found…`                       | Package rules — see [Build the package](/docs/solver/package)                                                                                                                                                            | Replace the entry with the file it stands for, add the missing files, or remove it                                      |
| `"…" is not a valid Submission path`                                                                   | A name breaks the name rule                                                                                                                                                                                              | Rename it, if the challenge allows that name                                                                            |
| `…differ only in case` / `…needs it to be a folder`                                                    | Two paths would collide once extracted                                                                                                                                                                                   | Rename one                                                                                                              |
| `The Submission holds more than 10000 files…`                                                          | Too many files                                                                                                                                                                                                           | Combine them into fewer files, if the challenge allows it                                                               |
| `The Submission holds N bytes of files and exceeds…`                                                   | Past the 250 MB extracted ceiling                                                                                                                                                                                        | Reduce the files                                                                                                        |
| `This Submission packs to N bytes encrypted and exceeds Elgora's 50 MiB…`                              | The archive is too big to store                                                                                                                                                                                          | Reduce the files, or compress large results inside them                                                                 |
| Disqualified by a Guardian for extracted size                                                          | An archive inside the Submission unpacks past 250 MB, or past a lower budget the challenge set                                                                                                                           | Size your archives by what they unpack to; `solver:check` counts them only at their own size                            |
| `…returned a ciphertext locator that does not match the locally derived one`                           | Your chain/Hub configuration does not match the API's deployment                                                                                                                                                         | Check `--network` and `--api-base-url` agree                                                                            |
| `…returned a submission_commitment that does not match the locally sealed envelope`                    | The API is describing a different Submission                                                                                                                                                                             | Stop. Do not sign                                                                                                       |
| `…returned a submit_transaction that does not match this submission`                                   | The prepared call disagrees with the verified values                                                                                                                                                                     | Stop. Do not sign                                                                                                       |
| `bounty_projection_unavailable`                                                                        | The read model has not caught up with a very recent on-chain change                                                                                                                                                      | The client retries with backoff automatically                                                                           |
| `unauthorized`                                                                                         | The signed request went stale, or the signature did not verify                                                                                                                                                           | The client re-signs and retries; if it persists, check your signer returns the signature for the exact typed data shown |
| `403 forbidden`                                                                                        | The signing wallet is not the Solver named in the path                                                                                                                                                                   | Check `--signer-address` names the wallet your signer signs for                                                         |
| `Solver has no ETH on chain N`                                                                         | No gas on a local key                                                                                                                                                                                                    | Fund the wallet                                                                                                         |
| Revert `StaleGuardianRoster`                                                                           | The 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 deployment                                                                      | Check `--network` and `--api-base-url` against the bounty you meant, then re-run                                        |
| Revert `DeadlinePassed` / `InvalidStatus`                                                              | The window closed while you were signing                                                                                                                                                                                 | Nothing to do                                                                                                           |
| Revert `SubmissionRejected`                                                                            | The 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 this | Deployment policy; ask the operator                                                                                     |
| Revert `ContractPaused`                                                                                | The protocol owner has paused the Hub                                                                                                                                                                                    | Wait — see [Limits and control](/docs/how-it-works/limits)                                                              |

<Callout type="warn" title="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 retrieve or decrypt
  does not become a Verdict against it. The exception is your own commitment:
  stored ciphertext that does not match the hash you committed disqualifies that
  Submission, while the bounty's other Submissions are judged as normal.
</Callout>

## 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.
