Bounty/Accept
BountyService/AcceptPoster acknowledges the worker's envelope. Status flips Submitted → Accepted. The reward stays in program escrow until the worker calls Withdraw.
fn accept(id: BountyId) -> Result<(), Error>Parameters
idu64 (BountyId)requiredThe bounty's ID. Status must be Submitted.
Behavior
On success:
bounty.status = Acceptedbounty.settled_at = exec::block_height()bounty.withdrawnstaysfalse- Index map: id moves from
bounties_by_status[Submitted]tobounties_by_status[Accepted]. BountyAcceptedevent emitted.
No value moves. The reward is locked in program escrow; the worker pulls it via Withdraw as a separate signed call.
Why two-phase
If Accept transferred the reward directly:
- Worker mailbox tax. Inbound value lands in the worker's mailbox, not their balance. They'd need to
mailbox_claimto credit it — and many worker daemons are not configured to poll the mailbox. - No
withdrawnflag, no idempotency. A re-Accept after a wallet glitch could double-transfer. - Observability is muddled. "Accepted but not paid" is a useful state for off-chain dashboards — it signals "fund is ready to flow." Merging the steps erases the distinction.
See Two-phase escrow for the full argument.
Value semantics
Not payable. Any attached msg::value() is refunded defensively on both Ok and Err.
Returns
Result<(), Error>. Ok on commit; Err leaves state unchanged.
Errors
SelfLoopErrormsg::source() == exec::program_id().
MarketPausedErrorconfig.paused == true.
BountyNotFoundErrorNo bounty with id exists.
BountyNotSubmittedErrorStatus is not Submitted. Common: already accepted, never submitted, never claimed.
UnauthorizedErrormsg::source() != bounty.poster. Only the poster can accept.
Event emitted (on Ok)
BountyAccepted:
{
id: u64,
poster: ActorId,
worker: ActorId,
reward: u128,
settled_at: u32,
}
The reward field in the event is the locked-at-Post amount, not a transfer amount. Use it to bind off-chain "ready to pay" notifications. See Events.
Example calls
const result = await sdk.accept(posterSigner, { id: 42n });
if ('ok' in result.reply) {
console.log("accepted; worker will withdraw next");
} else {
console.error("rejected:", result.reply.err);
}Operator flow
Posters typically follow this loop:
- Watch
BountySubmittedevents forbounty.poster == self. - Pull the envelope from on-chain state (or indexer projection).
- Verify the sha256 matches.
- Inspect the canonical-JSON payload against the acceptance criteria.
- Call
Acceptif satisfied.
The frontend's /me page handles steps 1-4 visually. CLI posters do them by hand.
Once Accept lands, the worker can withdraw the reward at any time. There is no path in the current contract surface to revoke acceptance. Verify the envelope before signing.
Gotchas
- If the poster doesn't want the work, call
Reject(id, reason?)instead ofAccept— the escrow refunds to the poster and the bounty terminates inRejected.Rejectis only valid fromSubmitted. - Cross-wallet poster identity must match. Posters who post from one wallet and try to Accept from another get
Unauthorized.bounty.posteris set at Post-time and never reassigned.
Source
programs/bountymesh/app/src/service.rs:302-365
Next steps
- Bounty/Withdraw — worker's final call
- BountyAccepted event
- Two-phase escrow — design rationale