CampaignVault contract. Unlike an order, no negotiation happens — the terms are fixed by the bounty.
Lifecycle
txIntent, the acting wallet broadcasts it, and either a matching confirm endpoint or the indexer projects the result.
Browse bounties
rewardPerSlot, currency, perProviderLimit, closesAt, maxSubmitSeconds, proofRequirements[], slotCounts.OPEN, and providerBond.
The bond gate
Compareavailable in the bounty’s currency against providerBond before claiming:
The bond is unlocked when the slot ends in the provider’s favour — approval, a dispute win, or
claim-after-timeout. It is slashed to the brand, with a reputation penalty, on every at-fault ending: a dispute loss, blowing the maxSubmitSeconds window, an uncontested rejection, or removal as an abandoned claim. A bounty with providerBond: "0" locks nothing.
Claim a slot
intentId, expectedSlotIdHash, and a campaignClaimSlot intent. No slot exists yet. Broadcast the intent, then confirm:
id and check status: "CLAIMED" and boundTxHash. An abandoned claim intent expires after 15 minutes without consuming a slot.
Submit proof
Match each item to aproofRequirements[].id. Upload files first via POST /api/v1/campaigns/slots/<slotId>/proof/upload-url, then:
CLAIMED or CHANGES_REQUESTED. Broadcast the returned campaignSubmitSlot intent, then confirm with POST /api/v1/campaigns/slots/submit/confirm { txHash }. Verify status: "SUBMITTED" and record reviewDeadline.
Brand review
All three decisions are two-phase: prepare, broadcast, confirm.
Every confirm body is
{ "txHash": "0x…" }. Requesting changes is limited to two rounds, after which the provider resubmits a new proof version.
Challenge a rejection
During a rejected slot’s challenge window:disputeId and a campaignOpenSlotChallenge intent with the evaluator panel committed in the calldata. There is no separate confirm endpoint — broadcast, then poll the slot and GET /api/v1/disputes/<disputeId> until the slot is CHALLENGED and the dispute is in EVIDENCE_PHASE. From there it follows the standard dispute flow.
Timeout paths
A held slot stays
CLAIMED past its TTL — nothing auto-expires it, so only remove-expired ends it. And there is no brand “close bounty” call: unfilled budget returns only through the permissionless reclaimExpired after on-chain expiry. Once that lands, the remaining OPEN slots are gone — claim before closesAt, not after.