Summary
Add an on-chain operation that lets a hackathon event owner recover the unawarded residual of an escrow event after select_winners. Today there is no way to get those funds back without canceling the whole event (which is only valid before winners are selected).
Why
The app now lets organizers run a task-first Winners flow where they pick a winner per prize and can deliberately leave a prize unawarded ("no submission earned the grand prize"), and prizes can also be unfilled when there are fewer eligible projects than prize slots. In both cases the prize's funds stay in the on-chain event.
The current escrow fund-out paths (EscrowOpKind) are:
SELECT_WINNERS — pays only the winners in the provided list. Anything not awarded stays in the event balance.
- The paged cancel flow (
START_CANCEL / PROCESS_CANCEL_BATCH / FINALIZE_CANCEL) — refunds everyone (partners-first, owner residual on finalize), and is only valid before winners are selected.
So once select_winners runs and the hackathon is COMPLETED, the unawarded residual is stranded with no recovery path. The app currently discloses this honestly at confirm ("That money stays in the prize pool and is not paid out."), but there is no way to act on it.
Proposed contract change
Add an owner-only op, e.g. release_residual(event_id, op_id) (name TBD), callable after select_winners, that transfers the remaining event balance out:
- Recommended payout policy: partners-first, owner residual — mirror the existing cancel/finalize residual logic so partner-contributed funds are returned to contributors before the owner takes the remainder. (Confirm whether contributions should be refunded or whether only the owner's own surplus is releasable — see open questions.)
- Idempotent +
op_id-tracked like the other ops; safe to retry; guarded so it can only run once and only on a Completed/post-select_winners event.
- Should be a no-op (or revert cleanly) when residual is zero.
App wiring (follow-up, boundless-nestjs + boundless)
Once the contract op exists:
boundless-nestjs: add an EscrowOpKind (e.g. RELEASE_RESIDUAL), EscrowContractClientService.buildReleaseResidual, orchestrator begin-method, a HackathonOrganizerService.releaseResidual, an organizer endpoint, and settlement handling in the escrow subscriber.
boundless (Winners page): a post-publish "Recover unawarded funds" action (confirm/OTP, owner/admin only), and update the confirm-time disclosure copy from "stays in the prize pool" to reflect that residual is now recoverable.
Acceptance criteria
- After winners are paid, the owner can recover exactly the unawarded remainder of the event balance.
- Partner-contributed unallocated funds are handled per the agreed policy (refund vs owner-release).
- The op is idempotent,
op_id-reconciled via EscrowOp, and cannot double-spend.
Open questions
- For unawarded prizes funded partly by partner contributions: refund those contributors, or release everything to the owner? (Leaning: refund partners-first to match cancel semantics.)
- Should this be one bulk "release all residual" or per-placement? (Bulk is simpler and sufficient for the current UX.)
References
- App disclosure:
boundless-nestjs hackathon-results.service.ts publishResults (no-winners gate) + the Winners confirm dialog in boundless app/(landing)/organizations/[id]/hackathons/[hackathonId]/winners/page.tsx.
- Withhold support:
Hackathon.withheldPlacementIds + winner-allocator.ts.
Summary
Add an on-chain operation that lets a hackathon event owner recover the unawarded residual of an escrow event after
select_winners. Today there is no way to get those funds back without canceling the whole event (which is only valid before winners are selected).Why
The app now lets organizers run a task-first Winners flow where they pick a winner per prize and can deliberately leave a prize unawarded ("no submission earned the grand prize"), and prizes can also be unfilled when there are fewer eligible projects than prize slots. In both cases the prize's funds stay in the on-chain event.
The current escrow fund-out paths (
EscrowOpKind) are:SELECT_WINNERS— pays only the winners in the provided list. Anything not awarded stays in the event balance.START_CANCEL/PROCESS_CANCEL_BATCH/FINALIZE_CANCEL) — refunds everyone (partners-first, owner residual on finalize), and is only valid before winners are selected.So once
select_winnersruns and the hackathon isCOMPLETED, the unawarded residual is stranded with no recovery path. The app currently discloses this honestly at confirm ("That money stays in the prize pool and is not paid out."), but there is no way to act on it.Proposed contract change
Add an owner-only op, e.g.
release_residual(event_id, op_id)(name TBD), callable afterselect_winners, that transfers the remaining event balance out:op_id-tracked like the other ops; safe to retry; guarded so it can only run once and only on aCompleted/post-select_winners event.App wiring (follow-up,
boundless-nestjs+boundless)Once the contract op exists:
boundless-nestjs: add anEscrowOpKind(e.g.RELEASE_RESIDUAL),EscrowContractClientService.buildReleaseResidual, orchestrator begin-method, aHackathonOrganizerService.releaseResidual, an organizer endpoint, and settlement handling in the escrow subscriber.boundless(Winners page): a post-publish "Recover unawarded funds" action (confirm/OTP, owner/admin only), and update the confirm-time disclosure copy from "stays in the prize pool" to reflect that residual is now recoverable.Acceptance criteria
op_id-reconciled viaEscrowOp, and cannot double-spend.Open questions
References
boundless-nestjshackathon-results.service.tspublishResults(no-winners gate) + the Winners confirm dialog inboundlessapp/(landing)/organizations/[id]/hackathons/[hackathonId]/winners/page.tsx.Hackathon.withheldPlacementIds+winner-allocator.ts.