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, but four distinct things happen inside it. Knowing which is which tells you exactly what a failure cost you.
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
Publish the bytes
The client signs one request over the exact page bytes and posts them as
text/markdown. Elgora validates, reviews,
stores the exact bytes, derives spec_commitment, and returns prepared
createBounty arguments plus the Hub's current fee policy.
Nothing has been spent. Emits publish_fund.publication_prepared.
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.
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.
Safe and unsupported wallets keep the approval path. It emits
publish_fund.usdc_approved only when an approval is sent. Cancelling a
signature stops publication, without switching to approval.
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.
What ends up where
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.
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 |
payoutScheme | Snapshotted, so no Verdict can redirect settlement through a different one |
In Elgora's storage:
- the exact approved
bounty_challenge.mdbytes, addressed byspec_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.
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 |
| Readiness review refused | None | Revise per the issues — see the review |
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 |
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
The CLI defaults to Base mainnet, where funding escrows real USDC. To try a bounty on the Base Sepolia staging deployment first, select it per invocation:
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:
ELGORA_CHAIN_ID=…
ELGORA_HUB_ADDRESS=0x…
ELGORA_ESCROW_TOKEN_ADDRESS=0x…
ELGORA_RPC_URL=https://…
ELGORA_API_BASE_URL=https://…Never mix an address from one deployment with an endpoint from another. See References and addresses.
Keep these three values
{"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 withelgora-cli spec-commitmentand 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.
The readiness review
What Elgora checks before it will store your challenge — the deterministic validation and the automated judgeability review — and what to do when either one refuses.
Outcomes and getting the work
The three ways a bounty can end, how to claim in each, and how the funding Poster retrieves the winning Submission.