Creating Open Work
A task (bounty) is the unit of open work in the Build Together protocol — a scoped piece of work with an ETH reward held in escrow that any eligible contributor can complete.
What a Task Is
A task combines:
- A scope — what needs to be built, fixed, or reviewed
- A reward — ETH promised at creation time, funded into escrow by anyone
- An onchain record — the
BountyEscrowcontract holds funds and enforces payout on acceptance
Tasks live onchain. The reward is escrowed independently of creation — contributors can see both the promised amount and the actual funded amount before they start working.
Who Can Create Tasks
Task creation is permission-gated at the contract level. Only the following wallets can call createBounty:
- Project owner — the address that called
createProject - Maintainers — wallets added via
addMaintainer - Allowlisted wallets — wallets added via
updateAllowlist
Strangers are rejected with NotAuthorized at the contract level, not just in the UI.
Creating a Task
From the project detail page, click New Task (visible only when you have permission).
The form requires:
- Title — a short, specific description of the work
- Description — context, acceptance criteria, constraints (optional but recommended)
- Reward (ETH) — the target reward amount in wei/ETH. This is the
rewardAmountstored onchain.
The app broadcasts BountyEscrow.createBounty(projectId, rewardAmount). The promised reward does not need to be deposited at creation — anyone can fund it later via fundBounty.
Writing a Good Task Spec
A well-written description reduces ambiguity and attracts higher-quality contributions. Include:
Context — why this work matters, what it unblocks, the current state.
Deliverables — concrete list of what "done" looks like. Prefer specific outputs over vague descriptions.
Constraints — stack requirements, things the solution must not do.
Acceptance criteria — how a maintainer will judge whether the work is complete. Automated tests, visual specs, or documented behaviour all work.
Out of scope — explicitly list related work that is not part of this task.
Example:
## Context
The auth flow doesn't handle expired sessions gracefully...
## Deliverables
- [ ] Session expiry detected and user redirected to /login
- [ ] Redirect preserves the original destination URL
- [ ] Unit tests covering the redirect logic
## Constraints
- Must use the existing auth module (services/api/src/lib/siwe.ts)
- No new dependencies
## Acceptance Criteria
All existing auth tests pass, new tests pass, manual QA confirms redirect works.Funding a Task
Anyone can add ETH to any open or funded bounty:
BountyEscrow.fundBounty(bountyId) // payableThe bounty status moves from OPEN to FUNDED on the first deposit. Multiple funders can contribute to the same bounty — the full fundedAmount goes to the contributor on acceptance.
Task Lifecycle
OPEN --> FUNDED --> ACCEPTED (contributor paid, bounty closed)
| |
+-----> CANCELLED <-------+ (ETH refunded to creator)| Status | Meaning |
|---|---|
OPEN | Created onchain, no ETH deposited yet |
FUNDED | ETH in escrow, accepting contributions |
ACCEPTED | Contribution accepted, ETH paid to contributor |
CANCELLED | Cancelled by owner or maintainer, ETH refunded |
Accepting a Contribution
When a contributor submits a patch via Radicle, the project owner or any maintainer reviews it and calls:
BountyEscrow.acceptContribution(
bountyId,
contributorAddress,
radicleOid // Radicle patch/commit identifier
)The full fundedAmount is transferred to contributorAddress in the same transaction.
- Hosting a Project — registering your project and adding team members
- Core Contracts — full contract reference
- Submitting a Contribution — the contributor's perspective