Bounty
Bounty
Escrow-based bounty with verifier arbitration. Submitter escrows coins, verifier accepts (pays recipient) or denies (refunds submitter). Expires if no decision.
Category: Token Types
Summary
Required standards: ["Bounty"]
1 token ID (vehicle for approval engine β minted directly to burn)
3 collection-level approvals: accept, deny, expire
Each approval: Mint β burn 1x token ID 1, triggers coinTransfer as side effect
Verifier decides outcome via MsgCastVote
Escrow pre-funded at creation via mintEscrowCoinsToTransfer
Fixed bounty amount, no amount scaling
All approvals maxNumTransfers = 1 (one-shot)
All permissions frozen after creation
Expiration enforced via transferTimes windows
Instructions
Bounty Standard
Mental Model
A bounty is an escrow-based agreement between three parties:
Submitter: Creates the bounty, deposits funds into escrow
Recipient: Receives the payout if the verifier accepts
Verifier: Decides whether to accept or deny the bounty
The escrow is funded upfront at collection creation via mintEscrowCoinsToTransfer. Each resolution path (accept/deny/expire) is a single approval that mints 1x token ID 1 from Mint β burn address. The token is just a vehicle β the real action is the coinTransfer side effect that pays out from escrow. The verifier votes to unlock accept or deny. If no vote before expiration, anyone can trigger expire to refund the submitter.
Token Structure
Token ID 1 = Bounty token (vehicle for approval engine)
validTokenIds: [{ start: "1", end: "1" }]
1 alias path:
ubountyβ token ID 1, symbol BOUNTY, 1 decimal, 1:1 conversion
3 Required Approvals
All 3 approvals share the same structure: Mint β burn address, 1x token ID 1. The token is just a vehicle to trigger the approval engine's coinTransfer. Each approval has maxNumTransfers = 1 (one-shot).
1. Accept (bounty-accept-*)
Verifier votes accept β mint-to-burn β coins to recipient.
Key fields:
fromListId: "Mint"
toListId: burn address (bb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqs7gvmv)
initiatedByListId: "All"
coinTransfers: [{ to: recipientAddress, overrideFromWithApproverAddress: true, overrideToWithInitiator: false, coins: [{ denom, amount: depositAmount }] }]
predeterminedBalances.incrementedBalances:
startBalances: [{ amount: "1", tokenIds: [{ start: "1", end: "1" }], ownershipTimes: fullRange }]
allowAmountScaling: false, maxScalingMultiplier: "0"
votingChallenges: [{ proposalId: "bounty-accept-*", quorumThreshold: "100", voters: [{ address: verifierAddress, weight: "1" }] }]
transferTimes: [{ start: "1", end: expirationTimestamp }]
maxNumTransfers.overallMaxNumTransfers: "1"
overridesFromOutgoingApprovals: true
overridesToIncomingApprovals: true
2. Deny (bounty-deny-*)
Verifier votes deny β mint-to-burn β coins to submitter.
Same as Accept but:
coinTransfers.to: submitterAddress (refund)
votingChallenges proposalId: "bounty-deny-*"
3. Expire (bounty-expire-*)
After expiration β mint-to-burn β coins to submitter. No verifier vote needed.
Same as Deny but:
NO votingChallenges (time-gated only)
transferTimes: [{ start: expirationTimestamp + 1, end: "18446744073709551615" }]
Settlement Flow
Verifier sends MsgCastVote to unlock accept or deny:
collection_id: collectionId
approval_level: "collection"
approval_id: accept or deny approval ID
proposal_id: matching proposal ID
yes_weight: "100"
After vote, anyone triggers the payout:
MsgTransferTokens from Mint to burn address (1x token ID 1)
prioritizedApprovals: [{ approvalId, approvalLevel: "collection" }]
Coin transfer executes automatically (escrow β recipient or submitter)
For expiration (no vote needed):
After expirationTimestamp, anyone can call MsgTransferTokens with the expire approval
Coins return to submitter automatically
Key Differences from Prediction Markets
Token is just a vehicle (minted directly to burn) β nobody holds it
3 approvals, not 7 (no separate mint/redeem/transfer/push)
Escrow pre-funded at creation (mintEscrowCoinsToTransfer)
Fixed amount, no allowAmountScaling
Fixed payout addresses (hardcoded in coinTransfers.to), NOT overrideToWithInitiator
All approvals maxNumTransfers = 1 (one-shot)
Expiration via transferTimes windowing (accept/deny before, expire after)
Creation Flow (Tool Calls)
Use per-field tools to initialize the collection
set_valid_token_idsβ set [{ start: "1", end: "1" }]set_standardsβ set ["Bounty"]set_invariantsβ set { noCustomOwnershipTimes: true, disablePoolCreation: true }set_mint_escrow_coinsβ fund escrow with bounty amountadd_approvalx3 β add accept, deny, expire approvalsadd_alias_pathβ ubounty alias (symbol BOUNTY, 1 decimal, 1:1 token ID 1)set_permissionsβ freeze all permissionsset_collection_metadataβ name, description, imageset_token_metadataβ token 1 metadatavalidate_transactionβ verify structuresimulate_transactionβ dry run
Permissions
All permissions MUST be frozen (permanentlyForbiddenTimes: fullRange):
canDeleteCollection
canArchiveCollection
canUpdateStandards
canUpdateCustomData
canUpdateManager
canUpdateCollectionMetadata
canUpdateValidTokenIds
canUpdateTokenMetadata
canUpdateCollectionApprovals
canAddMoreAliasPaths
canAddMoreCosmosCoinWrapperPaths
Common Mistakes
DON'T use allowAmountScaling β bounty amount is fixed at creation time
DON'T use overrideToWithInitiator β use hardcoded addresses in coinTransfers.to
DON'T set maxNumTransfers > 1 β each approval is one-shot
DON'T use fromListId "!Mint" β all 3 approvals mint from "Mint" to burn address
DON'T omit manualBalances: [] in predeterminedBalances
DON'T omit fields in orderCalculationMethod β include ALL boolean fields
DON'T make expiration transferTimes overlap with accept/deny transferTimes
DON'T forget set_mint_escrow_coins β without it, the escrow is empty and payouts fail
Advanced: Self-Referencing with mustOwnTokens
For bounties that require the verifier or submitter to hold a token from THIS collection (e.g., a reputation badge), use collectionId "0" in mustOwnTokens. The chain resolves "0" to the current collection ID at runtime, which is especially useful at creation time when the real ID is not yet known.
Last updated