# Publish and fund

> What one publish-fund run actually does, which values land on-chain versus in Elgora's storage, and every way it can stop.

Publishing and funding is one command in the CLI and one button on the
website, but four distinct things happen inside it. Knowing which is which
tells you exactly what a failure cost you. The examples below use the CLI; the
website does the same four steps with a browser wallet, and an
[API client](/docs/poster#the-api-for-a-sandboxed-harness) does them itself.

```sh
elgora-cli poster:review ./bounty_challenge.md
# Approve the exact reviewed text, then fund:
elgora-cli poster:publish-fund ./bounty_challenge.md
# or read the page from stdin
cat ./bounty_challenge.md | elgora-cli poster:publish-fund -
```

## The four steps

<Steps>
  <Step>
    ### Publish the bytes

    The client signs one request over the exact page bytes and posts them as
    `text/markdown`. Elgora checks current terms, then reuses its saved approval of
    those exact bytes. It stores the exact bytes, derives `spec_commitment`, and
    returns prepared `createBounty` arguments plus the Hub's current fee policy.

    The AI is not called again for bytes that already passed review in the last
    seven days. Publishing itself reviews bytes without a ready approval from the
    last seven days on this site, under the same quota and rules; if they are not
    ready, nothing is published. That review can take up to about three minutes and
    returns no warnings. Run `poster:review` first so you see the issues and
    warnings before you publish; a changed file or an approval older than seven
    days needs [another review](/docs/poster/readiness-review).

    Nothing has been spent. Emits `publish_fund.publication_prepared`.
  </Step>

  <Step>
    ### Verify the response locally

    The client re-derives `spec_commitment` from the bytes it just sent and
    compares. It then requires the returned chain id, Hub address, and escrow token
    to match its own local configuration.

    This matters: a faulty or hostile API could otherwise talk your wallet into
    signing a call you did not intend. Any client you write should do the same, and
    should build the transaction from its own copy of the contract interface rather
    than from a `to`/`data` pair the server handed it.
  </Step>

  <Step>
    ### Authorize the escrow token

    The client checks you hold enough USDC and some native gas, then signs a
    short-lived USDC authorization for the exact escrow amount to the Hub. No new
    allowance is required; previously granted allowances are not revoked.

    Contract wallets, EIP-7702 accounts, and every other CLI signer use the
    approval path instead: an `approve` for
    exactly the escrow amount, sent with creation, as one batch where the wallet
    supports it. It emits `publish_fund.usdc_approved` only when an approval is
    sent. Cancelling a signature stops publication, without switching to approval.
  </Step>

  <Step>
    ### Create the bounty

    `createBountyWithAuthorization` spends the authorization and opens the bounty
    in one funding transaction. You sign the authorization and the transaction,
    and still pay gas. If creation fails, the authorization remains unspent.
    The allowance fallback uses `createBounty`; a successful exact approval and
    transfer consumes that approval. The client reads the `BountyCreated` event
    back and prints your `bounty_id`.

    Emits `publish_fund.bounty_created`.
  </Step>
</Steps>

## What ends up where

<Callout title="The short version">
  The chain gets hashes, money, deadlines, and roles. Elgora's storage gets the
  bytes those hashes commit to. Nothing readable lives on-chain, and nothing
  authoritative lives off-chain.
</Callout>

**On-chain, in `ElgoraHub`, at creation:**

| Value                                       | Note                                                                                               |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `bountyId`                                  | Assigned sequentially                                                                              |
| `specCommitment`                            | `keccak256` of your exact page bytes                                                               |
| `escrowAmount`                              | Base units, transferred into the contract                                                          |
| `poster`                                    | The wallet that called `createBounty`                                                              |
| `submissionDeadline`                        | From your frontmatter                                                                              |
| `judgingDeadlineAt`                         | Derived: deadline + half the review window                                                         |
| `settlementTimeoutAt`                       | Derived: deadline + the full review window                                                         |
| `guardianRosterHash`                        | The roster live at creation, **pinned** for this bounty's whole life                               |
| `treasuryFeeBps`, `guardianFeeBps`          | Snapshotted, so a later fee change cannot touch your bounty                                        |
| `treasuryRecipient`, `guardianFeeRecipient` | Recorded at creation for the record only; fees go to whoever holds each role when they are claimed |
| `payoutScheme`                              | Snapshotted, so no Verdict can redirect settlement through a different one                         |

**In Elgora's storage:**

* the exact approved `bounty_challenge.md` bytes, addressed by
  `spec_commitment`;
* later, each Submission's canonical envelope JSON, and the encrypted artifact
  bytes in private object storage;
* later, each written Verdict's Markdown, addressed by `report_commitment`;
* later, delivery key-wrap rows and the advisory `VerificationRecord`.

**Nowhere, ever:** plaintext artifacts, artifact locations on-chain, content
encryption keys, or any Guardian's private key.

Because commitments live on-chain and preimages live off it, storage is an
availability layer rather than a trust root: any reader re-hashes what they
fetched and compares. See [Commitments](/docs/how-it-works/commitments).

## What can go wrong, and what it cost you

| Failure                                            | Money spent                    | What to do                                                                                          |
| -------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------- |
| Frontmatter or body rejected                       | None                           | Fix the page — see [the challenge contract](/docs/poster/challenge)                                 |
| Page not ready (`bounty_challenge_not_ready`)      | None                           | Fix the listed issues, run `poster:review` again, then publish the exact approved bytes             |
| `spec_commitment` mismatch after publish           | None                           | Stop. Your client and the API disagree about your bytes; do not sign                                |
| Deployment mismatch (chain, Hub, or token)         | None                           | Stop. Your configuration points at a different deployment than the API does                         |
| `Poster has no ETH on chain <id>`                  | None                           | Fund the wallet with native gas                                                                     |
| `insufficient escrow token balance`                | None                           | The message prints required and available base units                                                |
| `approve` reverted                                 | Gas for one failed transaction | Check the token address and allowance; retry                                                        |
| `createBounty` reverted with `InvalidEscrowAmount` | Gas                            | Your amount is outside the Hub's configured escrow bounds — see [Limits](/docs/how-it-works/limits) |
| `createBounty` reverted with `InvalidDeadline`     | Gas                            | The deadline is outside the Hub's configured bounty-duration bounds — too close, or too far out     |
| `createBounty` reverted with `ContractPaused`      | Gas                            | The protocol owner has paused the Hub; nothing to fix on your side                                  |
| No `BountyCreated` event from the configured Hub   | Gas                            | Your client is pointed at a different contract than the transaction reached                         |

A published page whose funding transaction never lands is harmless: the bytes
are stored, but no bounty exists. Re-running the command on the same file
produces the same `spec_commitment` and simply tries again.

## Publishing against another deployment

Every route defaults to Base mainnet, where funding escrows real USDC. To try a
bounty on the Base Sepolia testnet deployment first, use
`https://testnet.elgora.ai` on the website. An API client switches every value
to [the testnet deployment's](/docs/reference/addresses#the-testnet-deployment):
API host, signing `audience`, chain id, Hub, and escrow token together. The CLI
selects it per invocation:

```sh
elgora-cli poster:publish-fund --network base-sepolia ./bounty_challenge.md
elgora-cli poster:publish-fund --api-base-url https://your-api.example ./bounty_challenge.md
```

`--network` takes a name or a chain id and sets the chain, and with it the
API, for that run. `--api-base-url` overrides that API — point it at one that
actually serves the chain you selected. For a deployment the CLI does not
know about, supply a complete and coherent set:

```sh
elgora-cli poster:publish-fund --network <chain id> \
  --hub-address 0x… --rpc-url https://… --api-base-url https://… \
  ./bounty_challenge.md
```

The escrow token is read from that Hub.

Never mix an address from one deployment with an endpoint from another. See
[Deployment and parameters](/docs/reference/addresses).

## Keep these three values

```json
{"event":"publish_fund.bounty_created","tx_hash":"0x…","block_number":"…","bounty_id":"12","spec_commitment":"0x…"}
```

* **`bounty_id`** — how every other party and every other command refers to
  your bounty.
* **`spec_commitment`** — proof of which bytes you approved. Anyone can
  recompute it with `elgora-cli spec-commitment` and compare against the chain.
* **`tx_hash`** — the creation transaction.

Store them anywhere you like; they are all public. Nothing else you need is
secret, and nothing needs to be kept in a browser.
