# Quest

> Quest/reward collection — users complete criteria and claim a badge + coin payout

**Category:** Token Types

## Summary

Required standards: \["Quests"]

* Single token only: validTokenIds = \[{start: "1", end: "1"}]
* Quest approval MUST be properly gated — typically via an off-chain claim (merkle challenge with claimConfig), but can also use on-chain criteria (mustOwnTokens, dynamicStoreChallenges, evmQueryChallenges, votingChallenges)
* Coin transfers with overrideFromWithApproverAddress: true + overrideToWithInitiator: true
* predeterminedBalances: amount 1, no increments, no recurring, no duration
* Escrow funded upfront via set\_mint\_escrow\_coins (rewardAmount \* maxClaims)
* invariants.noCustomOwnershipTimes: true
* Permissions: use "locked-approvals" preset (recommended)
* Default balances: empty balances, all auto-approve flags true

## Instructions

## Quest Configuration

### Mental Model

A quest collection rewards users for completing criteria. Users receive a quest badge (token 1) + coin payout.

The quest approval MUST be properly gated so that only eligible users can claim. Gating options:

* **Off-chain claim (most common)**: A merkle challenge with claimConfig containing plugins (password, codes, whitelist, etc.). The claim is verified off-chain by BitBadges, and a merkle proof is issued for on-chain redemption.
* **On-chain criteria**: mustOwnTokens (require holding specific tokens/badges), dynamicStoreChallenges, evmQueryChallenges, votingChallenges — these are checked directly on-chain during the transfer.
* **Both**: Combine off-chain claims with on-chain criteria for layered verification.

Choose the gating approach based on the user's request. If they mention passwords, codes, or whitelists, use an off-chain claim. If they mention token ownership or on-chain conditions, use the corresponding on-chain criteria.

### Build Steps (call ALL in parallel in one round)

1. `set_standards` → `["Quests"]`
2. `set_valid_token_ids` → `[{ "start": "1", "end": "1" }]`
3. `set_invariants` → `{ "noCustomOwnershipTimes": true }`
4. `set_permissions` → `{ "preset": "locked-approvals" }`
5. `set_default_balances` → empty balances, all auto-approve true
6. `set_collection_metadata` / `set_token_metadata` — descriptive content
7. `add_approval` — the quest approval (see exact structure below)
8. **`set_mint_escrow_coins`** — REQUIRED for coin rewards. Amount = rewardPerClaim × maxClaims.

### Quest Approval (add\_approval)

Use approvalId `"quest-approval"`. The EXACT approvalCriteria structure:

```json
{
  "approvalId": "quest-approval",
  "fromListId": "Mint",
  "toListId": "All",
  "initiatedByListId": "All",
  "tokenIds": [{"start":"1","end":"1"}],
  "approvalCriteria": {
    "overridesFromOutgoingApprovals": true,
    "maxNumTransfers": {
      "overallMaxNumTransfers": "<maxClaims>"
    },
    "predeterminedBalances": {
      "manualBalances": [],
      "incrementedBalances": {
        "startBalances": [{ "amount": "1", "tokenIds": [{"start":"1","end":"1"}], "ownershipTimes": [{"start":"1","end":"18446744073709551615"}] }],
        "incrementTokenIdsBy": "0",
        "incrementOwnershipTimesBy": "0",
        "durationFromTimestamp": "0",
        "allowOverrideTimestamp": false,
        "recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }
      },
      "orderCalculationMethod": {
        "useOverallNumTransfers": true,
        "usePerToAddressNumTransfers": false,
        "usePerFromAddressNumTransfers": false,
        "usePerInitiatedByAddressNumTransfers": false,
        "useMerkleChallengeLeafIndex": false,
        "challengeTrackerId": ""
      }
    },
    "coinTransfers": [{
      "to": "",
      "overrideFromWithApproverAddress": true,
      "overrideToWithInitiator": true,
      "coins": [{ "amount": "<rewardAmount>", "denom": "<rewardDenom>" }]
    }]
  }
}
```

**Gating** — add ONE OR MORE of these to approvalCriteria based on the user's request:

* **Off-chain claim**: `"merkleChallenges": [{ "root": "", "expectedProofLength": "0", "maxUsesPerLeaf": "1", "uri": "", "customData": "", "useCreatorAddressAsLeaf": false, "claimConfig": { "approach": "in-site", "label": "...", "plugins": [...] } }]`
* **Token ownership**: `"mustOwnTokens": [{ "collectionId": "...", "amountRange": {"start":"1","end":"18446744073709551615"}, ... }]` — Use collectionId "0" to self-reference this collection (e.g., require holding token 1 from this quest collection itself).
* **Dynamic store**: `"dynamicStoreChallenges": [...]`
* **EVM query**: `"evmQueryChallenges": [...]`

Off-chain claims are the most common for quests (passwords, codes, whitelists). On-chain criteria can be combined with or used instead of claims.

### Escrow Funding (REQUIRED)

Call `set_mint_escrow_coins` in the SAME round as the other tools. Example for 10 ATOM reward × 50 claims:

```
set_mint_escrow_coins({ coins: [{ denom: "ibc/A4DB...", amount: "500000000" }] })
```

Without this, the escrow has no funds and claims will fail.

### Common Mistakes

* DON'T add extra fields to coinTransfers — the ONLY fields are: to, overrideFromWithApproverAddress, overrideToWithInitiator, coins. NO startTime, NO other fields.
* DON'T omit `manualBalances: []` in predeterminedBalances — SDK crashes without it
* DON'T omit fields in orderCalculationMethod — include ALL boolean fields
* DON'T forget `set_mint_escrow_coins` — without it, the escrow is empty and rewards can't be paid
* DON'T set maxUsesPerLeaf to anything other than "1" — each user claims once
* DON'T set allowOverrideTimestamp: true — quests require false
* DON'T set useCreatorAddressAsLeaf: true — quests require false
