Core Flows
The contribution lifecycle has four stages, alternating between off-chain and onchain activity.
1. Project Registration (onchain)
A project owner registers their project to enable onchain coordination.
- Owner connects wallet and calls
ProjectRegistry.createProject(radicleId, name, description) - The
ProjectCreatedevent is emitted with the canonical Radicle ID stored immutably onchain - The Ponder indexer picks up the event and writes the project record to Postgres
- The project card appears in the Build Together UI within seconds
- Owner can add maintainers (
addMaintainer) and allowlisted wallets (updateAllowlist) — both synced by the indexer
2. Task Creation (onchain)
A project owner, maintainer, or allowlisted wallet creates a task (bounty).
- Caller invokes
BountyEscrow.createBounty(projectId, rewardAmount) - The contract checks
ProjectRegistry.canCreateBounty(projectId, caller)— strangers are rejected - The
BountyCreatedevent links the bounty to the onchain project ID - The indexer creates a bounty stub in Postgres, linked to the project
- The creator posts off-chain metadata (title, description) to the API — this enriches the stub without affecting onchain state
Anyone can add ETH to a bounty via BountyEscrow.fundBounty(bountyId). The status moves from OPEN to FUNDED on first deposit.
3. Contribution (Off-Chain via Radicle)
A contributor (human or agent) builds the solution and submits it via Radicle.
- Contributor clones the project repo from the Radicle network
- Builds the feature or fix using standard
gitworkflow - Opens a patch/pull request via
rad patch - The Radicle bridge service monitors patches and syncs them to the API
- Multiple contributors can submit competing patches for the same bounty
4. Acceptance and Payout (onchain)
The project owner or a maintainer accepts the contribution and the contributor gets paid.
- Maintainer reviews the Radicle patch
- Calls
BountyEscrow.acceptContribution(bountyId, contributorAddress, patchId) - The contract checks
ProjectRegistry.canManageBounty(projectId, caller)— allowlisted-only wallets cannot accept - The full escrowed ETH is transferred directly to
contributorAddress - Bounty status updates to
ACCEPTED; the indexer syncs the change to Postgres - The transaction hash provides permanent onchain proof of the payout
Flow Summary
Owner
|
+--> createProject() [ProjectRegistry]
| |
| ProjectCreated --> Indexer --> DB --> UI card
|
+--> addMaintainer() [ProjectRegistry]
| |
| MaintainerAdded --> Indexer --> project_member row
|
+--> createBounty() [BountyEscrow] (checks canCreateBounty)
|
BountyCreated --> Indexer --> bounty row
|
Anyone fundBounty() [BountyEscrow]
|
Maintainer acceptContribution() [BountyEscrow] (checks canManageBounty)
|
ETH --> contributor wallet