Bounties and Escrow
Lock funds at creation and release them on a verifier's vote, a denial, or a deadline. Three approvals, one escrow address, no contract.
A bounty is funds locked at collection creation plus three ways they can leave: accept (paid to the recipient), deny (refunded to the submitter), and expire (refunded after the deadline). Each exit is a collection approval with a coin transfer from the mint escrow address. A named verifier decides accept or deny by casting one on-chain vote. Nobody, including the creator, can move the money any other way.
The same three-branch shape is a milestone contract, a job escrow, a dispute-resolved trade, or a multi-party agreement with six or twelve branches instead of three.
What Makes It Work
| Requirement | Token-standard primitive |
|---|---|
| Funds locked at creation | mintEscrowCoinsToTransfer on MsgUniversalUpdateCollection funds the collection's mint escrow address. See Coin Transfers |
| Escrow pays out | coinTransfers with overrideFromWithApproverAddress: true, so the escrow is the payer |
| A verifier decides | votingChallenges with the verifier as the only voter and quorumThreshold: "100". The verifier signs MsgCastVote; anyone can then execute. See Voting Challenges |
| Refund after the deadline with no action from anyone | An expire approval whose transferTimes starts one millisecond after the deadline |
| Each branch fires once | maxNumTransfers.overallMaxNumTransfers: "1" per approval |
| Nobody can edit the terms | All permissions locked at creation |
The Fields That Matter
{
"mintEscrowCoinsToTransfer": [{ "amount": "500000000", "denom": "ibc/E1116484B327AEE59CDC3DA73D319834781A13DB2A7DFC1F38A30CD45ABF58B8" }],
"collectionApprovals": [
{
"approvalId": "bounty-accept",
"fromListId": "Mint",
"toListId": "bb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqs7gvmv",
"initiatedByListId": "All",
"transferTimes": [{ "start": "1", "end": "1791331200000" }],
"approvalCriteria": {
"votingChallenges": [{ "proposalId": "bounty-accept-3f9a", "quorumThreshold": "100", "voters": [{ "address": "bb1zc268nctj8xwslgw7q22cahs6k4y048agr6fvf", "weight": "1" }] }],
"coinTransfers": [{ "to": "bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue", "coins": [{ "amount": "500000000", "denom": "ibc/E1116484B327AEE59CDC3DA73D319834781A13DB2A7DFC1F38A30CD45ABF58B8" }], "overrideFromWithApproverAddress": true }],
"maxNumTransfers": { "overallMaxNumTransfers": "1", "amountTrackerId": "bounty-accept-tracker" }
}
},
{
"approvalId": "bounty-expire",
"fromListId": "Mint",
"toListId": "bb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqs7gvmv",
"initiatedByListId": "All",
"transferTimes": [{ "start": "1791331200001", "end": "18446744073709551615" }],
"approvalCriteria": {
"coinTransfers": [{ "to": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d", "coins": [{ "amount": "500000000", "denom": "ibc/E1116484B327AEE59CDC3DA73D319834781A13DB2A7DFC1F38A30CD45ABF58B8" }], "overrideFromWithApproverAddress": true }],
"maxNumTransfers": { "overallMaxNumTransfers": "1", "amountTrackerId": "bounty-expire-tracker" }
}
}
]
}The bounty-deny approval is bounty-accept with the coin transfer pointed at the submitter. Each branch mints one receipt token straight to the burn address, so the chain records which branch ran.
bb build bounty --amount 500 --denom USDC --verifier bb1zc268nctj8xwslgw7q22cahs6k4y048agr6fvf \
--recipient bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue --submitter bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d \
--expiration 30d --uri ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/collection.jsonThis emits a Bounty collection that moves 500 USDC into escrow when the creator signs, with the accept, deny, and expire approvals above.
Variations
- Several verifiers: list them in
voterswith weights and setquorumThreshold: "51"for a majority. See Multi-Sig Voting. - Timelock:
delayAfterQuorumon the voting challenge holds the payout for a review window after the vote passes. - Partial payouts: one accept approval per milestone, each with its own amount, under the
ListView:Milestonesstandard. See Payment Protocol. - Long-lived escrow with deposits from many parties: hold the funds as a backed token instead, and write the release, refund, and dispute branches as its withdraw approvals.
- No escrow at all: use a payment request, where the payer funds the transfer at approval time.
Build It
- Skill: Bounty, Payment Protocol
- Reference: MsgCastVote
- CLI:
bb build bounty, thenbb bounties accept,deny, orclaim-refundfrom Standards
Load the bounty skill. Build a 250 USDC bounty where bb1zc268nctj8xwslgw7q22cahs6k4y048agr6fvf verifies, bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue is paid on accept, I am refunded on deny or after 14 days. Validate, review, simulate, then give me the review link.