# BitBadges Overview

Here, you will find documentation about BitBadges, how it works, how to interact, and how to contribute!

## 🚀 The Next-Generation Token Standard

BitBadges has built a brand new tokenization standard exclusively as a Cosmos SDK module, designed specifically for RWAs (Real World Assets), compliance, payments, and custom transferability requirements.

Unlike existing standards (`x/bank`, `x/tokenfactory`, `x/nft`, ICS20, ERC20, ERC-3643), our `x/tokenization` module provides native support for compliance checks on every transfer (even IBC transfers and in liquidity pools), custom transferability / compliance rules, issuer-level control, and enterprise-grade tokenization features—all out of the box with no code or smart contracts required, just a module! All plug-and-play and infinitely customizable.

Our revolutionary token standard goes far beyond ERC-20, ERC-721, and other existing standards with features like time-dependent ownership, fine-grained transferability controls, IBC compatibility, connecting to 7000+ apps, connecting to EVM, IBC, and more.

Our theses are:

1. The next wave of tokenization needs a next-generation standard. Existing ones are not enough and built on outdated technology.
2. Compliance / transferability is not just a matter of a simple whitelist/blacklist or transferable vs soulbound. It is a complex series of moving parts (time-gating, ownerships, approvals, who can send to who?, initiated by who?, revokable? freezable?, and so on). To truly make compliance work on-chain, you need to handle all these moving parts automatically, not with a manually updated whitelist/blacklist. And, this belongs on the token standard level.
3. The standardized, reusable, no-code approach wins over per use-case smart contracts over time.

<figure><img src="/files/8W2ojh77mwbglLVZt2zL" alt=""><figcaption></figcaption></figure>

## ❓ Why BitBadges?

BitBadges is simply tokenization-as-a-service. Create anything from subscriptions and memberships to tradable NFTs, credentials, and access tokens - all with the most advanced token standard ever built.

Traditional token standards are limited, inflexible, and locked to single blockchain ecosystems. BitBadges fixes this with a 100x improvement that supports:

* **No Code, No Smart Contracts, No Audits** - Everything works out-of-the-box with no code. One reusable module.
* **Compliance Checked Every Transfer, Swap, IBC Transfer** - Build complex transferability systems checked everywhere. No backdoors. Compliance checked every swap.
* **Drop-In 1000x Upgrade -** We are a superset of existing standards. One line of code change for 1000x unlock in features.
* **Supports Any IBC (ICS20) Currency -** We've designed it in a way such that it is seamlessly compatible with any ICS20 currency paired for payments, swaps, liquidity, or anything else.
* **IBC Compatibility** - One interface, one token experience for all blockchain ecosystems via IBC.
* **Time-Dependent Ownership** - Create subscriptions, time-locked tokens, and expiring credentials with time-dependent logic and approvals.
* **Advanced Transferability / Compliance** - Fine-grained controls over who can transfer what, when, and how on any level.
* **Three Transferability Levels** - Customize transferability on the collection, sender,and recipient levels.
* **Connect to 7000+ Apps** - Connect to 7000+ apps and integrations with seamless on/off-chain criteria checks
* **Connect to Cosmos via IBC** - Connect to Cosmos and beyond via IBC and use the BitBadges token standard on any Cosmos chain
* **Extend with EVM Contracts** - Extend the BitBadges token standard with EVM contracts or any other custom environment
* **Customizable Permissions** - Flexible manager controls for collections

## 🤔 Motive for building BitBadges?

The answer is simple. We believe in the potential of blockchains and interoperability, but this potential cannot be realized with the current infrastructure and token standards in place today.

## ⚠️ Problems with Existing Standards

Existing tokenization standards (ERC-20, ERC-721, CW-20, ICS-20, etc.) are **flawed from the ground up**:

* **Too Simple** - Basic mint/transfer/burn functionality lacks the flexibility needed for 90% of real-world applications. The industry has been stuck with these limited standards for 10+ years due to technical debt.
* **Vulnerable by Default** - Smart contract approach introduces new attack vectors with each token contract. Each deployment is a potential vulnerability.
* **Complex & Expensive** - Requires extensive technical knowledge to implement, deploy, and maintain contracts.
* **Low Interoperability** - Tokens are siloed to single ecosystems, forcing companies to split their userbase across chains.
* **Fragmented Standards** - Many competing standards with incompatible twists create confusion and fragmentation.

**The whole tokenization approach needs a complete overhaul.**

## 💡 Our Design Philosophy

BitBadges addresses these fundamental issues through core design decisions:

* **Universality** - One standard powerful enough for any use case: NFTs, fungible tokens, subscriptions, credentials, RWAs, compliance, or anything you can imagine. One standard to rule them all.
* **No-Code Module Approach** - Built as a Cosmos SDK module, not smart contracts. 99% of users will never need to write code, regardless of complexity. Promotes reusability and battle-tested security.
* **Ever-Evolving** - Purpose-built for next-generation tokenization. We're not stuck in the past like ERC-20/721. New features are added continuously with no technical debt accrual.
* **IBC-First** - Cosmos-native with IBC at the core. Custom wrappable to ICS-20/721, supports IBC denominations for payments/swaps/liquidity, and enables one-signature multi-hop IBC transfers.


# Use Cases

BitBadges is a powerful platform that enables tokenization of virtually any asset, service, or concept. With its flexible protocol-level controls, multi-currency support, and extensive customization options, BitBadges supports a wide range of use cases across industries.

Beyond tokenization / RWAs, BitBadges is very powerful as a Swiss-Army knife module for development. If you think about it, any use case in crypto is a tokenization use case in one form or another. We provide any primitive you may need in our module for any use case, not just tokenization.

<figure><img src="/files/13Vf6n1VszuKb0P9ggRF" alt=""><figcaption></figcaption></figure>

***

### Asset Tokenization

#### Compliant Tokenized Assets

Tokenize any asset with custom compliance checks enforced on every transfer at the protocol level. Works across liquidity pools, orderbooks, and any application. This ensures that all transfers meet your specific compliance requirements automatically, without relying on individual applications to enforce rules.

#### Real-World Assets

Tokenize physical assets like jewelry, art, collectibles, or any product that can be purchased with any IBC currency. Leverage our multi-currency support and permissioned IBC transfers to create a seamless experience for buyers and sellers across different blockchain networks.

#### Real Estate with License Verification

Tokenize real estate with property licenses verified on every transfer. Ensures only valid, licensed properties can be transferred, providing an additional layer of security and compliance for real estate transactions on-chain.

#### Compliant ICS20 Token Derivatives

Create compliant derivatives of existing ICS20 tokens (like USDC) that are backed 1:1 by the original asset but include custom compliance and transferability rules. For example, wrap USDC into a clUSDC derivative with custom rate limits, compliance checks, withdrawal restrictions, and other programmable constraints. This enables chains to use permissioned, compliant versions of standard tokens while maintaining full backing by the original asset. Perfect for creating siloed compliance environments where you need custom rules for specific use cases without modifying the underlying token standard.

### NFTs and Collectibles

#### Collectibles & Profile Pictures

Standard NFT collection mechanics with full transferability controls. Create collectibles and profile pictures with custom rarity and transfer rules. Perfect for digital art, gaming assets, and social media profile customization.

#### Gated NFTs & Quests

Mint NFTs and quests gated by criteria from 7000+ no-code apps. Email verification, Discord server membership, or any custom requirement can unlock access. This enables sophisticated access control without writing custom code.

#### Quest Rewards as NFTs

Reward quest completion with NFTs that have payouts attached. Use criteria-based approvals to gate quest rewards through 7000+ no-code apps. Ideal for gaming platforms, loyalty programs, and engagement campaigns.

### Soulbound Tokens

#### Soulbound Tokens

Non-transferable tokens for achievements, credentials, attestations, and proof of anything. Make the collection non-transferable to lock tokens to addresses. Perfect for representing permanent achievements, educational credentials, or identity verification that should never be transferred.

#### Compliance-as-a-Service

Integrate BitBadges as a modular compliance checking system alongside existing token standards like ERC-3643 or x/bank. While your token uses standard protocols, BitBadges provides sophisticated compliance verification by checking ownership of compliance tokens—including licenses, subscriptions, NFTs, badges, KYC credentials, address lists, and more. True on-chain compliance requires handling 20+ standardized components automatically.

### Subscriptions and Recurring Payments

#### Auto-Renewing Subscriptions

Create subscription tokens purchasable with any IBC currency that auto-renew via bots. Optional provider revocation capabilities for flexible cancellation policies. Enables seamless subscription management with automatic renewals and flexible cancellation options.

#### Payroll Automation

Develop complex payroll automation systems seamlessly with recurring payments. No-code setup with automated verification. Check if recipients are still employed with NFTs and custom criteria enforcement. Streamline payroll processes with blockchain-based verification and automation.

### Securities and Financial Instruments

#### Stocks with Accredited Investor KYC

Tokenize stocks with accredited investor KYC requirements enforced through custom transferability approvals. Only verified investors can receive securities, ensuring compliance with securities regulations automatically at the protocol level.

#### Freezable & Revocable Currencies

Tokenize currencies with issuer-controlled freezability and revocability features. Managers can freeze or revoke transfers when needed for compliance. Essential for regulated currencies, stablecoins, and financial instruments that require administrative oversight.

#### Bonds & CDs with Clawbacks

Tokenize bonds and certificates of deposit with clawback capabilities. Enables sophisticated financial instruments with built-in administrative controls for compliance and risk management.

### Time-Based Systems

#### Time-Vested Unlocks

Implement time-vested unlock mechanisms that gradually allocate tokens with time-dependent balances, approvals, and more. Perfect for employee stock options, token vesting schedules, and gradual reward distribution.

#### Time-Vested Escrows

Create escrow systems with time-based release conditions. Assets are held in custody until specific time requirements are met. Ideal for milestone-based payments, conditional releases, and time-gated transactions.

#### Expiring Access Tokens

Create authentication tokens, such as tickets, that expire using time-dependent balances. Perfect for temporary access credentials and session management. Enables time-limited access control for events, services, or digital resources.

#### Auto-Expiring Occupation Tokens

Create time-dependent occupation tokens for renting, bookings, or temporary ownership that automatically expire at specified times. Perfect for rental agreements, event tickets, and time-bound access. Ensures automatic expiration without manual intervention.

#### Rental Agreements

Establish time-dependent leases and rental agreements with custom terms of service using flexible approvals. All contracts, payments, and access rights are enforced on-chain with automatic expiration. Provides transparent, automated rental management on the blockchain.

### Legal and Compliance

#### Dispute Resolution & Clearing

Build dispute resolution or clearing protocols. Define the rules to hold, freeze, or reverse transactions. Enables sophisticated dispute management systems with programmable resolution logic.

#### Advanced Admin Controls

Enable advanced revocability and freezability features for administrators. Perfect for regulatory compliance or emergency asset protection. Provides powerful administrative tools for managing tokenized assets in regulated environments.

#### Intellectual Property Rights

Tokenize patents and IP rights with controlled licensing. Enforce IP ownership and licensing terms on every transfer. Ensures that intellectual property rights are properly managed and enforced throughout the token lifecycle.

### Business Operations

#### Refund & Return Policies

Implement receipt systems with automated return policies through custom transferability approvals. Enable seamless refunds with payouts on-chain. Streamlines e-commerce operations with automated refund processing.

#### Address Lists & Reputation

Maintain public address lists for scammers, compromised keys, and trusted entities. Enables reputation systems and security measures that can be referenced across applications and services.

### Key Features Across Use Cases

All BitBadges use cases benefit from:

* **Protocol-Level Enforcement**: Compliance and rules are enforced at the blockchain protocol level, not just in applications
* **Multi-Currency Support**: Accept payments in any IBC-compatible currency
* **Custom Transferability Controls**: Define exactly who can transfer tokens and under what conditions
* **Time-Dependence**: Create time-based logic for unlocks, expirations, and vesting
* **7000+ No-Code Integrations**: Gate access using existing services without writing custom code
* **On-Chain Verification**: All rules and compliance checks are verifiable on the blockchain
* **Flexible Approvals System**: Create complex approval workflows for transfers and operations


# BADGE

BADGE is the native gas token for the BitBadges blockchain. Please read our policies (including our [BADGE disclosure](https://bitbadges.io/credits-disclosure)) on our site for full disclaimers and information.

<figure><img src="/files/wVKIGnBzYiDMDpT1aj2w" alt=""><figcaption></figcaption></figure>

BADGE has 3 primary purposes:

1. Gas / Transaction Fees
2. Proof of Stake/Authority - Validators bonding BADGE for the security of the network via staking.
3. In-Site Currency - Although note that we will prioritize others like USDC and other more established ones for in-site use.
4. 0.1% Taker Fees on all transactions go to community pool

### **Distribution**

For current distributions, supply, and more, we refer you to our explorer: [https://explorer.bitbadges.io](https://explorer.bitbadges.io/).

BADGE is on Osmosis at <https://app.osmosis.zone/assets/BADGE> and compatible with other IBC-enabled services.

Excluding block rewards, the initial circulating supply of BADGE was 100M.

### **Security Model**

BitBadges operates under a hybrid model combining existing proof-of-stake validator rewards with a proof-of-authority delegation model.

**1. Existing Validator Rewards Program (\~20% of supply)**

An incentivized rewards program runs until 8/12/2026 (one year from the initial start date of 8/12/2025). Validators earn BADGE based on uptime, measured by sampling block signatures. Mission Decentralization candidates (the first \~40 validators) receive a base allocation of 200,000 BADGE scaled by uptime % (with a 50,000 BADGE floor). All other eligible validators (registered by block 8,998,000) receive a base of 100,000 BADGE scaled by uptime %. Upon reaching the end of this program, earned BADGE will be awarded and current delegations will shift to the proof of authority model. Note that current delegations are not reflective of awards — most validators are delegated \~200K+ which will go away at this time.

**2. Proof of Authority Model (\~60% of supply)**

The proof of authority model is effective immediately and runs in parallel with the rewards program until 8/12/2026, at which point it becomes the sole delegation model. The remaining BADGE allocations — including the 50M community pool, other team delegations, and leftover awards — will be allocated to well-known, trusted validators via governance proposals, following a know-your-validator setup with preference given to institutions and well-known brands. These delegated tokens can only be delegated, never sold, cutting the effective circulating supply by >60M BADGE.


# Official Links and Resources

Below is a list of official links for socials and other platforms. Our preferred contact is Discord. This is not an exhaustive list. See the rest of the documentation for more information on specific subjects.

* [BitBadges App](https://bitbadges.io)
* [Explorer](https://explorer.bitbadges.io)

### Install

One command installs everything — the chain binary, SDK CLI, and 104+ API routes:

```bash
curl -fsSL https://install.bitbadges.io | sh
```

Supports Linux, macOS (Intel + Apple Silicon), and Windows (Git Bash / WSL). See [CLI & Chain Binary](/for-developers/cli) for full documentation.

* [Chain Binary Releases](https://github.com/BitBadges/bitbadgeschain/releases)
* [SDK CLI (npm)](https://www.npmjs.com/package/bitbadges) — `npm install -g bitbadges`
* [BitBadges Builder Tools (npm)](https://www.npmjs.com/package/bitbadges) — `npm install -g bitbadges`

### Get Featured

* [Get Featured](https://tally.so/r/mBy2aR) - Explore Page

### Documentation

* [LLM .txt](https://github.com/trevormil/bitbadges-docs/blob/master/for-llms.txt) - Entire documentation dumped into one file
* [CLI Docs](/for-developers/cli) - Full CLI reference

### Socials

* [Discord](https://discord.com/invite/TJMaEd9bar)
* [LinkedIn](https://linkedin.com/company/bitbadges)
* [Twitter](https://twitter.com/bitbadges_io)
* [Telegram](https://t.me/bitbadges_chat)
* [GitHub](https://github.com/bitbadges)

### Integrations

* [Zapier](https://zapier.com/apps/bitbadges/integrations)

### API & SDK

* [NPM API / SDK Package](https://www.npmjs.com/package/bitbadges) ([Docs](/for-developers/bitbadges-sdk))
* [API Documentation](https://bitbadges.stoplight.io/docs/bitbadges) ([Docs](/for-developers/bitbadges-api))

```bash
# Using npm
npm install bitbadges

# Using pnpm
pnpm add bitbadges

# Using bun
bun add bitbadges
```


# Brand Guidelines

Feel free to use BitBadges name and logo in your site as you see fit. If you have any questions or concerns about usage, please let us know. We are also happy to provide any other logos, images, or information you may need.

<figure><img src="/files/mPrs43zjpoTELDIB5rGP" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/PGnnJW6n00CLGwwlssTx" alt=""><figcaption></figcaption></figure>


# BitBadges vs ERC-3643

Looking to understand how BitBadges and ERC-3643 (T-REX) relate? They are not competing standards -- they operate at different layers. BitBadges' protocol-level token standard handles the compliance logic natively, and developers familiar with ERC-3643 can use it as a Solidity interface that calls into the BitBadges standard through EVM precompiles. You get the familiar ERC-3643 API backed by protocol-level enforcement.

## Overview

**ERC-3643** (also known as T-REX, Token for Regulated EXchanges) is an Ethereum-based standard for compliant security tokens. It has been ratified by the ERC process and has facilitated over $32 billion in tokenized assets. It is the most widely adopted standard for institutional-grade tokenized securities, with strong backing from the financial sector.

**BitBadges** is a Cosmos-based Layer 1 blockchain with a built-in programmable token standard. Compliance rules, transfer restrictions, and approval logic are enforced directly at the protocol level -- no smart contract deployment required. For developers coming from an EVM background, BitBadges provides EVM precompiles that let you interact with the native token standard through familiar Solidity interfaces like ERC-3643. The protocol standard does the heavy lifting; the ERC-3643 interface is just one way to call into it.

## Feature Comparison

| Feature                      | BitBadges Protocol Standard                                                                                                                 | ERC-3643 Interface (via precompiles on BitBadges, or natively on Ethereum)                                                   |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **Enforcement layer**        | Protocol-level -- rules are enforced by the chain itself                                                                                    | Contract-level -- rules are enforced by Solidity smart contracts                                                             |
| **Smart contracts required** | No -- collections are configured through structured transaction messages                                                                    | Yes -- each token requires deploying multiple Solidity contracts (token, identity registry, compliance module, claim topics) |
| **Deployment experience**    | No-code UI, CLI template builders, or AI-assisted builder tools                                                                             | Developer-only -- requires Solidity expertise and contract deployment                                                        |
| **Multi-chain support**      | Native IBC (Inter-Blockchain Communication) to all Cosmos chains                                                                            | Bridge-dependent for cross-chain transfers (unless deployed on BitBadges, which has native IBC)                              |
| **Identity and compliance**  | Built-in approval criteria with configurable checks (ownership requirements, merkle proofs, signature challenges, on-chain queries)         | ONCHAINID identity framework with claim topics and trusted issuers                                                           |
| **Transfer restrictions**    | Configurable per-approval rules: address allowlists, time windows, amount limits, tracker-based caps, 2FA gating, coin payment requirements | Compliance modules with modular rule contracts (country restrictions, investor limits, time locks)                           |
| **Permissioning**            | Granular, lockable permission system -- each field can be frozen or made manager-controlled independently                                   | Owner/agent role system with recovery mechanisms                                                                             |
| **Token types supported**    | Fungible tokens, NFTs, subscriptions, vaults, prediction markets, bounties, and more -- all from the same standard                          | Primarily equity/security tokens (fungible)                                                                                  |
| **Forced transfers**         | Supported via admin override approvals                                                                                                      | Supported via recovery and forced transfer functions                                                                         |
| **Supply control**           | Configurable mint/burn rules with lockable permissions                                                                                      | Mint/burn controlled by token agents                                                                                         |

## ERC-3643 Strengths

* **Institutional adoption** -- ERC-3643 is a ratified Ethereum standard with $32B+ in tokenized assets and adoption by major financial institutions
* **Regulatory track record** -- Purpose-built for securities compliance with established legal frameworks
* **Ecosystem maturity** -- Rich ecosystem of identity providers, compliance modules, and institutional tooling
* **ONCHAINID framework** -- Mature decentralized identity solution for KYC/AML compliance

## BitBadges Protocol Standard Strengths

* **No smart contract development** -- Creating a compliant token requires zero code; everything is configured through transaction parameters or the no-code UI
* **Protocol-level guarantees** -- Transfer rules cannot be bypassed by contract bugs or upgradeable proxy exploits because they are enforced by the chain itself
* **Broader token types** -- The same standard handles subscriptions, prediction markets, vaults, auctions, bounties, NFTs, and more -- not just equity tokens
* **AI and automation friendly** -- builder tools and CLI template builders allow AI agents to create and manage compliant tokens programmatically
* **Cosmos ecosystem** -- Native IBC connectivity to 50+ Cosmos chains without bridges
* **Lower barrier to entry** -- No Solidity expertise, gas optimization, or contract auditing required

## How They Work Together

ERC-3643 and the BitBadges protocol standard are not separate systems -- ERC-3643 serves as a Solidity interface layer on top of the BitBadges standard. Through EVM precompiles, Solidity contracts can call into the native token standard using the ERC-3643 API that developers already know.

This means:

* **ERC-3643 as a familiar interface** -- Solidity developers can interact with BitBadges tokens using the ERC-3643 function signatures they already know, while the actual compliance enforcement happens at the protocol level
* **Protocol-level enforcement under the hood** -- Transfer rules, identity checks, and compliance logic are enforced by the chain itself, not by the Solidity contract. The precompile bridges the call into the native standard
* **No-code path still available** -- You don't need to use the ERC-3643 interface at all. The no-code UI, CLI template builders, and builder tools interact with the protocol standard directly
* **Best of both worlds** -- Get the institutional familiarity and tooling compatibility of ERC-3643 with the protocol-level guarantees and broader token type support of BitBadges

## Further Reading

* [BitBadges Token Standard Overview](/token-standard/x-tokenization)
* [Approvals and Transfer Rules](/token-standard/learn/transferability)
* [Permissions System](/token-standard/learn/permissions)
* [BitBadges L1 vs Other Protocols](https://github.com/trevormil/bitbadges-docs/blob/master/overview/comparing-bitbadges-to-other-protocols.md)
* [ERC-3643 Specification](https://erc3643.info/) (external)


# Compliance Zone Architecture

Where compliance lives on a chain matters: in the token, the bank module, every smart contract, or at a controlled boundary. BitBadges chooses the boundary.

Two zones:

* **Open zone** — vanilla Cosmos. `sdk.Coin` denoms (gas, IBC stablecoins, bridge wrappers), public bank, standard staking, vanilla IBC. No compliance gates.
* **Compliance zone** — `x/tokenization` collections. A siloed environment under issuer sovereignty: the issuer configures transfer rules (sanctions, KYC, jurisdiction, holding periods, multi-sig escrow, rate limits); the approval engine enforces them.

The zones connect via approval-engine-mediated boundary operations: 1:1 wrapping, pool swaps, required side payments, IBC-backed mints. Every crossing runs the approval engine.

## Why two zones

Gating compliance at every layer (bank, IBC, staking, gov, every contract) has four problems:

* **Surface area** — compliance code spreads across modules; auditors verify each integration.
* **Ecosystem compat** — if bank rejects vanilla transfers, IBC counterparties can't predict behavior; wallets and indexers break.
* **Update friction** — rule changes mean modifying multiple call sites, possibly across forked SDK modules.
* **All-or-nothing** — can't mix compliant and permissionless assets on the same chain.

The compliance-zone model:

* **One audit surface** — boundary lives in `x/tokenization`. No SDK fork, no scattered ante decorators.
* **Ecosystem compat preserved** — open zone behaves like any Cosmos chain.
* **Update via governance** — `MsgUpdateApproval` against `x/tokenization`. Approval criteria *are* the rules.
* **Mixed posture** — compliant equity tokens, public DEX, IBC stablecoins on the same chain, each in the appropriate zone.

## Boundary mechanics

The boundary is a category of operations, not one primitive. Every crossing runs the approval engine. Patterns:

* **1:1 wrapping** — `CosmosCoinBackedPath` (collection backed 1:1 by underlying `sdk.Coin`; wrap locks the underlying and mints, unwrap reverses) or `CosmosCoinWrapperPath` (alternate mode without backed mint).
* **Pool swaps** — when a pool (x/gamm-style AMM) exchanges `sdk.Coin` for a tokenization asset, the approval engine gates the trade. No separate wrap step needed.
* **Required side payments** — `CoinTransfer` clauses on an approval move `sdk.Coin` alongside a tokenization transfer (royalties, redemption payouts, subscription fees).
* **IBC-backed minting** — packet receipt mints directly into a collection, with approval gates running on receipt.

At boundary time, the chain can check:

* **Sanctions** — address in a `DynamicStore` flagged OFAC / SDN?
* **KYC** — holds a `kyc-passport` badge from an authorized issuer?
* **Jurisdiction** — `jurisdiction:US` badge but asset is non-US?
* **Threshold** — amount exceeds a FATF Travel Rule trigger?
* **External state** — `EVMQueryChallenge` against an external contract (e.g., Chainalysis-tagged).
* **Multi-sig** — `VotingChallenge`, N-of-M signers, optional `delayAfterQuorum` timelock.
* **Time** — `transferTimes` + `AltTimeChecks` for market hours, business days, blackout windows.

Inside the zone, the same engine governs ongoing activity: transfer restrictions, holding periods (`MustOwnTokens.ownershipTimes`), dividends (`IncrementedBalances` + `CoinTransfers`), multi-sig escrow (`VotingChallenge` + `delayAfterQuorum`), vesting (`Balance.ownershipTimes`).

## The open zone

The chain's standard surface:

* Native gas token as `sdk.Coin` (e.g. `ubadge`)
* IBC-arrived stablecoins (USDC, USDT) as ICS-20 vouchers
* Vanilla bank, staking, governance (typically PoA)
* Public DEX, lending pools, ecosystem-standard activity

Supervised at chain-config level (counterparty allowlists, validator set, permitted assets) but not gated per-transfer.

## Cross-chain via siloed environments

`x/tokenization` is a siloed environment. The issuer is sovereign inside it: they write the rules, the approval engine enforces them.

Tokenization tokens can't travel over vanilla IBC — vanilla IBC moves `sdk.Coin` with no compliance semantics. The cross-chain pattern:

1. **Exit the source silo** — unwrap, redeem, or burn. Issuer exit rules fire; underlying `sdk.Coin` releases (ATOM, USDC, whatever backs the silo).
2. **Travel as `sdk.Coin` over vanilla ICS-20** — no custom channels, no middleware. Counterparties see normal IBC traffic.
3. **Re-enter a silo on the destination chain** — destination issuer's entry rules fire; mint into the destination collection.

Each silo enforces its own rules; the cross-chain hop is neutral IBC. Result:

* **IBC compatibility** — no custom protocols, no counterparty changes.
* **Issuer sovereignty** — each silo's rules are independent; no cross-issuer coordination.
* **Boundary-only enforcement** — siloed state only under approved conditions; in-transit, vanilla `sdk.Coin` has no semantics to violate.

Mirrors how tokenized securities cross jurisdictions in TradFi: delivered to a destination depository that re-applies its own regulatory framework.

## Compared to the permissioned-token model

ERC-3643 puts compliance in the token contract — every transfer everywhere hits transfer-restriction logic. The token *is* the boundary.

EVM has no architectural seam between vanilla currency and regulated assets, so compliance must live in the contract. Cosmos has that seam — `sdk.Coin` and tokenization-managed assets are distinct first-class citizens. The compliance-zone model uses it.

|                            | Permissioned Token (ERC-3643)             | Compliance Zone (BitBadges)                       |
| -------------------------- | ----------------------------------------- | ------------------------------------------------- |
| **Where compliance lives** | Token contract                            | Approval engine                                   |
| **What's gated**           | Every transfer, everywhere                | Boundary entry + intra-zone activity              |
| **New regime**             | Deploy new contracts + compliance modules | Configure new approval criteria; issue new badges |
| **Updating rules**         | Redeploy or upgrade contracts             | `MsgUpdateApproval` via governance                |
| **Mixed activity**         | All-or-nothing                            | Two zones                                         |
| **Ecosystem compat**       | Permissioned tokens trip vanilla DeFi     | Open zone is vanilla Cosmos                       |
| **Boundary**               | Implicit (contract is the boundary)       | Explicit (approval-engine ops into a silo)        |

The two compose: `x/tokenization`'s EVM precompile lets ERC-3643 Solidity contracts inherit the compliance zone's gates without redesign. See [BitBadges vs ERC-3643](/overview/bitbadges-vs-erc3643).

## Deployment

For chain devs:

1. **Choose what enters the zone** — which assets wrap on receipt (IBC-USDC → wrapped token), which stay open (gas, DEX-only).
2. **Configure wrap-step approval criteria** — sanctions, KYC, jurisdiction. Configurations, not code.
3. **Define intra-zone rules** — holding periods, dividends, redemption, vesting. All `ApprovalCriteria`.
4. **Optionally restrict the open zone** — IBC channel allowlists, PoA validator set. Chain-config, not module changes.
5. **Document the boundary** — what's in which zone. The chain's architectural contract.

No SDK fork. No plugin pack. The compliance zone is a configuration pattern on vanilla Cosmos.

## Further Reading

* [Cosmos Coin Wrapper Paths](/token-standard/learn/cosmos-coin-wrapper-paths)
* [IBC Backed Minting](/token-standard/learn/ibc-backed-minting)
* [Transferability and Approval Rules](/token-standard/learn/transferability)
* [BitBadges vs ERC-3643](/overview/bitbadges-vs-erc3643)


# Overview

This directory contains comprehensive developer documentation for the BitBadges blockchain's `x/tokenization` module.

> 💡 **Note:** For most development, you may actually not need to know many of the underlying details of x/tokenization that we describe in this section. For example, you may only need high-level API getters like fetching balances and metadata as well as the no-code Create tab in the BitBadges site.

## Table of Contents

1. [Concepts](/token-standard/learn)- Core data structures and business logic
2. [Messages](/token-standard/messages) - Transaction messages and handlers
3. [Queries](/token-standard/queries) - Query types and endpoints
4. [Examples](/token-standard/examples) - Common usage patterns and building blocks

## Main Features

* No code, no smart contracts - All implemented as a Cosmos module
* 1000x transferability customization for whatever requirements you may need
* Three transferability levels enforced for as much or as little customization as needed (collection-level, sender, recipient approvals)
* Seamless compatibility for checking off-chain criteria like in the BitBadges site where you can gate mints by 7000+ no-code plugins
* Supports any IBC currency
* IBC interoperable through wrapping to x/bank IBC denoms
* Transferability is checked EVERY transfer on-chain, enforcing compliance seamlessly at the protocol level
* Extendible with smart contract frameworks

## Message Reference

### Collection Management

* [MsgCreateCollection](/token-standard/messages/msg-create-collection) - Create new collection
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Update existing collection
* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Universal create/update interface with invariants support
* [MsgDeleteCollection](/token-standard/messages/msg-delete-collection) - Delete collection

### Token Transfers

* [MsgTransferTokens](/token-standard/messages/msg-transfer-tokens) - Transfer tokens between addresses

### User Approvals

* [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) - Update transfer approvals
* [MsgCastVote](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/messages/msg-cast-vote.md) - Cast or update votes for voting challenges

### Address Lists & Dynamic Stores

* [MsgCreateAddressLists](/token-standard/messages/msg-create-address-lists) - Create reusable address lists
* [MsgCreateDynamicStore](/token-standard/messages/msg-create-dynamic-store) - Create boolean store
* [MsgUpdateDynamicStore](/token-standard/messages/msg-update-dynamic-store) - Update dynamic store properties
* [MsgDeleteDynamicStore](/token-standard/messages/msg-delete-dynamic-store) - Delete dynamic store
* [MsgSetDynamicStoreValue](/token-standard/messages/msg-set-dynamic-store-value) - Set boolean values for addresses
* [More messages...](/token-standard/messages) - See full message reference

## Query Reference

### Core Queries

* [GetCollection](/token-standard/queries/get-collection) - Retrieve collection data
* [GetBalance](/token-standard/queries/get-balance) - Get user balances
* [GetApprovalTracker](/token-standard/queries/get-approval-tracker) - Get approval usage data
* [GetAddressList](/token-standard/queries/get-address-list) - Retrieve address list
* [More queries...](/token-standard/queries) - See full query reference

## Quick Links

* [BitBadges Chain Repository](https://github.com/bitbadges/bitbadgeschain)
* [BitBadges Documentation](https://docs.bitbadges.io)
* [Proto Definitions](https://github.com/bitbadges/bitbadgeschain/tree/master/proto/tokenization)

## Documentation Style

This documentation follows the [Cosmos SDK module documentation standards](https://docs.cosmos.network/main/building-modules/README) and is designed for developers building on or integrating with the BitBadges blockchain.


# Learn


# Explore!

Familiarize yourself with the platform by exploring [bitbadges.io](https://bitbadges.io). We always recommend that the best way to learn is just go and try stuff out first. Plenty available in-site is developer-friendly and no-code.

* Review the landing and explore pages
* Complete the collection creation flow
* Examine transaction JSONs by clicking "Show Tx" at the end of different creation flows

You may have all you need directly in-site!

<figure><img src="/files/5RQu1TxK3oqhdJQOiBCk" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/rwl2B7iUYXA6XtbMqy45" alt=""><figcaption></figcaption></figure>


# Manager / Permissions

### Manager

The **manager** is the central authority for a collection, controlling all administrative operations. The manager executes actions according to permission rules defined in `collectionPermissions`.

```typescript
const collection: TokenCollection<bigint> = {
    manager: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    collectionPermissions: {
        // Permissions define what the manager can do
    },
    // ... other collection fields
};
```

#### Setting Initial Manager

During collection creation:

```typescript
const collection: TokenCollection<bigint> = {
    creator: 'bb1alice...',
    manager: 'bb1alice...',
    collectionPermissions: {
        canUpdateManager: [
            {
                permanentlyPermittedTimes: [
                    { start: 1n, end: 18446744073709551615n },
                ],
                permanentlyForbiddenTimes: [],
            },
        ],
        // ... other permission fields
    },
    // ... other collection fields
};
```

#### No Manager

If you don't want a manager, set the manager to an empty string. Permission values don't matter when there's no manager:

```typescript
const manager: string = '';
```

### Manager Capabilities

The manager can execute administrative actions according to permissions:

* Updating collection metadata
* Updating token metadata
* Updating transferability (collection approvals)
* Updating valid token IDs
* Archiving the collection
* Deleting the collection
* Updating manager
* And more...

All permissions are defined on-chain with time-based configuration, allowing permissions to change over time.

You may also implement off-chain permissions for the manager, but this is up to you and your use case.

```typescript
// Manager with full control (soft-enabled)
const collectionPermissions: CollectionPermissions<bigint> = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [],
    canUpdateValidTokenIds: [],
    canAddMoreAliasPaths: [],
    canAddMoreCosmosCoinWrapperPaths: [],
    // ... other permission fields
};
```

### User Permissions

User permissions control what individual users can do with their own badges and transfer approvals. Unlike manager permissions which control collection-wide administrative actions, user permissions control user-specific actions like updating auto-approve settings and managing their own incoming/outgoing transfer approvals.

These are almost always never needed unless in advanced situations. Typically, you just leave these soft-enabled (empty arrays) for all. These are only really needed in advanced situations where you want to lock down a user's ability to update their own approvals, such as escrow accounts.

```typescript
const userPermissions: UserPermissions<bigint> = {
    canUpdateAutoApproveSelfInitiatedOutgoingTransfers: [],
    canUpdateAutoApproveSelfInitiatedIncomingTransfers: [],
    canUpdateAutoApproveAllIncomingTransfers: [],
    canUpdateOutgoingApprovals: [],
    canUpdateIncomingApprovals: [],
};
```

### Permissions

Permissions control which actions can be performed and when those actions can be executed. The manager executes administrative actions according to these permission rules.

### Permission States

Permissions have three states:

| State                     | Description                    | Behavior                                                   |
| ------------------------- | ------------------------------ | ---------------------------------------------------------- |
| **Permanently Permitted** | Action ALWAYS allowed (frozen) | Can be executed                                            |
| **Permanently Forbidden** | Action ALWAYS blocked (frozen) | Cannot be executed                                         |
| **Neutral**               | Not specified                  | **Allowed by default now, but can change state in future** |

**Important:** Once set to permanently permitted or forbidden, it can never changed. This is by design to act as a check and balance enforced on-chain.

```typescript
// Lock collection deletion forever
const collectionPermissions: CollectionPermissions<bigint> = {
    canDeleteCollection: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
    // ... other permission fields
};

// Soft-enabled (default behavior - allowed but can be changed)
const collectionPermissions: CollectionPermissions<bigint> = {
    canDeleteCollection: [], // Empty = allowed by default
    // ... other permission fields
};
```

### First Match Policy

Permissions are evaluated as a linear array where each element has criteria and time controls. Only the **first matching element** is applied - all subsequent matches are ignored.

**Key Rules:**

* **First Match Only**: Only the first element that matches all criteria is used
* **Deterministic State**: Each criteria combination has exactly one permission state
* **No Overlap**: Times cannot be in both `permanentlyPermittedTimes` and `permanentlyForbiddenTimes`
* **Order Matters**: Array order affects which permissions are applied

**Example: Action Permissions**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateCollectionMetadata: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};
```

**Result:**

* Collection metadata updates: **Forbidden** (locked forever)

### Satisfying Criteria

All criteria in a permission element must match for it to be applied. Partial matches are ignored.

**Example: Token Metadata Permissions**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateTokenMetadata: [
        {
            tokenIds: [{ start: 1n, end: 10n }],
            permanentlyPermittedTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};
```

**This permission only covers:**

* Token IDs 1-10

**It does NOT cover:**

* Token ID 11

These combinations are **unhandled** and **allowed by default** since they do not match the permission criteria.

### Brute Force Pattern

To lock specific criteria, you must specify the target and set all other criteria to maximum ranges (brute forcing all other options).

**Example: Lock Token IDs 1-10**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateCollectionApprovals: [
        {
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            tokenIds: [{ start: 1n, end: 10n }],
            transferTimes: [{ start: 1n, end: 18446744073709551615n }],
            ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            approvalId: 'All',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};
```

### Permission Types

There are **four types** of permissions, each with different criteria:

| Type                            | Criteria                                       | Examples                                                                                                                                                                                                  |
| ------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Action Permissions**          | Time control only                              | `canDeleteCollection`, `canUpdateCollectionMetadata`, `canUpdateStandards`, `canUpdateCustomData`, `canUpdateManager`, `canArchiveCollection`, `canAddMoreAliasPaths`, `canAddMoreCosmosCoinWrapperPaths` |
| **Token ID Action Permissions** | Token IDs + time control                       | `canUpdateValidTokenIds`, `canUpdateTokenMetadata`                                                                                                                                                        |
| **Approval Permissions**        | Transfer criteria + approval ID + time control | `canUpdateCollectionApprovals`, `canUpdateIncomingApprovals`                                                                                                                                              |

#### Action Permissions

Simple time-based permissions with no additional criteria. Control when actions can be executed based solely on time.

**Collection Actions:**

* `canDeleteCollection` - Delete entire collection
* `canUpdateCollectionMetadata` - Update collection metadata
* `canUpdateStandards` - Update standards
* `canUpdateCustomData` - Update custom data
* `canUpdateManager` - Update manager
* `canArchiveCollection` - Archive/unarchive collection
* `canAddMoreAliasPaths` - Add new alias paths to collection
* `canAddMoreCosmosCoinWrapperPaths` - Add new cosmos coin wrapper paths to collection

**User Actions:**

* `canUpdateAutoApproveSelfInitiatedOutgoingTransfers` - Auto-approve outgoing transfers
* `canUpdateAutoApproveSelfInitiatedIncomingTransfers` - Auto-approve incoming transfers
* `canUpdateAutoApproveAllIncomingTransfers` - Auto-approve all incoming transfers

**Logic:**

```
For each action request:
    Check if current time is in permanentlyPermittedTimes
        → If yes: ALLOW
        → If no: Check if current time is in permanentlyForbiddenTimes
            → If yes: DENY
            → If no: ALLOW (neutral state)
```

**Examples:**

```typescript
// Lock collection deletion forever
const collectionPermissions: CollectionPermissions<bigint> = {
    canDeleteCollection: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};

// Allow collection deletion only during specific period
const collectionPermissions: CollectionPermissions<bigint> = {
    canDeleteCollection: [
        {
            permanentlyPermittedTimes: [
                { start: 1704067200000n, end: 1735689600000n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};

// Default behavior (no restrictions)
const collectionPermissions: CollectionPermissions<bigint> = {
    canDeleteCollection: [],
};
```

#### Token ID Action Permissions

Control when token ID-based fields can be updated. These permissions match on `tokenIds` to determine which token IDs can be updated.

**Token ID Action Permissions:**

* `canUpdateValidTokenIds` - Update valid token IDs
* `canUpdateTokenMetadata` - Update token metadata

**Logic:**

```
For each token ID update request:
    Check if token ID matches any tokenIds criteria
        → If no match: ALLOW (neutral state)
        → If match: Check if current time is in permanentlyPermittedTimes
            → If yes: ALLOW
            → If no: Check if current time is in permanentlyForbiddenTimes
                → If yes: DENY
                → If no: ALLOW (neutral state)
```

**Examples:**

```typescript
// Lock token metadata for specific tokens forever
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateTokenMetadata: [
        {
            tokenIds: [{ start: 1n, end: 100n }],
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};

// Allow token metadata updates only during specific period
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateTokenMetadata: [
        {
            tokenIds: [{ start: 1n, end: 100n }],
            permanentlyPermittedTimes: [
                { start: 1704067200000n, end: 1735689600000n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};
```

#### Token ID Action Permissions

Control which token-specific actions can be performed based on token IDs.

**Available Actions:**

* `canUpdateValidTokenIds` - Update valid token ID ranges

**Logic:**

```
For each token action request:
    Check if token ID matches any tokenIds criteria
        → If no match: ALLOW (neutral state)
        → If match: Check if current time is in permanentlyPermittedTimes
            → If yes: ALLOW
            → If no: Check if current time is in permanentlyForbiddenTimes
                → If yes: DENY
                → If no: ALLOW (neutral state)
```

**Examples:**

```typescript
// Lock all token ID updates
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateValidTokenIds: [
        {
            tokenIds: [{ start: 1n, end: 18446744073709551615n }],
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};

// Lock specific ID range
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateValidTokenIds: [
        {
            tokenIds: [{ start: 1n, end: 100n }],
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};

// Allow future token IDs only
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateValidTokenIds: [
        {
            tokenIds: [{ start: 101n, end: 18446744073709551615n }],
            permanentlyPermittedTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};
```

#### Approval Permissions

Control when transfer approvals can be updated, allowing you to freeze specific transfer rules. These permissions match on transfer criteria (from, to, initiatedBy, transferTimes, tokenIds, ownershipTimes) and approval ID.

**Available Actions:**

* `canUpdateCollectionApprovals` - Control collection-level approvals
* `canUpdateIncomingApprovals` - Control incoming transfer approvals (user)
* `canUpdateOutgoingApprovals` - Control outgoing transfer approvals (user)

**Note**: For user approvals, `fromListId` and `toListId` are automatically set:

* **Incoming**: `toListId` is hardcoded to the user's address
* **Outgoing**: `fromListId` is hardcoded to the user's address

**Logic:**

```
For each approval update request:
    Check if approval criteria match (from, to, initiatedBy, transferTimes, tokenIds, ownershipTimes, approvalId)
        → If no match: ALLOW (neutral state)
        → If match: Check if current time is in permanentlyPermittedTimes
            → If yes: ALLOW
            → If no: Check if current time is in permanentlyForbiddenTimes
                → If yes: DENY
                → If no: ALLOW (neutral state)
```

**Approval Tuple:** An approval tuple consists of: `(from, to, initiatedBy, tokenIds, transferTimes, ownershipTimes, approvalId)`

**Examples:**

```typescript
// Lock specific token ID range
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateCollectionApprovals: [
        {
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            tokenIds: [{ start: 1n, end: 100n }],
            transferTimes: [{ start: 1n, end: 18446744073709551615n }],
            ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            approvalId: 'All',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};

// Lock specific approval ID
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateCollectionApprovals: [
        {
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            tokenIds: [{ start: 1n, end: 18446744073709551615n }],
            transferTimes: [{ start: 1n, end: 18446744073709551615n }],
            ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            approvalId: 'specific-approval-id',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};

// Complete freeze - lock all approvals
const collectionPermissions: CollectionPermissions<bigint> = {
    canUpdateCollectionApprovals: [
        {
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            tokenIds: [{ start: 1n, end: 18446744073709551615n }],
            transferTimes: [{ start: 1n, end: 18446744073709551615n }],
            ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            approvalId: 'All',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};
```

**Protection Strategies:**

1. **Specific Approval Lock**: Lock a specific approval by its unique ID
2. **Range Lock with Overlap Protection**: Lock a token range AND all overlapping approvals
3. **Complete Freeze**: Lock all approvals for a collection


# Balances

The Balance system in BitBadges is designed to represent ownership of tokens across different IDs and time ranges. Ownership times are a new concept to BitBadges allowing you to set that someone owns a token during a specific time but not other times.

## Balance Interface

```typescript
export interface Balance<T extends NumberType> {
    amount: T;
    tokenIds: UintRange<T>[];
    ownershipTimes: UintRange<T>[];
}
```

* `amount`: The quantity of tokens owned
* `tokenIds`: An array of ID ranges representing the tokens owned
* `ownershipTimes`: An array of time ranges during which the tokens are owned

## Interpreting Balances

When interpreting balances, it's crucial to understand that multiple ranges of token IDs and ownership times within a single Balance structure represent all possible combinations.

### Interpretation Algorithm

```javascript
for (balance of balances) {
    for (tokenIdRange of balance.tokenIds) {
        for (ownershipTimeRange of balance.ownershipTimes) {
            // User owns x(balance.amount) of (tokenIdRange) for the times (ownershipTimeRange)
        }
    }
}
```

### Example

Consider the following balance:

```json
{
    "amount": 1,
    "tokenIds": [
        { "start": 1, "end": 10 },
        { "start": 20, "end": 30 }
    ],
    "ownershipTimes": [
        { "start": 20, "end": 50 },
        { "start": 100, "end": 200 }
    ]
}
```

This balance expands to:

1. 1x of IDs 1-10 from times 20-50
2. 1x of IDs 1-10 from times 100-200
3. 1x of IDs 20-30 from times 20-50
4. 1x of IDs 20-30 from times 100-200

## Balance Subtraction

When subtracting balances, you may need to represent the result as multiple Balance objects. For example, if we subtract the first set of balances from the example above (1x of IDs 1-10 from times 20-50), the result would be:

```json
[
    {
        "amount": 1,
        "tokenIds": [
            { "start": 1, "end": 10 },
            { "start": 20, "end": 30 }
        ],
        "ownershipTimes": [{ "start": 100, "end": 200 }]
    },
    {
        "amount": 1,
        "tokenIds": [{ "start": 20, "end": 30 }],
        "ownershipTimes": [{ "start": 20, "end": 50 }]
    }
]
```

## Handling Duplicates

When duplicate token IDs are specified in balances, they are combined and their amounts are added. For example:

```json
{
    "amount": 1,
    "tokenIds": [
        { "start": 1, "end": 10 },
        { "start": 1, "end": 10 }
    ],
    "ownershipTimes": [{ "start": 100, "end": 200 }]
}
```

This is equivalent to and will be treated as:

```json
{
    "amount": 2,
    "tokenIds": [{ "start": 1, "end": 10 }],
    "ownershipTimes": [{ "start": 100, "end": 200 }]
}
```

## Best Practices

1. **Efficient Representation**: Try to represent balances in the most compact form possible by combining overlapping ranges
2. **Careful Subtraction**: When subtracting balances, ensure that you correctly split the remaining balances to accurately represent the result
3. **Avoid Duplicates**: While the system handles duplicates by combining them, it's more efficient to represent balances without duplicates in the first place
4. **Time-Aware Operations**: Always consider the time dimension when performing operations on balances, as ownership can vary over time
5. **Range Calculations**: Familiarize yourself with range operations, as they are crucial for correctly manipulating and interpreting balances


# Transferability / Approvals

Transferability in BitBadges is controlled through a hierarchical approval system with three levels: collection, outgoing, and incoming.

## Three Transferability Levels

BitBadges supports three levels of transferability control:

| Level          | Controlled By  | Approval Level | Approver Address | Stored On                           | Msg                                       | Use Case                               |
| -------------- | -------------- | -------------- | ---------------- | ----------------------------------- | ----------------------------------------- | -------------------------------------- |
| **Collection** | Manager/Issuer | collection     | ""               | TokenCollection.collectionApprovals | MsgCreateCollection / MsgUpdateCollection | Global rules, freezability, compliance |
| **Outgoing**   | Sender         | outgoing       | bb1...           | UserBalanceStore.outgoingApprovals  | MsgUpdateUserApprovals                    | Listings, delegation                   |
| **Incoming**   | Recipient      | incoming       | bb1...           | UserBalanceStore.incomingApprovals  | MsgUpdateUserApprovals                    | Bids, access control                   |

Each transfer must satisfy collection-level AND (unless overridden) user-level approvals, while also having sufficient balances to transfer.

## Approval vs Permission vs Transfers

* Approvals define the rules for transfers on multiple levels.
* Transfers execute if the approval rules defined allow it and sufficient balances.
* Permissions can control the updatability of approvals - `canUpdateCollectionApprovals`

### Transfer Validation Process

Each transfer must 1) have sufficient balances, 2) satisfy the collection approvals, and 3) satisfy the corresponding user-level approvals (unless overridden).

**Validation flow:**

<figure><img src="/files/zFRLbZVLVTmHUjXSUsbG" alt=""><figcaption><p>Transfer validation flow</p></figcaption></figure>

## Collection Approvals

Collection approvals define transferability rules for the entire collection on a global level. These rules apply to both minting and post-minting transfers. These let the issuer / manager control global compliance and transferability, such as freezability, revocation, and more.

**All transfers must satisfy the collection approvals.**

**Important:** Approval IDs are unique identifiers and must not collide with other collection approvals.

```typescript
// Stored on TokenCollection.collectionApprovals[] (CollectionApproval<T>[])
interface CollectionApproval<T extends bigint> {
    // Core Fields - Define Who? When? What?
    toListId: string; // Who can receive?
    fromListId: string; // Who can send?
    initiatedByListId: string; // Who can initiate?
    transferTimes: UintRange<T>[]; // When can transfer happen?
    tokenIds: UintRange<T>[]; // Which token IDs?
    ownershipTimes: UintRange<T>[]; // Which ownership times?
    approvalId: string; // Unique identifier - must not collide with other approvals on the same level
    version: T; // Version control (incremented on each update)

    // Optional Fields
    uri?: string; // Metadata link
    customData?: string; // Custom data
    approvalCriteria?: ApprovalCriteria<T>; // Additional restrictions
}
```

### The Six Core Fields

Every approval defines **Who? When? What?** through these six core fields:

| Field               | Type                             | Purpose                                 | Example                                          |
| ------------------- | -------------------------------- | --------------------------------------- | ------------------------------------------------ |
| `toListId`          | Address List ID                  | Who can receive tokens                  | `"All"`, `"Mint"`, `"bb1..."`                    |
| `fromListId`        | Address List ID                  | Who can send tokens                     | `"Mint"`, `"!Mint"`                              |
| `initiatedByListId` | Address List ID                  | Who can initiate transfer               | `"All"`, `"bb1..."`                              |
| `transferTimes`     | UintRange\[] (UNIX Milliseconds) | When transfer can occur                 | `[{start: 1691931600000n, end: 1723554000000n}]` |
| `tokenIds`          | UintRange\[] (Token IDs)         | Which token IDs                         | `[{start: 1n, end: 100n}]`                       |
| `ownershipTimes`    | UintRange\[] (UNIX Milliseconds) | Which ownership times to be transferred | `[{start: 1n, end: 18446744073709551615n}]`      |

### Example Approval

```typescript
const mintApproval: CollectionApproval<bigint> = {
    fromListId: 'Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1691931600000n, end: 1723554000000n }],
    tokenIds: [{ start: 1n, end: 100n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'mint-to-all',
    version: 0n,
    approvalCriteria: {
        maxNumTransfers: {
            overallMaxNumTransfers: 1000n,
            perFromAddressMaxNumTransfers: 0n,
            perToAddressMaxNumTransfers: 0n,
            perInitiatedByAddressMaxNumTransfers: 1n,
            amountTrackerId: 'mint-to-all',
        },
        overridesFromOutgoingApprovals: true, // Required for Mint
        // ... other criteria
    },
};
```

**Translation:** Allow anyone to claim tokens 1-100 from the Mint address between Aug 13, 2023 and Aug 13, 2024.

### Approval Criteria

Approval criteria adds additional restrictions beyond basic approval matching. They are used to control who can transfer, when, how much, how often, and more. If criteria is not satisfied, the approval is not satisfied.

```typescript
interface ApprovalCriteria<T extends bigint> {
    // Transfer limits and amounts
    maxNumTransfers?: MaxNumTransfers<T>; // Limit number of transfers
    approvalAmounts?: ApprovalAmounts<T>; // Limit transfer amounts
    predeterminedBalances?: PredeterminedBalances<T>; // Exact balance requirements

    // Automatic actions
    coinTransfers?: CoinTransfer<T>[]; // Automatic coin transfers (BADGE or sdk.Coin)
    userRoyalties?: UserRoyalties<T>; // Percentage-based transfer fees

    // Challenge requirements
    merkleChallenges?: MerkleChallenge<T>[]; // Require merkle proofs
    mustOwnTokens?: MustOwnToken<T>[]; // Require owning specific tokens
    dynamicStoreChallenges?: DynamicStoreChallenge<T>[]; // On-chain numeric checks
    ethSignatureChallenges?: ETHSignatureChallenge<T>[]; // Ethereum signature requirements
    votingChallenges?: VotingChallenge<T>[]; // Require weighted quorum thresholds

    // Address relationship requirements
    requireToEqualsInitiatedBy?: boolean; // to == initiatedBy
    requireFromEqualsInitiatedBy?: boolean; // from == initiatedBy
    requireToDoesNotEqualInitiatedBy?: boolean; // to != initiatedBy
    requireFromDoesNotEqualInitiatedBy?: boolean; // from != initiatedBy

    // Address type checks
    senderChecks?: AddressChecks; // Address checks for sender
    recipientChecks?: AddressChecks; // Address checks for recipient
    initiatorChecks?: AddressChecks; // Address checks for initiator

    // Overrides (collection-level only)
    overridesFromOutgoingApprovals?: boolean; // Override sender approvals
    overridesToIncomingApprovals?: boolean; // Override recipient approvals

    // Time-based restrictions
    altTimeChecks?: AltTimeChecks; // Alternative time checks (offline hours/days)

    // Approval behavior
    autoDeletionOptions?: AutoDeletionOptions; // Auto-delete after use
    mustPrioritize?: boolean; // Require explicit prioritization

    // Special address flags (collection-level only)
    allowBackedMinting?: boolean; // Allow approval for IBC backed path operations
    allowSpecialWrapping?: boolean; // Allow approval for cosmos coin wrapper path operations
}
```

See [Approval Criteria](/token-standard/learn/approval-criteria) for all available criteria.

## User-Level Approvals

Senders and recipients can configure user-level approvals that gate transfers. These follow the same structure as collection approvals (minus hardcoded sender/recipient logic respectively and no override logic). Sender approvals control who can send tokens on behalf of the user. Recipient approvals control who can send tokens to the user.

Stored on UserBalanceStore.outgoingApprovals\[] (OutgoingApproval\[]) and UserBalanceStore.incomingApprovals\[] (IncomingApproval\[]).

```typescript
interface UserBalanceStore<T extends bigint> {
    balances: Balance<T>[];
    outgoingApprovals: OutgoingApproval<T>[];
    incomingApprovals: IncomingApproval<T>[];
    autoApproveSelfInitiatedOutgoingTransfers: boolean;
    autoApproveSelfInitiatedIncomingTransfers: boolean;
    autoApproveAllIncomingTransfers: boolean;
    userPermissions: UserPermissions<T>;
    // ... other fields
}
```

#### Outgoing Approvals

Control who can send tokens on behalf of the user:

```typescript
const outgoingApproval: OutgoingApproval<bigint> = {
    // fromListId: 'bb1user...', // Locked to the user's address, not in interface
    toListId: 'bb1...', // Who can receive from this user
    initiatedByListId: 'bb1...', // Who can initiate the transfer
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'my-listing',
    version: 0n,
    approvalCriteria: {
        // ... criteria fields
    },
};
```

#### Incoming Approvals

Control who can send tokens to the user:

```typescript
const incomingApproval: IncomingApproval<bigint> = {
    // toListId: 'bb1user...', // Locked to the user's address, not in interface
    fromListId: 'bb1...', // Who can send to this user
    initiatedByListId: 'bb1...', // Who can initiate the transfer
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'my-bids',
    version: 0n,
    approvalCriteria: {
        // ... criteria fields
    },
};
```

### User-Level Auto-Approval Flags

We provide auto-approval flags to automatically approve transfers without requiring explicit approval matching. These flags are used for convenience and ease of use for user-level approval handling. Typically, we recommend leaving all the auto-approval flags set to true.

#### Auto-Approve Self-Initiated Outgoing Transfers

When `autoApproveSelfInitiatedOutgoingTransfers: true`, outgoing transfers initiated by the user are automatically approved without checking outgoing approvals.

**Use case:** Convenience for users who want to freely send tokens they own without managing outgoing approval configurations.

#### Auto-Approve Self-Initiated Incoming Transfers

When `autoApproveSelfInitiatedIncomingTransfers: true`, incoming transfers initiated by the user are automatically approved without checking incoming approvals.

**Use case:** Convenience for users who want to receive tokens they request (e.g., claiming, requesting airdrops) without managing incoming approval configurations.

#### Auto-Approve All Incoming Transfers

When `autoApproveAllIncomingTransfers: true`, **all** incoming transfers are automatically approved regardless of who initiates them.

**Use case:** Users who want to accept all incoming transfers without restrictions. Useful for open wallets or accounts that should receive tokens from anyone.

## Override Behavior

Collection approvals can override user-level approvals for administrative controls like freezing, revocation, or forced transfers. This is done via the `approvalCriteria` field and only available on the collection approval criteria interface.

If set to true, we do NOT check the corresponding user-level approvals for the sender and/or recipient.

```typescript
const collectionApproval: CollectionApproval<bigint> = {
    approvalCriteria: {
        overridesFromOutgoingApprovals: true, // Skip sender approvals
        overridesToIncomingApprovals: true, // Skip recipient approvals
        // ... other criteria
    },
    // ... other fields
};
```

#### Mint Address Overrides

Because the Mint address cannot control its own user-level approvals, it must always override the sender's outgoing approvals to properly work.

```typescript
const mintApproval: CollectionApproval<bigint> = {
    fromListId: 'Mint',
    // ... other fields
    approvalCriteria: {
        overridesFromOutgoingApprovals: true, // Required for Mint
        // ... other criteria
    },
};
```

#### When Overrides Apply

**Outgoing overrides:** When `overridesFromOutgoingApprovals: true`, the collection approval bypasses the sender's outgoing approvals. This enables:

* Freezing tokens (prevent transfers regardless of user settings)
* Forced revocation (remove tokens from users)
* Administrative transfers (manager-controlled actions)

**Incoming overrides:** When `overridesToIncomingApprovals: true`, the collection approval bypasses the recipient's incoming approvals. This enables:

* Forced transfers (send tokens even if recipient blocks them)
* Administrative actions (manager-controlled distributions)

**Important:** Overrides are powerful and should be used carefully. They allow executing transfers that would otherwise be blocked by user settings.

If you want to setup your collection without any overrides, you can simply set the `invariants.noForcefulPostMintTransfers` to true. This will prevent any collection approvals from ever using override flags.

## Break-Down Logic

The system can break down transfers and approvals into partial matches to make transfers succeed. It deducts as much as possible from each approval as it iterates.

For proper design, you should try to design your approvals such that they never have to match to more than one. However, if needed, we break down the transfer and approvals as fine-grained as we can to make it succeed.

See [Auto-Scan and Prioritized Approvals](/token-standard/learn/auto-scan-and-prioritized-approvals) for details on how the system selects approvals and how you can selectively prioritize approvals.


# Approval Criteria

Additional restrictions and conditions that determine whether a transfer is approved beyond the basic approval matching.

## Interface

```typescript
export interface iApprovalCriteria<T extends NumberType> {
    /** The BADGE or other sdk.Coin transfers to be executed upon every approval. */
    coinTransfers?: iCoinTransfer<T>[];
    /** The list of merkle challenges that need valid proofs to be approved. */
    merkleChallenges?: iMerkleChallenge<T>[];
    /** The list of must own tokens that need valid proofs to be approved. */
    mustOwnTokens?: iMustOwnToken<T>[];
    /** The predetermined balances for each transfer. These allow approvals to use predetermined balance amounts rather than an incrementing tally system. */
    predeterminedBalances?: iPredeterminedBalances<T>;
    /** The maximum approved amounts for this approval. */
    approvalAmounts?: iApprovalAmounts<T>;
    /** The max num transfers for this approval. */
    maxNumTransfers?: iMaxNumTransfers<T>;
    /** Whether the approval should be deleted after one use. */
    autoDeletionOptions?: iAutoDeletionOptions;
    /** Whether the to address must equal the initiatedBy address. */
    requireToEqualsInitiatedBy?: boolean;
    /** Whether the from address must equal the initiatedBy address. */
    requireFromEqualsInitiatedBy?: boolean;
    /** Whether the to address must not equal the initiatedBy address. */
    requireToDoesNotEqualInitiatedBy?: boolean;
    /** Whether the from address must not equal the initiatedBy address. */
    requireFromDoesNotEqualInitiatedBy?: boolean;
    /** Whether this approval overrides the from address's approved outgoing transfers. */
    overridesFromOutgoingApprovals?: boolean;
    /** Whether this approval overrides the to address's approved incoming transfers. */
    overridesToIncomingApprovals?: boolean;
    /** The royalties to apply to the transfer. */
    userRoyalties?: iUserRoyalties<T>;
    /** The list of dynamic store challenges that must pass for approval. Can check initiator, sender, recipient, or a hardcoded address. */
    dynamicStoreChallenges?: iDynamicStoreChallenge<T>[];
    /** The list of ETH signature challenges that require valid Ethereum signatures for approval. */
    ethSignatureChallenges?: iETHSignatureChallenge<T>[];
    /** The list of voting challenges that require weighted quorum thresholds to be met. */
    votingChallenges?: iVotingChallenge<T>[];
    /** Address checks for sender */
    senderChecks?: iAddressChecks;
    /** Address checks for recipient */
    recipientChecks?: iAddressChecks;
    /** Address checks for initiator */
    initiatorChecks?: iAddressChecks;
    /** Alternative time-based checks for approval denial (offline hours/days). */
    altTimeChecks?: iAltTimeChecks;
    /** Whether this approval must be prioritized during evaluation. */
    mustPrioritize?: boolean;
    /** Whether this approval can be used for IBC backed path operations (collection-level only). */
    allowBackedMinting?: boolean;
    /** Whether this approval can be used for cosmos coin wrapper path operations (collection-level only). */
    allowSpecialWrapping?: boolean;
    /** The list of EVM query challenges that must pass for approval. Executes read-only calls to EVM contracts. */
    evmQueryChallenges?: iEVMQueryChallenge[];
    /** User-level approval settings propagated during transfer matching (collection-level only). */
    userApprovalSettings?: iUserApprovalSettings<T>;
}
```

## Core Components

* [**Approval Trackers**](/token-standard/learn/approval-criteria/approval-trackers) - Tracking transfer amounts and counts
* [**Tallied Approval Amounts**](/token-standard/learn/approval-criteria/tallied-approval-amounts) - Amount limits and thresholds
* [**Max Number of Transfers**](/token-standard/learn/approval-criteria/max-number-of-transfers) - Transfer count limits
* [**Predetermined Balances**](/token-standard/learn/approval-criteria/predetermined-balances) - Exact balance requirements
* [**Merkle Challenges**](/token-standard/learn/approval-criteria/merkle-challenges) - Cryptographic proof requirements
* [**Dynamic Store Challenges**](/token-standard/learn/approval-criteria/dynamic-store-challenges) - On-chain numeric checks
* [**ETH Signature Challenges**](/token-standard/learn/approval-criteria/eth-signature-challenges) - Ethereum signature requirements
* [**Voting Challenges**](/token-standard/learn/approval-criteria/voting-challenges) - Weighted quorum threshold requirements
* [**Token Ownership**](/token-standard/learn/approval-criteria/badge-ownership) - Required token holdings
* [**Cosmos Coin Transfers**](/token-standard/learn/approval-criteria/usdbadge-transfers) - Payments per approval (BADGE or other sdk.Coin)
* [**Overrides**](/token-standard/learn/approval-criteria/overrides) - Bypassing user-level approvals
* [**Requires**](/token-standard/learn/approval-criteria/requires) - Address relationship restrictions
* [**Address Checks**](/token-standard/learn/approval-criteria/address-checks) - Address type restrictions (EVM contracts, liquidity pools)
* [**Auto-Deletion Options**](/token-standard/learn/approval-criteria/auto-deletion-options) - Automatic approval cleanup
* [**User Royalties**](/token-standard/learn/approval-criteria/user-royalties) - Percentage-based transfer fees
* [**Alt Time Checks**](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-criteria/alt-time-checks.md) - Time-based restrictions (offline hours/days)
* [**Must Prioritize**](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-criteria/must-prioritize.md) - Requiring explicit approval prioritization
* [**Special Address Flags**](https://github.com/trevormil/bitbadges-docs/blob/master/token-standard/learn/approval-criteria/special-address-flags.md) - Control approval eligibility for backed minting and special wrapping (collection-level only)
* [**EVM Query Challenges**](/token-standard/learn/approval-criteria/evm-query-challenges) - Read-only EVM contract queries as approval conditions
* [**User Approval Settings**](https://github.com/trevormil/bitbadges-docs/blob/master/token-standard/learn/approval-criteria/user-approval-settings.md) - Denom restrictions, coin transfer controls, and royalties (collection-level only)

## Key Concepts

### Tracker IDs

Trackers use IDs with format: `approvalId-trackerId` plus identifying details. All trackers are scoped to a specific `approvalId`.

**Important**: Trackers are increment-only and immutable. Never reuse tracker IDs with prior history.

### Best Practices - Creating / Updating / Deleting

Trackers are increment-only and immutable. Never reuse tracker IDs with prior history when creating approvals that should start from scratch.


# Address Checks

Additional restrictions on address types for transfer approval. These checks validate whether addresses involved in a transfer meet specific requirements (e.g., must be an EVM contract, must not be a liquidity pool).

## Interface

```typescript
interface AddressChecks {
    /** Require the address to be an EVM contract. */
    mustBeEvmContract?: boolean;
    /** Require the address to not be an EVM contract. */
    mustNotBeEvmContract?: boolean;
    /** Require the address to be a liquidity pool. */
    mustBeLiquidityPool?: boolean;
    /** Require the address to not be a liquidity pool. */
    mustNotBeLiquidityPool?: boolean;
}
```

## How It Works

Address checks validate the type of addresses involved in a transfer. The checks are applied to different parties depending on the approval type:

### Collection Approvals

Collection approvals can check all three parties:

* **`senderChecks`**: Validates the sender address (`from`)
* **`recipientChecks`**: Validates the recipient address (`to`)
* **`initiatorChecks`**: Validates the initiator address (`initiatedBy`)

### Incoming Approvals

Incoming approvals can check the sender and initiator (but not the recipient, since the recipient is always the approval owner):

* **`senderChecks`**: Validates the sender address (`from`)
* **`initiatorChecks`**: Validates the initiator address (`initiatedBy`)

### Outgoing Approvals

Outgoing approvals can check the recipient and initiator (but not the sender, since the sender is always the approval owner):

* **`recipientChecks`**: Validates the recipient address (`to`)
* **`initiatorChecks`**: Validates the initiator address (`initiatedBy`)

## Available Checks

### EVM Contract Checks

* **`mustBeEvmContract`**: Requires the address to be an EVM smart contract
* **`mustNotBeEvmContract`**: Requires the address to NOT be an EVM smart contract

### Liquidity Pool Checks

* **`mustBeLiquidityPool`**: Requires the address to be a liquidity pool
* **`mustNotBeLiquidityPool`**: Requires the address to NOT be a liquidity pool

## Examples

### Collection Approval: Require EVM Contract Recipients

Only allow transfers to EVM contracts:

```json
{
    "approvalCriteria": {
        "recipientChecks": {
            "mustBeEvmContract": true
        }
    }
}
```

### Collection Approval: Block Liquidity Pool Senders

Prevent transfers from liquidity pools:

```json
{
    "approvalCriteria": {
        "senderChecks": {
            "mustNotBeLiquidityPool": true
        }
    }
}
```

### Incoming Approval: Require EVM Contract Initiators

Only allow transfers initiated by EVM contracts:

```json
{
    "approvalCriteria": {
        "initiatorChecks": {
            "mustBeEvmContract": true
        }
    }
}
```

### Outgoing Approval: Block Liquidity Pool Recipients

Prevent sending tokens to liquidity pools:

```json
{
    "approvalCriteria": {
        "recipientChecks": {
            "mustNotBeLiquidityPool": true
        }
    }
}
```

### Combined Checks

You can combine multiple checks for a single party:

```json
{
    "approvalCriteria": {
        "recipientChecks": {
            "mustBeEvmContract": true,
            "mustNotBeLiquidityPool": true
        },
        "initiatorChecks": {
            "mustNotBeEvmContract": true
        }
    }
}
```

## Use Cases

* **Smart Contract Integration**: Require transfers to/from specific EVM contract types
* **Liquidity Protection**: Prevent transfers to/from liquidity pools to maintain token economics
* **Security**: Enforce that certain operations can only be initiated by contracts or regular addresses
* **Protocol Compliance**: Ensure transfers comply with protocol-specific address requirements

## Constraints

All checks are evaluated after the address lists (`toList`, `fromList`, `initiatedByList`) are matched. An address must first be in the appropriate list, then pass the address checks.


# Approval Trackers

Track transfer amounts and counts using increment-only tallies with thresholds.

## How It Works

Trackers use an incrementing tally system with thresholds:

1. **Setup**: Approved for x10 of token IDs 1-10 with tracker ID "xyz"
2. **Transfer x5**: Tracker "xyz" goes from 0/10 → 5/10
3. **Transfer x5**: Tracker "xyz" goes to 10/10
4. **Transfer x1**: Exceeds threshold, transfer fails

## Tracker Identification

Tracker IDs include multiple components:

```
ID: collectionId-approvalLevel-approverAddress-approvalId-amountTrackerId-trackerType-approvedAddress
```

### Tracker ID Details Interface

```typescript
interface ApprovalTrackerIdDetails<T extends NumberType> {
    collectionId: T;
    approvalLevel: 'collection' | 'incoming' | 'outgoing' | '';
    approvalId: string;
    approverAddress: string;
    amountTrackerId: string;
    trackerType: 'overall' | 'to' | 'from' | 'initiatedBy' | '';
    approvedAddress: string;
}
```

### Component Breakdown

* **collectionId**: The collection this tracker belongs to
* **approvalLevel**: Level of approval ("collection", "incoming", "outgoing", or empty)
* **approvalId**: Unique identifier for the specific approval
* **approverAddress**: Address of the approver (empty for collection-level)
* **amountTrackerId**: User-defined tracker identifier specified in approvalAmounts or maxNumTransfers (see below)
* **trackerType**: Type of tracking ("overall", "to", "from", "initiatedBy", or empty)
* **approvedAddress**: Specific address being tracked (empty for "overall")

```typescript
interface iApprovalAmounts<T extends NumberType> {
    amountTrackerId: string; // Key for tracking tallies
}

interface iMaxNumTransfers<T extends NumberType> {
    amountTrackerId: string; // Key for tracking tallies
}
```

### Tracker Types

* **"overall"**: Universal tally for any transfer (approvedAddress empty)
* **"to"**: Per-recipient tally (approvedAddress = recipient)
* **"from"**: Per-sender tally (approvedAddress = sender)
* **"initiatedBy"**: Per-initiator tally (approvedAddress = initiator)

## Increment Only and Immutable

Trackers are increment only and immutable in storage. To start an approval tally from scratch, you will need to map the approval to a new unused tracker ID. This can be done simply by editing `amountTrackerId` (because this changes the whole ID) or restructuring to change one of the other fields that make up the overall ID.

**IMPORTANT**: Because of the immutable nature, be careful to not revert to a previously used ID unintentionally because the starting point will be the previous tally (not starting from scratch).

## As-Needed Basis

Only increment when necessary (e.g., if no amount restrictions, don't track amounts). Meaning, if there is no need to increment the tally (unlimited limit and/or not restrictions), we do not increment for efficiency purposes. For example, if we only have requirements for numTransfers but do not need the amounts, we do not increment the amounts.

### Example Tracker States

```json
{
    "fullTrackerId1": {
        "numTransfers": 5,
        "amounts": [
            {
                "amount": 50,
                "tokenIds": [{ "start": 1, "end": 10 }],
                "ownershipTimes": [{ "start": 1, "end": 100000000000 }]
            }
        ],
        "lastUpdatedAt": 1691978400000
    },
    "fullTrackerId2": {
        "numTransfers": 3,
        "amounts": [
            {
                "amount": 15,
                "tokenIds": [{ "start": 1, "end": 5 }],
                "ownershipTimes": [{ "start": 1, "end": 100000000000 }]
            }
        ],
        "lastUpdatedAt": 1691978400000
    }
}
```

## Periodic Resets

Trackers support periodic resets to zero using time intervals.

Leave the values at 0 to disable periodic resets.

```typescript
interface ResetTimeIntervals<T extends NumberType> {
    startTime: T; // Original start time of the first interval
    intervalLength: T; // Interval length in unix milliseconds
}
```

### How It Works

* **First Update**: If it's the first update of the interval, all tracker progress is reset to zero
* **Recurring**: Useful for recurring subscriptions (e.g., one transfer per month)
* **No Reset**: Set both values to 0 for no periodic resets

### Example

```json
{
    "approvalAmounts": {
        "overallApprovalAmount": "100",
        "amountTrackerId": "monthly-tracker",
        "resetTimeIntervals": {
            "startTime": "1691978400000", // Aug 13, 2023
            "intervalLength": "2592000000" // 30 days in milliseconds
        }
    }
}
```

This creates a monthly reset cycle starting from August 13, 2023.


# Auto-Deletion Options

Automatically delete approvals after specific conditions are met.

## Interface

```typescript
interface AutoDeletionOptions {
    afterOneUse: boolean;
    afterOverallMaxNumTransfers: boolean;
    allowCounterpartyPurge?: boolean;
    allowPurgeIfExpired?: boolean;
}
```

## How It Works

Auto-deletion options allow approvals to be automatically removed when certain conditions are met:

* **`afterOneUse`**: Delete the approval after it's used once
* **`afterOverallMaxNumTransfers`**: Delete the approval after the overall max number of transfers threshold is met
* **`allowCounterpartyPurge`**: If true, allows the counterparty (the only initiator in `initiatedByList`, must be a whitelist with exactly one address) to purge the approval, even if they are not the owner. This may be used for like a rejection of the approval.
* **`allowPurgeIfExpired`**: If true, allows others (in addition to the approval owner) to purge expired approvals on the owner's behalf. This may be used for a cleanup-like system.

## Usage Examples

### Single-Use Approval

```json
{
    "autoDeletionOptions": {
        "afterOneUse": true,
        "afterOverallMaxNumTransfers": false
    }
}
```

**Result**: Approval is deleted immediately after the first transfer.

### Limited-Use Approval

```json
{
    "maxNumTransfers": {
        "overallMaxNumTransfers": "10"
    },
    "autoDeletionOptions": {
        "afterOneUse": false,
        "afterOverallMaxNumTransfers": true
    }
}
```

**Result**: Approval is deleted after 10 transfers are completed.

### Allow Counterparty Purge

```json
{
    "autoDeletionOptions": {
        "afterOneUse": false,
        "afterOverallMaxNumTransfers": false,
        "allowCounterpartyPurge": true
    }
}
```

**Result**: The counterparty (if they are the only initiator in a whitelist) can purge this approval, even if not the owner.

### Allow Others to Purge Expired Approvals

```json
{
    "autoDeletionOptions": {
        "afterOneUse": false,
        "afterOverallMaxNumTransfers": false,
        "allowPurgeIfExpired": true
    }
}
```

**Result**: Any user can purge this approval if it is expired (no future valid transfer times), not just the owner.


# Token Ownership

Require specific token holdings from the initiator as a prerequisite for transfer approval. This approval criteria checks on-chain balances to ensure users own required tokens before allowing transfers.

## Overview

Token ownership requirements enable gating mechanisms where users must possess specific tokens to access certain transfers. This creates dependency relationships between collections and enables sophisticated access control systems.

**Key Benefits**:

* **Access Control**: Gate transfers based on token ownership
* **Collection Dependencies**: Create relationships between different collections
* **On-Chain Verification**: Automatic balance checking without external data
* **Flexible Requirements**: Support for amount ranges, time-based ownership, and multiple token types

## Interface

```typescript
interface MustOwnTokens<T extends NumberType> {
    collectionId: T;
    amountRange: UintRange<T>; // Min/max amount expected
    ownershipTimes: UintRange<T>[];
    tokenIds: UintRange<T>[];

    overrideWithCurrentTime: boolean; // Use current block time. Overrides ownershipTimes with [{ start: currentTime, end: currentTime }]
    mustSatisfyForAllAssets: boolean; // All vs one requirement
    ownershipCheckParty: string; // Which party to check ownership for: "initiator", "sender", "recipient", or a hardcoded bb1 address (default: "initiator" if empty)
}
```

## Field Descriptions

### collectionId

* **Type**: `T` (NumberType)
* **Description**: The ID of the collection containing the required tokens
* **Example**: `"1"` for collection ID 1

### amountRange

* **Type**: `UintRange<T>`
* **Description**: Minimum and maximum amount of tokens the user must own
* **Format**: `{ start: "minAmount", end: "maxAmount" }`
* **Example**: `{ start: "1", end: "10" }` requires 1-10 tokens (amounts)

### ownershipTimes

* **Type**: `UintRange<T>[]`
* **Description**: Time ranges when the user must have owned the tokens (UNIX milliseconds)
* **Example**: `[{ start: "1691931600000", end: "1723554000000" }]` for Aug 13, 2023 - Aug 13, 2024

### tokenIds

* **Type**: `UintRange<T>[]`
* **Description**: Specific token IDs that must be owned
* **Example**: `[{ start: "1", end: "100" }]` for token IDs 1-100

### overrideWithCurrentTime

* **Type**: `boolean`
* **Description**: When true, ignores `ownershipTimes` and uses current block time
* **Behavior**: Sets ownership time to `[{ start: currentTime, end: currentTime }]`
* **Use Case**: Require current ownership only, not historical ownership

### mustSatisfyForAllAssets

* **Type**: `boolean`
* **Description**: Controls whether all specified token requirements must be met or just one
* **True**: User must own ALL specified token combinations
* **False**: User must own AT LEAST ONE of the specified token combinations

### ownershipCheckParty

* **Type**: `string`
* **Description**: Specifies which party of the transfer to check ownership for
* **Options**:
  * `"initiator"` (default): Check ownership for the address that initiated the transfer
  * `"sender"`: Check ownership for the address sending the tokens
  * `"recipient"`: Check ownership for the address receiving the tokens
  * Hardcoded bb1 address (e.g., `"bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls"`): Check ownership for a specific address, regardless of transfer parties
* **Default**: `"initiator"` (if empty or not specified)
* **Example**: `"sender"` to require the sender to own specific tokens before allowing the transfer
* **Hardcoded Address Example**: `"bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls"` to require a specific address to own tokens (useful for multi-sig or contract-based checks)

## Example

Require users to own specific tokens to access premium features or exclusive transfers.

```json
{
    "mustOwnTokens": [
        {
            "collectionId": "1",
            "amountRange": { "start": "1", "end": "1" },
            "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
            "tokenIds": [{ "start": "1", "end": "1" }],
            "overrideWithCurrentTime": false,
            "mustSatisfyForAllAssets": true,
            "ownershipCheckParty": "initiator"
        }
    ]
}
```

### Party-Specific Examples

#### Check Initiator Ownership (Default)

```json
{
    "mustOwnTokens": [
        {
            "collectionId": "1",
            "amountRange": { "start": "1", "end": "1" },
            "tokenIds": [{ "start": "1", "end": "1" }],
            "ownershipCheckParty": "initiator"
        }
    ]
}
```

#### Check Sender Ownership

```json
{
    "mustOwnTokens": [
        {
            "collectionId": "1",
            "amountRange": { "start": "1", "end": "1" },
            "tokenIds": [{ "start": "1", "end": "1" }],
            "ownershipCheckParty": "sender"
        }
    ]
}
```

#### Check Recipient Ownership

```json
{
    "mustOwnTokens": [
        {
            "collectionId": "1",
            "amountRange": { "start": "1", "end": "1" },
            "tokenIds": [{ "start": "1", "end": "1" }],
            "ownershipCheckParty": "recipient"
        }
    ]
}
```

#### Check Hardcoded Address Ownership

You can also specify a hardcoded bb1 address to check ownership for a specific address, regardless of who is involved in the transfer. This is useful for multi-sig wallets, contract addresses, or other scenarios where you need to verify ownership for a specific address.

```json
{
    "mustOwnTokens": [
        {
            "collectionId": "1",
            "amountRange": { "start": "1", "end": "1" },
            "tokenIds": [{ "start": "1", "end": "1" }],
            "ownershipCheckParty": "bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls"
        }
    ]
}
```

This example requires the address `bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls` to own the specified tokens, regardless of who initiates, sends, or receives the transfer.


# Dynamic Store Challenges

Require specified parties (initiator, sender, recipient, or a hardcoded address) to pass checks against dynamic stores. Typically, these are used in combination with smart contracts or other custom extensions.

Dynamic stores are standalone (address -> boolean) stores controlled by whoever creates them. They are stored and maintained by the creator. These are powerful for creating dynamic approval criteria with smart contracts and other custom use cases liek global kill switches.

## How It Works

Dynamic store challenges check if a specified party has a value of `true` in specified dynamic stores. The system:

1. **Checks Global Kill Switch**: First checks if the dynamic store's `globalEnabled` field is `true`. If `globalEnabled = false`, the approval fails immediately with error: "dynamic store storeId {id} is globally disabled"
2. **Determines Check Party**: Determines which address to check based on the `ownershipCheckParty` field (defaults to "initiator" if not specified)
3. **Looks Up Address**: If the store is globally enabled, looks up the specified party's address in the dynamic store
4. **Evaluates Boolean**: Returns the boolean value for that address (or `defaultValue` if not set)
5. **Requires All True**: All challenges must return `true` for approval
6. **Fails if Any False**: If any challenge returns `false`, transfer is denied

## Interface

```typescript
interface DynamicStoreChallenge {
    storeId: string; // Dynamic store ID to check
    ownershipCheckParty?: string; // Which party to check: "initiator", "sender", "recipient", or a hardcoded bb1 address (default: "initiator")
}
```

## Usage in Approval Criteria

```json
{
    "dynamicStoreChallenges": [
        { "storeId": "1", "ownershipCheckParty": "initiator" }, // Member store (must be true for initiator, default)
        { "storeId": "2", "ownershipCheckParty": "sender" } // Subscription store (must be true for sender)
    ]
}
```

## Field Descriptions

### storeId

* **Type**: `string`
* **Description**: The ID of the dynamic store to check
* **Required**: Yes
* **Example**: `"1"` for store ID 1

### ownershipCheckParty

* **Type**: `string` (optional)
* **Description**: Specifies which party of the transfer to check the dynamic store value for
* **Options**:
  * `"initiator"` (default): Check the dynamic store value for the address that initiated the transfer
  * `"sender"`: Check the dynamic store value for the address sending the tokens
  * `"recipient"`: Check the dynamic store value for the address receiving the tokens
  * Hardcoded bb1 address (e.g., `"bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls"`): Check the dynamic store value for a specific address, regardless of transfer parties
* **Default**: `"initiator"` (if empty or not specified)
* **Example**: `"sender"` to require the sender to have `true` in the dynamic store before allowing the transfer
* **Hardcoded Address Example**: `"bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls"` to require a specific address to have `true` in the store (useful for multi-sig or contract-based checks)

## Global Kill Switch

Each dynamic store has a `globalEnabled` field that acts as a global kill switch. When `globalEnabled = false`, **all approvals** using that store via `DynamicStoreChallenge` will fail immediately, regardless of per-address values.

This is useful for quickly halting all approvals that depend on a specific dynamic store (e.g., if a protocol is compromised and needs to be disabled immediately).

### Behavior

* **New stores**: `globalEnabled` defaults to `true` (enabled by default)
* **Existing stores**: All existing stores are set to `globalEnabled = true` for backward compatibility
* **Disabling**: Set `globalEnabled = false` via [MsgUpdateDynamicStore](/token-standard/messages/msg-update-dynamic-store) to halt all dependent approvals
* **Re-enabling**: Set `globalEnabled = true` to restore normal operation

### Usage Example

```json
// Disable all approvals using store "1"
{
    "creator": "bb1...",
    "storeId": "1",
    "defaultValue": true,  // Must pass current value
    "globalEnabled": false  // Disable kill switch
}

// Re-enable when ready
{
    "creator": "bb1...",
    "storeId": "1",
    "defaultValue": true,
    "globalEnabled": true  // Re-enable
}
```

## Managing Dynamic Stores

### Creating Stores

Use [MsgCreateDynamicStore](/token-standard/messages/msg-create-dynamic-store) to create new dynamic stores with a default boolean value. New stores are created with `globalEnabled = true` by default.

### Updating Stores

Use [MsgUpdateDynamicStore](/token-standard/messages/msg-update-dynamic-store) to update the store's `defaultValue`, `globalEnabled`, `uri`, and `customData` fields. The `uri` and `customData` fields allow storing additional metadata and arbitrary data associated with the store.

### Setting Values

Use [MsgSetDynamicStoreValue](/token-standard/messages/msg-set-dynamic-store-value) to set boolean values for specific addresses.

### Querying Values

Use [GetDynamicStoreValue](/token-standard/queries/get-dynamic-store-value) to check current values for addresses.

Use [GetDynamicStore](/token-standard/queries/get-dynamic-store) to retrieve the store's configuration, including `globalEnabled` status, `uri`, and `customData` fields.

## Alternatives

For fully off-chain solutions, consider:

* [Merkle Challenges](/token-standard/learn/approval-criteria/merkle-challenges) to save gas costs
* [ETH Signature Challenges](/token-standard/learn/approval-criteria/eth-signature-challenges) for direct authorization


# ETH Signature Challenges

ETH Signature Challenges are a type of approval criteria that require users to provide valid Ethereum signatures from a predetermined signer to complete transfers. The signer approves the transfer by signing a message that contains the nonce and contextual information about the transfer. This feature allows for secure, on-chain verification of off-chain authorization without the complexity of Merkle trees.

## Overview

ETH Signature Challenges work by requiring users to provide Ethereum signatures that prove they have authorization from specific Ethereum addresses. Each signature can only be used once, preventing replay attacks and ensuring the security of the approval system.

## How It Works

### Signature Scheme

The signature scheme follows the pattern:

```
ETHSign(nonce + "-" + initiatorAddress + "-" + collectionId + "-" + approverAddress + "-" + approvalLevel + "-" + approvalId + "-" + challengeId)
```

Where:

* `nonce`: A unique identifier provided by the user
* `initiatorAddress`: The address initiating the transfer
* `collectionId`: The ID of the collection being transferred
* `approverAddress`: The address that needs to approve the transfer
* `approvalLevel`: The approval level (e.g., "initial", "update")
* `approvalId`: The ID of the approval being checked
* `challengeId`: The challenge tracker ID
* `-`: Literal dash characters separating the values

This comprehensive signature format ensures that signatures are bound to specific transfer contexts, preventing signature reuse across different transfers, collections, or approval scenarios.

### Challenge Structure

Each ETH Signature Challenge contains:

* `signer`: The Ethereum address that must sign the challenge
* `challengeTrackerId`: Unique identifier for tracking used signatures
* `uri`: Optional metadata URI
* `customData`: Optional custom data

### Proof Structure

Users provide ETH Signature Proofs containing:

* `nonce`: The nonce that was signed (this is the only user-provided value in the signature message)
* `signature`: The Ethereum signature of the full message string

## Key Features

### One-Time Use Signatures

Each signature can only be used once per challenge tracker. This prevents:

* Replay attacks
* Double-spending of approvals
* Unauthorized reuse of signatures

The signature tracking is scoped to the combination of:

* Collection ID
* Approver address
* Approval level
* Approval ID
* Challenge tracker ID
* The signature itself

### Multiple Signers

You can require signatures from multiple Ethereum addresses in a single approval:

```json
{
    "ethSignatureChallenges": [
        {
            "signer": "0x1234567890123456789012345678901234567890",
            "challengeTrackerId": "challenge1"
        },
        {
            "signer": "0x0987654321098765432109876543210987654321",
            "challengeTrackerId": "challenge2"
        }
    ]
}
```

### Context-Aware Signatures

The new signature format includes contextual information about the transfer, ensuring that:

* Signatures cannot be reused across different collections
* Signatures cannot be reused across different approvers
* Signatures cannot be reused across different approval levels
* Signatures are bound to specific approval IDs and challenge IDs

This provides stronger security guarantees compared to simpler signature schemes.

## Implementation Details

### Signature Verification

The system verifies signatures by:

1. Constructing the signed message: `nonce + "-" + initiatorAddress + "-" + collectionId + "-" + approverAddress + "-" + approvalLevel + "-" + approvalId + "-" + challengeId`
2. Recovering the signer address from the signature using elliptic curve signature verification
3. Comparing the recovered address with the expected `signer` address
4. Checking that the signature hasn't been used before (tracked per collection, approver, approval level, approval ID, and challenge ID)

### Storage

Used signatures are tracked in the blockchain state using:

* **Key**: `ETHSignatureTrackerKey` constructed from:
  * Collection ID
  * Approver address
  * Approval level
  * Approval ID
  * Challenge tracker ID
  * The signature itself
* **Value**: Number of times the signature has been used (increment-only, must be 0 for first use)

### Proof Matching

The system checks all provided ETH signature proofs against each challenge. A challenge is satisfied if at least one proof:

1. Has a valid signature from the expected signer
2. Has not been used before
3. Matches the signature scheme with the correct contextual parameters

## Quick Reference

### Interface Definitions

```typescript
interface ETHSignatureChallenge {
    signer: string; // Ethereum address that must sign
    challengeTrackerId: string; // Unique ID for tracking used signatures
    uri?: string; // Optional metadata URI
    customData?: string; // Optional custom data
}

interface ETHSignatureProof {
    nonce: string; // The nonce that was signed (user-provided)
    signature: string; // Ethereum signature of the full message
}
```

### Signature Message Format

When signing, the message string is constructed as:

```
{nonce}-{initiatorAddress}-{collectionId}-{approverAddress}-{approvalLevel}-{approvalId}-{challengeId}
```

All values are joined with literal dash (`-`) characters. The signer must sign this exact string.

## Error Handling

Common error scenarios:

* **Invalid Signature**: Signature doesn't match the expected signer or the message format is incorrect
* **Already Used**: Signature has been used before for this specific combination of collection, approver, approval level, approval ID, and challenge ID
* **Missing Proof**: Required ETH signature proof not provided
* **Invalid Nonce**: Nonce format or content is invalid
* **Context Mismatch**: Signature was created for a different transfer context (different collection, approver, etc.)

The system provides clear error messages to help users understand and resolve issues.

## Security Considerations

### Replay Attack Prevention

The combination of contextual information in the signature and one-time-use tracking prevents:

* Reusing signatures across different transfers
* Reusing signatures across different collections
* Reusing signatures across different approvers
* Reusing signatures after the challenge tracker ID changes

### Signature Binding

By including transfer context in the signature, the system ensures that:

* Signatures cannot be extracted and used in unauthorized contexts
* Each signature is cryptographically bound to specific transfer parameters
* Signers can review exactly what they are approving before signing

### Challenge Tracker ID Management

Changing the `challengeTrackerId` resets the usage tracker, allowing signatures to be reused. This can be useful for:

* Rotating challenge configurations
* Resetting usage counters
* Creating new challenge periods

However, care should be taken to ensure old signatures cannot be maliciously reused when tracker IDs are changed.


# Max Number of Transfers

Limit the number of transfers that can occur using approval trackers.

See [Approval Trackers](/token-standard/learn/approval-criteria/approval-trackers) for more information on how trackers work.

## Interface

```typescript
interface ApprovalCriteria<T extends NumberType> {
    maxNumTransfers?: MaxNumTransfers<T>;
}
```

## How It Works

Similar to approval amounts, specify maximum transfers on:

* **Overall**: Universal limit for all transfers
* **Per Sender**: Limit per unique sender address
* **Per Recipient**: Limit per unique recipient address
* **Per Initiator**: Limit per unique initiator address

"0" means unlimited and not tracked. "N" means max N transfers allowed.

## Example

```json
{
    "maxNumTransfers": {
        "overallMaxNumTransfers": "0",
        "perFromAddressMaxNumTransfers": "0",
        "perToAddressMaxNumTransfers": "0",
        "perInitiatedByAddressMaxNumTransfers": "1",
        "amountTrackerId": "uniqueID"
    }
}
```

Alice can initiate 1 transfer, then no more. Bob can still transfer (different tracker).

```typescript
{
    "fullTrackerId": {
        "numTransfers": 1,
        "amounts": [],
        "lastUpdatedAt": 1691978400000
    }
}
```

## As-Needed Basis

We track on an as-needed basis, meaning if we do not have requirements that use the number of transfers, we will not increment the tracker.

Edge Case: In Predetermined Balances, you may need the number of transfers for determining the balances to assign to each transfer (e.g. transfer #10 -> token ID 10). In this case, we do need to track the number of transfers. This is all facilitated via the same tracker, so even if you have "0" or unlimited set for the corresponding value in maxNumTransfers, the tracker may be incremented behind the scenes. Consider this when editing / creating approvals. You do not want to use a tracker that has prior history when you expect it to start from scratch.


# Merkle Challenges

## Overview

Merkle challenges provide cryptographic proof-based approval mechanisms using SHA256 Merkle trees. They enable secure, gas-efficient whitelisting and claim code systems without storing large address lists on-chain.

**Key Benefits**:

* **Gas Efficiency**: Distribute gas costs among users instead of collection creators
* **Security**: Cryptographic proof verification prevents unauthorized access
* **Flexibility**: Support both whitelist trees and claim code systems
* **Scalability**: Handle large user bases without on-chain storage

## Interface Definition

```typescript
export interface MerkleChallenge<T extends NumberType> {
    root: string; // SHA256 Merkle tree root hash
    expectedProofLength: T; // Required proof length (security)
    useCreatorAddressAsLeaf: boolean; // Use initiator address as leaf?
    maxUsesPerLeaf: T; // Maximum uses per leaf
    uri: string; // Metadata URI
    customData: string; // Custom data field
    challengeTrackerId: string; // Unique tracker identifier
    leafSigner: string; // Optional leaf signature authority
}
```

## Basic Example

```json
{
    "merkleChallenges": [
        {
            "root": "758691e922381c4327646a86e44dddf8a2e060f9f5559022638cc7fa94c55b77",
            "expectedProofLength": "1",
            "useCreatorAddressAsLeaf": false,
            "maxUsesPerLeaf": "1",
            "uri": "ipfs://Qmbbe75FaJyTHn7W5q8EaePEZ9M3J5Rj3KGNfApSfJtYyD",
            "customData": "",
            "challengeTrackerId": "uniqueId",
            "leafSigner": "0x"
        }
    ]
}
```

## Challenge Types

### 1. Claim Code Challenges

Create a Merkle tree of secret claim codes that users must provide to claim tokens.

**Use Case**: Private claim codes, invitation systems, promotional campaigns

**Process**:

1. Generate secret claim codes
2. Build Merkle tree from hashed codes
3. Distribute codes privately to users with leaf signatures
4. Users provide code + Merkle proof in transfer

### 2. Whitelist Challenges

Create a Merkle tree of user addresses for gas-efficient whitelisting.

**Use Case**: Large whitelists, community access, gas cost distribution

**Process**:

1. Collect user addresses
2. Build Merkle tree from hashed addresses
3. Users provide their address + Merkle proof
4. System verifies address is in whitelist / valid proof

**Gas Cost Distribution**: Instead of the collection creator paying gas to store N addresses on-chain, each user pays their own gas for proof verification.

## Understanding useCreatorAddressAsLeaf

The `useCreatorAddressAsLeaf` field determines how the system handles the leaf value in Merkle proofs:

### Whitelist Trees (`useCreatorAddressAsLeaf: true`)

**Purpose**: Verify that the transaction initiator is in the whitelist.

**How It Works**:

1. **Automatic Override**: The system expects the provided leaf to be the initiator's BitBadges address ("bb1...")
2. **Address Verification**: Checks if the initiator's address exists in the Merkle tree
3. **No Manual Leaf**: Users don't need to provide their address as the leaf - the system handles it

**Recommended Configuration**:

* Set `initiatedByList` to "All" (whitelist tree handles the restriction)
* Set `useCreatorAddressAsLeaf: true`
* Build Merkle tree from BitBadges addresses as leaves \["bb1...", "bb2...", "bb3..."]

### Claim Code Trees (`useCreatorAddressAsLeaf: false`)

**Purpose**: Verify that the user possesses a valid claim code.

**How It Works**:

1. **Manual Leaf**: User must provide the actual claim code as the leaf
2. **Code Verification**: System verifies the provided code exists in the Merkle tree
3. **User Responsibility**: Users must know and provide their claim code

**Recommended Configuration**:

* Set `useCreatorAddressAsLeaf: false`
* Build Merkle tree from claim codes as leaves \["secret1", "secret2", "secret3"]
* Post root hash on-chain as challenge
* Distribute codes privately to users with leaf signatures

## Security Features

### Expected Proof Length

**Critical Security Feature**: All proofs must have the same length to prevent preimage and second preimage attacks.

```typescript
// All proofs must match this length
expectedProofLength: '2'; // 2-level proof required
```

**Design Requirement**: Your Merkle tree must be constructed so all leaves are at the same depth.

### Max Uses Per Leaf

Control how many times each leaf can be used:

| Setting         | Behavior          | Use Case             |
| --------------- | ----------------- | -------------------- |
| `"0"` or `null` | Unlimited uses    | Public claim codes   |
| `"1"`           | One-time use      | Single-use codes     |
| `"5"`           | Five uses maximum | Limited distribution |

**Critical Security Requirement**: For claim code challenges (`useCreatorAddressAsLeaf: false`), `maxUsesPerLeaf` must be `"1"` to prevent replay attacks.

### Replay Attack Protection

**⚠️ CRITICAL SECURITY RISK**: Non-address trees (claim codes) are vulnerable to front-running attacks.

**The Problem**:

1. User submits transaction with valid Merkle proof
2. Proof becomes visible in mempool (public blockchain)
3. Malicious actor sees the proof and front-runs the transaction
4. Original user's transaction fails, attacker gets the token

**Why This Happens**:

* Merkle proofs for claim codes are reusable until consumed
* Once in mempool, proofs are publicly visible
* No built-in protection against proof reuse

**The Solution**: Leaf signatures provide cryptographic protection against this attack.

## Challenge Tracking

### Tracker System

Uses increment-only, immutable trackers to prevent double-spending:

```typescript
{
    collectionId: T;
    approvalId: string;
    approvalLevel: 'collection' | 'incoming' | 'outgoing';
    approverAddress: string; // blank if collection-level
    challengeTrackerId: string;
    leafIndex: T; // Leftmost base layer leaf index = 0, rightmost = numLeaves - 1
}
```

Note the fact we use leaf indices to track usage and not leaf values.

### Tracker Examples

```
1-collection- -approvalId-uniqueID-0  → USED 1 TIME
1-collection- -approvalId-uniqueID-1  → UNUSED
1-collection- -approvalId-uniqueID-2  → USED 3 TIMES
```

**Important**: Trackers are scoped to specific approvals and cannot be shared between different approval configurations.

### Tracker Management

* **Increment-Only**: Once used, the number of uses cannot be decremented
* **Immutable**: Tracker state cannot be modified
* **Best Practice**: Use unique `challengeTrackerId` for fresh tracking of new approvals

## Leaf Signatures

### Protection Against Front-Running

Leaf signatures provide cryptographic protection against front-running attacks on claim code challenges.

**How It Works**:

```typescript
// Signature scheme
signature = ETHSign(leaf + '-' + bitbadgesAddressOfInitiator);
```

**Security Mechanism**:

1. **Address Binding**: Each proof is cryptographically tied to a specific BitBadges address
2. **Replay Prevention**: Even if proof is intercepted, it cannot be used by other addresses
3. **Mempool Safety**: Intercepted proofs in mempool are useless to attackers

### Implementation

```typescript
// Only Ethereum addresses supported currently
leafSigner: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6';
```

**Critical Benefits**:

* **Front-Running Protection**: Prevents attackers from stealing tokens via mempool interception
* **Address-Specific**: Each proof is cryptographically bound to the intended recipient
* **Mempool Safety**: Makes intercepted proofs useless to malicious actors
* **Required for Claim Codes**: Strongly recommended for all non-address tree challenges

**⚠️ IMPORTANT**: For claim code challenges, leaf signatures are not just recommended—they are essential for security against front-running attacks.

## Merkle Tree Construction

### Standard Configuration

```typescript
import { SHA256 } from 'crypto-js';
import MerkleTree from 'merkletreejs';

// For claim codes
const codes = ['secret1', 'secret2', 'secret3'];
const hashedCodes = codes.map((x) => SHA256(x).toString());

// For whitelists
const addresses = ['bb1...', 'bb1...', 'bb1...'];
const hashedAddresses = addresses.map((x) => SHA256(x));

// Tree options (tested configuration)
const treeOptions = {
    fillDefaultHash:
        '0000000000000000000000000000000000000000000000000000000000000000',
};

// Build tree
const tree = new MerkleTree(hashedCodes, SHA256, treeOptions);
const root = tree.getRoot().toString('hex');
const expectedProofLength = tree.getLayerCount() - 1;
```

### Critical Requirements

1. **Same Layer**: All leaves must be at the same depth
2. **Consistent Proof Length**: All proofs must have identical length
3. **Test Thoroughly**: Verify all paths work before deployment
4. **Use Tested Options**: Stick to the `fillDefaultHash` configuration

## Transfer Integration

### Providing Proofs

Include Merkle proofs in [MsgTransferTokens](https://github.com/trevormil/bitbadges-docs/blob/master/bitbadges-blockchain/cosmos-sdk-msgs/x-tokenization/msgtransferbadges.md):

```typescript
const txCosmosMsg: MsgTransferTokens<bigint> = {
    creator: chain.bitbadgesAddress,
    collectionId: collectionId,
    transfers: [
        {
            // ... other fields
            merkleProofs: [
                {
                    aunts: proofObj.map((proof) => ({
                        aunt: proof.data.toString('hex'),
                        onRight: proof.position === 'right',
                    })),
                    leaf: isWhitelist ? '' : passwordCodeToSubmit,
                    leafSignature: leafSignature, // if applicable
                },
            ],
        },
    ],
};
```

### Proof Generation

```typescript
// Generate proof for user submission
const passwordCodeToSubmit = 'secretCode123';
const leaf = isWhitelist
    ? SHA256(chain.bitbadgesAddress).toString()
    : SHA256(passwordCodeToSubmit).toString();

const proofObj = tree.getProof(leaf, whitelistIndex);
const isValidProof = proofObj && proofObj.length === tree.getLayerCount() - 1;

// Create signature if needed
const leafSignature = signLeaf(leaf + '-' + chain.bitbadgesAddress);
```

## Comparison with ETH Signature Challenges

Merkle challenges and ETH signature challenges are very similar. The main difference is that Merkle challenges must also check that the signed message was pre-committed to in the tree, whereas ETH signature challenges only need to check that the signature is valid and not used before.

For more information, see [ETH Signature Challenges](/token-standard/learn/approval-criteria/eth-signature-challenges).

## Best Practices

### Design Considerations

1. **Tree Structure**: Ensure all leaves at same depth
2. **Proof Length**: Test all proof lengths are identical
3. **Tracker Management**: Use unique IDs for fresh tracking
4. **Security**: **MANDATORY** - Enable leaf signatures for claim codes to prevent front-running
5. **Testing**: Verify all paths work before mainnet

### Performance Optimization

1. **Small Lists**: For <100 users, consider regular address lists
2. **Gas Distribution**: Merkle trees excel with large user bases
3. **Proof Verification**: On-chain verification is gas-efficient
4. **Storage**: No on-chain storage of large lists required


# Override User Level Approvals

Collection-level approvals can override user-level approvals to force transfers.

## Interface

```typescript
interface ApprovalCriteria<T extends NumberType> {
    overridesFromOutgoingApprovals?: boolean;
    overridesToIncomingApprovals?: boolean;
}
```

## How It Works

* **`overridesFromOutgoingApprovals: true`**: Skip sender's outgoing approvals
* **`overridesToIncomingApprovals: true`**: Skip recipient's incoming approvals

This enables forced transfers without user consent.

## Use Cases

* **Force Revoke**: Remove tokens from users
* **Freeze Tokens**: Prevent transfers regardless of user settings
* **Emergency Actions**: Administrative control over transfers

## Mint Address Requirement

**CRITICAL**: Mint address approvals must always override outgoing approvals:

```json
{
    "fromListId": "Mint",
    "approvalCriteria": {
        "overridesFromOutgoingApprovals": true
    }
}
```

The Mint address has no user-level approvals, so overrides are required for functionality.

## Dangerous Configuration Warning

⚠️ **Dangerous Configuration Detected**

When an approval enables forceful transfers without a non-Mint address sender consent (via `overridesFromOutgoingApprovals: true`), this can cause dangerous side effects if not handled properly.

### Risks

Forceful transfers can break protocols that rely on escrow or alternate state. Ensure you understand all side effects of every initiated transfer before proceeding.

**Example**: Revoking from liquidity pools without handling pool shares can desync balances and enable exploits like infinite glitches (e.g. repeated revoke → rejoin pool → infinite liquidity).

### Recommendations

1. **Educate All Potential Initiators**
   * Ensure all approved initiators understand the risks and will handle transfers responsibly.
2. **Use Multi-Sig**
   * Use multi-sig or governance addresses to prevent single points of failure and unauthorized use.
3. **Consider Alternatives**
   * Do you really need revocation capabilities? If not, consider using safer approaches like whitelists, ownership checks, or other freezing methods instead of revoking to avoid these risks entirely.

## Reserved Addresses Protection

BitBadges reserves certain addresses that cannot be forcefully overridden as a sanity check. This protection is **not a replacement for due diligence** but serves as an additional safety measure to prevent accidental or malicious transfers from critical protocol addresses.

When designing your collection, treat these as external contracts. You can control the flow to / from, but if stuff is already escrowed, you cannot forcefully revoke it. Consider using the approval criteria address checks for EVM contracts and / or liquidity pool addresses to prevent this.

### Protected Address Types

The following addresses are protected from forceful overrides:

1. **Liquidity Pool Addresses**
   * Every liquidity pool address is protected to prevent desyncing balances and enabling exploits
2. **Cosmos Coin Wrapper Path Addresses**
   * Every cosmos coin wrapper path address is protected to maintain wrapper functionality integrity
3. **Governance-Configured Addresses**
   * Any addresses explicitly set by governance as protected addresses

### Important Notes

* This protection is a **sanity check**, not a comprehensive security measure
* You should still perform thorough due diligence when designing approvals and initializing forceful transfers
* The protection applies to forceful transfers (when `overridesFromOutgoingApprovals: true` is used)
* This does not apply to transfers to / from these addresses that actually check their user-level approvals.
* We bypass these protections if the initiatedBy === from address (the initiator is the protected address)
* Normal transfers (with user consent) are not affected by this protection


# Predetermined Balances

## Overview

Predetermined balances provide fine-grained control over the exact amounts and order of transfers in an approval. Unlike traditional tally-based systems where you approve a total amount (e.g., 100 tokens) without controlling the specific combinations, predetermined balances let you explicitly define:

* **Exact amounts** that must be transferred
* **Specific order** of transfers
* **Precise token IDs and ownership times** for each transfer

**Key Principle**: The transfer will fail if the balances are not EXACTLY as defined in the predetermined balances.

## Interface Definition

```typescript
export interface PredeterminedBalances<T extends NumberType> {
    manualBalances: ManualBalances<T>[];
    incrementedBalances: IncrementedBalances<T>;
    orderCalculationMethod: PredeterminedOrderCalculationMethod;
}
```

## Balance Definition Methods

There are two mutually exclusive ways to define balances:

### 1. Manual Balances

Define an array of specific balance sets manually. Each element corresponds to a different transfer.

```json
{
    "manualBalances": [
        {
            "amount": "1",
            "tokenIds": [
                {
                    "start": "1",
                    "end": "1"
                }
            ],
            "ownershipTimes": [
                {
                    "start": "1691978400000",
                    "end": "1723514400000"
                }
            ]
        },
        {
            "amount": "5",
            "tokenIds": [
                {
                    "start": "2",
                    "end": "6"
                }
            ],
            "ownershipTimes": [
                {
                    "start": "1691978400000",
                    "end": "1723514400000"
                }
            ]
        }
    ]
}
```

**Use Case**: When you need complete control over each specific transfer amount and timing.

### 2. Incremented Balances

Define starting balances and rules for subsequent transfers. Perfect for sequential minting or time-based releases or other common patterns. Note that most options are incompatible with each other.

```json
{
    "incrementedBalances": {
        "startBalances": [
            {
                "amount": "1",
                "tokenIds": [
                    {
                        "start": "1",
                        "end": "1"
                    }
                ],
                "ownershipTimes": [
                    {
                        "start": "1691978400000",
                        "end": "1723514400000"
                    }
                ]
            }
        ],
        "incrementTokenIdsBy": "1",
        "incrementOwnershipTimesBy": "0",
        "durationFromTimestamp": "0",
        "allowOverrideTimestamp": false,
        "allowOverrideWithAnyValidToken": false,
        "allowAmountScaling": false,
        "recurringOwnershipTimes": {
            "startTime": "0",
            "intervalLength": "0",
            "chargePeriodLength": "0"
        }
    }
}
```

#### Increment Options

| Field                            | Description                                           | Example                                              |
| -------------------------------- | ----------------------------------------------------- | ---------------------------------------------------- |
| `incrementTokenIdsBy`            | Amount to increment token IDs by after each transfer  | `"1"` = next transfer gets token ID 2, then 3, etc.  |
| `incrementOwnershipTimesBy`      | Amount to increment ownership times by                | `"86400000"` = add 1 day to ownership times          |
| `durationFromTimestamp`          | Calculate ownership times from timestamp + duration   | `"2592000000"` = 30 days from transfer time          |
| `allowOverrideTimestamp`         | Allow custom timestamp override in transfer           | `true` = users can specify custom start time         |
| `allowOverrideWithAnyValidToken` | Allow any valid token ID (one) override               | `true` = users can specify any single valid token ID |
| `allowAmountScaling`             | Allow proportional integer multiples of startBalances | `true` = transfer any quantity, coinTransfers scale  |
| `maxScalingMultiplier`           | Maximum scaling multiplier (required when scaling on) | `"1000000000000"` = set high for micro-unit bases    |
| `recurringOwnershipTimes`        | Define recurring time intervals                       | Monthly subscriptions, weekly rewards                |

#### Duration From Timestamp

Dynamically calculate ownership times from a timestamp plus a set duration. This overwrites all ownership times in the starting balances.

```json
{
    "durationFromTimestamp": "2592000000", // 30 days in milliseconds
    "allowOverrideTimestamp": true
}
```

**Behavior**:

* **Default**: Uses transfer time as the base timestamp
* **Override**: If `allowOverrideTimestamp` is true, users can specify a custom timestamp in `MsgTransferTokens` `precalculationOptions`
* **Calculation**: `ownershipTime end = baseTimestamp + durationFromTimestamp - 1`
* **Overwrite**: All ownership times in starting balances are replaced with \[{ "start": baseTimestamp, "end": baseTimestamp + durationFromTimestamp - 1 }]

**Common Duration Values (milliseconds):**

| Duration  | Milliseconds |
| --------- | ------------ |
| 5 minutes | 300000       |
| 1 hour    | 3600000      |
| 1 day     | 86400000     |
| 1 week    | 604800000    |
| 30 days   | 2592000000   |
| 1 year    | 31536000000  |

#### Recurring Ownership Times

Define repeating time intervals for subscriptions or periodic rewards:

```json
{
    "recurringOwnershipTimes": {
        "startTime": "1691978400000", // When intervals begin
        "intervalLength": "2592000000", // 30 days in milliseconds
        "chargePeriodLength": "604800000" // 7 days advance charging
    }
}
```

**Example**: Monthly subscription starting August 13, 2023, with 7-day advance charging period.

#### Amount Scaling

Enable proportional transfers where users can transfer any integer multiple of the base amount. When `allowAmountScaling` is true, `startBalances` defines the 1x base unit, and `approvalCriteria.coinTransfers` scale by the same multiplier automatically.

```json
{
    "incrementedBalances": {
        "startBalances": [
            {
                "amount": "1",
                "tokenIds": [{"start": "1", "end": "1"}],
                "ownershipTimes": [{"start": "1", "end": "18446744073709551615"}]
            }
        ],
        "incrementTokenIdsBy": "0",
        "incrementOwnershipTimesBy": "0",
        "durationFromTimestamp": "0",
        "allowOverrideTimestamp": false,
        "allowOverrideWithAnyValidToken": false,
        "allowAmountScaling": true,
        "maxScalingMultiplier": "1000000000000",
        "recurringOwnershipTimes": {
            "startTime": "0",
            "intervalLength": "0",
            "chargePeriodLength": "0"
        }
    }
}
```

**Constraints**: When `allowAmountScaling` is true, all other incrementedBalances fields must be zero/false/nil. The base must be a static balance set — no dynamic behavior. `maxScalingMultiplier` must be > 0.

**How it works**:

* The chain computes `multiplier = transferAmount / baseAmount`
* The multiplier must be an integer >= 1 (no fractional scaling)
* The multiplier must be <= `maxScalingMultiplier`
* Each `approvalCriteria.coinTransfers` amount is multiplied by the same factor
* Precalculation supports scaling via `scalingMultiplier` in `precalculationOptions` — set it to the desired multiplier (e.g., `"5"` for 5x) and the chain returns scaled balances directly. Alternatively, compute balances client-side and set them on the transfer.

**Best practice**: Set `startBalances` to the smallest possible base unit (e.g., `amount: "1"` for 1 micro-unit) and use a large `maxScalingMultiplier`. This ensures users can transfer any granular amount — since scaling only works with integer multiples, a micro-unit base avoids fractional limitations.

**Use cases**:

* **Pay-per-token**: Base = 1 micro-unit, coinTransfers = 1 micro-unit of payment denom. Users buy any exact amount.
* **Prediction market deposits**: Base = 1 micro-YES + 1 micro-NO. Deposit 1 USDC (1,000,000 micro) → 1,000,000x multiplier.
* **Credit token purchases**: Base = 1 micro-credit for 1 micro-payment. Buy any amount in one transaction.

**Security considerations**:

* `maxScalingMultiplier` MUST be > 0 when `allowAmountScaling` is true — the chain rejects 0 (no unlimited scaling)
* **`maxScalingMultiplier` is enforced per transfer, not cumulatively.** A user can execute multiple transfers each up to the max. To cap total exposure, pair scaling with `maxNumTransfers` (limit number of uses) or `approvalAmounts` (limit total token quantity). Without these, the only limit is the escrow/approver's available balance.
* When `coinTransfers` use `overrideFromWithApproverAddress: true`, the escrow/approver pays `multiplier * baseAmount` per transfer — set `maxScalingMultiplier` conservatively and always set `maxNumTransfers` or `approvalAmounts` to bound total payout
* Amount scaling is **incompatible** with Quest, Subscription, Invoice, Product, Bid/Listing, and Scheduled Payment standards (these require fixed amounts per transfer)
* The `review_collection` tool flags `allowAmountScaling + overrideFromWithApproverAddress` as a warning for review

## Precalculating Balances

### The Race Condition Problem

Predetermined balances can change rapidly between transaction broadcast and confirmation. For example:

* Other users' mints get processed
* Token IDs shift due to concurrent activity
* Manual balance specification becomes unreliable

### The Solution: Precalculation

Use `precalculateBalancesFromApproval` in [MsgTransferTokens](https://github.com/trevormil/bitbadges-docs/blob/master/token-standard/x-tokenization/messages/msg-transfer-tokens.md) to dynamically calculate balances at execution time.

```typescript
{
  precalculateBalancesFromApproval: {
    approvalId: string;           // The approval to precalculate from
    approvalLevel: string;        // "collection" | "incoming" | "outgoing"
    approverAddress: string;      // "" if collection-level
    version: string;              // Must specify exact version
    precalculationOptions: {
      overrideTimestamp: string;  // Optional: override timestamp (milliseconds)
      tokenIdsOverride: UintRange[]; // Optional: override token IDs
      scalingMultiplier: string;  // Optional: scale balances by this multiplier (requires allowAmountScaling)
    }
  }
}
```

## Precalculation Options

When using `precalculateBalancesFromApproval`, you can override calculation parameters. These options only apply when the corresponding flags are enabled in `IncrementedBalances`.

| Field               | Type          | When It Applies                                                    | Validation                                                               |
| ------------------- | ------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `overrideTimestamp` | string (Uint) | `durationFromTimestamp` set and `allowOverrideTimestamp` is `true` | If zero, uses current block time                                         |
| `tokenIdsOverride`  | UintRange\[]  | `allowOverrideWithAnyValidToken` is `true`                         | Must be exactly one range with `start == end`                            |
| `scalingMultiplier` | string (Uint) | `allowAmountScaling` is `true`                                     | Must be <= `maxScalingMultiplier`. 0 means no scaling (returns 1x base). |

**overrideTimestamp**: Overrides the base timestamp for ownership time calculation. Ownership times become `[overrideTimestamp, overrideTimestamp + durationFromTimestamp - 1]`.

**tokenIdsOverride**: Replaces incrementally calculated token IDs. The token ID must be in the collection's `validTokenIds`.

**scalingMultiplier**: When `allowAmountScaling` is true, multiplies all precalculated balance amounts by this value. The chain computes the 1x base as usual, then scales. This lets API/SDK callers use standard precalculation with scaling instead of computing balances client-side.

```json
{
    "precalculateBalancesFromApproval": {
        "approvalId": "approval-1",
        "approvalLevel": "collection",
        "approverAddress": "",
        "version": "1",
        "precalculationOptions": {
            "overrideTimestamp": "1704067200000",
            "tokenIdsOverride": [{ "start": "5", "end": "5" }]
        }
    }
}
```

If the corresponding flags are `false`, the options are ignored (no error).

## Order Calculation Methods

The system needs to determine which balance set to use for each transfer. This is controlled by the `orderCalculationMethod`.

### How Order Numbers Work

The order number determines which balances to transfer, but it works differently depending on the balance type:

#### Manual Balances

* **Order number = 0**: Transfer `manualBalances[0]` (first element)
* **Order number = 1**: Transfer `manualBalances[1]` (second element)
* **Order number = 5**: Transfer `manualBalances[5]` (sixth element)

**Example**: If you have 3 manual balance sets, order numbers 0, 1, and 2 will use each set once. Order number 3 would be out of bounds.

#### Incremented Balances

* **Order number = 0**: Use starting balances as-is (no increments)
* **Order number = 1**: Apply increments once to starting balances
* **Order number = 5**: Apply increments five times to starting balances

**Example**: Starting with token ID 1, increment by 1:

* Order 0: Token ID 1
* Order 1: Token ID 2
* Order 2: Token ID 3
* Order 5: Token ID 6

### Transfer-Based Order Numbers

Track the number of transfers to determine order:

| Method                                 | Description           | Use Case                    |
| -------------------------------------- | --------------------- | --------------------------- |
| `useOverallNumTransfers`               | Global transfer count | Simple sequential transfers |
| `usePerToAddressNumTransfers`          | Per-recipient count   | User-specific limits        |
| `usePerFromAddressNumTransfers`        | Per-sender count      | Sender-specific limits      |
| `usePerInitiatedByAddressNumTransfers` | Per-initiator count   | Initiator-specific limits   |

**Important**: Uses the same tracker as [Max Number of Transfers](/token-standard/learn/approval-criteria/max-number-of-transfers). Trackers are:

* Increment-only and immutable
* Shared between predetermined balances and max transfer limits
* Must be carefully managed to avoid conflicts

### Merkle-Based Order Numbers

Use Merkle challenge leaf indices (leftmost = 0, rightmost = numLeaves - 1) for reserved transfers:

```typescript
{
  "useMerkleChallengeLeafIndex": true,
  "challengeTrackerId": "uniqueId"
}
```

**Use Case**: Reserve specific token IDs for specific users or claim codes.

## Order Calculation Interface

```typescript
export interface PredeterminedOrderCalculationMethod {
    useOverallNumTransfers: boolean;
    usePerToAddressNumTransfers: boolean;
    usePerFromAddressNumTransfers: boolean;
    usePerInitiatedByAddressNumTransfers: boolean;
    useMerkleChallengeLeafIndex: boolean;
    challengeTrackerId: string;
}
```

## Boundary Handling

### Understanding Bounds

Every approval defines bounds through its core fields (tokenIds, ownershipTimes, etc.). For example:

* **Token IDs**: 1-100
* **Ownership Times**: Mon-Fri only
* **Transfer Times**: Specific date range

Predetermined balances must work within these bounds, but note that order numbers can eventually exceed them.

### Boundary Scenarios

#### Complete Out-of-Bounds

**Scenario**: Order number corresponds to balances completely outside approval bounds.

**Example**:

* Approval allows token IDs 1-100
* Increment by 1 for each transfer
* Order number 101 would require token ID 101 (out of bounds)

**Result**: Transfer is ignored because token ID 101 never matches the approval's token ID range.

#### Partial Overlap

**Scenario**: Order number corresponds to balances that partially overlap with approval bounds.

**Example**:

* Approval allows token IDs 1-100
* Transfer requires token IDs 95-105
* Token IDs 95-100 are in bounds, 101-105 are out of bounds

**Result**:

* Only in-bounds balances (95-100) are approved by current approval
* Out-of-bounds balances (101-105) must be approved by a separate approval
* The complete transfer (95-105) must still be exactly as defined

**Important**: The transfer will fail unless all out-of-bounds balances are approved by other approvals.


# Requires

Additional address relationship restrictions for transfer approval.

## Interface

```typescript
interface ApprovalCriteria<T extends NumberType> {
    requireToEqualsInitiatedBy?: boolean;
    requireToDoesNotEqualInitiatedBy?: boolean;
    requireFromEqualsInitiatedBy?: boolean;
    requireFromDoesNotEqualInitiatedBy?: boolean;
}
```

## How It Works

Enforce additional checks on address relationships:

* **`requireToEqualsInitiatedBy`**: Recipient must equal initiator
* **`requireToDoesNotEqualInitiatedBy`**: Recipient must not equal initiator
* **`requireFromEqualsInitiatedBy`**: Sender must equal initiator
* **`requireFromDoesNotEqualInitiatedBy`**: Sender must not equal initiator

## Constraints

All checks are bounded by the respective address lists (`toList`, `fromList`, `initiatedByList`).


# Tallied Approval Amounts

Limit transfer amounts using increment-only trackers with thresholds.

## Interface

```typescript
interface ApprovalCriteria<T extends NumberType> {
    approvalAmounts?: ApprovalAmounts<T>;
}
```

## How It Works

Specify maximum amounts that can be transferred using four tracker types:

* **Overall** (`trackerType = "overall"`): Universal limit for all transfers
* **Per To Address** (`trackerType = "to"`): Limit per unique recipient
* **Per From Address** (`trackerType = "from"`): Limit per unique sender
* **Per Initiated By Address** (`trackerType = "initiatedBy"`): Limit per unique initiator

"0" means unlimited and not tracked. "N" means max N amount allowed.

## Example

```json
{
    "approvalAmounts": {
        "overallApprovalAmount": "1000",
        "perFromAddressApprovalAmount": "0",
        "perToAddressApprovalAmount": "0",
        "perInitiatedByAddressApprovalAmount": "10",
        "amountTrackerId": "uniqueID"
    }
}
```

## Tracker Types

### Overall Tracker

* **ID**: `1-collection- -approvalId-uniqueID-overall-`
* **Behavior**: Increments for all transfers regardless of sender/recipient/initiator
* **Use Case**: Global collection limits

### Per-To Address Tracker

* **ID**: `1-collection- -approvalId-uniqueID-to-recipientAddress`
* **Behavior**: Separate tracker for each unique recipient
* **Use Case**: Limit how much each user can receive

### Per-From Address Tracker

* **ID**: `1-collection- -approvalId-uniqueID-from-senderAddress`
* **Behavior**: Separate tracker for each unique sender
* **Use Case**: Limit how much each user can send

### Per-InitiatedBy Address Tracker

* **ID**: `1-collection- -approvalId-uniqueID-initiatedBy-initiatorAddress`
* **Behavior**: Separate tracker for each unique initiator
* **Use Case**: Limit how much each user can initiate

## Detailed Example

Using the approval amounts defined above, when Alice initiates a transfer of x10 from Bob:

### Two Trackers Get Incremented

**#1) Overall Tracker**

* **ID**: `1-collection- -approvalId-uniqueID-overall-`
* **Before**: 0/1000
* **After**: 10/1000
* **Behavior**: Any subsequent transfers (from Charlie, etc.) will also increment this universal tracker

**#2) Per-Initiator Tracker**

* **ID**: `1-collection- -approvalId-uniqueID-initiatedBy-alice`
* **Before**: 0/10
* **After**: 10/10 (fully used)
* **Behavior**: Only incremented when Alice initiates. Charlie's transfers use a separate tracker: `1-collection- -approvalId-uniqueID-initiatedBy-charlie`

### Amount Tracking with Balance Type

Trackers store amounts using the balance type structure. Above, we simplified it to just the amount.

```json
{
    "amounts": [
        {
            "amount": 10n,
            "tokenIds": [{ "start": 1n, "end": 1n }],
            "ownershipTimes": [{ "start": 1n, "end": 100000000000n }]
        }
    ]
}
```

**What Gets Incremented**:

* **Amount**: The total quantity transferred
* **Token IDs**: Specific token IDs that were transferred
* **Ownership Times**: The ownership time ranges that were transferred

### Unlimited Trackers (No Increment)

Since "to" and "from" trackers are set to "0" (unlimited), no tracking occurs for these types.

## Tracker Behavior

* **As-Needed**: Only increment trackers when necessary (unlimited = no tracking)
* **Separate Counts**: Each tracker type maintains independent tallies
* **Address Scoped**: Per-address trackers create unique counters per address
* **Balance Tracking**: Increments for specific token IDs and ownership times transferred

## Resets and ID Changes

### Changing Tracker ID

When you update `amountTrackerId` from "uniqueID" to "uniqueID2":

```
1-collection- -approvalId-uniqueID-initiatedBy-alice
↓
1-collection- -approvalId-uniqueID2-initiatedBy-alice
```

**Result**: All tracker IDs change, so all tallies start from scratch.

### Reusing Old IDs

If you later change back to "uniqueID", the starting point will be the previous tally:

* Alice's initiatedBy tracker: 10/10 used (not 0/10)

**Important**: Never reuse tracker IDs unless you want to continue from the previous state. They are increment-only.


# Coin Transfers

Automatic token transfers to be executed on every approval use. These are triggered every time this approval is used. This is useful for payments, payouts, swaps, and more.

Couple notes:

* This forfeits auto-scan mode for the approval
* Subject to allowed denominations set by the module parameters
* Can be used with x/bank denominations or BitBadges alias denominations

## Interface

```typescript
interface iCoinTransfer<T extends NumberType> {
    to: string; // Recipient BitBadges address
    coins: iCosmosCoin<T>[];

    overrideFromWithApproverAddress: boolean; // By default (false), this is the initiator address. In the case of a collection approval, approver address is the mint escrow address.
    overrideToWithInitiator: boolean; // By default (false), this is the to address specified
}

interface iCosmosCoin<T extends NumberType> {
    amount: T;
    denom: string; // Any Cosmos SDK denomination (e.g., "ubadge", "uatom", "uosmo")
}
```

## Mint Escrow Address

The Mint Escrow Address (*mintEscrowAddress*) is a special reserved address generated from the collection ID that holds Cosmos native funds on behalf of the "Mint" address for a specific collection. This address has no known private key and is not controlled by anyone. The only way to get funds out is via collection approvals from the Mint address.

For collection approvals with `overrideFromWithApproverAddress: true`, the approver address is this special mint escrow address.

### Generation

```typescript
const mintEscrowAddress = generateAlias(
    'tokenization',
    getAliasDerivationKeysForCollection(collectionId)
);
```

### Properties

* Longer than normal addresses
* No private key (cannot be controlled by users)
* Can receive Cosmos-native tokens
* Only collection approvals can trigger transfers from it
* Holds Cosmos native tokens (like "ubadge" tokens) associated with the Mint address for a specific collection

### Auto-Escrow During Collection Creation

The `MsgCreateCollection` interface includes a `mintEscrowCoinsToTransfer` field of type `repeated cosmos.base.v1beta1.Coin` that allows you to automatically escrow native coins to the Mint Escrow Address during collection creation.

**Benefits:**

* **Unknown collection ID** - Escrow coins before knowing the final collection ID
* **Automatic transfer** - Coins are automatically transferred to the generated Mint Escrow Address
* **Collection initialization** - Funds are available immediately when the collection is created
* **Single transaction** - Combine collection creation and coin escrow in one operation

**Usage:**

```typescript
const msgCreateCollection: MsgCreateCollection = {
    creator: 'cosmos1...',
    collectionId: '0',
    mintEscrowCoinsToTransfer: [
        {
            denom: 'ubadge',
            amount: '1000000',
        },
    ],
    // ... other collection fields
};
```

This field is particularly useful when you need to fund the Mint Escrow Address but don't know the collection ID beforehand, since the escrow address is derived from the collection ID itself. Thus, it can be done all in one transaction.

## Example

```json
[
    {
        "to": "bb1...",
        "coins": [{ "amount": "1000000000", "denom": "ubadge" }],
        "overrideFromWithApproverAddress": false,
        "overrideToWithInitiator": false
    }
]
```


# User Royalties

Apply percentage-based royalties to transfers.

## Interface

```typescript
interface UserRoyalties {
    percentage: string; // 1 to 10000 represents basis points (0.01% to 100%)
    payoutAddress: string; // Address to receive the royalties
}
```

## How It Works

User royalties automatically deduct a percentage from transfers and send it to a specified payout address:

* **Percentage**: Expressed in basis points (1 = 0.01%, 100 = 1%, 10000 = 100%)
* **Payout**: Automatically sent to the specified address on each transfer
* **Deduction**: Applied to the transfer amount before the transfer is processed

## Usage Examples

### 5% Royalty

```json
{
    "userRoyalties": {
        "percentage": "500", // 500 basis points = 5%
        "payoutAddress": "bb1creator..."
    }
}
```

**Result**: 5% of each transfer amount is sent to the creator's address.

### 2.5% Royalty

```json
{
    "userRoyalties": {
        "percentage": "250", // 250 basis points = 2.5%
        "payoutAddress": "bb1artist..."
    }
}
```

**Result**: 2.5% of each transfer amount is sent to the artist's address.

## Edge Case: One per Transfer

Currently, we only support one specific royalty percentage applied per transfer. If a transfer matches to different approvals with multiple royalties, the transfer may fail.


# Voting Challenges

Voting challenges require a weighted quorum threshold to be met through votes from specified voters. This enables multi-signature-like approval where multiple parties must vote to approve transfers, with each voter having a configurable weight. Votes are stored separately and can be updated.

The system requires a minimum percentage of total voter weight to vote "yes" before a transfer can be approved. Each voter has a weight, and each vote allocates a percentage (0-100%) to "yes" with the remainder going to "no".

## Structure

### Challenge Fields

| Field                 | Type              | Description                                                                                     |
| --------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
| `proposalId`          | string            | Unique identifier for tracking votes                                                            |
| `quorumThreshold`     | string            | Percentage (0-100) of total possible weight that must vote "yes"                                |
| `voters`              | Voter\[]          | List of voters with addresses and weights                                                       |
| `uri`                 | string            | Optional metadata URI                                                                           |
| `customData`          | string            | Optional custom data                                                                            |
| `resetAfterExecution` | boolean           | If true, all votes are cleared after a successful transfer uses this challenge                  |
| `delayAfterQuorum`    | string (Uint, ms) | Mandatory wait period (in milliseconds) after quorum is reached before the transfer can execute |

### Vote Fields

| Field        | Type   | Description                                                   |
| ------------ | ------ | ------------------------------------------------------------- |
| `proposalId` | string | Proposal ID this vote is for                                  |
| `voter`      | string | Address of the voter casting the vote                         |
| `yesWeight`  | string | Percentage (0-100) allocated to "yes"; remainder goes to "no" |

## Voting Process

1. **Cast Vote**: Voters use `MsgCastVote` with `yesWeight` (0-100%)
2. **Store Vote**: Votes stored with key: `collectionId-approverAddress-approvalLevel-approvalId-proposalId-voterAddress`
3. **Update Vote**: Casting a new vote with same parameters overwrites the previous vote
4. **Calculate Threshold**: On transfer attempt:
   * Retrieve all votes for the proposal
   * Calculate each voter's yes contribution: `(voterWeight × yesWeight) / 100`
   * Sum all yes contributions
   * Calculate percentage: `(totalYesWeight × 100) / totalPossibleWeight`
   * Compare to `quorumThreshold`

## Weighted Voting

Each voter has a configurable weight:

```json
{
    "votingChallenges": [
        {
            "proposalId": "proposal-1",
            "quorumThreshold": "50",
            "voters": [
                { "address": "bb1abc...", "weight": "100" },
                { "address": "bb1def...", "weight": "200" },
                { "address": "bb1ghi...", "weight": "50" }
            ]
        }
    ]
}
```

**Example:**

* Total possible weight: 350
* Quorum threshold: 50% of 350 = 175 weight must vote "yes"

## Partial Votes

Voters can allocate a percentage to "yes" with the remainder going to "no":

| `yesWeight` | Yes % | No % |
| ----------- | ----- | ---- |
| `100`       | 100%  | 0%   |
| `70`        | 70%   | 30%  |
| `50`        | 50%   | 50%  |
| `0`         | 0%    | 100% |

## Threshold Calculation

**Important**: The threshold is calculated as a percentage of **total possible weight** (all voters), not just voters who have cast votes. Non-voting voters count as 0% yes.

**Example:**

* Voter A: weight 100, votes 100% yes → contributes 100 yes weight
* Voter B: weight 200, votes 50% yes → contributes 100 yes weight
* Voter C: weight 50, doesn't vote → contributes 0 yes weight
* Total possible weight: 350
* Quorum threshold: 50%

**Calculation:**

* Total yes weight: 100 + 100 + 0 = 200
* Percentage: (200 × 100) / 350 = 57.14%
* Result: 57.14% ≥ 50% → **Challenge satisfied**

## Implementation Details

### Vote Key Format

Votes are stored with key: `collectionId-approverAddress-approvalLevel-approvalId-proposalId-voterAddress`

This scopes votes to specific collection, approver, approval level, approval ID, proposal ID, and voter.

### Vote Verification

When checking if a challenge is satisfied:

1. Retrieve all votes for voters in the challenge
2. For each voter:
   * If vote exists: calculate yes contribution `(voterWeight × yesWeight) / 100`
   * If no vote: contribution is 0
3. Sum all yes contributions
4. Calculate percentage: `(totalYesWeight × 100) / totalPossibleWeight`
5. Compare to `quorumThreshold`

## Type Definitions

```typescript
interface VotingChallenge {
    proposalId: string; // Unique ID for tracking votes
    quorumThreshold: string; // Percentage (0-100) of total weight that must vote yes
    voters: Voter[]; // List of voters with their weights
    uri?: string; // Optional metadata URI
    customData?: string; // Optional custom data
    resetAfterExecution?: boolean; // If true, votes clear after successful transfer
    delayAfterQuorum?: string; // Mandatory wait (ms) after quorum before execution
}

interface Voter {
    address: string; // Address of the voter
    weight: string; // Weight of this voter's vote
}
```

Cast votes using [MsgCastVote](https://github.com/trevormil/bitbadges-docs/blob/master/token-standard/x-tokenization/messages/msg-cast-vote.md).

## Vote Reset Behavior

When `resetAfterExecution` is set to `true`, all votes for the challenge are cleared after a transfer successfully uses the challenge. This makes the challenge reusable without needing to change the `proposalId`.

**Use cases:**

* **Vault withdrawals** — a multi-sig vault that requires fresh approval for every withdrawal. After each approved transfer, votes reset and signers must re-approve the next one.
* **Recurring multi-sig** — any scenario where the same set of voters needs to repeatedly approve transfers, such as a treasury that pays out monthly.

Without `resetAfterExecution`, votes persist indefinitely. Once quorum is reached, any subsequent transfer matching the approval would also pass the challenge (assuming votes are not manually changed). Setting this flag ensures each transfer requires a new round of voting.

## Quorum Delay

The `delayAfterQuorum` field specifies a mandatory waiting period (in milliseconds) between when quorum is reached and when the transfer can actually execute. The chain records the timestamp when quorum is first reached; any transfer attempt before the delay elapses will be rejected.

**Use cases:**

* **Vault timelocks** — require a 24-hour (86400000 ms) delay after quorum so that stakeholders have time to review and potentially revoke votes before execution.
* **Safety delays** — prevent rushed execution of high-value transfers by enforcing a cooling-off period after quorum.
* **Governance windows** — give dissenting voters time to change their votes or escalate concerns before the transfer proceeds.

If `delayAfterQuorum` is not set or is `"0"`, the transfer can execute immediately once quorum is met.

## Use Cases

### Multi-Signature Approval

Require unanimous approval from multiple parties:

```json
{
    "votingChallenges": [
        {
            "proposalId": "multisig-1",
            "quorumThreshold": "100",
            "voters": [
                { "address": "bb1alice...", "weight": "1" },
                { "address": "bb1bob...", "weight": "1" },
                { "address": "bb1charlie...", "weight": "1" }
            ]
        }
    ]
}
```

Requires all three voters to vote 100% yes.

### Weighted Governance

Different stakeholders have different voting power:

```json
{
    "votingChallenges": [
        {
            "proposalId": "governance-1",
            "quorumThreshold": "66",
            "voters": [
                { "address": "bb1founder...", "weight": "1000" },
                { "address": "bb1investor...", "weight": "500" },
                { "address": "bb1community...", "weight": "100" }
            ]
        }
    ]
}
```

Requires 66% of total weight (1056 out of 1600) to vote yes.

### Flexible Approval

Lower threshold for partial approval:

```json
{
    "votingChallenges": [
        {
            "proposalId": "flexible-1",
            "quorumThreshold": "30",
            "voters": [
                { "address": "bb1voter1...", "weight": "100" },
                { "address": "bb1voter2...", "weight": "100" },
                { "address": "bb1voter3...", "weight": "100" }
            ]
        }
    ]
}
```

Allows approval with 30% of total weight voting yes.

## Error Conditions

The system fails if:

* Voter is not in the challenge's voters list
* `yesWeight` is greater than 100
* Percentage of yes votes is less than the quorum threshold
* `proposalId` doesn't match any voting challenge in the approval
* Challenge has no voters or all voters have zero weight

## Security Considerations

### Proposal ID Management

Changing the `proposalId` resets the vote tracker. Use unique proposal IDs for each challenge to prevent vote overlap. Useful for rotating challenge configurations or creating new voting periods, but ensure old votes cannot be maliciously reused.

### Vote Tampering Prevention

Votes are stored on-chain and cannot be tampered with. Votes can be updated by the voter, are scoped to specific contexts (preventing cross-context reuse), and the system validates voters are in the challenge's voters list.

### Abstention Handling

Non-voting voters are treated as 0% yes (100% no). Set realistic thresholds that account for expected participation. High thresholds with many voters may be difficult to meet if voters abstain.

### Weight Distribution

Weight distribution affects security. If one voter has most of the weight, they can control approvals. Distribute weights appropriately to prevent single points of failure. Use equal weights for multi-signature scenarios.

Use the `uri` and `customData` fields to provide context about what voters are approving. Monitor vote tallies to understand approval status.


# EVM Query Challenges

EVM Query Challenges allow approvals to be gated by read-only EVM contract queries. The same challenge structure is also used in **invariants** (`invariants.evmQueryChallenges`), which run after every transfer and are typically used for supply control, balance caps, or max-holder checks rather than per-transfer approval gating.

* **Approval criteria:** `approvalCriteria.evmQueryChallenges` — checked before a transfer is allowed; placeholders: `$initiator`, `$sender`, `$recipient`, `$collectionId`.
* **Invariants:** `invariants.evmQueryChallenges` — checked after all balance updates; same placeholders plus `$recipients` (all recipients as concatenated 32-byte hex). See [Collection setup – invariants](/token-standard/learn/collection-setup-fields#invariants) for where invariants are configured.

The challenge executes a `staticcall` to the specified contract with the given calldata. The result is compared against the expected result using the specified comparison operator. All challenges must pass for the transfer to be approved (or for the invariant to pass).

## Structure

### Challenge Fields

| Field                | Type   | Description                                                                                  |
| -------------------- | ------ | -------------------------------------------------------------------------------------------- |
| `contractAddress`    | string | EVM contract address to query (0x format or bb1 format)                                      |
| `calldata`           | string | ABI-encoded function selector + arguments (hex string without 0x prefix)                     |
| `expectedResult`     | string | Expected return value (hex string without 0x prefix). If empty, any non-error result passes. |
| `comparisonOperator` | string | How to compare: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`. Default is `eq`.                       |
| `gasLimit`           | string | Gas limit for the query (default 100000, max 500000)                                         |
| `uri`                | string | Optional metadata URI                                                                        |
| `customData`         | string | Optional custom data                                                                         |

## Placeholders

The `calldata` field supports dynamic placeholders that are replaced at runtime. **Placeholders differ between approval criteria and invariants** because approval checks run per (from, to) pair while invariants run once after all transfers and can see multiple recipients.

### Approval criteria (transfer gating)

Used in `approvalCriteria.evmQueryChallenges` on collection or user approvals. One recipient per check.

| Placeholder     | Description                          |
| --------------- | ------------------------------------ |
| `$initiator`    | Address of the transfer initiator    |
| `$sender`       | Address sending the tokens (from)    |
| `$recipient`    | Address receiving the tokens (to)    |
| `$collectionId` | Collection ID (uint256, 32-byte hex) |

**Not available in approval context:** `$recipients` (approval runs per single recipient).

### Invariants (post-transfer)

Used in `invariants.evmQueryChallenges`. Checked once after all balance updates; can reference multiple recipients.

| Placeholder     | Description                                                               |
| --------------- | ------------------------------------------------------------------------- |
| `$initiator`    | Address of the transfer initiator                                         |
| `$sender`       | Address sending the tokens (from)                                         |
| `$recipient`    | First recipient only (convenience for single-recipient transfers)         |
| `$recipients`   | All recipient addresses concatenated as 32-byte-padded hex (no separator) |
| `$collectionId` | Collection ID (uint256, 32-byte hex)                                      |

**Note:** `$recipients` is each address ABI-padded to 32 bytes then concatenated (e.g. for two recipients, 64 hex chars + 64 hex chars). It is not comma-separated.

**Example with placeholder:**

```json
{
    "evmQueryChallenges": [
        {
            "contractAddress": "0x1234567890123456789012345678901234567890",
            "calldata": "70a08231000000000000000000000000$initiator",
            "expectedResult": "0000000000000000000000000000000000000000000000000000000000000001",
            "comparisonOperator": "gte",
            "gasLimit": "100000"
        }
    ]
}
```

This checks that the initiator has at least 1 token balance in the ERC-20 contract.

## Comparison Operators

| Operator | Description           | Use Case                           |
| -------- | --------------------- | ---------------------------------- |
| `eq`     | Equals                | Exact value matching               |
| `ne`     | Not equals            | Exclusion checks                   |
| `gt`     | Greater than          | Minimum balance/value requirements |
| `gte`    | Greater than or equal | Minimum threshold checks           |
| `lt`     | Less than             | Maximum limit checks               |
| `lte`    | Less than or equal    | Maximum threshold checks           |

**Note:** Only `eq` and `ne` work reliably for non-numeric return types.

## Query Execution

1. **Placeholder Replacement**: All placeholders in `calldata` are replaced with actual addresses
2. **Static Call**: Execute `eth_call` (staticcall) to the contract with the calldata
3. **Gas Limit**: Query is limited by the specified `gasLimit` to prevent DoS attacks
4. **Result Comparison**: Compare the returned value against `expectedResult` using `comparisonOperator`
5. **Pass/Fail**: If comparison succeeds, challenge passes; otherwise, transfer is rejected

## Type Definitions

```typescript
interface EVMQueryChallenge {
    contractAddress: string;     // EVM contract address to query
    calldata: string;            // ABI-encoded function call (hex without 0x)
    expectedResult?: string;     // Expected return value (hex without 0x)
    comparisonOperator?: string; // eq, ne, gt, gte, lt, lte (default: eq)
    gasLimit: string;            // Gas limit for query (default 100000, max 500000)
    uri?: string;                // Optional metadata URI
    customData?: string;         // Optional custom data
}
```

## Use Cases

### ERC-20 Balance Check

Require the sender to hold at least 100 tokens of an ERC-20:

```json
{
    "evmQueryChallenges": [
        {
            "contractAddress": "0xUSDCAddress...",
            "calldata": "70a08231000000000000000000000000$sender",
            "expectedResult": "0000000000000000000000000000000000000000000000000000000000000064",
            "comparisonOperator": "gte",
            "gasLimit": "100000"
        }
    ]
}
```

The `70a08231` is the function selector for `balanceOf(address)`.

### NFT Ownership Verification

Require the initiator to own a specific NFT:

```json
{
    "evmQueryChallenges": [
        {
            "contractAddress": "0xNFTContract...",
            "calldata": "6352211e0000000000000000000000000000000000000000000000000000000000000001",
            "expectedResult": "$initiator",
            "comparisonOperator": "eq",
            "gasLimit": "100000"
        }
    ]
}
```

The `6352211e` is the function selector for `ownerOf(uint256)`.

## Building Calldata

To build the calldata for a function call:

1. **Get Function Selector**: First 4 bytes of `keccak256(functionSignature)`
2. **Encode Parameters**: ABI-encode the function parameters
3. **Concatenate**: Combine selector + encoded parameters
4. **Add Placeholders**: Replace address parameters with placeholders as needed

**Example for `balanceOf(address)`:**

1. Function signature: `balanceOf(address)`
2. Selector: `keccak256("balanceOf(address)")` → `70a08231`
3. Address parameter: Pad to 32 bytes → `000000000000000000000000<address>`
4. With placeholder: `70a08231000000000000000000000000$initiator`

## Gas Limits

| Query Type      | Recommended Gas |
| --------------- | --------------- |
| Simple storage  | 30,000          |
| Balance check   | 50,000          |
| Complex logic   | 100,000         |
| Multiple calls  | 200,000         |
| Maximum allowed | 500,000         |

## Error Conditions

The challenge fails if:

* Contract address is invalid or doesn't exist
* Calldata is malformed or empty
* Contract reverts during the call
* Query exceeds gas limit
* Return value doesn't match expected result
* Comparison operator is invalid
* Result cannot be compared (e.g., numeric comparison on non-numeric data)

## Security Considerations

### Read-Only Execution

EVM Query Challenges execute as `staticcall`, which:

* Cannot modify state
* Cannot emit events
* Cannot create or destroy contracts
* Is fully deterministic within a block

### Gas Limit Protection

Set appropriate gas limits to prevent:

* DoS attacks through expensive queries
* Excessive resource consumption
* Unpredictable execution costs

### Contract Trust

Only query trusted contracts:

* Malicious contracts could return misleading data
* Ensure the contract's logic is verified
* Consider upgrade risks for proxy contracts

### Determinism

All queries are deterministic within a block because:

* EVM state is consistent within a block
* `staticcall` cannot modify state
* Results are reproducible

### Placeholder Security

Placeholders are replaced at runtime:

* Cannot be manipulated by users
* Values come from the transfer context
* Prevents injection attacks

## Best Practices

1. **Use verified contracts**: Only query well-audited contracts
2. **Set appropriate gas limits**: Balance between reliability and cost
3. **Test thoroughly**: Verify calldata produces expected results
4. **Document requirements**: Use `uri` to explain what the challenge verifies
5. **Handle edge cases**: Consider what happens with zero balances, non-existent tokens, etc.
6. **Combine with other challenges**: Use alongside merkle challenges, voting, etc. for defense in depth


# Minting and Circulating Supply

### Mint Address

The **Mint address** is a reserved address string (`"Mint"`) representing each collection's minting source. It has unlimited balances, and any transfer from the Mint address creates new tokens out of thin air. The Mint address cannot receive tokens, only send/mint them.

```typescript
const transferMsg: MsgTransferTokens = {
    from: 'Mint',
    toAddresses: ['bb1...'],
    balances: [
        {
            amount: 1n,
            tokenIds: [{ start: 1n, end: 1n }],
            ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
        },
    ],
    // ... other fields
}
```

```typescript
const approval: CollectionApproval<bigint> = {
    fromListId: 'Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    // ... other fields
    approvalCriteria: {
        // ... other criteria
        overridesFromOutgoingApprovals: true, // Required for Mint
    },
}
```

#### Manager Controls Minting Flow

The **manager** of the collection controls minting by setting and updating collection transferability rules (`collectionApprovals`) and the updatability of such rules via permissions (`collectionPermissions.canUpdateCollectionApprovals`). The manager can:

* Create, edit, or remove mint approvals (approvals where `fromListId: 'Mint'`) that allow specific minting patterns, according to the updatability permissions set (`canUpdateCollectionApprovals`)
* Set the updatability permissions to lock the future updatability of mint approvals (locking, enabling, soft-enabling, etc.). This is flexible and allows fine-grained patterns like locking for specific times, to specific addresses, etc.

**Important distinction:** Approvals define what transfers are **allowed**, but minting only occurs on **executed transfers**. Approvals are transferability rules. Transfers execute dependent on those rules. Permissions control the updatability of approvals.

```typescript
// Manager sets initial mint approval
const collection: TokenCollection<bigint> = {
    manager: 'bb1...',
    collectionApprovals: [
        {
            fromListId: 'Mint',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: [{ start: 1n, end: 18446744073709551615n }],
            tokenIds: [{ start: 1n, end: 18446744073709551615n }],
            ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            approvalId: 'mint-approval',
            version: 0n,
            // ... other fields
        },
    ],
    collectionPermissions: {
        canUpdateCollectionApprovals: [
            {
                fromListId: 'Mint',
                toListId: 'All',
                initiatedByListId: 'All',
                transferTimes: [{ start: 1n, end: 18446744073709551615n }],
                tokenIds: [{ start: 1n, end: 18446744073709551615n }],
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
                approvalId: 'All',
                permanentlyPermittedTimes: [],
                permanentlyForbiddenTimes: [
                    { start: 1n, end: 18446744073709551615n },
                ], // Locks it forever
            },
        ],
        // ... other permission fields
    },
};
```

**To lock minting:** Disable approval updates permanently for any Mint approval. The current approvals set will be frozen and cannot be updated.

#### Common Approaches

Two common approaches for managing supply:

**1. Mint All at Genesis, Then Lock**

Mint all tokens you need upon collection creation, then lock mint approval updates. Control future flow with post-mint transferability.

For example:

1. Create collection with Mint approvals
2. Mint all tokens to yourself you may ever need (via MsgTransferTokens)
3. Lock minting forever

**Result:** Fixed supply. All tokens minted. Control distribution via post-mint transferability rules.

**2. Keep Mint as "Escrow" for Future Mints**

Keep the Mint address as an escrow for future mints. Control flow with approval updatability and current approvals. For example:

1. Create collection with Mint approvals
2. Edit Mint approvals over time (according to permissions) to enable future minting as needed

**Result:** More dynamic supply. Manager can update mint approvals to enable future minting as needed. Current approvals control immediate minting capabilities, but the manager can always update them to enable more minting in the future.

### Circulating Supply

Circulating supply is the cumulative total of all tokens transferred from the Mint address. It's dynamic, not static. This may be a slightly different concept than you're used to, but it's a powerful one because it allows for you to control the supply of your tokens dynamically and flexibly over time.

Thus, design of your Mint approvals is crucial to the supply control of your collection.

```typescript
// Supply control via approvals
const mintApproval: CollectionApproval<bigint> = {
    fromListId: 'Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'supply-control',
    version: 0n,
    // Approval criteria control who can mint, when, how much
    approvalCriteria: {
        maxNumTransfers: {
            overallMaxNumTransfers: 1000n,
            perFromAddressMaxNumTransfers: 0n,
            perToAddressMaxNumTransfers: 0n,
            perInitiatedByAddressMaxNumTransfers: 0n,
            amountTrackerId: 'supply-control',
            resetTimeIntervals: null
        },
        overridesFromOutgoingApprovals: true, // Required for Mint address
        // ... other criteria
    },
};
```

#### Importance of Updatability Permissions

Even if current approvals don't allow minting, if the manager can update approvals, there is effectively unlimited supply potential. They can simply update and create a new unlimited mint approval. Thus, it is crucial to control both the current approvals and the updatability permissions.

**Visual Example: The Supply Cap Bypass**

Imagine a collection with a capped supply of 1,000 tokens:

```
Current State:
│ Collection: "Limited Edition Badge"      
│ Current Supply: 500/1,000                
│                                          
│ Current Mint Approval:                   
│   ✅ Allows: 500 more tokens            
│   ❌ Blocks: Any additional minting      
│                                           
│ Manager Permission:                      
│   ⚠️ Can Update Approvals: YES           
```

On the outside, it may seem like only 500 more are allowed. However, the manager still has the ability to update or create new approvals. Thus, the supply is effectively unlimited because they can simply update the approvals to allow unlimited minting.

**Key Takeaway:**

To truly cap supply, you must lock BOTH:

1. **Current approvals** - What minting is allowed now
2. **Updatability permissions** - Whether approvals can be created

### Important Disclaimers

#### Never Mix Mint with Other Addresses

**CRITICAL**: When handling collection approvals, you do NOT want to handle the Mint address with other addresses. By default, lists with reserved aliases like "All" include the Mint address. You do not want to accidentally allow minting when you don't intend to.

For proper design, we highly recommend separating minting and post-mint approvals (`fromListId: '!Mint'` vs `fromListId: 'Mint'`).

```typescript
// ❌ BAD - Never do this
const badApproval: CollectionApproval<bigint> = {
    fromListId: 'All', // Mixing Mint with other addresses
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'bad-approval',
    version: 0n,
    // ... other fields
};

// ✅ GOOD - Separate mint and post-mint approvals
const mintApproval: CollectionApproval<bigint> = {
    fromListId: 'Mint', // Minting only
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'mint-only',
    version: 0n,
    // ... other fields
};

const postMintApproval: CollectionApproval<bigint> = {
    fromListId: '!Mint', // All addresses except Mint
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'post-mint',
    version: 0n,
    // ... other fields
};
```

**Rules:**

* Use `"Mint"` for minting approvals only
* Use `"!Mint"` or `"AllWithoutMint"` for post-mint approvals
* Never use `"All"` for `fromListId` (it includes Mint)
* Never mix `"Mint"` with other addresses in the same list

#### Mint Approvals Must Override

Mint address approvals must always override its user-level approvals to properly work, since it cannot control its own user-level approvals itself.

```typescript
const mintApproval: CollectionApproval<bigint> = {
    fromListId: 'Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'mint-approval',
    version: 0n,
    approvalCriteria: {
        overridesFromOutgoingApprovals: true, // Required
        // ... other criteria
    },
};
```

#### Mint vs Mint Escrow Address

Most use cases use `"Mint"` as the reserved address in approvals. For advanced cases needing escrows or payouts, you can use the `mintEscrowAddress` as a helper.

```typescript
// Mint Escrow Address (for advanced cases)
const mintEscrowAddress = generateAlias(
    'tokenization',
    getAliasDerivationKeysForCollection(collectionId)
);
```

The Mint Escrow Address is a generated `bb1` address that holds Cosmos native funds on behalf of the Mint address. It's used for coin transfers, payouts, and escrows. See [Coin Transfers](/token-standard/learn/approval-criteria/usdbadge-transfers#mint-escrow-address) for details.


# Auto-Scan vs. Prioritized Approvals

The transfer approval system operates in two modes: **auto-scan** (default) and **prioritized approvals**. When a transfer is submitted without explicitly prioritizing specific approvals, the system operates in **auto-scan mode** and automatically searches through available approvals to find a match.

However, **not all approvals are safe for auto-scanning**. In auto-scan mode, unsafe approvals are ignored to prevent unexpected behavior. If a transfer needs to be prioritized, it MUST always be prioritized with proper versioning specified to prevent malicious approval changes after submission.

This is important to consider when designing your systems. For example, auto-scan environments like liquidity pools should account for this.

**Why:** Prioritization ensures users know exactly which approval they're using and its side effects (e.g., coin transfers, predetermined balances, version control to prevent malicious approval changes after submission).

### Auto-Scan Mode (Default)

```typescript
const msg: MsgTransferTokens = {
    creator: 'bb1initiator...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1sender...',
            toAddresses: ['bb1recipient...'],
            balances: [
                {
                    amount: '1',
                    tokenIds: [{ start: '1', end: '1' }],
                    ownershipTimes: [
                        { start: '1', end: '18446744073709551615' },
                    ],
                },
            ],
            // No prioritizedApprovals specified - uses auto-scan
            // System automatically finds matching approval
        },
    ],
};
```

### Prioritized Approvals Mode

```typescript
import { MsgTransferTokens } from 'bitbadges';

const msg: MsgTransferTokens = {
    creator: 'bb1initiator...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1sender...',
            toAddresses: ['bb1recipient...'],
            balances: [
                {
                    amount: 1n,
                    tokenIds: [{ start: 1n, end: 1n }],
                    ownershipTimes: [
                        {
                            start: 1n,
                            end: 18446744073709551615n,
                        },
                    ],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'abc123',
                    approvalLevel: 'collection',
                    approverAddress: '', // Empty for collection, address for user approvals
                    version: 2n, // Must specify exact version
                },
            ],
            onlyCheckPrioritizedCollectionApprovals: true,
        },
    ],
};
```

#### Safety Check Logic

An approval is considered **safe for auto-scan mode** if:

1. **Must prioritize flag is false** - `mustPrioritize` is not `true`
2. **No coin transfers** - `approvalCriteria.coinTransfers` is nil or empty
3. **No predetermined balances** - `approvalCriteria.predeterminedBalances` is nil or empty / not used
4. **No merkle challenges** - `approvalCriteria.merkleChallenges` is nil or empty
5. **No ETH signature challenges** - `approvalCriteria.ethSignatureChallenges` is nil or empty
6. **Not special context** - certain special protocol contexts require prioritized approvals (ex: IBC backed minting, Cosmos wrapping)

#### Complete Function Reference

The complete implementation of the auto-scannable check:

```go
func CollectionApprovalIsAutoScannable(approvalCriteria *ApprovalCriteria) bool {
	if approvalCriteria == nil {
		return true
	}

	if approvalCriteria.MustPrioritize {
		return false
	}

	if approvalCriteria.CoinTransfers != nil && len(approvalCriteria.CoinTransfers) > 0 {
		return false
	}

	if approvalCriteria.PredeterminedBalances != nil && !PredeterminedBalancesIsBasicallyNil(approvalCriteria.PredeterminedBalances) {
		return false
	}

	if approvalCriteria.MerkleChallenges != nil && len(approvalCriteria.MerkleChallenges) > 0 {
		return false
	}

	if approvalCriteria.EthSignatureChallenges != nil && len(approvalCriteria.EthSignatureChallenges) > 0 {
		return false
	}

	return true
}
```

```typescript
// ✅ Auto-scannable approval
const approval: CollectionApproval<bigint> = {
    approvalId: 'simple-transfer',
    fromListId: 'Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    version: 0n,
    approvalCriteria: {
        // Only read-only checks - safe for auto-scan
        mustOwnTokens: [
            {
                tokenIds: [{ start: 1n, end: 1n }],
                ownershipTimes: [
                    {
                        start: 1n,
                        end: 18446744073709551615n,
                    },
                ],
            },
        ],
        // ... other criteria
    },
};

// ❌ NOT auto-scannable - has coin transfers
const approvalWithSideEffects: CollectionApproval<bigint> = {
    approvalId: 'paid-transfer',
    fromListId: '!Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    version: 0n,
    approvalCriteria: {
        coinTransfers: [
            {
                to: 'bb1...',
                coins: [{ denom: 'ubadge', amount: 1000000n }],
            },
        ],
        // ... other criteria
    },
};
```

#### Safe Criteria for Auto-Scan

Read-only criteria are safe and won't prevent auto-scanning:

* `mustOwnTokens`, address checks
* `requireToEqualsInitiatedBy`, etc.
* `overridesFromOutgoingApprovals`, `overridesToIncomingApprovals`
* `autoDeletionOptions`

#### Versioning Control

Versioning ensures users know the exact approval they're using before submitting, preventing race conditions. Every time you update an approval, the version will increment automatically.

```typescript
// Approval version increments on each update
const approval: CollectionApproval<bigint> = {
    approvalId: 'my-approval',
    fromListId: '!Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    version: 0n, // Initial version
    // ... other fields
};

// After update, version becomes '1'

// Transfer must specify version '1' to use updated approval
// If mismatched version, transfer will fail / ignore that approval
import { MsgTransferTokens } from 'bitbadges';

const msg: MsgTransferTokens = {
    creator: 'bb1initiator...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1sender...',
            toAddresses: ['bb1recipient...'],
            balances: [
                {
                    amount: 1n,
                    tokenIds: [{ start: 1n, end: 1n }],
                    ownershipTimes: [
                        {
                            start: 1n,
                            end: 18446744073709551615n,
                        },
                    ],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'my-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: 1n, // Must match current version
                },
            ],
        },
    ],
};
```

#### Only Check Prioritized Approvals

You can restrict the system to only check prioritized approvals. If true, we will only check the prioritized approvals provided and fail if none match (i.e. do not check any non-prioritized approvals).

If false, we will check the prioritized approvals first and then scan through the rest of the approvals in auto-scan mode.

```typescript
const msg: MsgTransferTokens = {
    // ... other fields
    transfers: [
        {
            // ... other fields
            prioritizedApprovals: [
                {
                    approvalId: 'abc123',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: '0',
                },
            ],
            onlyCheckPrioritizedCollectionApprovals: true, // Only check prioritized, fail if none match
            onlyCheckPrioritizedIncomingApprovals: true,
            onlyCheckPrioritizedOutgoingApprovals: true,
        },
    ],
};
```

### Must Prioritize Flag

The `mustPrioritize` flag explicitly requires an approval to be prioritized. This is an easy way to prevent auto-scanning of approvals that you do not want to be auto-scanned.

> **Note:** The chain automatically sets `mustPrioritize: true` for any approval that is not auto-scannable (has coin transfers, merkle challenges, ETH signature challenges, or predetermined balances). Non-auto-scannable approvals already require explicit prioritization in the transfer logic, so this normalization ensures the stored value accurately reflects the actual behavior.

```typescript
const approval: CollectionApproval<bigint> = {
    approvalCriteria: {
        mustPrioritize: true, // Cannot be auto-scanned
        // ... other criteria
    },
};
```

**Recommended to use `mustPrioritize: true` when:**

* Forceful transfers (overrides user approvals)
* Stateful approvals (increment trackers)
* Sensitive operations requiring explicit control

### Selective Prioritization

Prioritization is not only used for non auto-scannable approvals. It can also be used to selectively prioritize approvals over others or match to a single approval for a specific transfer context.

### User-Level Auto Approval Flags

All user-level auto approval flags are auto-scannable by default. These flags have no disallowed criteria, so they don't need to be prioritized.

```typescript
// User enables auto-approval flags
const userBalanceStore: UserBalanceStore<bigint> = {
    balances: [],
    outgoingApprovals: [],
    incomingApprovals: [],
    autoApproveSelfInitiatedOutgoingTransfers: true,
    autoApproveSelfInitiatedIncomingTransfers: true,
    autoApproveAllIncomingTransfers: true,
    userPermissions: {
        // ... permission fields
    },
    // ... other fields
};
```

### Examples

#### Collection-Level Transferability

Oftentimes, you may want to make collection-level approvals fully transferable with certain approval criteria restrictions like `mustOwnTokens`. This is fine and compatible with liquidity pools and other auto-scan environments. Handle accordingly in your design.

```typescript
// ✅ Auto-scannable - fully transferable with read-only restrictions
const collectionApproval: CollectionApproval<bigint> = {
    fromListId: '!Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'transferable-approval',
    version: 0n,
    approvalCriteria: {
        // Read-only checks - safe for auto-scan
        mustOwnTokens: [
            {
                tokenIds: [{ start: 1n, end: 1n }],
                ownershipTimes: [
                    {
                        start: 1n,
                        end: 18446744073709551615n,
                    },
                ],
            },
        ],
        // ... other criteria
    },
};
```

**Compatible with:** Liquidity pools, automated trading, and other auto-scan environments.

However, if you introduce `coinTransfers` or other disallowed logic, the approval becomes incompatible with auto-scan mode:

```typescript
// ❌ NOT auto-scannable - has coin transfers
const collectionApproval: CollectionApproval<bigint> = {
    fromListId: '!Mint',
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: [{ start: 1n, end: 18446744073709551615n }],
    tokenIds: [{ start: 1n, end: 18446744073709551615n }],
    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
    approvalId: 'paid-approval',
    version: 0n,
    approvalCriteria: {
        coinTransfers: [
            {
                to: 'bb1...',
                coins: [{ denom: 'ubadge', amount: 1000000n }],
            },
        ],
        // ... other criteria
    },
};
```

**Not compatible with:** Auto-scan environments. Transfers must be prioritized.

#### Auto-Scan Compatible Transfer

```typescript
import { MsgTransferTokens } from 'bitbadges';

// Simple transfer - no side effects
const msg: MsgTransferTokens = {
    creator: 'bb1initiator...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1sender...',
            toAddresses: ['bb1recipient...'],
            balances: [
                {
                    amount: 1n,
                    tokenIds: [{ start: 1n, end: 1n }],
                    ownershipTimes: [
                        {
                            start: 1n,
                            end: 18446744073709551615n,
                        },
                    ],
                },
            ],
            // No prioritizedApprovals - system auto-scans
        },
    ],
};
```

#### Prioritized Transfer with Coin Transfers

```typescript
// Transfer with payment side effects - MUST prioritize
const msg: MsgTransferTokens = {
    creator: 'bb1initiator...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1sender...',
            toAddresses: ['bb1recipient...'],
            balances: [
                {
                    amount: '1',
                    tokenIds: [{ start: '1', end: '1' }],
                    ownershipTimes: [
                        { start: '1', end: '18446744073709551615' },
                    ],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'reward-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: '1', // Must match current version
                },
            ],
        },
    ],
};
```

#### Multiple Prioritized Approvals

```typescript
// Transfer using multiple approvals
const msg: MsgTransferTokens = {
    creator: 'bb1initiator...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1sender...',
            toAddresses: ['bb1recipient...'],
            balances: [
                {
                    amount: '1',
                    tokenIds: [{ start: '1', end: '1' }],
                    ownershipTimes: [
                        { start: '1', end: '18446744073709551615' },
                    ],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'collection-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: '2',
                },
                {
                    approvalId: 'outgoing-approval',
                    approvalLevel: 'outgoing',
                    approverAddress: 'bb1sender...',
                    version: '0',
                },
            ],
        },
    ],
};
```

See [Transferability](/token-standard/learn/transferability) for detailed documentation.


# Alias Compatibility

In many instances, you may see BitBadges provide alias denomination support for compatibility with existing Cosmos SDK interfaces like `sdk.Coin`. This enables seamless integration with liquidity pools, multi-standard environments, and other systems that expect standard Cosmos coin formats (denom, amount).

Note that the environment must support aliases for this to work.

## Alias Denomination Format

BitBadges uses the format `badgeslp:COLLECTION_ID:denom` for alias denominations:

* **Format**: `badgeslp:COLLECTION_ID:denom`
* **Example**: `5 badgeslp:73:utoken`
  * Collection ID: `73`
  * Base denomination: `utoken` (from the collection's `aliasPaths` array)
  * Amount: `5`

## How It Works

The alias denomination converts an integer amount to `Balances[]` using the collection's `aliasPaths` field, which defines the conversion rate.

### Conversion Process

1. **Parse the alias**: Extract collection ID and denom from `badgeslp:COLLECTION_ID:denom`
2. **Find alias path**: Look up the matching `AliasPath` in the collection's `aliasPaths` array by denom
3. **Convert amount**: Use the path's `conversion` field to convert the integer amount to `Balances[]`. The conversion rate is: `conversion.sideA.amount` alias units = `conversion.sideB[]` tokens. For example, if `sideA.amount = "1"` and `sideB = [{ amount: 1n, ... }]`, then `1 badgeslp:73:utoken = 1 token` (1:1 conversion)
4. **Execute transfer**: Process the transfer using the converted `Balances[]` via `MsgTransferTokens`

### Important Notes

* **No wrapping involved**: This is not a wrapping/unwrapping process. The conversion is simply an alias for the full `Balances[]` field.
* **Conversion rate defined**: The conversion rate is defined in the collection's `aliasPaths` field, specifically in the `conversion.sideA.amount` and `conversion.sideB[]` fields of each path.
* **Auto-scan mode**: Implementations that support alias denominations almost always operate in **auto-scan mode** (no prioritized approvals required).

## Use Cases

### Liquidity Pool Environments

Alias denominations enable BitBadges tokens to participate in liquidity pools that expect standard `sdk.Coin` formats:

```typescript
// Example: Adding liquidity to a pool
const coins = [
    {
        denom: 'badgeslp:73:utoken',
        amount: '1000000', // Converts to Balances[] via aliasPaths behind the scenes
    },
    {
        denom: 'uatom',
        amount: '500000',
    },
];
```

### Multi-Standard Support

In environments where you need to support multiple token standards, alias denominations provide a unified interface:

```typescript
// Works seamlessly with standard Cosmos SDK coins
const transfer = {
    from: 'bb1...',
    to: 'bb1...',
    amount: [
        {
            denom: 'badgeslp:73:utoken', // BitBadges token (alias)
            amount: '1000',
        },
        {
            denom: 'uatom', // Standard Cosmos SDK coin
            amount: '500',
        },
    ],
};
```

## Configuration

The conversion is defined in the collection's `aliasPaths` field:

```typescript
const collection: MsgCreateCollection = {
    // ... other fields
    aliasPathsToAdd: [
        {
            denom: 'utoken',
            conversion: {
                sideA: {
                    amount: '1', // Required: amount of alias unit
                },
                sideB: [
                    {
                        amount: 1n,
                        tokenIds: [{ start: 1n, end: 100n }],
                        ownershipTimes: [
                            { start: 1n, end: 18446744073709551615n },
                        ],
                    },
                ],
            },
            symbol: 'BASETOKEN',
            denomUnits: [
                {
                    decimals: 6n,
                    symbol: 'TOKEN',
                    isDefaultDisplay: true,
                },
            ],
            metadata: { uri: '', customData: '' }, // Optional PathMetadata
        },
    ],
};
```

**Metadata**: Alias paths use the standard metadata structure with `uri` (e.g., `ipfs://Qm...`) pointing to hosted JSON containing `{ name, image, description }`. The image is the primary use case. The on-chain `symbol` field is typically used for identification, not the metadata name.

In this example:

* `1 badgeslp:COLLECTION_ID:utoken` converts to `1` token with IDs `1-100` and full ownership times
* The conversion rate is `1:1` (1 alias unit = 1 token) because `conversion.sideA.amount = "1"` and `conversion.sideB[0].amount = 1n`
* The conversion structure uses `ConversionWithoutDenom` because the denom is stored separately at the path level

## Permission Control

Adding new alias paths to a collection is controlled by the `canAddMoreAliasPaths` permission. This permission allows you to control when managers can add new alias paths.

### Default Behavior

* **Empty/Nil Permissions**: When `canAddMoreAliasPaths` is empty or nil, adding paths is **allowed** (neutral state)
* **Migration**: Collections migrated from v21 will have empty permissions, meaning adding paths is allowed by default

### Permission Structure

The permission uses the `ActionPermission` type with time-based controls:

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreAliasPaths: [
        {
            permanentlyPermittedTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};
```

### Usage Examples

**Allow adding paths at all times:**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreAliasPaths: [], // Empty = allowed by default
};
```

**Lock adding paths forever:**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreAliasPaths: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};
```

**Allow adding paths only during specific period:**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreAliasPaths: [
        {
            permanentlyPermittedTimes: [
                { start: 1704067200000n, end: 1735689600000n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};
```

### Permission Check

When using `MsgUniversalUpdateCollection` to add alias paths via `aliasPathsToAdd`, the system checks the `canAddMoreAliasPaths` permission before processing the paths. If the permission check fails, the transaction will be rejected with an appropriate error message.

**Note**: The permission is checked before paths are added, but the permission itself can be updated at the end of the transaction (if `updateCollectionPermissions` is set to `true`).

## Benefits

* **Drop-In Replacement**: Upgrade existing systems to support BitBadges tokens with simply changing a line of code
* **Seamless Integration**: Works with existing Cosmos SDK interfaces and tools
* **Liquidity Pool Compatibility**: Enables participation in AMM pools and DeFi protocols
* **Multi-Standard Support**: Unified interface for different token types
* **No Wrapping Overhead**: Direct alias conversion without minting/burning


# IBC Backed Minting

IBC Backed Paths enable bidirectional conversion between our standard and IBC coins (standard Cosmos SDK coins). This creates a special address that acts as an intermediary, allowing tokens created with our standard to be "backed" by IBC-denominated tokens for seamless interoperability with the broader Cosmos ecosystem.

This can be very useful for use cases like reverse-wrapping IBC (ICS20) tokens with added compliance.

## Core Concept

An IBC Backed Path creates a special address that converts our standard tokens to and from IBC coins:

* **Back tokens**: Send our standard tokens to the special address → receive IBC coins
* **Unback tokens**: Send IBC coins to the special address → receive our standard tokens

```typescript
// Collection with IBC backed path
const collection: MsgCreateCollection = {
    creator: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    collectionId: '0', // 0 for new collection
    validTokenIds: [{ start: 1n, end: 1n }],
    invariants: {
        cosmosCoinBackedPath: {
            // address: auto-generated from conversion.sideA.denom
            conversion: {
                sideA: {
                    amount: '1000000', // IBC coin amount (from old ibcAmount)
                    denom: 'ibc/1234567890ABCDEF', // IBC denomination (from old ibcDenom)
                },
                sideB: [
                    {
                        amount: 1n,
                        tokenIds: [{ start: 1n, end: 1n }],
                        ownershipTimes: [
                            { start: 1n, end: 18446744073709551615n },
                        ],
                    },
                ],
            },
        },
        noCustomOwnershipTimes: false,
        maxSupplyPerId: '0',
        noForcefulPostMintTransfers: false,
        disablePoolCreation: false,
        evmQueryChallenges: [],
    },
    // ... other fields (collectionPermissions, manager, etc.)
};
```

## Special Address

Each IBC backed path has a **special address** automatically generated from the IBC denomination in `conversion.sideA.denom`:

```typescript
import { generateAliasAddressForIBCBackedDenom } from 'bitbadges';

const ibcDenom = 'ibc/1234567890ABCDEF';
const specialAddress = generateAliasAddressForIBCBackedDenom(ibcDenom);
console.log('Special Address:', specialAddress);
```

**Properties:**

* Deterministically derived from the IBC denom using a hash function
* Acts as the intermediary for all conversions
* Marked as a reserved protocol address
* Holds the IBC coins that back the badge tokens

## Conversion Mechanism

The conversion uses a structured `Conversion` format that combines the IBC denom and amount into `sideA`, with badge tokens in `sideB`. You can't fractionalize it, but if you make the denominations as small as possible, you can get as fine-grained as you want.

```
conversion.sideA (amount + denom) = conversion.sideB[] (x/tokenization)
```

**Example:**

```typescript
// Configuration
const backedPath = {
    conversion: {
        sideA: {
            amount: '1000000', // IBC coin amount (from old ibcAmount)
            denom: 'ibc/1234567890ABCDEF', // IBC denomination (from old ibcDenom)
        },
        sideB: [
            {
                amount: 1n,
                tokenIds: [{ start: 1n, end: 1n }],
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            },
        ],
    },
};
```

**Conversion Structure:**

* **`Conversion`** (with denom): Used by backed paths because the denom is part of the conversion
* **`sideA`**: Contains both `amount` and `denom` (from old `ibcAmount` and `ibcDenom` fields)
* **`sideB`**: Array of `Balance` objects that define the badge tokens (from old `balances` field)
* **Conversion rate**: `conversion.sideA.amount` of `conversion.sideA.denom` = `conversion.sideB[]` tokens

## Configuration

IBC backed paths are configured as **collection invariants**, meaning:

* Set only during collection creation
* Cannot be modified after creation
* Only **one** backed path allowed per collection

```typescript
const collection: MsgCreateCollection = {
    creator: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    collectionId: '0', // 0 for new collection
    validTokenIds: [{ start: 1n, end: 100n }],
    invariants: {
        cosmosCoinBackedPath: {
            conversion: {
                sideA: {
                    amount: '1000000', // IBC coin amount
                    denom: 'ibc/1234567890ABCDEF', // IBC denomination
                },
                sideB: [
                    {
                        amount: 1n,
                        tokenIds: [{ start: 1n, end: 100n }],
                        ownershipTimes: [
                            { start: 1n, end: 18446744073709551615n },
                        ],
                    },
                ],
            },
        },
        noCustomOwnershipTimes: false,
        maxSupplyPerId: '0',
        noForcefulPostMintTransfers: false,
        disablePoolCreation: false,
        evmQueryChallenges: [],
    },
    collectionPermissions: {
        // ... permission fields
    },
    manager: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    // ... other collection fields
};
```

## Mint Address Restrictions

When an IBC backed path is configured:

* Transfers **from** the Mint address (mints) are never allowed
* All mints must be done through the IBC backed path
* Collection approvals cannot include the Mint address in `fromListId`

This prevents minting tokens that would bypass the backing mechanism and cause desyncs.

If you want a more hybrid approach or more customization, don't use the invariant and rather implement it with custom transferability logic instead.

```typescript
// ❌ Invalid - Cannot use Mint address with IBC backed path
const invalidApproval = {
    fromListId: 'Mint', // Not allowed when cosmosCoinBackedPath is set
    toListId: 'All',
    // ...
};

// ✅ Valid - Must use special address for minting
const validApproval = {
    fromListId: specialAddress, // Use the IBC backed path address
    toListId: 'All',
    // ...
};
```

## Transferability Requirements

The special address is subject to the same transferability requirements as any other address. You can user-gate, rate-limit, or apply any approval logic.

**Important:** Collection approvals used for IBC backed path operations must have `allowBackedMinting: true` set in their `approvalCriteria`. See [Special Address Flags](https://github.com/trevormil/bitbadges-docs/blob/master/token-standard/learn/approval-criteria/special-address-flags.md) for details.

```typescript
// Example: Rate-limited backing
const collectionApprovals = [
    {
        fromListId: specialAddress,
        toListId: 'All',
        initiatedByListId: 'All',
        transferTimes: [{ start: 1n, end: 18446744073709551615n }],
        tokenIds: [{ start: 1n, end: 100n }],
        ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
        approvalId: 'backing-approval',
        version: 0n,
        approvalCriteria: {
            allowBackedMinting: true, // Required for IBC backed path operations
            // overridesFromOutgoingApprovals is irrelevant — backing addresses are protocol-controlled with auto-set approvals
            mustPrioritize: true, // Required for IBC backed operations
            maxNumTransfers: {
                perInitiatedByAddressMaxNumTransfers: 10n, // 10 backs per day
                // ... reset time intervals
            },
        },
    },
];
```

## Technical Implementation

### Address Detection

The system automatically detects transfers involving the special address:

* Checks if `to` address matches the backed path address (backing)
* Checks if `from` address matches the backed path address (unbacking)
* Triggers the appropriate conversion logic

**Important:** Detection happens in `MsgTransferTokens` implementation. Using the bank module alone won't work because it doesn't know about IBC backed paths. To trigger a minting or unminting, you simply wire up a `MsgTransferTokens` message to the special address. Approvals of the special address are handled behind the scenes by the system. Note: The approval must be prioritized as this is a special context / environment.

## Example: Backing Tokens

```typescript
// User sends badge tokens to special address
// ⚠️ IMPORTANT: IBC backed path operations require prioritized approvals (not compatible with auto-scan mode)
// The initiator must equal the sender/recipient - no doing this on behalf of another user
const backTokens: MsgTransferTokens = {
    creator: 'bb1user...', // Must equal from address
    collectionId: '1',
    transfers: [
        {
            from: 'bb1user...', // Must equal creator
            toAddresses: [specialAddress], // Special IBC backed path address
            balances: [
                {
                    amount: 5n,
                    tokenIds: [{ start: 1n, end: 1n }],
                    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'backing-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: 0n,
                },
            ],
            onlyCheckPrioritizedCollectionApprovals: true,
        },
    ],
};

// Result: User receives corresponding IBC coins automatically based on the conversion rate
```

## Example: Unbacking Tokens

Approvals are actually handled behind the scenes by the system for the special address. You do not need to do anything special here besides having sufficient balances to unback the tokens.

```typescript
// User initiates transfer on behalf of special address to receive tokens
// ⚠️ IMPORTANT: IBC backed path operations require prioritized approvals (not compatible with auto-scan mode)
// The initiator must equal the recipient - no doing this on behalf of another user
const unbackTokens: MsgTransferTokens = {
    creator: 'bb1user...', // Must equal toAddress
    collectionId: '1',
    transfers: [
        {
            from: specialAddress, // Transfer from special IBC backed path address
            toAddresses: ['bb1user...'], // Must equal creator
            balances: [
                {
                    amount: 5n,
                    tokenIds: [{ start: 1n, end: 1n }],
                    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'unbacking-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: 0n,
                },
            ],
            onlyCheckPrioritizedCollectionApprovals: true,
        },
    ],
};

// Result: User receives 5 badge tokens
// Corresponding IBC coins are deducted from special address
```

## Differences from Wrapper Paths

| Feature              | IBC Backed Path                        | Wrapper Path                 |
| -------------------- | -------------------------------------- | ---------------------------- |
| **Minting**          | No minting/burning (uses existing IBC) | Minting/burning of new denom |
| **Denom Source**     | Existing IBC denom                     | Generated denom              |
| **Configuration**    | Collection invariant                   | Can add paths, no edits      |
| **Standard Minting** | Disabled                               | Enabled                      |


# Cosmos Coin Wrapper Paths

Cosmos Wrapper Paths enable wrapping between BitBadges tokens and native Cosmos SDK coin (x/bank) asset types, making tokens IBC-compatible. These paths automatically mint and burn tokens when transferring to/from specific wrapper addresses.

This is used to generate a 1:1 compatible mapping between our standard tokens and native Cosmos SDK coins. Note that this does not use an existing IBC denom, but rather creates a custom generated denomination.

Use cases could include:

* Using our standard tokens for time-dependent logic but eventually making them native Cosmos SDK coins
* Compatibility with existing Cosmos services and chains like Osmosis, Juno, etc.

> **Important**: Since wrapper addresses are uncontrollable (no private keys), approval design requires careful consideration. You must override the wrapper address's user-level approvals where necessary using collection approvals to ensure wrapping/unwrapping functions properly. Additionally, collection approvals used for wrapper path operations must have `allowSpecialWrapping: true` set in their `approvalCriteria`. See [Special Address Flags](https://github.com/trevormil/bitbadges-docs/blob/master/token-standard/learn/approval-criteria/special-address-flags.md) for details.

## Core Concept

Wrapper paths create special addresses that convert our standard tokens to native Cosmos SDK coins:

* **Wrapping**: Send our standard tokens to wrapper address → receive native coins (tokens are burned, x/bank coins are minted)
* **Unwrapping**: Send native coins to wrapper address → receive our standard tokens (x/bank coins are burned, tokens are minted)

```typescript
// Collection with wrapper path
const collection: MsgCreateCollection = {
    creator: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    collectionId: '0', // 0 for new collection
    validTokenIds: [{ start: 1n, end: 100n }],
    cosmosCoinWrapperPathsToAdd: [
        {
            denom: 'utoken',
            conversion: {
                sideA: {
                    amount: '1', // Required: amount of wrapped coin
                },
                sideB: [
                    {
                        amount: 1n,
                        tokenIds: [{ start: 1n, end: 100n }],
                        ownershipTimes: [
                            { start: 1n, end: 18446744073709551615n },
                        ],
                    },
                ],
            },
            symbol: 'TOKEN',
            denomUnits: [
                {
                    decimals: 6n,
                    symbol: 'TOKEN',
                    isDefaultDisplay: true,
                },
            ],
            allowOverrideWithAnyValidToken: false,
            metadata: { uri: '', customData: '' }, // Optional metadata
        },
    ],
    aliasPathsToAdd: [
        {
            denom: 'utoken-alias',
            conversion: {
                sideA: {
                    amount: '1', // Required: amount of wrapped coin
                },
                sideB: [
                    {
                        amount: 1n,
                        tokenIds: [{ start: 1n, end: 100n }],
                        ownershipTimes: [
                            { start: 1n, end: 18446744073709551615n },
                        ],
                    },
                ],
            },
            symbol: 'ALIAS',
            denomUnits: [
                {
                    decimals: 6n,
                    symbol: 'ALIAS',
                    isDefaultDisplay: true,
                },
            ],
            metadata: { uri: '', customData: '' }, // Optional metadata
        },
    ],
    // ... other fields
};
```

## Wrapper Paths vs Alias Paths

The system now distinguishes between two separate path types:

### Cosmos Coin Wrapper Paths

Used for actual wrapping/unwrapping with minting and burning:

* **Purpose**: Convert tokens to native Cosmos SDK coins and vice versa
* **Behavior**: Tokens are burned when wrapping, coins are minted. Coins are burned when unwrapping, tokens are minted
* **Use case**: IBC transfers, converting tokens to native coins for Cosmos ecosystem compatibility
* **Storage**: Stored in `cosmosCoinWrapperPaths` array
* **Features**: Includes `address` field (wrapper address) and `allowOverrideWithAnyValidToken` option

```typescript
{
    denom: 'utoken',
            conversion: {
                sideA: {
                    amount: '1', // Required: amount of wrapped coin
                },
        sideB: [
            {
                amount: 1n,
                tokenIds: [{ start: 1n, end: 100n }],
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            },
        ],
    },
    symbol: 'TOKEN',
    denomUnits: [
        {
            decimals: 6n,
            symbol: 'TOKEN',
            isDefaultDisplay: true,
        },
    ],
    allowOverrideWithAnyValidToken: false,
    metadata: { uri: '', customData: '' }, // Optional PathMetadata
}
```

### Alias Paths

Used for compatibility with existing Cosmos SDK interfaces without actual wrapping:

* **Purpose**: Alias denomination support (e.g., `badgeslp:COLLECTION_ID:denom`)
* **Behavior**: No minting/burning occurs, only an alias for information purposes
* **Use case**: Compatibility with liquidity pools, DeFi protocols that expect `sdk.Coin` format
* **Storage**: Stored in `aliasPaths` array (separate from wrapper paths)
* **See**: [Alias Compatibility](/token-standard/learn/alias-compatibility) for details
* **Note**: Does not include `address` or `allowOverrideWithAnyValidToken` fields

```typescript
{
    denom: 'utoken',
            conversion: {
                sideA: {
                    amount: '1', // Required: amount of alias unit
                },
        sideB: [
            {
                amount: 1n,
                tokenIds: [{ start: 1n, end: 100n }],
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            },
        ],
    },
    symbol: 'TOKEN',
    denomUnits: [
        {
            decimals: 6n,
            symbol: 'TOKEN',
            isDefaultDisplay: true,
        },
    ],
    metadata: { uri: '', customData: '' }, // Optional PathMetadata
}
```

## Wrapper Address Generation

Wrapper addresses are auto-generated based on the denom:

```typescript
import { generateAliasAddressForDenom } from 'bitbadges';

const denom = 'utoken';
const wrapperAddress = generateAliasAddressForDenom(denom);
console.log('Wrapper Address:', wrapperAddress);
```

**Note:** The address is generated from the custom denom, not the full `badges:collectionId:denom` format.

## Conversion Structure

Wrapper paths and alias paths use a structured conversion format to define the relationship between wrapped/alias units and badge tokens:

### ConversionWithoutDenom

Used by wrapper paths and alias paths (denom stored separately):

```typescript
{
    conversion: {
        sideA: {
            amount: '1', // Required: amount of wrapped/alias coin (Uint type)
        },
        sideB: [
            // Balances[] that define which tokens participate
            {
                amount: 1n,
                tokenIds: [{ start: 1n, end: 100n }],
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            },
        ],
    },
}
```

**Key Points:**

* `sideA.amount`: The amount of wrapped/alias coin units (required, must be specified)
* `sideB`: Array of `Balance` objects that define the tokens involved in the conversion
* The denom is stored at the path level, not in the conversion (hence "WithoutDenom")
* Conversion rate: `sideA.amount` wrapped/alias units = `sideB[]` tokens

**Example:**

* If `sideA.amount = "1"` and `sideB = [{ amount: 1n, tokenIds: [...], ... }]`
* Then: `1 wrapped coin = 1 token` (1:1 conversion)

**Example with different rate:**

* If `sideA.amount = "100"` and `sideB = [{ amount: 1n, tokenIds: [...], ... }]`
* Then: `100 wrapped coins = 1 token` (100:1 conversion)

## Configuration Fields

### Denom

The base denomination for the wrapped coin. The full Cosmos denomination will be `badges:collectionId:denom`. Note that `badges:` is different from the `badgeslp:` format used for aliases elsewhere.

```typescript
{
    denom: 'utoken', // Base denom
    // Full denom: badges:1:utoken
}
```

### Conversion

Defines the conversion rate between wrapped coins and tokens using a structured conversion format:

```typescript
{
    conversion: {
        sideA: {
            amount: '1', // Required: amount of wrapped coin
        },
        sideB: [
            {
                amount: 1n, // Token amount
                tokenIds: [{ start: 1n, end: 100n }], // Token IDs that can wrap
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }], // Ownership times
            },
        ],
    },
}
```

**Conversion rate:** `conversion.sideA.amount wrapped coin = conversion.sideB[] tokens`

**Key Points:**

* `sideA.amount` is required and must be specified (cannot be "0" or nil)
* `sideB` contains the `Balances[]` that define which tokens participate in wrapping
* The denom is stored separately at the path level (not in the conversion)

### Denomination Units

Multiple denomination units allow different display formats:

```typescript
{
    denomUnits: [
        {
            decimals: 3n, // 3 decimal places
            symbol: 'mtoken', // Milli-token
            isDefaultDisplay: false,
            metadata: { uri: '', customData: '' }, // Optional PathMetadata
        },
        {
            decimals: 6n, // 6 decimal places
            symbol: 'TOKEN', // Full token
            isDefaultDisplay: true, // Shown by default
            metadata: { uri: '', customData: '' }, // Optional PathMetadata
        },
    ],
}
```

**System:**

* `utoken` = base unit (0 decimals)
* `mtoken` = 1,000 `utoken` (3 decimals)
* `TOKEN` = 1,000,000 `utoken` (6 decimals, default display)

**Note:** Each `DenomUnit` now includes an optional `metadata` field of type `PathMetadata` for additional information.

### Allow Override With Any Valid Token

When `true`, allows the wrapper to accept any SINGLE valid token ID from the collection's `validTokenIds` range:

```typescript
{
    denom: 'utoken',
    conversion: {
        sideA: {
            amount: '1',
        },
        sideB: [
            {
                amount: 1n,
                tokenIds: [{ start: 1n, end: 1n }], // Overridden during transfer
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            },
        ],
    },
    allowOverrideWithAnyValidToken: true, // Accept any valid token ID
}
```

**How it works:**

1. User transfers token ID 5 to wrapper
2. System validates token ID 5 is in `validTokenIds`
3. System temporarily overrides `conversion.sideB[].tokenIds` with `[{ start: 5n, end: 5n }]` ignoring the values set in the `sideB` array
4. Conversion proceeds with token ID 5

## {id} Placeholder Support

You can use `{id}` in the denom to dynamically replace it with the actual token ID:

```typescript
{
    denom: 'utoken{id}', // Dynamic denom
    symbol: 'TOKEN:{id}',
    conversion: {
        sideA: {
            amount: '1',
        },
        sideB: [
            {
                amount: 1n,
                tokenIds: [{ start: 1n, end: 1n }],
                ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
            },
        ],
    },
    allowOverrideWithAnyValidToken: true,
}
```

**Example:** Transferring token ID 5 results in denom `utoken5`.

### Metadata

Both wrapper paths and alias paths support optional metadata using the standard metadata structure:

```typescript
{
    metadata: {
        uri: 'ipfs://Qm...', // Optional URI to hosted JSON metadata
        customData: '{"key": "value"}', // Optional custom JSON data
    },
}
```

The hosted JSON at the URI typically contains `{ name, image, description }`, though the image is the primary use case. The on-chain `symbol` field is used for identification, not the metadata name.

**Note:** Metadata is optional. It's also available on `DenomUnit` objects for additional per-unit metadata.

## Transferability Requirements

Wrapper addresses are subject to the same transferability requirements as any other address. You can user-gate, rate-limit, or apply custom logic:

```typescript
// Example: Rate-limited wrapping
const collectionApprovals = [
    {
        fromListId: 'AllWithoutMint',
        toListId: wrapperAddress,
        initiatedByListId: 'All',
        transferTimes: [{ start: 1n, end: 18446744073709551615n }],
        tokenIds: [{ start: 1n, end: 100n }],
        ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
        approvalId: 'wrap-approval',
        version: 0n,
        approvalCriteria: {
            allowSpecialWrapping: true, // Required for wrapper path operations
            mustPrioritize: true, // Chain-enforced: required for allowSpecialWrapping
            maxNumTransfers: {
                perInitiatedByAddressMaxNumTransfers: 10n, // 10 wraps per day
                // ... reset time intervals
            },
        },
    },
    {
        fromListId: wrapperAddress,
        toListId: 'All',
        initiatedByListId: 'All',
        transferTimes: [{ start: 1n, end: 18446744073709551615n }],
        tokenIds: [{ start: 1n, end: 100n }],
        ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
        approvalId: 'unwrap-approval',
        version: 0n,
        approvalCriteria: {
            allowSpecialWrapping: true, // Required for wrapper path operations
            mustPrioritize: true, // Chain-enforced: required for allowSpecialWrapping
            // Override wrapper's outgoing approvals (wrapper is the sender for unwrapping)
            overridesFromOutgoingApprovals: true,
        },
    },
];
```

## Conversion Process

### Token to Coin (Wrapping)

1. User transfers tokens to wrapper address
2. System processes denom (replaces `{id}` if present, validates override if enabled)
3. System burns tokens from user's balance
4. System mints equivalent native coins
5. Coins credited to user's account

```typescript
// Wrapping tokens
// ⚠️ IMPORTANT: Wrapping/unwrapping requires prioritized approvals (not compatible with auto-scan mode)
const wrapTokens: MsgTransferTokens = {
    creator: 'bb1user...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1user...',
            toAddresses: [wrapperAddress],
            balances: [
                {
                    amount: 10n,
                    tokenIds: [{ start: 1n, end: 100n }],
                    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'wrap-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: 0n,
                },
            ],
            onlyCheckPrioritizedCollectionApprovals: true,
        },
    ],
};

// Result: User receives 10 badges:1:utoken coins (based on conversion.sideA.amount = 1)
// 10 badge tokens are burned (based on conversion.sideB balances)
```

### Coin to Token (Unwrapping)

Unwrapping still uses `MsgTransferTokens`. You initiate a transfer on behalf of the wrapper address:

1. User initiates `MsgTransferTokens` with wrapper address as `from`
2. System processes denom (replaces `{id}` if present, validates override if enabled)
3. System burns native coins from wrapper address
4. System mints equivalent tokens
5. Tokens credited to user's balance

```typescript
// Unwrapping coins
// ⚠️ IMPORTANT: Wrapping/unwrapping requires prioritized approvals (not compatible with auto-scan mode)
// You initiate a transfer on behalf of the wrapper address
const unwrapCoins: MsgTransferTokens = {
    creator: 'bb1user...',
    collectionId: '1',
    transfers: [
        {
            from: wrapperAddress, // Transfer from wrapper address
            toAddresses: ['bb1user...'], // To user
            balances: [
                {
                    amount: 10n,
                    tokenIds: [{ start: 1n, end: 100n }],
                    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'unwrap-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: 0n,
                },
            ],
            onlyCheckPrioritizedCollectionApprovals: true,
        },
    ],
};

// Result: User receives 10 badge tokens (based on conversion.sideB balances)
// 10 badges:1:utoken coins are burned from wrapper address (based on conversion.sideA.amount = 1)
```

## Use Cases

### IBC Transfers

Enable cross-chain transfers of wrapped tokens:

```typescript
// Wrap tokens for IBC transfer
// ⚠️ IMPORTANT: Requires prioritized approvals
// The conversion rate is defined in the wrapper path's conversion field
const wrapForIBC: MsgTransferTokens = {
    creator: 'bb1user...',
    collectionId: '1',
    transfers: [
        {
            from: 'bb1user...',
            toAddresses: [wrapperAddress],
            balances: [
                {
                    amount: 100n,
                    tokenIds: [{ start: 1n, end: 100n }],
                    ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
                },
            ],
            prioritizedApprovals: [
                {
                    approvalId: 'wrap-approval',
                    approvalLevel: 'collection',
                    approverAddress: '',
                    version: 0n,
                },
            ],
            onlyCheckPrioritizedCollectionApprovals: true,
        },
    ],
};

// Transfer wrapped coins via IBC
const ibcTransfer = {
    sourcePort: 'transfer',
    sourceChannel: 'channel-0',
    token: {
        denom: 'badges:1:utoken',
        amount: '100',
    },
    sender: 'bb1user...',
    receiver: 'cosmos1...',
};
```

### DeFi Integration

Use wrapped tokens in Cosmos DeFi protocols:

```typescript
// Add wrapped tokens to liquidity pool
const addLiquidity = {
    poolId: '1',
    sender: 'bb1user...',
    tokenInMaxs: [
        {
            denom: 'badges:1:utoken',
            amount: '1000000',
        },
        {
            denom: 'uatom',
            amount: '500000',
        },
    ],
};
```

## Permission Control

Adding new cosmos coin wrapper paths to a collection is controlled by the `canAddMoreCosmosCoinWrapperPaths` permission. This permission allows you to control when managers can add new wrapper paths.

### Default Behavior

* **Empty/Nil Permissions**: When `canAddMoreCosmosCoinWrapperPaths` is empty or nil, adding paths is **allowed** (neutral state)
* **Migration**: Collections migrated from v21 will have empty permissions, meaning adding paths is allowed by default

### Permission Structure

The permission uses the `ActionPermission` type with time-based controls:

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreCosmosCoinWrapperPaths: [
        {
            permanentlyPermittedTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};
```

### Usage Examples

**Allow adding paths at all times:**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreCosmosCoinWrapperPaths: [], // Empty = allowed by default
};
```

**Lock adding paths forever:**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreCosmosCoinWrapperPaths: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: [
                { start: 1n, end: 18446744073709551615n },
            ],
        },
    ],
};
```

**Allow adding paths only during specific period:**

```typescript
const collectionPermissions: CollectionPermissions<bigint> = {
    canAddMoreCosmosCoinWrapperPaths: [
        {
            permanentlyPermittedTimes: [
                { start: 1704067200000n, end: 1735689600000n },
            ],
            permanentlyForbiddenTimes: [],
        },
    ],
};
```

### Permission Check

When using `MsgUniversalUpdateCollection` to add wrapper paths via `cosmosCoinWrapperPathsToAdd`, the system checks the `canAddMoreCosmosCoinWrapperPaths` permission before processing the paths. If the permission check fails, the transaction will be rejected with an appropriate error message.

**Note**: The permission is checked before paths are added, but the permission itself can be updated at the end of the transaction (if `updateCollectionPermissions` is set to `true`).

## Differences from IBC Backed Paths

| Feature           | Wrapper Path                 | IBC Backed Path                        |
| ----------------- | ---------------------------- | -------------------------------------- |
| **Minting**       | Minting/burning of new denom | No minting/burning (uses existing IBC) |
| **Denom Source**  | Generated denom              | Existing IBC denom                     |
| **Configuration** | Can add paths, no edits      | Collection invariant                   |
| **Mint Address**  | Enabled                      | Disabled                               |


# IBC Transfer Tokens Hook

The `transfer_tokens` IBC hook allows incoming IBC token transfers to automatically trigger BitBadges token transfers (minting, distributing, etc.) in a single atomic cross-chain transaction. This is a powerful primitive for cross-chain minting, airdrops, purchases, and more.

## Overview

When an IBC transfer arrives with a `transfer_tokens` memo, the module:

1. Receives the IBC token transfer (x/bank assets)
2. Derives an **intermediate sender** address from the IBC channel + original sender
3. Parses the `transfer_tokens` hook data from the memo
4. Executes a `MsgTransferTokens` on the specified collection **as the intermediate sender** (the intermediate sender is the `creator` of the transaction)
5. Returns an error acknowledgement if the transfer fails, rolling back the entire IBC transfer

This means you can, for example, send ATOM from Osmosis and atomically mint a badge on BitBadges in a single transaction.

> **Important:** The `creator` of the resulting `MsgTransferTokens` is always the **intermediate sender** — not the original sender on the source chain. Your collection's approval rules must authorize this intermediate address. The module automatically tries to initiate the transfer as the intermediate sender, but your collection approvals must still permit the transfer (e.g. a mint approval that allows the intermediate sender as the initiator).

## Memo Format

The hook data is specified in the IBC transfer memo as JSON:

```json
{
  "transfer_tokens": {
    "collection_id": "123",
    "transfers": [
      {
        "from": "Mint",
        "to_addresses": ["bb1..."],
        "balances": [
          {
            "amount": "1",
            "badge_ids": [{ "start": "1", "end": "1" }],
            "ownership_times": [{ "start": "1", "end": "18446744073709551615" }]
          }
        ],
        "prioritized_approvals": [],
        "only_check_prioritized_collection_approvals": false,
        "only_check_prioritized_incoming_approvals": false,
        "only_check_prioritized_outgoing_approvals": false
      }
    ],
    "fail_on_error": true,
    "recover_address": ""
  }
}
```

> **Note:** The memo JSON uses **snake\_case** keys (e.g. `to_addresses`, `badge_ids`, `ownership_times`). These are automatically converted to camelCase internally for protobuf processing.

## Fields

### TransferTokensAction

| Field             | Type        | Required    | Description                                                                                  |
| ----------------- | ----------- | ----------- | -------------------------------------------------------------------------------------------- |
| `collection_id`   | string      | Yes         | The collection ID to execute transfers on                                                    |
| `transfers`       | Transfer\[] | Yes         | Array of transfers to execute (same format as `MsgTransferTokens`)                           |
| `fail_on_error`   | bool        | Yes         | If `true`, fail the entire IBC transfer on error. If `false`, fall back to `recover_address` |
| `recover_address` | string      | Conditional | Required if `fail_on_error` is `false`. Address to send IBC tokens to on failure             |

### Transfer Object

The `transfers` array uses the same structure as [MsgTransferTokens](/token-standard/messages/msg-transfer-tokens) transfers, but with snake\_case JSON keys:

| Field                                         | Type                         | Description                                |
| --------------------------------------------- | ---------------------------- | ------------------------------------------ |
| `from`                                        | string                       | Sender address (use `"Mint"` for minting)  |
| `to_addresses`                                | string\[]                    | Recipient addresses                        |
| `balances`                                    | Balance\[]                   | Token balances to transfer                 |
| `prioritized_approvals`                       | ApprovalIdentifierDetails\[] | Priority approval identifiers              |
| `merkle_proofs`                               | MerkleProof\[]               | Merkle challenge solutions (if applicable) |
| `eth_signature_proofs`                        | ETHSignatureProof\[]         | ETH signature proofs (if applicable)       |
| `memo`                                        | string                       | Transfer memo                              |
| `only_check_prioritized_collection_approvals` | bool                         | Only check collection-level approvals      |
| `only_check_prioritized_incoming_approvals`   | bool                         | Only check incoming approvals              |
| `only_check_prioritized_outgoing_approvals`   | bool                         | Only check outgoing approvals              |

## Error Handling

### fail\_on\_error: true (Default)

If the token transfer fails, the entire IBC transfer is rolled back. The sender receives their tokens back on the source chain via the standard IBC error acknowledgement flow.

### fail\_on\_error: false (With Recovery)

If the token transfer fails, the IBC tokens are sent to the `recover_address` instead. The IBC transfer itself succeeds (success acknowledgement), but the token transfer does not execute. This is useful when you don't want the sender to have to recover tokens on the source chain.

```json
{
  "transfer_tokens": {
    "collection_id": "123",
    "transfers": [{ "..." }],
    "fail_on_error": false,
    "recover_address": "bb1recoveryaddress..."
  }
}
```

## Intermediate Sender (Transaction Creator)

The `creator` of the `MsgTransferTokens` is **not** the original sender on the source chain. Instead, the module derives a deterministic **intermediate sender** address from the IBC channel and original sender. This is important because:

* The intermediate sender is the `creator` of the executed `MsgTransferTokens`
* It receives the IBC tokens on BitBadges
* It is automatically granted auto-approval flags on the collection
* **Your collection approvals must authorize this address** — for example, if you want cross-chain minting, your mint approval's `initiatedByList` must include the intermediate sender (or use a wildcard list)

You can derive this address ahead of time to configure your approvals:

```typescript
import { deriveIntermediateSender } from 'bitbadges';

// Derive the intermediate sender for a given channel + source address
const creator = deriveIntermediateSender('channel-0', 'osmo1...', 'bb');
// Use this address in your collection's approval initiatedByList
```

## Using Minimal-Value Triggers

The `transfer_tokens` hook rides on standard IBC / ICS-20 transfer rails. There is **no restriction on what denomination or amount is transferred** — the hook simply needs an IBC transfer to carry the memo. This means you can use a negligible amount of any ICS-20 asset as a pure trigger:

```json
// IBC MsgTransfer fields:
// token: { denom: "ubadge", amount: "1" }   ← 0.000001 BADGE, effectively free
// memo: { "transfer_tokens": { ... } }
```

The IBC token being transferred does not need to relate to the token transfer being executed. For example:

* Send **1 ubadge** (\~zero value) to trigger a mint of an NFT badge
* Send **1 uatom** to trigger a badge distribution
* Send **any ICS-20 asset** available on the source chain

This makes the hook useful as a **cross-chain trigger mechanism** where the IBC transfer itself is just a vehicle for the memo, not a meaningful value transfer. The actual business logic lives entirely in the `transfers` array and the collection's approval rules.

> **Tip:** If you're building a system where the IBC transfer is purely a trigger, consider using the cheapest available denomination on the source chain. The transferred tokens will end up with the intermediate sender (or `recover_address` on failure).

## Mutual Exclusivity

An IBC transfer memo cannot contain both `swap_and_action` and `transfer_tokens` hooks. Only one hook type is allowed per transfer. If both are present, the transfer will fail.

## Atomicity

All operations execute atomically using a cached context:

* If the IBC transfer succeeds but the token transfer fails (and `fail_on_error` is `true`), everything is rolled back
* State changes are only committed if all operations succeed
* When `fail_on_error` is `false`, the fallback to `recover_address` is also atomic

## Use Cases

* **Cross-chain minting**: Send tokens from another chain to mint badges on BitBadges
* **Cross-chain purchases**: Pay with IBC tokens and receive badges atomically
* **Cross-chain airdrops**: Distribute badges triggered by IBC transfers
* **Bridge-and-transfer**: Receive IBC tokens and redistribute BitBadges tokens in one step

## Limitations

* **Memo size**: Maximum 64KB for the IBC transfer memo (DoS prevention)
* **Native assets**: x/tokenization assets cannot be IBC transferred — only x/bank assets can trigger this hook
* **Collection must exist**: The target collection must already exist on BitBadges
* **Approval rules apply**: Standard collection approval rules apply to the transfer — the intermediate sender must be authorized

## See Also

* [MsgTransferTokens](/token-standard/messages/msg-transfer-tokens) — Transfer message reference
* [Swap and Action Hook](/other-modules/x-custom-ibc-hooks/overview) — The other IBC hook type
* [IBC Backed Minting](/token-standard/learn/ibc-backed-minting) — Minting tokens backed by IBC assets


# Collection Configuration

There are plenty of fields to define the core configuration for your collection during creation. These are all stored on the collection object controlled by the manager. These fields control metadata, standards, valid token IDs, custom data, and immutable invariants.

Most fields are updatable over time, according to their corresponding permissions set in the collection permissions object (e.g. canUpdateValidTokenIds, canUpdateCollectionMetadata, etc). However, some fields are immutable and cannot be changed after creation (invariants).

## Complete Example

```typescript
import { MsgCreateCollection } from 'bitbadges';

const msg: MsgCreateCollection = {
    creator: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    defaultBalances: {
        balances: [],
        outgoingApprovals: [],
        incomingApprovals: [],
        autoApproveSelfInitiatedOutgoingTransfers: false,
        autoApproveSelfInitiatedIncomingTransfers: true,
        autoApproveAllIncomingTransfers: false,
        userPermissions: {
            // ... permission fields
        },
    },
    validTokenIds: [{ start: 1n, end: 100n }],
    collectionPermissions: {
        // ... permission fields
    },
    manager: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    collectionMetadata: {
        uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub',
        customData: '',
    },
    tokenMetadata: [
        {
            uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub/{id}',
            tokenIds: [{ start: 1n, end: 100n }],
            customData: '',
        },
    ],
    customData: 'Application-specific data',
    collectionApprovals: [
        // ... approval fields
    ],
    standards: ['Tradable', 'NFT'],
    isArchived: false,
    invariants: {
        noCustomOwnershipTimes: false,
        maxSupplyPerId: '0',
        cosmosCoinBackedPath: undefined,
        noForcefulPostMintTransfers: false,
        disablePoolCreation: false,
    },
    // ... other fields
};
```

## Valid Token IDs

`validTokenIds` defines the range of token IDs that exist within a collection. This is mainly informational but may also be used to enforce certain rules like canOverrideWithAnyValidTokenId.

Must be sequential from 1 to the total supply of the collection.

```typescript
const validTokenIds: UintRange<bigint>[] = [{ start: 1n, end: 100n }];
```

**Key points:**

* Set during collection creation or updated via `MsgUpdateCollection` (not invariants — see below)
* Controlled by `canUpdateValidTokenIds` permission
* Strongly recommended to be set at genesis and locked forever. Expanding / reducing valid token IDs is not recommended in most cases and advanced.

## Standards

`standards` provides informational tags that guide how to interpret and implement collection features. Standards are purely informational—no blockchain validation.

```typescript
const standards: string[] = ['Tradable', 'NFT', 'Cosmos Wrappable'];
```

**BitBadges Site Standards:**

* **Tradable**: Enables trading interface, orderbook tracking, and marketplace features
* **NFT**: Expects supply = 1 per token ID with full ownership times
* **Cosmos Wrappable**: Can be wrapped into Cosmos SDK coin denominations
* **Subscriptions**: Designed for recurring content delivery and subscription systems
* **Quests**: Achievement-based systems and quest completion tracking

For more information on compatibility with the BitBadges site, please reach out.

**Important:** Standards are informational only. Applications must verify compliance.

## Collection Metadata

`collectionMetadata` defines metadata for the entire collection.

```typescript
const collectionMetadata: CollectionMetadata = {
    uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub',
    customData: '',
};
```

**Metadata Format:** The BitBadges API expects metadata to follow this interface:

```typescript
interface Metadata {
    name: string;
    description: string;
    image: string;
}
```

**Permission Control:** Updates controlled by `canUpdateCollectionMetadata` permission.

## Token Metadata

`tokenMetadata` defines metadata for individual tokens. Uses first-match approach via linear scan for specific token IDs. We support the dynamic token ID replacement feature {id} in the URI.

```typescript
const tokenMetadata: TokenMetadata[] = [
    {
        uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub/{id}',
        tokenIds: [{ start: 1n, end: 100n }],
        customData: '',
    },
];
```

**Key Features:**

* **Dynamic Token ID Replacement**: URI `{id}` placeholder is replaced with actual token ID
* **First-Match Logic**: Entries evaluated in order; first matching entry for a token ID is used, subsequent entries are ignored.
* **Permission Control**: Updates controlled by `canUpdateTokenMetadata` permission

## Custom Data

`customData` provides generic string storage for application-specific information. It is not interpreted by the chain itself—use it for any application-specific implementations or contract integrations. You may also see `customData` used elsewhere like in approvals, address lists, and other structures.

```typescript
const customData: string = 'Any string value you want to store';
```

**Where Custom Data Appears:**

* Collection-level: `customData`
* Token metadata: `customData` within token metadata structures
* Address lists: `customData` for list-specific information
* Messages: Various transaction messages include custom data fields

**Permission Control:** Updates controlled by `canUpdateCustomData` permission.

### Inline metadata via `customData`

Wherever a metadata-bearing entity exposes a `(uri, customData)` pair (collection metadata, token metadata, address lists, dynamic stores, paths, and approvals), `customData` can also hold an inline JSON metadata document with the same shape that a hosted metadata file would have:

```typescript
const collectionMetadata: CollectionMetadata = {
    uri: '',
    customData: JSON.stringify({
        name: 'My Collection',
        image: 'ipfs://Qm.../image.png',
        description: 'A short description.',
    }),
};
```

The indexer, SDK, and frontend resolve metadata with `uri` taking priority: if `uri` is non-empty it is fetched as today; otherwise `customData` is parsed as inline JSON and surfaced as the resolved metadata. This is a zero-hosting alternative to IPFS-hosted JSON—no Pinata account, no pin to maintain. Approval metadata uses `name` + `description` only (no `image`); every other entity expects `name` + `image` + `description`.

`customData` is still a free-form string. Anything that does not parse as a JSON object with at least one of `name`, `image`, or `description` is ignored as metadata and the entity falls through to "no metadata" rather than rendering attacker-controlled fields.

#### Cost considerations: keep images off-chain

Inline `customData` is stored on-chain. **You pay gas proportional to the byte size, every block has a hard size cap, and the bytes live in chain state forever.** This is fine for short text fields and a tiny URL pointing at an image, but it is the wrong place for large payloads.

Strong recommendation: **do not store image bytes (or any base64-encoded media) inline in `customData`.** Even a small JPEG is tens of KB and a PNG can easily run into hundreds of KB or MB — orders of magnitude more expensive than a text-only payload, and large enough to start fighting block-size limits during bulk updates. Pre-host images on IPFS (or any other URL host) and reference them by URL inside the inline JSON:

```typescript
const collectionMetadata: CollectionMetadata = {
    uri: '',
    customData: JSON.stringify({
        name: 'My Collection',
        image: 'ipfs://Qm.../image.png', // <-- URL only, NOT base64
        description: 'A short description.',
    }),
};
```

Rule of thumb:

* **Inline customData is great for**: name, description, attributes, links, small structured fields — the metadata wrapper.
* **Inline customData is the wrong place for**: image bytes, video, audio, anything binary, anything more than a few KB.
* If your full metadata JSON (after stringification) is more than \~4 KB, prefer hosting the JSON externally and using `uri` instead.

It is a tradeoff. Inline customData buys you zero hosting setup and no IPFS pin to maintain; remote hosting buys you cheap storage of large assets. Pick per entity, not per collection.

#### Optional: deterministic SVG placeholder art (zero-hosting image)

If you want zero hosting **and** an image, the SDK ships a deterministic SVG generator that produces 1-8 KB `data:image/svg+xml;base64,...` URIs you can drop directly into the `image` field of inline customData:

```typescript
import { generatePlaceholderArt } from 'bitbadges';

const art = generatePlaceholderArt({ seed: 'My Collection' });
const customData = JSON.stringify({
    name: 'My Collection',
    description: '...',
    image: art.imageUri, // data:image/svg+xml;base64,...
});
// On-chain: collectionMetadata.uri = '', collectionMetadata.customData = customData
```

Same seed always produces the same art (six curated presets × 24 palettes, hash-picked deterministically). Pin a specific look with `style` / `paletteName` if needed.

**This is not free.** The SVG bytes still live on-chain — you're shifting the cost from a hosting fee to chain gas, not eliminating it. Concretely:

* Inline SVG: 1-8 KB on-chain → roughly 10-80k extra gas per write.
* Inline customData with hosted image URL (`ipfs://...`): \~250 B on-chain wrapper, plus an IPFS pin you maintain. Cheapest on-chain when you have an image.
* Full URI mode (`uri` set, `customData` empty): \~50 B on-chain (just the URL), plus an IPFS pin for JSON + image. Cheapest on-chain overall, but two pinning concerns.

Pick the SVG generator when "no hosting setup at all" is worth the extra \~80k gas per write. For high-frequency mints, image-heavy collections, or anything where the art *is* the product, prefer a hosted image URL.

## Default Balances

`defaultBalances` are predefined balance stores automatically assigned to new users (uninitialized balance stores) when they first interact with a collection. Set during collection creation only—cannot be updated after genesis.

This is not often used but can be useful for certain use cases:

* Blocking incoming transfers by default (enforcing opt-in only restrictions)
* Default starting balances for all users
* Default approval settings for all users

```typescript
const defaultBalances: UserBalanceStore<bigint> = {
    balances: [],
    outgoingApprovals: [],
    incomingApprovals: [],
    autoApproveSelfInitiatedOutgoingTransfers: false,
    autoApproveSelfInitiatedIncomingTransfers: true,
    autoApproveAllIncomingTransfers: false,
    userPermissions: {
        // ... permission fields
    },
};
```

**Important Limitations:**

* **No complex approval criteria**: Cannot include any approvals not compatible with auto-scan mode. See [Auto-Scan and Prioritized Approvals](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/learn/auto-scan-and-prioritized-approvals.md) for more information.

**Key points:**

* Creation-only field—set once during collection creation
* Establishes baseline behavior for all new users
* Users can customize their own approval settings after initialization

## IsArchived

`isArchived` controls whether a collection is archived, temporarily or permanently disabling all transactions while keeping collection data verifiable and public on-chain.

This is a collection-level option that can be used to temporarily or permanently disable all transactions until unarchived. Controlled by the manager. Useful for pausing, halts, or other maintenance operations.

Note that there are also other ways to implement halting logic which may be better suited for your use case.

* Chain-level x/circuit breakers
* Dynamic stores / token ownership requirements that can be controlled by another entity

```typescript
const isArchived: boolean = false;
```

**Transaction Behavior:**

* **When archived**: All transactions fail (no updates, transfers, or changes allowed). Read operations continue. Only unarchiving transactions can succeed.
* **When unarchived**: Normal operations resume. All collection data remains intact. Standard permission checks apply.

**Permission Control:** Updates controlled by `canArchiveCollection` permission. Note that this permission controls the updatability of the `isArchived` field, not the current archive status. You can lock the archive status forever (either `true` or `false`) by permanently forbidding updates.

## Invariants

> **Important: Invariants are creation-only.** Once a collection is created, its invariants cannot be modified or removed. Do not include invariants in update transactions — they will be ignored or may cause unexpected behavior. Set invariants only when creating a new collection (`collectionId = "0"` in `MsgUniversalUpdateCollection`, or via `MsgCreateCollection`).

`invariants` are immutable rules set upon collection creation that cannot be broken or modified afterward. They enforce fundamental constraints on collection behavior.

```typescript
const invariants: CollectionInvariants<bigint> = {
    noCustomOwnershipTimes: false,
    maxSupplyPerId: '0',
    cosmosCoinBackedPath: undefined,
    noForcefulPostMintTransfers: false,
    disablePoolCreation: false,
    evmQueryChallenges: [],
};
```

### Available Invariants

**noCustomOwnershipTimes**

* Enforces all ownership times must be full ranges `[{ start: 1n, end: 18446744073709551615n }]` for all time-dependent structures - balances, approvals, etc.
* Prevents time-based restrictions on token ownership
* Affects collection approvals, user approvals, and transfer balances
* Typically recommended to be set to true if you do not need such functionality.

**maxSupplyPerId**

* Maximum supply per token ID (string-based uint64)
* Prevents any balance amount from exceeding the specified maximum
* Zero value means no enforcement
* Sanity check to enforce a minting cap. Should NOT replace proper approval design.

**cosmosCoinBackedPath**

* IBC backed (sdk.coin) path for the collection
* Only one path allowed per collection
* See [IBC Backed Minting Invariants](/token-standard/learn/ibc-backed-minting) for details

**noForcefulPostMintTransfers**

* Disallows collection approvals with `overridesFromOutgoingApprovals` or `overridesToIncomingApprovals` set to `true` (excluding the Mint address which is exempt)
* Prevents forceful transfers that bypass user-level approvals
* Easy way for you to enforce that there will never be any forceful transfers that bypass user-level approvals ever in your collection.

**disablePoolCreation**

* Prevents creation of liquidity pools using assets from this collection in our gamm module.
* When enabled, pool creation attempts will fail

**evmQueryChallenges**

* Post-transfer EVM query invariants that are checked after all balance updates
* Each challenge executes a read-only EVM contract query via staticcall
* If any query returns a result that doesn't match the expected result, the transfer is reverted
* Useful for enforcing global invariants like maximum holder count, balance caps, or custom compliance rules
* Placeholders: `$collectionId`, `$sender`, `$initiator`, `$recipient` (first recipient), `$recipients` (all recipients as concatenated 32-byte hex). See [EVM Query Challenges – Placeholders](/token-standard/learn/approval-criteria/evm-query-challenges#placeholders) for approval vs invariant placeholder details.
* See [EVM Query Challenges](/token-standard/learn/approval-criteria/evm-query-challenges) for the full challenge structure (fields, operators, gas limits)

```typescript
const evmQueryChallenges: EVMQueryChallenge<bigint>[] = [
    {
        contractAddress: '0xComplianceContract...',
        calldata: '70a08231000000000000000000000000$collectionId',
        expectedResult: '0000000000000000000000000000000000000000000000000000000000000001',
        comparisonOperator: 'eq',
        gasLimit: '100000',
        uri: '',
        customData: '',
    },
];
```

**Important:** Invariants can only be set during collection creation and cannot be updated or removed afterward.


# Address Lists

Address lists define collections of addresses for use in approval configurations (`fromListId`, `toListId`, `initiatedByListId`). They support reserved IDs, inline colon-separated lists, and user-created stored lists.

## Proto Definition

```protobuf
message AddressList {
  string listId = 1;
  repeated string addresses = 2;
  bool whitelist = 3;  // true = whitelist, false = blacklist
  string uri = 4;
  string customData = 5;
  string createdBy = 6;
}
```

## Matching Logic

```javascript
function checkAddress(address, addressList) {
    const found = addressList.addresses.includes(address);
    return addressList.whitelist ? found : !found;
}
```

* **Whitelist** (`whitelist: true`): Only listed addresses are included
* **Blacklist** (`whitelist: false`): All addresses except listed ones are included

## Reserved IDs

Dynamically generated, zero storage overhead.

| ID                    | Logic                                               | Description                            |
| --------------------- | --------------------------------------------------- | -------------------------------------- |
| `"Mint"`              | `{addresses: ["Mint"], whitelist: true}`            | Only Mint address                      |
| `"All"`               | `{addresses: [], whitelist: false}`                 | All addresses (including Mint)         |
| `"None"`              | `{addresses: [], whitelist: true}`                  | No addresses                           |
| `"AllWithout<addrs>"` | `{addresses: [addrs...], whitelist: false}`         | All except specified (colon-separated) |
| `"addr1:addr2:..."`   | `{addresses: [addr1, addr2, ...], whitelist: true}` | Inline whitelist                       |

## Mint Address Handling

The set of valid addresses includes any valid `bb1` address **and** `"Mint"` (a reserved address representing the collection's mint address).

**Important**: `"All"` includes the Mint address. When creating approvals, especially `fromListId`, be careful with Mint handling since the Mint address has unlimited balances.

```json
// Exclude Mint from senders (common pattern)
{
    "fromListId": "AllWithoutMint",  // All addresses except Mint
    "toListId": "All"
}

// Allow only Mint to send (minting only)
{
    "fromListId": "Mint",
    "toListId": "All"
}
```

## Inversion

Prefix with `!` to invert whitelist/blacklist behavior. **Only works with reserved IDs and inline lists, not user-created lists.** Supports parentheses too for syntax `!(...)`.

```javascript
'!All'; // Inverts All → None behavior
'!bb1alice...:bb1bob...'; // Inverts inline list → blacklist
'!(AllWithoutMint)'; // Inverts AllWithoutMint → whitelist with only Mint
```

Formats: `"!listId"` (if listId doesn't end with `)`) or `"!(listId)"`.

## User-Created Lists

Created via `MsgCreateAddressLists`. **Immutable** once created. Referenced by the custom listId where needed. This approach is useful for large lists that are referenced multiple times to save on gas.

### ID Validation

* Alphanumeric only (`a-z`, `A-Z`, `0-9`)
* Cannot be empty
* Cannot start with `!`
* Cannot conflict with reserved IDs (`"All"`, `"Mint"`, `"None"`, `"AllWithout*"`)
* Must be unique

### Example

```json
{
    "listId": "vipMembers",
    "addresses": ["bb1alice...", "bb1bob...", "bb1charlie..."],
    "whitelist": true,
    "uri": "",
    "customData": "",
    "createdBy": "bb1manager..."
}
```

## Usage Examples

```json
// Universal access
{
    "fromListId": "AllWithoutMint",
    "toListId": "All",
    "initiatedByListId": "All"
}

// Restricted with inline list
{
    "fromListId": "bb1alice...:bb1bob...:bb1charlie...",
    "toListId": "AllWithoutMint:bb1blocked...",
    "initiatedByListId": "All"
}

// User-created list
{
    "fromListId": "vipMembers",
    "toListId": "!bb1banned...",  // Inversion works with inline lists
    "initiatedByListId": "All"
}
```

## Performance

* **Reserved IDs**: Zero storage, dynamic generation, minimal gas
* **Inline lists**: Zero storage, dynamic parsing, efficient for < 10 addresses
* **User-created**: On-chain storage, reusable short ID, efficient lookups, best for large/repeated lists


# UintRanges

The `UintRange` is the fundamental data structure used throughout the tokens module to represent inclusive ranges of unsigned integers efficiently. This type enables powerful range-based operations and is primarily used for token IDs, time ranges, and amounts.

## Proto Definition

```protobuf
message UintRange {
  string start = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
  string end = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
}
```

## Usage Patterns

UintRanges are used to represent:

* **Token ID ranges**: `[1-100]` represents token IDs 1 through 100 (inclusive)
* **Time ranges**: `[1640995200000-1672531200000]` represents a year in UNIX milliseconds
* **Amount ranges**: `[1-5]` represents quantities from 1 to 5
* **Ownership time ranges**: When tokens are valid for ownership

## Restrictions & Valid Values

Unless otherwise specified, we only allow numbers in the ranges to be from **1 to Go Max UInt64**:

* **Valid range**: 1 to 18446744073709551615 (Go's `math.MaxUint64`)
* **Zero and negative values**: Not allowed
* **Values greater than maximum**: Not allowed

## Validation Rules

* `start` must be ≤ `end`
* Ranges in the same array cannot overlap
* Zero amounts are not allowed in balance ranges
* All values must be within the valid range (1 to MaxUint64)

## Special Cases

### Full Range

To represent a complete range covering all possible values:

```protobuf
// Full range from 1 to maximum
{
  start: "1",
  end: "18446744073709551615"
}
```

### Single Value

To represent a single value, use the same value for start and end:

```protobuf
// Single token ID 5
{
  start: "5",
  end: "5"
}
```

### Range Inversion

Inverting a range results in all values from 1 to 18446744073709551615 that are **not** in the current range. This is useful for exclusion logic.

## Examples

### Token ID Examples

```typescript
// Token IDs 1-10 (inclusive)
const badgeRange: UintRange[] = [{ start: '1', end: '10' }];

// Multiple non-overlapping ranges
const multipleBadges: UintRange[] = [
    { start: '1', end: '10' },
    { start: '20', end: '50' },
];
```

### Go Code Examples

```go
// Token IDs 1-10
tokenIdRange := UintRange{Start: NewUint(1), End: NewUint(10)}

// Unlimited amount
unlimitedAmount := UintRange{Start: NewUint(1), End: MaxUint}

// Single token ID
singleBadge := UintRange{Start: NewUint(5), End: NewUint(5)}
```

## Efficiency Benefits

* **Compact representation**: Ranges avoid storing individual values
* **Range operations**: Efficient intersection, union, and containment checks
* **Gas optimization**: Reduces transaction size and computational costs
* **Scalability**: Handles large ranges without performance degradation


# Messages

This directory contains detailed documentation for all message types supported by the tokens module.

## Message Categories

### Collection Management

* [MsgCreateCollection](/token-standard/messages/msg-create-collection) - Create new collection
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Update existing collection properties
* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Universal create/update interface with invariants support
* [MsgDeleteCollection](/token-standard/messages/msg-delete-collection) - Archive/delete collection

### Helper Collection Update Messages

* [MsgSetValidTokenIds](/token-standard/messages/msg-set-valid-token-ids) - Update valid token IDs and permissions
* [MsgSetManager](/token-standard/messages/msg-set-manager) - Update manager and permissions
* [MsgSetCollectionMetadata](/token-standard/messages/msg-set-collection-metadata) - Update collection metadata and permissions
* [MsgSetTokenMetadata](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/messages/msg-set-badge-metadata.md) - Update token metadata and permissions
* [MsgSetCustomData](/token-standard/messages/msg-set-custom-data) - Update custom data and permissions
* [MsgSetStandards](/token-standard/messages/msg-set-standards) - Update standards and permissions
* [MsgSetCollectionApprovals](/token-standard/messages/msg-set-collection-approvals) - Update collection approvals and permissions
* [MsgSetIsArchived](/token-standard/messages/msg-set-is-archived) - Update isArchived status and permissions

### Token Transfers

* [MsgTransferTokens](/token-standard/messages/msg-transfer-tokens) - Transfer tokens between addresses with approval validation

### User Approval Management

* [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) - Update user transfer approval settings
* [MsgSetIncomingApproval](/token-standard/messages/msg-set-incoming-approval) - Set a single incoming approval (helper)
* [MsgDeleteIncomingApproval](/token-standard/messages/msg-delete-incoming-approval) - Delete a single incoming approval (helper)
* [MsgSetOutgoingApproval](/token-standard/messages/msg-set-outgoing-approval) - Set a single outgoing approval (helper)
* [MsgDeleteOutgoingApproval](/token-standard/messages/msg-delete-outgoing-approval) - Delete a single outgoing approval (helper)
* [MsgPurgeApprovals](/token-standard/messages/msg-purge-approvals) - Purge expired approvals (helper)
* [MsgCastVote](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/messages/msg-cast-vote.md) - Cast or update a vote for a voting challenge in approval criteria

### Address List Management

* [MsgCreateAddressLists](/token-standard/messages/msg-create-address-lists) - Create reusable address lists for access control

### Dynamic Store Management

* [MsgCreateDynamicStore](/token-standard/messages/msg-create-dynamic-store) - Create boolean stores for approval criteria
* [MsgUpdateDynamicStore](/token-standard/messages/msg-update-dynamic-store) - Update dynamic store configuration
* [MsgDeleteDynamicStore](/token-standard/messages/msg-delete-dynamic-store) - Delete dynamic store
* [MsgSetDynamicStoreValue](/token-standard/messages/msg-set-dynamic-store-value) - Set boolean values for addresses in dynamic store

## Additional Message Types

The following message types exist in the protocol but may be documented separately:

* **MsgUpdateParams** - Update module parameters via governance


# MsgCreateAddressLists

Creates reusable address lists by ID for gas optimizations. Note these serve no other purpose than to be an immutable shorthand ID reference to save on gas. These are permanent once created and cannot be deleted or edited.

## Important Notes

1. **Create Only**: There are no update, edit, or delete functions for address lists. Once created, they are immutable.
2. **Optional Efficiency Tool**: This is completely optional and serves as a reusable shorthand ID to avoid repetition of long reserved address list IDs. The primary purpose is gas efficiency.
3. **Minimal Metadata**: Typically, `uri` and `customData` are left blank as these fields are not supported on the BitBadges site and are different from off-chain lists you may see elsewhere.

## Proto Definition

```protobuf
message MsgCreateAddressLists {
  string creator = 1; // Address creating the address lists
  repeated AddressList addressLists = 2; // Lists to create in single transaction
}

message MsgCreateAddressListsResponse {}
```

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization create-address-lists '[tx-json]' --from creator-key
```

### JSON Example

```json
{
    "creator": "bb1...",
    "addressLists": [
        {
            "listId": "", // Unique ID for the address list
            "addresses": ["bb1...", "bb1..."],
            "whitelist": true,
            "uri": "",
            "customData": ""
        }
    ]
}
```


# MsgCreateCollection

Creates a new collection.

The collectionId will be assigned at execution time and is obtainable in the transaction response. Subsequent updates to the collection will be through MsgUpdateCollection.

## Creation Only Properties

The creation or genesis transaction for a collection is unique in a couple ways.

There are no permissions previously set, so there are no restrictions for what can be set vs not. Subsequent updates to the collection must follow any previously set permissions.

This is the only time that you can specify the `defaultBalances` information.

## Proto Definition

```protobuf
message MsgCreateCollection {
  string creator = 1; // Address creating the collection
  UserBalanceStore defaultBalances = 2;
  repeated UintRange validTokenIds  = 3; // Token ID ranges to include
  CollectionPermissions collectionPermissions = 4;
  string manager = 5;
  CollectionMetadata collectionMetadata = 6;
  repeated TokenMetadata tokenMetadata = 7;
  string customData = 8;
  repeated CollectionApproval collectionApprovals = 9;
  repeated string standards = 10;
  bool isArchived = 11;
  repeated cosmos.base.v1beta1.Coin mintEscrowCoinsToTransfer = 12;
  repeated CosmosCoinWrapperPathAddObject cosmosCoinWrapperPathsToAdd = 13;
  repeated AliasPathAddObject aliasPathsToAdd = 14; // NEW: Separate array for alias paths
  CollectionInvariants invariants = 15;
}

message MsgCreateCollectionResponse {
  string collectionId = 1; // ID of the created collection
  repeated ApprovalChange approvalChanges = 2; // Details of each approval created
  repeated string reviewItems = 3; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`collectionId`**: The ID of the newly created collection
* **`approvalChanges`**: A list of `ApprovalChange` entries describing each approval that was created during collection setup (see [Approval Change Events](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md))
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization create-collection '[tx-json]' --from creator-key
```

### JSON Example

For complete transaction examples, see [MsgCreateCollection Examples](/token-standard/examples/txs/msgcreatecollection).

```json
{
    "creator": "bb1...",
    "defaultBalances": {
        "balances": [],
        "outgoingApprovals": [],
        "incomingApprovals": [],
        "autoApproveSelfInitiatedOutgoingTransfers": false,
        "autoApproveSelfInitiatedIncomingTransfers": true,
        "autoApproveAllIncomingTransfers": false,
        "userPermissions": {
            "canUpdateOutgoingApprovals": [],
            "canUpdateIncomingApprovals": [],
            "canUpdateAutoApproveSelfInitiatedOutgoingTransfers": [],
            "canUpdateAutoApproveSelfInitiatedIncomingTransfers": [],
            "canUpdateAutoApproveAllIncomingTransfers": []
        }
    },
    "validTokenIds": [{ "start": "1", "end": "100" }],
    "collectionPermissions": {
        "canDeleteCollection": [],
        "canArchiveCollection": [],
        "canUpdateStandards": [],
        "canUpdateCustomData": [],
        "canUpdateManager": [],
        "canUpdateCollectionMetadata": [],
        "canUpdateValidTokenIds": [],
        "canUpdateTokenMetadata": [],
        "canUpdateCollectionApprovals": [],
        "canAddMoreAliasPaths": [],
        "canAddMoreCosmosCoinWrapperPaths": []
    },
    "manager": "",
    "collectionMetadata": {},
    "tokenMetadata": [],
    "customData": "",
    "collectionApprovals": [],
    "standards": [],
    "isArchived": false,
    "mintEscrowCoinsToTransfer": [],
    "cosmosCoinWrapperPathsToAdd": [],
    "aliasPathsToAdd": [],
    "invariants": {
        "noCustomOwnershipTimes": false,
        "maxSupplyPerId": "0",
        "cosmosCoinBackedPath": undefined,
        "noForcefulPostMintTransfers": false,
        "disablePoolCreation": false,
        "evmQueryChallenges": []
    }
}
```


# MsgCreateDynamicStore

Creates a new dynamic store for boolean key-value storage. New stores are created with `globalEnabled = true` by default, meaning the store is active and can be used in approval criteria.

## Proto Definition

```protobuf
message MsgCreateDynamicStore {
  string creator = 1; // Address creating the dynamic store
  bool defaultValue = 2; // Default boolean value for uninitialized addresses
  string uri = 3; // Optional: URI for additional metadata or resources
  string customData = 4; // Optional: Custom data field for arbitrary data
}

message MsgCreateDynamicStoreResponse {
  string storeId = 1; // ID of the created dynamic store
  repeated string reviewItems = 2; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`storeId`**: The ID of the newly created dynamic store
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization create-dynamic-store [true|false] --from creator-key
```

### JSON Examples

**Basic example (without metadata):**

```json
{
    "creator": "bb1...",
    "defaultValue": false
}
```

**With optional metadata fields:**

```json
{
    "creator": "bb1...",
    "defaultValue": false,
    "uri": "https://example.com/store-metadata",
    "customData": "{\"description\": \"Member store\", \"version\": \"1.0\"}"
}
```

## Metadata Fields

Both `uri` and `customData` are **optional** fields:

* **`uri`**: URI for additional metadata or resources associated with this dynamic store. Typically used for URLs pointing to hosted JSON metadata or documentation. No validation is performed on the URI format.
* **`customData`**: Custom data field for storing arbitrary string data. Commonly used to store JSON-encoded structured data, but can contain any string value. No validation is performed on the content.


# MsgDeleteCollection

Deletes a collection.

## Authorization

Collection deletion can only be performed by the **current manager** of the collection and requires the `canDeleteCollection` permission to be enabled at the current time in the collection's permissions.

## Proto Definition

```protobuf
message MsgDeleteCollection {
  string creator = 1; // Address requesting deletion (must be manager)
  string collectionId = 2; // ID of collection to delete
}

message MsgDeleteCollectionResponse {}
```

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization delete-collection [collection-id] --from manager-key
```

### JSON Example

```json
{
    "creator": "bb1...",
    "collectionId": "1"
}
```


# MsgDeleteDynamicStore

Deletes a dynamic store.

## Proto Definition

```protobuf
message MsgDeleteDynamicStore {
  string creator = 1; // Address deleting the store (must be creator)
  string storeId = 2; // ID of dynamic store to delete
}

message MsgDeleteDynamicStoreResponse {}
```

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization delete-dynamic-store [store-id] --from creator-key
```

### JSON Example

```json
{
    "creator": "bb1...",
    "storeId": "1"
}
```


# MsgDeleteIncomingApproval

A helper message to delete a single incoming approval for token transfers. This is a developer-friendly wrapper around `MsgUpdateUserApprovals` that simplifies deleting individual incoming approvals. For more information, we refer to the [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) documentation.

## Overview

This message allows you to delete a single incoming approval by its ID without having to construct the full `MsgUpdateUserApprovals` message with an empty approval list.

## Proto Definition

```protobuf
message MsgDeleteIncomingApproval {
  string creator = 1; // User deleting the approval
  string collectionId = 2; // Target collection for approval
  string approvalId = 3; // The ID of the approval to delete
}

message MsgDeleteIncomingApprovalResponse {
  bool found = 1; // Whether the approval was found and deleted
  string version = 2; // The version of the deleted approval
  repeated string reviewItems = 3; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`found`**: Whether the approval was found and successfully deleted
* **`version`**: The version of the approval that was deleted
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization delete-incoming-approval [collection-id] [approval-id] --from user-key
```

### Example

```bash
bitbadgeschaind tx tokenization delete-incoming-approval 1 "my-approval-1" --from user-key
```

## Behavior

* **Approval Lookup**: The system searches for an incoming approval with the specified `approvalId`
* **Deletion**: If found, the approval is removed from the user's incoming approvals list
* **Error Handling**: If the approval ID is not found, an error is returned
* **Validation**: The deletion is validated according to the collection's permissions and user's approval update permissions

## Authorization & Permissions

Users can only delete their own incoming approvals. The operation must be performed according to the permissions set (i.e. the `userPermissions` previously set for that user).

## Related Messages

* [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) - Full approval management
* [MsgSetIncomingApproval](/token-standard/messages/msg-set-incoming-approval) - Set an incoming approval
* [MsgDeleteOutgoingApproval](/token-standard/messages/msg-delete-outgoing-approval) - Delete an outgoing approval


# MsgDeleteOutgoingApproval

A helper message to delete a single outgoing approval for token transfers. This is a developer-friendly wrapper around `MsgUpdateUserApprovals` that simplifies deleting individual outgoing approvals. For more information, we refer to the [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) documentation.

## Overview

This message allows you to delete a single outgoing approval by its ID without having to construct the full `MsgUpdateUserApprovals` message with an empty approval list.

## Proto Definition

```protobuf
message MsgDeleteOutgoingApproval {
  string creator = 1; // User deleting the approval
  string collectionId = 2; // Target collection for approval
  string approvalId = 3; // The ID of the approval to delete
}

message MsgDeleteOutgoingApprovalResponse {
  bool found = 1; // Whether the approval was found and deleted
  string version = 2; // The version of the deleted approval
  repeated string reviewItems = 3; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`found`**: Whether the approval was found and successfully deleted
* **`version`**: The version of the approval that was deleted
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization delete-outgoing-approval [collection-id] [approval-id] --from user-key
```

### Example

```bash
bitbadgeschaind tx tokenization delete-outgoing-approval 1 "my-approval-1" --from user-key
```

## Behavior

* **Approval Lookup**: The system searches for an outgoing approval with the specified `approvalId`
* **Deletion**: If found, the approval is removed from the user's outgoing approvals list
* **Error Handling**: If the approval ID is not found, an error is returned
* **Validation**: The deletion is validated according to the collection's permissions and user's approval update permissions

## Authorization & Permissions

Users can only delete their own outgoing approvals. The operation must be performed according to the permissions set (i.e. the `userPermissions` previously set for that user).

## Related Messages

* [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) - Full approval management
* [MsgSetOutgoingApproval](/token-standard/messages/msg-set-outgoing-approval) - Set an outgoing approval
* [MsgDeleteIncomingApproval](/token-standard/messages/msg-delete-incoming-approval) - Delete an incoming approval


# MsgPurgeApprovals

A message to purge specific approvals from approval lists. This is a targeted approach that requires specifying exactly which approvals to purge.

## Overview

This message allows you to purge specific approvals by their identifier.

### Usage 1: Self-Purge (Creator purging their own approvals)

* **`purgeExpired` must be `true`**
* **`purgeCounterpartyApprovals` must be `false`**
* **`approvalsToPurge` must contain the specific approvals to purge**
* Specified approvals will be purged if they are expired (no future transfer times)

### Usage 2: Other-Purge (Creator purging someone else's approvals)

* Can set either `purgeExpired` or `purgeCounterpartyApprovals` (or both)
* **`approvalsToPurge` must contain the specific approvals to purge**
* Purge permissions are determined by the approval's auto-deletion options in `approvalCriteria`:
  * `allowPurgeIfExpired`: Allows others to purge expired approvals
  * `allowCounterpartyPurge`: Allows counterparty to purge if they are the only initiator (initiatedByList must be a whitelist with exactly one address matching the counterparty)
* Specified approvals that match the conditions will be purged

## Fields

* `creator`: The address submitting the transaction.
* `collectionId`: The target collection for approval cleanup.
* `purgeExpired`: Whether to purge expired approvals (must be true for self-purge).
* `approverAddress`: The address whose approvals to purge. If empty, defaults to `creator`.
* `purgeCounterpartyApprovals`: Whether to purge counterparty approvals (must be false for self-purge).
* `approvalsToPurge`: **Required** - An array of approval identifier details specifying exactly which approvals to purge. Cannot be empty.

## ApprovalIdentifierDetails

Each approval to purge must be specified with:

```typescript
interface ApprovalIdentifierDetails {
    approvalId: string; // The ID of the approval
    approvalLevel: string; // "collection", "incoming", or "outgoing"
    approverAddress: string; // Address of the approver (empty for collection-level)
    version: string; // Version of the approval (must match or else we will not purge)
}
```

## Auto-Deletion Options

The following flags in approval criteria control purge permissions in `approvalCriteria`:

* `allowCounterpartyPurge`: Allows the counterparty to purge the approval if they are the ONLY initiator in `initiatedByList` (must be a whitelist with exactly one address matching the counterparty).
* `allowPurgeIfExpired`: Allows others (besides the approval owner) to call `PurgeApprovals` on their behalf for expired approvals.

## Permissions

Although user approval permissions are rarely disabled, we still check these purges obey them. If the user does not have permission to purge their own approval, the purge will fail. With counterparty purges, this can be thought of purging on behalf of the user, so the user's permissions are still checked.

## Example Usage

```bash
# [collectionId, purgeExpired, approverAddress, purgeCounterpartyApprovals, approvalsToPurge]

bitbadgeschaind tx tokenization purge-approvals 1 true "" false '[{"approvalId":"my-approval","approvalLevel":"outgoing","approverAddress":"bb1...","version":"0"}]' --from user-key
```

## Response

The response includes:

* **`purgedApprovalIds`**: The IDs of the approvals that were successfully purged
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Related Messages

* [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) - Full approval management
* [MsgSetIncomingApproval](/token-standard/messages/msg-set-incoming-approval) - Set an incoming approval
* [MsgDeleteIncomingApproval](/token-standard/messages/msg-delete-incoming-approval) - Delete a single incoming approval
* [MsgSetOutgoingApproval](/token-standard/messages/msg-set-outgoing-approval) - Set a single outgoing approval
* [MsgDeleteOutgoingApproval](/token-standard/messages/msg-delete-outgoing-approval) - Delete a single outgoing approval


# MsgSetTokenMetadata


# MsgSetCollectionApprovals

**Disclaimer:**\
This message is a streamlined alternative to [MsgUpdateCollection](/token-standard/messages/msg-update-collection). If you need to update many fields at once, we recommend using MsgUpdateCollection instead.

## MsgSetCollectionApprovals

Sets the collection approvals and update permissions for a collection. This is a convenience message that focuses specifically on collection approvals management.

### Overview

This message allows you to:

* Set collection approvals for the collection
* Configure permissions to update the collection approvals in the future

### Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. The manager must have permission to update collection approvals according to the collection's current permission settings.

### Proto Definition

```protobuf
message MsgSetCollectionApprovals {
  option (cosmos.msg.v1.signer) = "creator";
  option (amino.name) = "tokenization/SetCollectionApprovals";

  // Address of the creator.
  string creator = 1;

  // ID of the collection.
  string collectionId = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];

  // New collection approvals to set.
  repeated CollectionApproval collectionApprovals = 3;

  // Permission to update collection approvals
  repeated CollectionApprovalPermission canUpdateCollectionApprovals = 4;
}

message MsgSetCollectionApprovalsResponse {
  // ID of the collection.
  string collectionId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
  // Details of each approval that was created, edited, or deleted.
  repeated ApprovalChange approvalChanges = 2;
  // Advisory review items about the transaction.
  repeated string reviewItems = 3;
}
```

### Response

The response includes:

* **`collectionId`**: The ID of the updated collection
* **`approvalChanges`**: A list of `ApprovalChange` entries describing each approval that was created, edited, or deleted (see [Approval Change Events](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md))
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

### Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-collection-approvals '[tx-json]' --from manager-key
```

#### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "collectionApprovals": [
        {
            "fromListId": "list1",
            "toListId": "list2",
            "initiatedByListId": "list3",
            "transferTimes": [{ "start": "1000", "end": "2000" }],
            "tokenIds": [{ "start": "1", "end": "10" }],
            "ownershipTimes": [{ "start": "1", "end": "100" }],
            "approvalId": "approval1",
            "approvalCriteria": {
                "mustOwnTokens": [],
                "merkleChallenges": [],
                "ethSignatureChallenges": [],
                "coinTransfers": [],
                "predeterminedBalances": null,
                "approvalAmounts": null,
                "autoDeletionOptions": null,
                "maxNumTransfers": null,
                "dynamicStoreChallenges": []
            }
        }
    ],
    "canUpdateCollectionApprovals": [
        {
            "fromListId": "list1",
            "toListId": "list2",
            "initiatedByListId": "list3",
            "transferTimes": [{ "start": "1000", "end": "2000" }],
            "tokenIds": [{ "start": "1", "end": "10" }],
            "ownershipTimes": [{ "start": "1", "end": "100" }],
            "approvalId": "approval1",
            "permanentlyPermittedTimes": [{ "start": "1000", "end": "2000" }],
            "permanentlyForbiddenTimes": []
        }
    ]
}
```

### Related Messages

* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Full collection update with all fields
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Legacy update message


# MsgSetCollectionMetadata

**Disclaimer:**\
This message is a streamlined alternative to [MsgUpdateCollection](/token-standard/messages/msg-update-collection). If you need to update many fields at once, we recommend using MsgUpdateCollection instead.

## MsgSetCollectionMetadata

Sets the collection metadata and update permissions for a collection. This is a convenience message that focuses specifically on collection metadata management.

### Overview

This message allows you to:

* Set collection metadata for the collection
* Configure permissions to update the collection metadata in the future

### Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. The manager must have permission to update the collection metadata according to the collection's current permission settings.

### Proto Definition

```protobuf
message MsgSetCollectionMetadata {
  option (cosmos.msg.v1.signer) = "creator";
  option (amino.name) = "tokenization/SetCollectionMetadata";

  // Address of the creator.
  string creator = 1;

  // ID of the collection.
  string collectionId = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];

  // New collection metadata to set.
  CollectionMetadata collectionMetadata = 3;

  // Permission to update collection metadata
  repeated ActionPermission canUpdateCollectionMetadata = 4;
}

message MsgSetCollectionMetadataResponse {
  // ID of the collection.
  string collectionId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
}
```

### Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-collection-metadata '[tx-json]' --from manager-key
```

#### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "collectionMetadata": {
        "uri": "https://example.com/collection.json",
        "customData": ""
    },
    "canUpdateCollectionMetadata": [
        {
            "permanentlyPermittedTimes": [{ "start": "1000", "end": "2000" }],
            "permanentlyForbiddenTimes": []
        }
    ]
}
```

#### JSON Example — Inline Metadata via `customData`

As an alternative to hosting the metadata JSON behind a URI, leave `uri` empty and put the metadata document inline in `customData` as a JSON-encoded string. The indexer parses it on read and surfaces the result as the resolved metadata; URI always wins when both are populated. See [Collection Configuration › Inline metadata via customData](/token-standard/learn/collection-setup-fields#inline-metadata-via-customdata).

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "collectionMetadata": {
        "uri": "",
        "customData": "{\"name\":\"My Collection\",\"image\":\"ipfs://Qm.../image.png\",\"description\":\"A short description.\"}"
    },
    "canUpdateCollectionMetadata": []
}
```

### Related Messages

* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Full collection update with all fields
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Legacy update message


# MsgSetCustomData

**Disclaimer:**\
This message is a streamlined alternative to [MsgUpdateCollection](/token-standard/messages/msg-update-collection). If you need to update many fields at once, we recommend using MsgUpdateCollection instead.

## MsgSetCustomData

Sets the custom data and update permissions for a collection. This is a convenience message that focuses specifically on custom data management.

### Overview

This message allows you to:

* Set custom data for the collection
* Configure permissions to update the custom data in the future

### Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. The manager must have permission to update the custom data according to the collection's current permission settings.

### Proto Definition

```protobuf
message MsgSetCustomData {
  option (cosmos.msg.v1.signer) = "creator";
  option (amino.name) = "tokenization/SetCustomData";

  // Address of the creator.
  string creator = 1;

  // ID of the collection.
  string collectionId = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];

  // New custom data to set.
  string customData = 3;

  // Permission to update custom data
  repeated ActionPermission canUpdateCustomData = 4;
}

message MsgSetCustomDataResponse {
  // ID of the collection.
  string collectionId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
}
```

### Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-custom-data '[tx-json]' --from manager-key
```

#### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "customData": "{\"description\": \"My custom data\", \"version\": \"1.0\"}",
    "canUpdateCustomData": [
        {
            "permanentlyPermittedTimes": [{ "start": "1000", "end": "2000" }],
            "permanentlyForbiddenTimes": []
        }
    ]
}
```

### Related Messages

* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Full collection update with all fields
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Legacy update message


# MsgSetDynamicStoreValue

Sets a boolean value for a specific address in a dynamic store.

## Proto Definition

```protobuf
message MsgSetDynamicStoreValue {
  string creator = 1; // Address setting the value (must be store creator)
  string storeId = 2; // ID of the dynamic store
  string address = 3; // Address to set the value for
  bool value = 4; // Boolean value to set
}

message MsgSetDynamicStoreValueResponse {
  bool previousValue = 1; // The previous value before the update
  repeated string reviewItems = 2; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`previousValue`**: The boolean value that was stored before this update
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-dynamic-store-value [store-id] [address] [true|false] --from creator-key
```

### JSON Example

```json
{
  "creator": "bb1...",
  "storeId": "1",
  "address": "bb1...",
  "value": true
}
```


# MsgSetIncomingApproval

A helper message to set a single incoming approval for token transfers. This is a developer-friendly wrapper around `MsgUpdateUserApprovals` that simplifies setting individual incoming approvals. For more information, we refer to the [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) documentation.

## Overview

This message allows you to set or update a single incoming approval without having to construct the full `MsgUpdateUserApprovals` message. It automatically handles version management and validation.

## Proto Definition

```protobuf
message MsgSetIncomingApproval {
  string creator = 1; // User setting the approval
  string collectionId = 2; // Target collection for approval
  UserIncomingApproval approval = 3; // The incoming approval to set
}

message MsgSetIncomingApprovalResponse {
  string action = 1; // "created" or "edited"
  string version = 2; // The new version of the approval
  repeated string reviewItems = 3; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`action`**: Whether the approval was `"created"` or `"edited"`
* **`version`**: The new version number of the approval after the operation
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-incoming-approval [collection-id] '[approval-json]' --from user-key
```

## Behavior

* **New Approval**: If the approval ID doesn't exist, a new approval is created with version 0
* **Update Existing**: If the approval ID already exists, the approval is updated and the version is incremented
* **No Change**: If the approval content hasn't changed, the version remains the same
* **Validation**: The approval is validated according to the collection's permissions and user's approval update permissions

## Authorization & Permissions

Users can only set their own incoming approvals. The operation must be performed according to the permissions set (i.e. the `userPermissions` previously set for that user).

## Related Messages

* [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) - Full approval management
* [MsgDeleteIncomingApproval](/token-standard/messages/msg-delete-incoming-approval) - Delete an incoming approval
* [MsgSetOutgoingApproval](/token-standard/messages/msg-set-outgoing-approval) - Set an outgoing approval


# MsgSetIsArchived

**Disclaimer:**\
This message is a streamlined alternative to [MsgUpdateCollection](/token-standard/messages/msg-update-collection). If you need to update many fields at once, we recommend using MsgUpdateCollection instead.

## MsgSetIsArchived

Sets the isArchived status and update permissions for a collection. This is a convenience message that focuses specifically on archiving management.

### Overview

This message allows you to:

* Set isArchived status for the collection
* Configure permissions to archive the collection in the future

### Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. The manager must have permission to archive the collection according to the collection's current permission settings.

### Proto Definition

```protobuf
message MsgSetIsArchived {
  option (cosmos.msg.v1.signer) = "creator";
  option (amino.name) = "tokenization/SetIsArchived";

  // Address of the creator.
  string creator = 1;

  // ID of the collection.
  string collectionId = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];

  // New isArchived status to set.
  bool isArchived = 3;

  // Permission to archive collection
  repeated ActionPermission canArchiveCollection = 4;
}

message MsgSetIsArchivedResponse {
  // ID of the collection.
  string collectionId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
}
```

### Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-is-archived '[tx-json]' --from manager-key
```

#### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "isArchived": true,
    "canArchiveCollection": [
        {
            "permanentlyPermittedTimes": [{ "start": "1000", "end": "2000" }],
            "permanentlyForbiddenTimes": []
        }
    ]
}
```

### Related Messages

* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Full collection update with all fields
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Legacy update message


# MsgSetManager

**Disclaimer:**\
This message is a streamlined alternative to [MsgUpdateCollection](/token-standard/messages/msg-update-collection). If you need to update many fields at once, we recommend using MsgUpdateCollection instead.

## MsgSetManager

Sets the manager and update permissions for a collection. This is a convenience message that focuses specifically on manager management.

### Overview

This message allows you to:

* Set who manages the collection
* Configure permissions to update the manager in the future

### Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. The manager must have permission to update the manager according to the collection's current permission settings.

### Proto Definition

```protobuf
message MsgSetManager {
  option (cosmos.msg.v1.signer) = "creator";
  option (amino.name) = "tokenization/SetManager";

  // Address of the creator.
  string creator = 1;

  // ID of the collection.
  string collectionId = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];

  // New manager to set.
  string manager = 3;

  // Permission to update manager
  repeated ActionPermission canUpdateManager = 4;
}

message MsgSetManagerResponse {
  // ID of the collection.
  string collectionId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
}
```

### Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-manager '[tx-json]' --from manager-key
```

#### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "manager": "bb1def456...",
    "canUpdateManager": [
        {
            "permanentlyPermittedTimes": [{ "start": "1000", "end": "2000" }],
            "permanentlyForbiddenTimes": []
        }
    ]
}
```

### Related Messages

* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Full collection update with all fields
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Legacy update message


# MsgSetOutgoingApproval

A helper message to set a single outgoing approval for token transfers. This is a developer-friendly wrapper around `MsgUpdateUserApprovals` that simplifies setting individual outgoing approvals. For more information, we refer to the [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) documentation.

## Overview

This message allows you to set or update a single outgoing approval without having to construct the full `MsgUpdateUserApprovals` message. It automatically handles version management and validation.

## Proto Definition

```protobuf
message MsgSetOutgoingApproval {
  string creator = 1; // User setting the approval
  string collectionId = 2; // Target collection for approval
  UserOutgoingApproval approval = 3; // The outgoing approval to set
}

message MsgSetOutgoingApprovalResponse {
  string action = 1; // "created" or "edited"
  string version = 2; // The new version of the approval
  repeated string reviewItems = 3; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`action`**: Whether the approval was `"created"` or `"edited"`
* **`version`**: The new version number of the approval after the operation
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-outgoing-approval [collection-id] '[approval-json]' --from user-key
```

## Behavior

* **New Approval**: If the approval ID doesn't exist, a new approval is created with version 0
* **Update Existing**: If the approval ID already exists, the approval is updated and the version is incremented
* **No Change**: If the approval content hasn't changed, the version remains the same
* **Validation**: The approval is validated according to the collection's permissions and user's approval update permissions

## Authorization & Permissions

Users can only set their own outgoing approvals. The operation must be performed according to the permissions set (i.e. the `userPermissions` previously set for that user).

## Related Messages

* [MsgUpdateUserApprovals](/token-standard/messages/msg-update-user-approvals) - Full approval management
* [MsgDeleteOutgoingApproval](/token-standard/messages/msg-delete-outgoing-approval) - Delete an outgoing approval
* [MsgSetIncomingApproval](/token-standard/messages/msg-set-incoming-approval) - Set an incoming approval


# MsgSetStandards

**Disclaimer:**\
This message is a streamlined alternative to [MsgUpdateCollection](/token-standard/messages/msg-update-collection). If you need to update many fields at once, we recommend using MsgUpdateCollection instead.

## MsgSetStandards

Sets the standards and update permissions for a collection. This is a convenience message that focuses specifically on standards management.

### Overview

This message allows you to:

* Set standards for the collection
* Configure permissions to update the standards in the future

### Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. The manager must have permission to update the standards according to the collection's current permission settings.

### Proto Definition

```protobuf
message MsgSetStandards {
  option (cosmos.msg.v1.signer) = "creator";
  option (amino.name) = "tokenization/SetStandards";

  // Address of the creator.
  string creator = 1;

  // ID of the collection.
  string collectionId = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];

  // New standards to set.
  repeated string standards = 3;

  // Permission to update standards
  repeated ActionPermission canUpdateStandards = 4;
}

message MsgSetStandardsResponse {
  // ID of the collection.
  string collectionId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
}
```

### Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-standards '[tx-json]' --from manager-key
```

#### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "standards": ["ERC1155", "ERC721"],
    "canUpdateStandards": [
        {
            "permanentlyPermittedTimes": [{ "start": "1000", "end": "2000" }],
            "permanentlyForbiddenTimes": []
        }
    ]
}
```


# MsgSetValidTokenIds

**Disclaimer:**\
This message is a streamlined alternative to [MsgUpdateCollection](/token-standard/messages/msg-update-collection). If you need to update many fields at once, we recommend using MsgUpdateCollection instead.

## MsgSetValidTokenIds

Sets the valid token IDs and update permissions for a collection. This is a convenience message that focuses specifically on token ID management.

### Overview

This message allows you to:

* Set which token IDs are valid for the collection
* Configure permissions to update the valid token IDs in the future

### Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. The manager must have permission to update valid token IDs according to the collection's current permission settings.

### Proto Definition

```protobuf
message MsgSetValidTokenIds {
  option (cosmos.msg.v1.signer) = "creator";
  option (amino.name) = "tokenization/SetValidTokenIds";

  // Address of the creator.
  string creator = 1;

  // ID of the collection.
  string collectionId = 2 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];

  // New token IDs to add to this collection
  repeated UintRange validTokenIds = 3;

  // Permission to update valid token IDs
  repeated TokenIdsActionPermission canUpdateValidTokenIds = 4;
}

message MsgSetValidTokenIdsResponse {
  // ID of the collection.
  string collectionId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
}
```

### Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization set-valid-token-ids '[tx-json]' --from manager-key
```

#### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "validTokenIds": [
        { "start": "1", "end": "100" },
        { "start": "200", "end": "300" }
    ],
    "canUpdateValidTokenIds": [
        {
            "tokenIds": [{ "start": "1", "end": "50" }],
            "permanentlyPermittedTimes": [{ "start": "1000", "end": "2000" }],
            "permanentlyForbiddenTimes": []
        }
    ]
}
```

### Related Messages

* [MsgUniversalUpdateCollection](/token-standard/messages/msg-universal-update-collection) - Full collection update with all fields
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection) - Legacy update message


# MsgTransferTokens

Executes token transfers between addresses.

## Proto Definition

```protobuf
message MsgTransferTokens {
  string creator = 1; // Address initiating the transfer
  string collectionId = 2; // Collection containing tokens to transfer
  repeated Transfer transfers = 3; // Transfer operations (must pass approvals)
}

message MsgTransferTokensResponse {
  repeated ApprovalUsed approvalsUsed = 1;
  repeated CoinTransferProto coinTransfers = 2;
  repeated Balance balancesTransferred = 3;
  repeated string reviewItems = 4;
}

message Transfer {
  // The address of the sender of the transfer.
  string from = 1;
  // The addresses of the recipients of the transfer.
  repeated string toAddresses = 2;
  // The balances to be transferred.
  repeated Balance balances = 3;
  // If defined, we will use the predeterminedBalances from the specified approval to calculate the balances at execution time.
  // We will override the balances field with the precalculated balances. Only applicable for approvals with predeterminedBalances set.
  PrecalculateBalancesFromApprovalDetails precalculateBalancesFromApproval = 4;
  // The Merkle proofs / solutions for all Merkle challenges required for the transfer.
  repeated MerkleProof merkleProofs = 5;
  // The ETH signature proofs / solutions for all ETH signature challenges required for the transfer.
  repeated ETHSignatureProof ethSignatureProofs = 6;
  // The memo for the transfer.
  string memo = 7;
  // The prioritized approvals for the transfer. By default, we scan linearly through the approvals and use the first match.
  // This field can be used to prioritize specific approvals and scan through them first.
  repeated ApprovalIdentifierDetails prioritizedApprovals = 8;
  // Whether to only check prioritized approvals for the transfer.
  // If true, we will only check the prioritized approvals and fail if none of them match (i.e. do not check any non-prioritized approvals).
  // If false, we will check the prioritized approvals first and then scan through the rest of the approvals.
  bool onlyCheckPrioritizedCollectionApprovals = 9;
  // Whether to only check prioritized approvals for the transfer.
  // If true, we will only check the prioritized approvals and fail if none of them match (i.e. do not check any non-prioritized approvals).
  // If false, we will check the prioritized approvals first and then scan through the rest of the approvals.
  bool onlyCheckPrioritizedIncomingApprovals = 10;
  // Whether to only check prioritized approvals for the transfer.
  // If true, we will only check the prioritized approvals and fail if none of them match (i.e. do not check any non-prioritized approvals).
  // If false, we will check the prioritized approvals first and then scan through the rest of the approvals.
  bool onlyCheckPrioritizedOutgoingApprovals = 11;
}

message PrecalculateBalancesFromApprovalDetails {
  string approvalId = 1;
  string approvalLevel = 2;  // "collection", "incoming", or "outgoing"
  string approverAddress = 3;  // "" if collection-level
  string version = 4 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
  PrecalculationOptions precalculationOptions = 5;
}

message PrecalculationOptions {
  string overrideTimestamp = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
  repeated UintRange tokenIdsOverride = 2;
}
```

## Response

The response includes structured data about the transfer execution:

* **`approvalsUsed`**: Details of which approvals were matched and consumed for the transfer
* **`coinTransfers`**: Any coin transfers that occurred as side effects (e.g., from approval criteria with coin transfer requirements)
* **`balancesTransferred`**: The actual token balances that were transferred
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Auto-Scan vs Prioritized Approvals

The transfer approval system operates in two modes to balance efficiency and precision:

### Auto-Scan Mode (Default)

By default, the system automatically scans through available approvals to find a match for the transfer. This mode:

* **Works with**: Approvals using [Empty Approval Criteria](/token-standard/examples/empty-approval-criteria) (no side effects)
* **Behavior**: Automatically finds and uses the first matching approval
* **Use case**: Simple transfers without custom logic or side effects
* **No versioning required**: The system handles approval selection automatically

### Prioritized Approvals (Required for Side Effects)

**CRITICAL REQUIREMENT**: Any transfer with side effects or custom approval criteria MUST always be prioritized with proper versioning set. No exceptions.

#### Race Condition Protection

The versioning control ensures that before submitting, the user knows the exact approval they are using:

```typescript
"prioritizedApprovals": [
    {
        "approvalId": "abc123",
        "approvalLevel": "collection",
        "approverAddress": "",
        "version": "2" // Must specify exact version
    }
]
```

#### Example: Coin Transfer Approval

```typescript
// MUST be prioritized - has coin transfer side effects
"prioritizedApprovals": [
    {
        "approvalId": "reward-approval",
        "approvalLevel": "collection",
        "approverAddress": "",
        "version": "1"
    }
],
"onlyCheckPrioritizedCollectionApprovals": true
```

#### Example: Auto-Scan Safe Transfer

```typescript
// Can use auto-scan - no side effects
"prioritizedApprovals": [], // Empty - will auto-scan

// Only will succeed if it finds an approval has empty approval criteria with no custom logic
```

### Control Flags

* `onlyCheckPrioritizedCollectionApprovals`: If true, only check prioritized approvals
* `onlyCheckPrioritizedIncomingApprovals`: If true, only check prioritized incoming approvals
* `onlyCheckPrioritizedOutgoingApprovals`: If true, only check prioritized outgoing approvals

**Setting these to `true` is recommended when using prioritized approvals to ensure deterministic behavior.**

### Related Documentation

* [Empty Approval Criteria](/token-standard/examples/empty-approval-criteria) - Template for auto-scan compatible approvals
* [Approval Criteria](/token-standard/learn/approval-criteria) - Understanding approval complexity
* [Coin Transfers](/token-standard/learn/approval-criteria/usdbadge-transfers) - Side effect examples

## Transfer Validation Process

Each transfer undergoes a systematic validation process to ensure security and proper authorization:

### Validation Steps

```
PRE. CALCULATE BALANCES (if needed)
  └── If precalculateBalancesFromApproval is specified, we will use the predeterminedBalances from the specified approval to pre-calculate the balances at execution time.

1. BALANCE CHECK
   └── Verify sender has sufficient balances for the transfer including ownership times
   └── FAIL if insufficient balances

2. COLLECTION APPROVAL CHECK
   └── Scan collection-level approvals (prioritized first, then auto-scan) to find a match for the entire transfer
   └── If match found:
       ├── Check approval criteria (merkle proofs, amounts, timing, etc.) and constraints
       ├── Check if it overrides sender approvals (overridesFromOutgoingApprovals)
       ├── Check if it overrides recipient approvals (overridesToIncomingApprovals)
       └── PROCEED with override flags set
   └── Else:
        └── Continue scanning
   └── If some attempted transfer balances have no valid collection approval: FAIL

3. SENDER APPROVAL CHECK (if not overridden)
   └── Check sender's outgoing approvals for this transfer
   └── Verify approval criteria and constraints
   └── FAIL if no valid outgoing approval found

4. RECIPIENT APPROVAL CHECK (if not overridden)
   └── Check each recipient's incoming approvals
   └── Verify approval criteria and constraints
   └── FAIL if any recipient lacks valid incoming approval

5. EXECUTE TRANSFER
   └── Update balances
   └── Execute any approved side effects
   └── Emit transfer events
   └── SUCCESS
```

### Override Behavior

Collection approvals can override user-level approvals:

* **`overridesFromOutgoingApprovals: true`** - Forcefully skips sender approval check
* **`overridesToIncomingApprovals: true`** - Forcefully skips recipient approval checks

This allows collection managers to enable transfers that would otherwise be blocked by user settings.

### Failure Points

Transfers fail at the first validation step that doesn't pass:

1. **Insufficient Balances** - Sender doesn't own the tokens
2. **No Collection Approval** - No valid collection-level approval found
3. **Blocked by Sender** - Sender's outgoing approvals reject the transfer
4. **Blocked by Recipient** - Recipient's incoming approvals reject the transfer

### ETH Signature Proofs

ETH Signature Proofs are required when transfers use [ETH Signature Challenges](/token-standard/learn/approval-criteria/eth-signature-challenges). Each proof contains:

* **`nonce`**: The unique identifier that was signed
* **`signature`**: The Ethereum signature of the message `nonce + "-" + creatorAddress`

**Important**: Each signature can only be used once per challenge tracker. The system tracks used signatures to prevent replay attacks.

### Related Documentation

* [Transferability](/token-standard/learn/transferability) - Approval system overview
* [Collection Approvals](/token-standard/examples/building-collection-approvals) - Collection-level controls
* [User Approvals](/token-standard/examples/building-user-approvals) - User-level settings
* [ETH Signature Challenges](/token-standard/learn/approval-criteria/eth-signature-challenges) - Ethereum signature requirements

## Precalculating Balances

When using `precalculateBalancesFromApproval`, you can override certain calculation parameters using `precalculationOptions`. These options only apply when the corresponding flags are enabled in the approval's `IncrementedBalances`.

### PrecalculationOptions

| Field               | Type          | Description                                                      |
| ------------------- | ------------- | ---------------------------------------------------------------- |
| `overrideTimestamp` | string (Uint) | Override timestamp for ownership time calculation (milliseconds) |
| `tokenIdsOverride`  | UintRange\[]  | Override token IDs (must be single ID if provided)               |

**overrideTimestamp**:

* Only applies when `IncrementedBalances.durationFromTimestamp` is set and `allowOverrideTimestamp` is `true`
* If zero or not provided, uses current block time
* Used to calculate ownership times as `[overrideTimestamp, overrideTimestamp + durationFromTimestamp - 1]`

**tokenIdsOverride**:

* Only applies when `IncrementedBalances.allowOverrideWithAnyValidToken` is `true`
* Must contain exactly one `UintRange` with `start == end` (single token ID)
* Token ID must be in the collection's `validTokenIds`

For detailed documentation, see [Predetermined Balances](/token-standard/learn/approval-criteria/predetermined-balances#precalculation-options).

## Collection ID Auto-Lookup

If you specify `collectionId` as `"0"`, it will automatically lookup the latest collection ID created. This can be used if you are creating a collection and do not know the official collection ID yet but want to perform a multi-message transaction.

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization transfer-tokens '[tx-json]' --from sender-key
```

### JSON Example

```json
{
    "creator": "bb1initiator123...",
    "collectionId": "1",
    "transfers": [
        {
            "from": "bb1sender123...",
            "toAddresses": ["bb1recipient123..."],
            // Balances to transfer (can be left blank if you are using precalculateBalancesFromApproval)
            "balances": [
                {
                    "amount": "10",
                    "ownershipTimes": [
                        { "start": "1", "end": "18446744073709551615" }
                    ],
                    "tokenIds": [{ "start": "1", "end": "5" }]
                }
            ],
            // Specific approval to calculate balances dynamically for (from the approvalCriteria.predeterminedBalances)
            "precalculateBalancesFromApproval": {
                "approvalId": "approval-1",
                "approvalLevel": "collection",
                "approverAddress": "",
                "version": "1",
                "precalculationOptions": {
                    "overrideTimestamp": "0", // Optional: override timestamp (milliseconds)
                    "tokenIdsOverride": [] // Optional: override token IDs (must be single ID if provided)
                }
            },
            // Supply all merkle proofs for any merkle challenges that need to be satisfied
            "merkleProofs": [],
            // Supply all ETH signature proofs for any ETH signature challenges that need to be satisfied
            "ethSignatureProofs": [],
            // Memo for the transfer (can be left blank)
            "memo": "",

            // Any approval IDs that you want to prioritize for this transfer
            // Note: All approvals with side effects must be prioritized with proper versioning
            "prioritizedApprovals": [
                {
                    "approvalId": "abc123",
                    "approvalLevel": "collection",
                    "approverAddress": "", // blank for collection, otherwise the address of the approver
                    "version": "0"
                }
            ],

            // If specified, we will stop checking after the prioritized approvals list.
            // If false, we will check prioritized first, but then continue to check the rest of the approvals in auto-scan mode
            "onlyCheckPrioritizedCollectionApprovals": false,
            "onlyCheckPrioritizedIncomingApprovals": false,
            "onlyCheckPrioritizedOutgoingApprovals": false
        }
    ]
}
```


# MsgUniversalUpdateCollection

A universal message that can be used to either create a new collection or update an existing one. This message combines the functionality of both `MsgCreateCollection` and `MsgUpdateCollection` into a single interface.

## Dual Purpose

* **Collection Creation**: When `collectionId` is set to `"0"`, this message creates a new collection
* **Collection Update**: When `collectionId` is set to an existing collection ID, this message updates that collection

## Update Flag Pattern

This message uses an update flag + value pattern for selective updates. Each updatable field has a corresponding boolean flag (e.g., `updateValidTokenIds`, `updateCollectionPermissions`).

* **If update flag is `true`**: The corresponding value field is processed and the collection is updated with the new value
* **If update flag is `false`**: The corresponding value field is completely ignored, regardless of what data is provided

## Authorization & Permissions

* **For Collection Creation**: Can be executed by any address
* **For Collection Updates**: Can only be executed by the **current manager** of the collection. All updates must obey the previously set permissions.

### Path Addition Permissions

When adding paths to an existing collection, the following permissions are checked:

* **`cosmosCoinWrapperPathsToAdd`**: Requires `canAddMoreCosmosCoinWrapperPaths` permission
* **`aliasPathsToAdd`**: Requires `canAddMoreAliasPaths` permission

These permissions are checked before paths are processed. If the permission check fails, the transaction will be rejected. Both permissions use the `ActionPermission` type with time-based controls. Empty/nil permissions mean the action is allowed (neutral state).

## Proto Definition

```protobuf
message MsgUniversalUpdateCollection {
  string creator = 1; // Address creating/updating collection
  string collectionId = 2; // "0" for new collection, existing ID for updates

  // Creation-only fields (only used when collectionId = "0")
  UserBalanceStore defaultBalances = 3;

  // Updateable fields (used for both creation and updates)
  repeated UintRange validTokenIds = 4;
  bool updateCollectionPermissions = 5;
  CollectionPermissions collectionPermissions = 6;
  bool updateManager = 7;
  string manager = 8;
  bool updateCollectionMetadata = 9;
  CollectionMetadata collectionMetadata = 10;
  bool updateTokenMetadata = 11;
  repeated TokenMetadata tokenMetadata = 12;
  bool updateCustomData = 13;
  string customData = 14;
  bool updateCollectionApprovals = 15;
  repeated CollectionApproval collectionApprovals = 16;
  bool updateStandards = 17;
  repeated string standards = 18;
  bool updateIsArchived = 19;
  bool isArchived = 20;

  // Transfer fields
  repeated cosmos.base.v1beta1.Coin mintEscrowCoinsToTransfer = 21;
  repeated CosmosCoinWrapperPathAddObject cosmosCoinWrapperPathsToAdd = 22; // Requires canAddMoreCosmosCoinWrapperPaths permission
  repeated AliasPathAddObject aliasPathsToAdd = 23; // Requires canAddMoreAliasPaths permission

  // Invariants (creation-only)
  CollectionInvariants invariants = 24;
}

message MsgUniversalUpdateCollectionResponse {
  string collectionId = 1; // ID of created/updated collection
  repeated ApprovalChange approvalChanges = 2; // Details of each approval created/edited/deleted
  repeated string reviewItems = 3; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`collectionId`**: The ID of the created or updated collection
* **`approvalChanges`**: A list of `ApprovalChange` entries describing each approval that was created, edited, or deleted (see [Approval Change Events](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md))
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization universal-update-collection '[tx-json]' --from creator-key
```

### JSON Example - Creating a New Collection

```json
{
    "creator": "bb1abc123...",
    "collectionId": "0",
    "defaultBalances": {
        "balances": [],
        "outgoingApprovals": [],
        "incomingApprovals": [],
        "autoApproveSelfInitiatedOutgoingTransfers": false,
        "autoApproveSelfInitiatedIncomingTransfers": true,
        "autoApproveAllIncomingTransfers": false,
        "userPermissions": {
            "canUpdateOutgoingApprovals": [],
            "canUpdateIncomingApprovals": [],
            "canUpdateAutoApproveSelfInitiatedOutgoingTransfers": [],
            "canUpdateAutoApproveSelfInitiatedIncomingTransfers": [],
            "canUpdateAutoApproveAllIncomingTransfers": []
        }
    },
    "validTokenIds": [{ "start": "1", "end": "100" }],
    "updateCollectionPermissions": true,
    "collectionPermissions": {
        "canDeleteCollection": [],
        "canArchiveCollection": [],
        "canUpdateStandards": [],
        "canUpdateCustomData": [],
        "canUpdateManager": [],
        "canUpdateCollectionMetadata": [],
        "canUpdateValidTokenIds": [],
        "canUpdateTokenMetadata": [],
        "canUpdateCollectionApprovals": [],
        "canAddMoreAliasPaths": [],
        "canAddMoreCosmosCoinWrapperPaths": []
    },
    "updateManager": true,
    "manager": "",
    "updateCollectionMetadata": true,
    "collectionMetadata": {},
    "updateTokenMetadata": true,
    "tokenMetadata": [],
    "updateCustomData": true,
    "customData": "",
    "updateCollectionApprovals": true,
    "collectionApprovals": [],
    "updateStandards": true,
    "standards": [],
    "updateIsArchived": true,
    "isArchived": false,
    "mintEscrowCoinsToTransfer": [],
    "cosmosCoinWrapperPathsToAdd": [],
    "aliasPathsToAdd": [],
    "invariants": {
        "noCustomOwnershipTimes": false,
        "maxSupplyPerId": "0",
        "cosmosCoinBackedPath": undefined,
        "noForcefulPostMintTransfers": false,
        "disablePoolCreation": false,
        "evmQueryChallenges": []
    }
}
```

### JSON Example - Updating an Existing Collection

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "updateValidTokenIds": true,
    "validTokenIds": [{ "start": "1", "end": "200" }],
    "updateCollectionPermissions": false,
    "collectionPermissions": {},
    "updateManager": false,
    "manager": "",
    "updateCollectionMetadata": false,
    "collectionMetadata": {},
    "updateTokenMetadata": false,
    "tokenMetadata": [],
    "updateCustomData": false,
    "customData": "",
    "updateCollectionApprovals": false,
    "collectionApprovals": [],
    "updateStandards": false,
    "standards": [],
    "updateIsArchived": false,
    "isArchived": false,
    "mintEscrowCoinsToTransfer": [],
    "cosmosCoinWrapperPathsToAdd": [],
    "aliasPathsToAdd": []
}
```

> **Note:** When updating an existing collection (`collectionId != "0"`), the `invariants` field must be omitted entirely. Invariants are creation-only and are ignored (or may cause errors) if included in update transactions. Only include `invariants` when creating a new collection (`collectionId = "0"`).

## Key Differences from Other Messages

### vs MsgCreateCollection

* More flexible update flag pattern
* Can be used for both creation and updates
* Includes invariants support

### vs MsgUpdateCollection

* Can create new collections when collectionId = "0"
  * Includes creation-only fields like `defaultBalances`
* Includes invariants support

## Invariants Support

When creating a new collection (collectionId = "0"), you can set collection invariants using the `invariants` field. Invariants cannot be modified after collection creation.

```json
{
    "invariants": {
        "noCustomOwnershipTimes": true,
        "maxSupplyPerId": "0",
        "cosmosCoinBackedPath": undefined,
        "noForcefulPostMintTransfers": false,
        "disablePoolCreation": false,
        "evmQueryChallenges": []
    }
}
```

## Related Messages

* [MsgCreateCollection](/token-standard/messages/msg-create-collection)
* [MsgUpdateCollection](/token-standard/messages/msg-update-collection)
* [Collection Setup Fields](/token-standard/learn/collection-setup-fields)


# MsgUpdateCollection

Updates an existing collection's properties.

## Update Flag Pattern

This message uses an update flag + value pattern for selective updates. Each updatable field has a corresponding boolean flag (e.g., `updateValidTokenIds`, `updateCollectionPermissions`).

* **If update flag is `true`**: The corresponding value field is processed and the collection is updated with the new value
* **If update flag is `false`**: The corresponding value field is completely ignored, regardless of what data is provided

This allows you to update only specific fields without affecting others, and you can safely leave unused value fields empty or with placeholder data.

## Authorization & Permissions

Updates can only be performed by the **current manager** of the collection. All updates must obey the previously set permissions - meaning the permission settings that were in effect *before* this message was started.

**Important**: If you update the permissions in the current message, those new permissions are applied last and will not be applicable until the following transaction. This prevents circumventing permission restrictions within the same transaction.

## Proto Definition

```protobuf
message MsgUpdateCollection {
  string creator = 1; // Address updating collection (must be manager)
  string collectionId = 2; // ID of collection to update
  bool updateValidTokenIds = 3;
  repeated UintRange validTokenIds = 4;
  bool updateCollectionPermissions = 5;
  CollectionPermissions collectionPermissions = 6;
  bool updateManager = 7;
  string manager = 8;
  bool updateCollectionMetadata = 9;
  CollectionMetadata collectionMetadata = 10;
  bool updateTokenMetadata = 11;
  repeated TokenMetadata tokenMetadata = 12;
  bool updateCustomData = 13;
  string customData = 14;
  bool updateCollectionApprovals = 15;
  repeated CollectionApproval collectionApprovals = 16;
  bool updateStandards = 17;
  repeated string standards = 18;
  bool updateIsArchived = 19;
  bool isArchived = 20;
  repeated cosmos.base.v1beta1.Coin mintEscrowCoinsToTransfer = 21;
  repeated CosmosCoinWrapperPathAddObject cosmosCoinWrapperPathsToAdd = 22;
  repeated AliasPathAddObject aliasPathsToAdd = 23;
}

message MsgUpdateCollectionResponse {
  string collectionId = 1; // ID of updated collection
}
```

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization update-collection '[tx-json]' --from manager-key
```

### JSON Example

```json
{
    "creator": "bb1abc123...",
    "collectionId": "1",
    "updateValidTokenIds": true,
    "validTokenIds": [{ "start": "1", "end": "200" }],
    "updateCollectionPermissions": false,
    "collectionPermissions": {
        "canDeleteCollection": [],
        "canArchiveCollection": [],
        "canUpdateStandards": [],
        "canUpdateCustomData": [],
        "canUpdateManager": [],
        "canUpdateCollectionMetadata": [],
        "canUpdateValidTokenIds": [],
        "canUpdateTokenMetadata": [],
        "canUpdateCollectionApprovals": [],
        "canAddMoreAliasPaths": [],
        "canAddMoreCosmosCoinWrapperPaths": []
    },
    "updateManager": false,
    "manager": "",
    "updateCollectionMetadata": false,
    "collectionMetadata": {},
    "updateTokenMetadata": false,
    "tokenMetadata": [],
    "updateCustomData": false,
    "customData": "",
    "updateCollectionApprovals": false,
    "collectionApprovals": [],
    "updateStandards": false,
    "standards": [],
    "updateIsArchived": false,
    "isArchived": false,
    "mintEscrowCoinsToTransfer": [],
    "cosmosCoinWrapperPathsToAdd": [],
    "aliasPathsToAdd": []
}
```

> **Note:** Invariants are creation-only and cannot be set or modified via `MsgUpdateCollection`. Use the `invariants` field in `MsgUniversalUpdateCollection` or `MsgCreateCollection` when creating a new collection (`collectionId = '0'`).


# MsgUpdateDynamicStore

Updates an existing dynamic store's default value, global kill switch status, and optional metadata fields.

## Proto Definition

```protobuf
message MsgUpdateDynamicStore {
  string creator = 1; // Address updating the store (must be creator)
  string storeId = 2; // ID of dynamic store to update
  bool defaultValue = 3; // New default value for uninitialized addresses
  bool globalEnabled = 4; // Global kill switch. When false, all approvals using this store fail immediately
  string uri = 5; // Optional: URI for additional metadata or resources
  string customData = 6; // Optional: Custom data field for arbitrary data
}

message MsgUpdateDynamicStoreResponse {}
```

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization update-dynamic-store [store-id] [default-value] [global-enabled] --from creator-key
```

### JSON Examples

**Update default value only (keep globalEnabled unchanged):**

```json
{
    "creator": "bb1...",
    "storeId": "1",
    "defaultValue": true,
    "globalEnabled": true  // Must pass current value to avoid changing it
}
```

**Disable global kill switch (halt all approvals using this store):**

```json
{
    "creator": "bb1...",
    "storeId": "1",
    "defaultValue": true,  // Must pass current value
    "globalEnabled": false  // Disable kill switch
}
```

**Re-enable global kill switch:**

```json
{
    "creator": "bb1...",
    "storeId": "1",
    "defaultValue": true,
    "globalEnabled": true  // Re-enable
}
```

**Update metadata fields:**

```json
{
    "creator": "bb1...",
    "storeId": "1",
    "defaultValue": true,
    "globalEnabled": true,
    "uri": "https://example.com/updated-metadata",
    "customData": "{\"updated\": true, \"timestamp\": \"2024-01-01\"}"
}
```

**Clear metadata fields (set to empty strings):**

```json
{
    "creator": "bb1...",
    "storeId": "1",
    "defaultValue": true,
    "globalEnabled": true,
    "uri": "",
    "customData": ""
}
```

## Global Kill Switch

The `globalEnabled` field acts as a global kill switch for the dynamic store. When `globalEnabled = false`:

* All approvals using this store via `DynamicStoreChallenge` will fail immediately
* The error message will be: "dynamic store storeId {id} is globally disabled"
* Per-address values are ignored when the kill switch is disabled

This is useful for quickly halting all approvals that depend on a specific dynamic store (e.g., if a protocol is compromised).

**Note**: When updating a store, you must pass the current `globalEnabled` value if you want to keep it unchanged. Proto3 bools default to `false`, so you must explicitly pass `true` to maintain an enabled state.

## Metadata Fields

The `uri` and `customData` fields can be updated along with other store properties:

* **`uri`**: URI for additional metadata or resources. Can be set, updated, or cleared (set to empty string).
* **`customData`**: Custom data field for arbitrary string data. Can be set, updated, or cleared (set to empty string).


# MsgUpdateUserApprovals

Updates a user's approval settings for token transfers.

## Collection ID Auto-Lookup

If you specify `collectionId` as `"0"`, it will automatically lookup the latest collection ID created. This can be used if you are creating a collection and do not know the official collection ID yet but want to perform a multi-message transaction.

## Update Flag Pattern

This message uses an update flag + value pattern for selective updates. Each updatable field has a corresponding boolean flag (e.g., `updateOutgoingApprovals`, `updateIncomingApprovals`, `updateAutoApproveSelfInitiatedOutgoingTransfers`).

* **If update flag is `true`**: The corresponding value field is processed and the user's settings are updated with the new value
* **If update flag is `false`**: The corresponding value field is completely ignored, regardless of what data is provided

This allows you to update only specific approval settings without affecting others, and you can safely leave unused value fields empty or with placeholder data.

## Authorization & Permissions

Users can only update their own approvals. Updates must be performed according to the permissions set (i.e. the `userPermissions` previously set for that user).

**Note**: Typically, user permissions are almost always permanently allowed/set to enabled. These permissions only need to be customized in advanced cases where fine-grained control over user approval updates is required.

## Proto Definition

```protobuf
message MsgUpdateUserApprovals {
  string creator = 1; // User updating their approval settings
  string collectionId = 2; // Target collection for approval updates
  bool updateOutgoingApprovals = 3;
  repeated UserOutgoingApproval outgoingApprovals = 4;
  bool updateIncomingApprovals = 5;
  repeated UserIncomingApproval incomingApprovals = 6;
  bool updateAutoApproveSelfInitiatedOutgoingTransfers = 7;
  bool autoApproveSelfInitiatedOutgoingTransfers = 8;
  bool updateAutoApproveSelfInitiatedIncomingTransfers = 9;
  bool autoApproveSelfInitiatedIncomingTransfers = 10;
  bool updateAutoApproveAllIncomingTransfers = 11;
  bool autoApproveAllIncomingTransfers = 12;
  bool updateUserPermissions = 13;
  UserPermissions userPermissions = 14;
}

message MsgUpdateUserApprovalsResponse {
  repeated ApprovalChange incomingChanges = 1; // Details of each incoming approval created/edited/deleted
  repeated ApprovalChange outgoingChanges = 2; // Details of each outgoing approval created/edited/deleted
  repeated string reviewItems = 3; // Advisory review items about the transaction
}
```

## Response

The response includes:

* **`incomingChanges`**: A list of `ApprovalChange` entries for incoming approvals that were created, edited, or deleted
* **`outgoingChanges`**: A list of `ApprovalChange` entries for outgoing approvals that were created, edited, or deleted
* **`reviewItems`**: Advisory strings about the transaction (see [Review Items](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md#review-items))

For more on approval changes, see [Approval Change Events](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/concepts/approval-change-events.md).

## Usage Example

```bash
# CLI command
bitbadgeschaind tx tokenization update-user-approved-transfers '[tx-json]' --from user-key
```

### JSON Example

For complete transaction examples, see [MsgUpdateUserApprovals Examples](/token-standard/examples/txs/msgupdate-user-approvals).

```json
{
    "creator": "bb1user123...",
    "collectionId": "1",

    "updateOutgoingApprovals": false,
    "outgoingApprovals": [],

    "updateIncomingApprovals": false,
    "incomingApprovals": [],

    "updateAutoApproveSelfInitiatedOutgoingTransfers": true,
    "autoApproveSelfInitiatedOutgoingTransfers": true,

    "updateAutoApproveSelfInitiatedIncomingTransfers": false,
    "autoApproveSelfInitiatedIncomingTransfers": true,

    "updateAutoApproveAllIncomingTransfers": false,
    "autoApproveAllIncomingTransfers": false,

    "updateUserPermissions": false,
    "userPermissions": {
        "canUpdateOutgoingApprovals": [],
        "canUpdateIncomingApprovals": [],
        "canUpdateAutoApproveSelfInitiatedOutgoingTransfers": [],
        "canUpdateAutoApproveSelfInitiatedIncomingTransfers": [],
        "canUpdateAutoApproveAllIncomingTransfers": []
    }
}
```


# Queries

This directory contains detailed documentation for all query types supported by the tokens module.

## Query Categories

### Collection Queries

* [GetCollection](/token-standard/queries/get-collection) - Retrieve collection data and properties
* [GetCollectionStats](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/queries/get-collection-stats.md) - Get holder count and circulating supply statistics

### Balance Queries

* [GetBalance](/token-standard/queries/get-balance) - Get user balances for a collection
* [GetWrappableBalances](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/queries/get-wrappable-balances.md) - Get maximum amount of tokens that can be wrapped into cosmos coins

### Address List Queries

* [GetAddressList](/token-standard/queries/get-address-list) - Retrieve address list information

### Approval Tracking Queries

* [GetApprovalTracker](/token-standard/queries/get-approval-tracker) - Get approval usage tracking data and limits
* [GetChallengeTracker](/token-standard/queries/get-challenge-tracker) - Get challenge completion tracking status
* [GetETHSignatureTracker](/token-standard/queries/get-eth-signature-tracker) - Get ETH signature challenge usage tracking status

### Dynamic Store Queries

* [GetDynamicStore](/token-standard/queries/get-dynamic-store) - Get dynamic store configuration and metadata
* [GetDynamicStoreValue](/token-standard/queries/get-dynamic-store-value) - Get numeric value for specific address in store

### System Queries

* [Params](/token-standard/queries/params) - Get current module parameters and configuration


# GetAddressList

Retrieves information about a specific address list.

## Proto Definition

```protobuf
message QueryGetAddressListRequest {
  string listId = 1; // ID of address list to retrieve
}

message QueryGetAddressListResponse {
  AddressList list = 1;
}

message AddressList {
  string listId = 1; // Unique identifier for the address list
  repeated string addresses = 2; // List of addresses included in the list
  bool whitelist = 3; // Whether list includes (true) or excludes (false) specified addresses
  string uri = 4; // URI providing metadata, if applicable
  string customData = 5; // Custom arbitrary data or additional information
  string createdBy = 6; // The user or entity who created the address list
}
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-address-list [id]

# REST API
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_address_list/1"
```

### Response Example

```json
{
    "list": {
        "listId": "1",
        "addresses": ["bb1...", "bb1..."],
        "whitelist": true,
        "uri": "",
        "customData": "",
        "createdBy": "bb1..."
    }
}
```


# GetApprovalTracker

Retrieves tracking information for approval usage.

## Proto Definition

```protobuf
message QueryGetApprovalTrackerRequest {
  string amountTrackerId = 1; 
  string approvalLevel = 2; // "collection", "incoming", or "outgoing"
  string approverAddress = 3; // Leave blank if approvalLevel is "collection"
  string trackerType = 4; // "overall", "to", "from", "initiatedBy"
  string collectionId = 5;
  string approvedAddress = 6; // Leave blank if trackerType is "overall"
  string approvalId = 7;
}

message QueryGetApprovalTrackerResponse {
  ApprovalTracker tracker = 1;
}

message ApprovalTracker {
  string numTransfers = 1; // Number of transfers that have been processed
  repeated Balance amounts = 2; // Cumulative balances associated with processed transfers
  string lastUpdatedAt = 3; // Last updated at time (UNIX millisecond timestamp)
}
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-approval-tracker [collectionId] [approvalLevel] [approverAddress] [approvalId] [amountTrackerId] [trackerType] [approvedAddress]

# REST API
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_approvals_tracker/1/outgoing/bb1.../approval-1/tracker-1/overall/"
```

### Response Example

```json
{
  "tracker": {
    "numTransfers": "5",
    "amounts": [
      {
        "amount": "100",
        "tokenIds": [{"start": "1", "end": "10"}],
        "ownershipTimes": [{"start": "1672531200000", "end": "18446744073709551615"}]
      }
    ],
    "lastUpdatedAt": "1672531200000"
  }
}
```


# GetBalance

Retrieves balances for a specific address in a collection.

## Proto Definition

```protobuf
message QueryGetBalanceRequest {
  string collectionId = 1; // Collection ID to query
  string address = 2; // Address to get balances for
}

message QueryGetBalanceResponse {
  UserBalanceStore balance = 1;
}

message UserBalanceStore {
  repeated Balance balances = 1; // List of balances associated with this user
  repeated UserOutgoingApproval outgoingApprovals = 2; // Approved outgoing transfers
  repeated UserIncomingApproval incomingApprovals = 3; // Approved incoming transfers
  bool autoApproveSelfInitiatedOutgoingTransfers = 4; // Auto-approve self-initiated outgoing transfers
  bool autoApproveSelfInitiatedIncomingTransfers = 5; // Auto-approve self-initiated incoming transfers
  bool autoApproveAllIncomingTransfers = 6; // Auto-approve all incoming transfers
  UserPermissions userPermissions = 7; // Permissions for this user's actions
}

// See all the proto definitions [here](https://github.com/BitBadges/bitbadgeschain/tree/master/proto/tokenization)
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-balance [collection-id] [address]

# REST API
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_balance/1/bb1..."
```

### Response Example

```json
{
    "balance": {
        "balances": [
            {
                "amount": "1",
                "tokenIds": [{ "start": "1", "end": "1" }],
                "ownershipTimes": [
                    { "start": "1672531200000", "end": "18446744073709551615" }
                ]
            }
        ],
        "outgoingApprovals": [
            // ...
        ],
        "incomingApprovals": [
            // ...
        ],
        "autoApproveSelfInitiatedOutgoingTransfers": true,
        "autoApproveSelfInitiatedIncomingTransfers": true,
        "autoApproveAllIncomingTransfers": true,
        "userPermissions": {
            // ...
        }
    }
}
```


# GetChallengeTracker

Retrieves the number of times a given leaf has been used for a specific challenge tracker.

## Proto Definition

```protobuf
message QueryGetChallengeTrackerRequest {
  string collectionId = 1;
  string approvalLevel = 2; // "collection", "incoming", or "outgoing"
  string approverAddress = 3; // Leave blank if approvalLevel is "collection"
  string challengeTrackerId = 4;
  string leafIndex = 5;
  string approvalId = 6;
}

message QueryGetChallengeTrackerResponse {
  string numUsed = 1; // Number of times this leaf has been used
}
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-challenge-tracker [collectionId] [approvalLevel] [approverAddress] [approvalId] [challengeTrackerId] [leafIndex]

# REST API
# Note for blank values, use "" so you may have // in the query
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_challenge_tracker/1/collection//approval-123/challenge-1/42"
```

### Response Example

```json
{
    "numUsed": "1"
}
```


# GetCollection

Retrieves complete information about a collection.

## Proto Definition

```protobuf
message QueryGetCollectionRequest {
  string collectionId = 1; // ID of collection to retrieve
}

message QueryGetCollectionResponse {
  TokenCollection collection = 1;
}

message TokenCollection {
  string collectionId = 1; // Unique identifier for this collection
  CollectionMetadata collectionMetadata = 2; // Collection metadata
  repeated TokenMetadata tokenMetadata = 3; // Token metadata
  string customData = 4; // Arbitrary custom data
  string manager = 5; // Manager address
  CollectionPermissions collectionPermissions = 6; // Collection permissions
  repeated CollectionApproval collectionApprovals = 7; // Collection-level approvals
  repeated string standards = 8; // Standards
  bool isArchived = 9; // Archive status
  UserBalanceStore defaultBalances = 10; // Default balance store for users
  string createdBy = 11; // Creator of the collection
  repeated UintRange validTokenIds = 12; // Valid token ID ranges
  string mintEscrowAddress = 13; // Generated escrow address for the collection
}

// See all the proto definitions [here](https://github.com/bitbadges/bitbadgeschain/tree/master/proto/tokenization)
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-collection [id]

# REST API
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_collection/1"
```

### Response Example

```json
{
    "collection": {
        "collectionId": "1"
        // ...
    }
}
```


# GetDynamicStore

Retrieves information about a dynamic store.

## Proto Definition

```protobuf
message QueryGetDynamicStoreRequest {
  string storeId = 1;
}

message QueryGetDynamicStoreResponse {
  DynamicStore store = 1;
}

message DynamicStore {
  // The unique identifier for this dynamic store. This is assigned by the blockchain.
  string storeId = 1 [(gogoproto.customtype) = "Uint", (gogoproto.nullable) = false];
  // The address of the creator of this dynamic store.
  string createdBy = 2;
  // The default value for uninitialized addresses.
  bool defaultValue = 3;
  // Global kill switch. When false, all approvals using this store fail immediately.
  bool globalEnabled = 4;
  // URI for additional metadata or resources associated with this dynamic store.
  string uri = 5;
  // Custom data field for storing arbitrary data associated with this dynamic store.
  string customData = 6;
}
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-dynamic-store [store-id]

# REST API
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_dynamic_store/1"
```

### Response Example

```json
{
    "store": {
        "storeId": "1",
        "createdBy": "bb1...",
        "defaultValue": false,
        "globalEnabled": true,
        "uri": "https://example.com/metadata",
        "customData": "{\"key\": \"value\"}"
    }
}
```

**Note**: Both `uri` and `customData` are optional fields. They may be empty strings (`""`) if not set when creating or updating the store.

## Global Kill Switch

The `globalEnabled` field indicates whether the global kill switch is enabled for this store. When `globalEnabled = false`, all approvals using this store via `DynamicStoreChallenge` will fail immediately, regardless of per-address values.

* **New stores**: Default to `globalEnabled = true`
* **Existing stores**: Set to `globalEnabled = true` for backward compatibility
* **Disabling**: Use [MsgUpdateDynamicStore](/token-standard/messages/msg-update-dynamic-store) to set `globalEnabled = false`


# GetDynamicStoreValue

Retrieves the boolean value for a specific address in a dynamic store.

## Proto Definition

```protobuf
message QueryGetDynamicStoreValueRequest {
  string storeId = 1; // ID of dynamic store to query
  string address = 2; // Address to get value for
}

message QueryGetDynamicStoreValueResponse {
  DynamicStoreValue value = 1;
}

message DynamicStoreValue {
  string storeId = 1; // The dynamic store ID
  string address = 2; // The address this value applies to
  bool value = 3; // The boolean value
}
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-dynamic-store-value [store-id] [address]

# REST API
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_dynamic_store_value/1/bb1..."
```

### Response Example

```json
{
    "value": {
        "storeId": "1",
        "address": "bb1...",
        "value": true
    }
}
```


# GetETHSignatureTracker

Retrieves the number of times a given signature has been used for a specific ETH signature challenge tracker.

## Proto Definition

```protobuf
message QueryGetETHSignatureTrackerRequest {
  string collectionId = 1;
  string approvalLevel = 2; // "collection", "incoming", or "outgoing"
  string approverAddress = 3; // Leave blank if approvalLevel is "collection"
  string approvalId = 4;
  string challengeTrackerId = 5;
  string signature = 6;
}

message QueryGetETHSignatureTrackerResponse {
  string numUsed = 1; // Number of times this signature has been used
}
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization get-num-used-for-eth-signature-challenge [collectionId] [approvalLevel] [approverAddress] [approvalId] [challengeTrackerId] [signature]

# REST API
# Note for blank values, use "" so you may have // in the query
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/get_eth_signature_tracker/1/collection//approval-123/challenge-1/bb1..."
```

### Response Example

```json
{
    "numUsed": "1"
}
```

## Notes

* Each signature can only be used once per challenge tracker
* If a signature has never been used, the response will be "0"
* The signature parameter should be the full Ethereum signature (0x-prefixed hex string)


# Params

Retrieves the current module parameters.

## Proto Definition

```protobuf
message QueryParamsRequest {}

message QueryParamsResponse {
  Params params = 1;
}

message Params {
  // Array of allowed denominations for fee payments and escrow operations
  repeated string allowed_denoms = 1;
}
```

## Usage Example

```bash
# CLI query
bitbadgeschaind query tokenization params

# REST API
curl "https://lcd.bitbadges.io/bitbadges/bitbadgeschain/tokenization/params"
```

### Response Example

```json
{
  "params": {
    "allowedDenoms": ["ubadge", "ibc/1234567890"]
  }
}
```


# Examples and Snippets

This directory contains practical examples and building blocks for x/tokenization.

## Contents

* [Base Collection Configuration](/token-standard/examples/base-collection-details) - Standard base collection configuration template
* [Empty Approval Criteria](/token-standard/examples/empty-approval-criteria) - Template for unrestricted approval criteria
* [Defining Circulating Supply](/token-standard/examples/defining-circulating-supply) - How to define and lock circulating supply
* [Building Collection Approvals](/token-standard/examples/building-collection-approvals) - Guide to building collection-level approvals
* [Building User Approvals](/token-standard/examples/building-user-approvals) - Guide to building user-level incoming and outgoing approvals
* [Building Collection Permissions](/token-standard/examples/building-collection-permissions) - Guide to configuring collection permissions
* [Building User Permissions](/token-standard/examples/building-user-permissions) - Guide to configuring user-level permissions
* [Cosmos Coin Wrapper Example](/token-standard/examples/cosmos-coin-wrapper-example) - Example of wrapping tokens as Cosmos coins
* [Mint All to Self Tutorial](/token-standard/examples/mint-all-to-self-tutorial) - Tutorial for creating collection and minting tokens to yourself
* [Approvals](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/examples/approvals/README.md) - Common approval patterns and examples
  * [Transferable Approval](/token-standard/examples/approvals/transferable-approval) - Basic transferable approval configuration
  * [Burnable Approval](/token-standard/examples/approvals/burnable-approval) - Approval allowing tokens to be burned
  * [Cosmos Wrapper Approval](/token-standard/examples/approvals/cosmos-wrapper-approval) - Approval for wrapping tokens as Cosmos coins
  * [Cosmos Unwrapper Approval](/token-standard/examples/approvals/cosmos-unwrapper-approval) - Approval for unwrapping Cosmos coins back to tokens
  * [Admin Override Approval](/token-standard/examples/approvals/admin-override-approval) - Admin approval that overrides user-level restrictions
* [Permissions](/token-standard/examples/permissions) - Common permission patterns and examples
* [Transactions](/token-standard/examples/txs) - Full transaction examples
  * [MsgCreateCollection](/token-standard/examples/txs/msgcreatecollection) - Complete transaction examples for creating collections


# Base Collection Details

BitBadges collections are very expressive but also can lead to verbose configurations. We will provide additional examples in this section but also refer you to the corresponding concepts section for more details on any specific field.

### Reference Links

For detailed information about each field, see the corresponding concepts documentation:

| Field                         | Concepts Link                                                                                    |
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
| `validTokenIds`               | [Collection Setup Fields](/token-standard/learn/collection-setup-fields)                         |
| `manager`                     | [Manager / Permissions](/token-standard/learn/permissions)                                       |
| `collectionMetadata`          | [Collection Setup Fields](/token-standard/learn/collection-setup-fields)                         |
| `tokenMetadata`               | [Collection Setup Fields](/token-standard/learn/collection-setup-fields)                         |
| `customData`                  | [Collection Setup Fields](/token-standard/learn/collection-setup-fields)                         |
| `standards`                   | [Collection Setup Fields](/token-standard/learn/collection-setup-fields)                         |
| `isArchived`                  | [Collection Setup Fields](/token-standard/learn/collection-setup-fields)                         |
| `defaultBalances`             | [Collection Setup Fields](/token-standard/learn/collection-setup-fields)                         |
| `mintEscrowCoinsToTransfer`   | [Coin Transfers](/token-standard/learn/approval-criteria/usdbadge-transfers#mint-escrow-address) |
| `cosmosCoinWrapperPathsToAdd` | [Cosmos Coin Wrapper Paths](/token-standard/learn/cosmos-coin-wrapper-paths)                     |

## Base Collection Details

For most collections, your base configuration for these fields will be very similar to this. Note that this excludes collection permissions and approvals. See the [Building Collection Approvals](/token-standard/examples/building-collection-approvals) example and [Building Collection Permissions](/token-standard/examples/building-collection-permissions) example for these.

```typescript
const BaseCollectionDetails = {
    validTokenIds: [
        {
            start: '1',
            end: '100', // Set to your max ID
        },
    ],
    manager: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls', // Set to your address
    collectionMetadata: {
        uri: 'ipfs://QmSTZZPgYF58gS9bM7q3nWVegUJH51WBdT91fz7q94qDwS', // Points to a valid .json metadata file
        customData: '',
    },
    tokenMetadata: [
        {
            uri: 'ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/{id}', // Points to a valid .json metadata file (replacing {id} with the token ID)
            tokenIds: [
                {
                    start: '1',
                    end: '100',
                },
            ],
            customData: '',
        },
        // You can have multiple entries. This is useful for placeholder metadata.
        {
            uri: 'ipfs://QmSTZZPgYF58gS9bM7q3nWVegUJH51WBdT91fz7q94qDwS', // Placeholder metadata
            tokenIds: [
                {
                    start: '101',
                    end: '100000000',
                },
            ],
            customData: '',
        },
    ],
    customData: '',
    standards: ['Subscriptions'],
    isArchived: false,

    // Coins to send to the mint escrow address. You can also fund after the fact. This is just useful for genesis since the address is dependent on the collectionId which you don't know until after the collection is created.
    mintEscrowCoinsToTransfer: [
        {
            denom: 'ubadge',
            amount: '1',
        },
    ],

    // If you want to add paths to wrap tokens as Cosmos coins, you can do so here.
    cosmosCoinWrapperPathsToAdd: [],

    defaultBalances: {
        // Everyone starts with empty balances and no approvals
        balances: [],
        incomingApprovals: [],
        outgoingApprovals: [],
        // Empty = Soft Enabled (i.e. enabled but can be disabled at any time by each user)
        userPermissions: {
            canUpdateOutgoingApprovals: [],
            canUpdateIncomingApprovals: [],
            canUpdateAutoApproveSelfInitiatedOutgoingTransfers: [],
            canUpdateAutoApproveSelfInitiatedIncomingTransfers: [],
            canUpdateAutoApproveAllIncomingTransfers: [],
        },

        // Typically, these flags are all you need to set.
        autoApproveSelfInitiatedIncomingTransfers: true,
        autoApproveSelfInitiatedOutgoingTransfers: true,
        autoApproveAllIncomingTransfers: true,
    },
};
```

For information on building collection approvals, see [Building Collection Approvals](/token-standard/examples/building-collection-approvals).


# Building Your Collection Approvals

The collection-level transferability is determined by the collection-level approvals. The important thing to consider here is that any approval that allows transfers from the "Mint" address will mint balances out of thin air.

## Approval Categories

It is typically recommended to split into two categories:

* **Mint Approvals** (`fromListId: 'Mint'`)
* **Post-Mint Approvals** (`fromListId: '!Mint'`)

## Important Notes

1. The reserved "All" list ID includes Mint. Do not use "All" for the fromListId for post-mint approvals.
2. To function, the "Mint" approval must forcefully override the user-level outgoing approval because it cannot be managed.

## Code Example

Mix and match the approvals as you see fit. See the examples in the Approvals folder for a bunch of examples.

* [Transferable Approval](/token-standard/examples/approvals/transferable-approval)
* [Burnable Approval](/token-standard/examples/approvals/burnable-approval)

```typescript
const mintApprovals = [
    // Mint approvals with fromListId: 'Mint'
];

const postMintApprovals = [
    // Post-mint approvals with fromListId: '!Mint'
    transferableApproval,
    burnableApproval,
];

const collectionApprovals = [...mintApprovals, ...postMintApprovals];

const collectionApprovals = [...mintApprovals, ...postMintApprovals];
```


# Building Your Collection Permissions

Collection permissions are executable by the manager. They are used to control who can perform various management actions on your collection and when those actions are allowed.

```typescript
const manager = collection.getCurrentManager();
```

## Setting Your Permissions

You have a few options for setting your permissions.

1. No Manager

If you simply don't want a manager, you can set the manager to an empty string. Then, the permission values never matter.

```typescript
const manager = '';
```

2. Complete Control - Soft Enabled

Each permission is enabled by default, unless you permanently disabled it. Thus, an empty array means that the permission is enabled for all times. However, it is soft enabled, meaning that the manager can disable it at any time. This configuration offers full control with ability to disable in the future.

```typescript
const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [],
    canUpdateValidTokenIds: [],
    canAddMoreAliasPaths: [],
    canAddMoreCosmosCoinWrapperPaths: [],
};
```

3. Custom Permissions

Oftentimes, you want a little more control over your permissions though.

Each permission follows the same pattern:

1. For the times `permanentlyPermittedTimes`, the permission is always permitted for the given values.
2. For the times `permanentlyForbiddenTimes`, the permission is always forbidden for the given values.
3. If the item is not explicity in either, then the permission is enabled for the given values, but the status can change.

```typescript
const CanArchiveCollection = {
    permanentlyPermittedTimes: [],
    permanentlyForbiddenTimes: FullTimeRanges,
};
```

Each permission type follows the same pattern of two categories:

```typescript
// Part 1. Enabled vs Disabled Times For The Execution Of The Permission
const permanentlyPermittedTimes = [];
const permanentlyForbiddenTimes = FullTimeRanges;

// Part 2. For what values (if any) does this apply? This is dependent on the permission type.
const {
    tokenIds,
    fromListId,
    toListId,
    initiatedByListId,
    transferTimes,
    ownershipTimes,
    approvalId,
} = permission;
```

## Main Permissions To Consider

1. Should the number of token IDs in the collection be expandable? frozen upon genesis? -> Handle with `canUpdateValidTokenIds`
2. What about the transferability? -> Handle with `canUpdateCollectionApprovals`
   * Should the transferability be frozen upon genesis?
   * Should we disallow updating transferability for only some token IDs? some approvals? Mint? Post-Mint?
   * This could be critical for enforcing total circulating supply. For example, if you can create more approvals from the Mint address, then you can theoretically mint however many tokens you want.

## Examples

We refer you to the [examples](/token-standard/examples/permissions) or relevant concepts for more detailed examples.


# Building User-Level Approvals

User-level approvals allow individual users to control their token transfers through incoming and outgoing approvals. These work similarly to [collection-level approvals](/token-standard/examples/building-collection-approvals) with key restrictions.

We refer you to the collection-level examples and just apply the same logic to the user-level types with these differences.

## Key Differences from Collection Approvals

* **Fixed Address Lists**:
  * Incoming approvals: `fromListId` is locked to the user's address
  * Outgoing approvals: `toListId` is locked to the user's address
* **No Override Functionality**: Cannot override other approval levels
* **User-Controlled**: Only the user can update their own approvals

## Incoming Approvals

Control what tokens the user can receive:

```typescript
const userIncomingApproval = {
    fromListId: 'user-address', // Locked to approver's address
    toListId: 'All', // Can specify recipients
    initiatedByListId: 'All',
    transferTimes: [{ start: '1', end: '18446744073709551615' }],
    tokenIds: [{ start: '1', end: '100' }],
    ownershipTimes: [{ start: '1', end: '18446744073709551615' }],
    approvalId: 'user-incoming-approval',

    // Use any approval criteria from collection examples
    approvalCriteria: {
        // See: transferable-approval.md, burnable-approval.md, etc.
        // OR use EmptyApprovalCriteria for no restrictions
        ...EmptyApprovalCriteria,
    },
};
```

## Outgoing Approvals

Control what tokens the user can send:

```typescript
const userOutgoingApproval = {
    fromListId: 'All', // Can specify senders
    toListId: 'user-address', // Locked to approver's address
    initiatedByListId: 'All',
    transferTimes: [{ start: '1', end: '18446744073709551615' }],
    tokenIds: [{ start: '1', end: '100' }],
    ownershipTimes: [{ start: '1', end: '18446744073709551615' }],
    approvalId: 'user-outgoing-approval',

    // Use any approval criteria from collection examples
    approvalCriteria: {
        // See: transferable-approval.md, burnable-approval.md, etc.
        // OR use EmptyApprovalCriteria for no restrictions
        ...EmptyApprovalCriteria,
    },
};
```

## Implementation

Users update their approvals via `MsgUpdateUserApprovals`:

```typescript
const updateUserApprovals = {
    creator: 'bb1...', // Your address
    collectionId: '1',
    updateIncomingApprovals: true,
    incomingApprovals: [userIncomingApproval],
    updateOutgoingApprovals: true,
    outgoingApprovals: [userOutgoingApproval],
    // ...
};
```

## Reference

For approval criteria examples, see:

* [Empty Approval Criteria](/token-standard/examples/empty-approval-criteria) - No restrictions template
* [Transferable Approval](/token-standard/examples/approvals/transferable-approval) - Basic transfer restrictions
* [Burnable Approval](/token-standard/examples/approvals/burnable-approval) - Burn functionality
* [Address Checks](/token-standard/learn/approval-criteria/address-checks) - Address type restrictions
* [EVM Query Challenges](/token-standard/learn/approval-criteria/evm-query-challenges) - Token-gating via EVM contract queries
* [Building Collection Approvals](/token-standard/examples/building-collection-approvals) - Collection-level patterns

For concepts, see:

* [Transferability](/token-standard/learn/transferability)
* [Approval Criteria](/token-standard/learn/approval-criteria)


# Building User-Level Permissions

User-level permissions allow individual users to control their ability to update their own approvals. Note that these are almost always never needed unless in advanced situations. Typically, you just leave these soft-enabled (empty arrays) for all. These are only really needed in advanced situations where you want to lock down a user's ability to update their own approvals, such as escrow accounts.

The canUpdateOutgoingApprovals and canUpdateIncomingApprovals work similarly to [canUpdateCollectionApprovals](/token-standard/examples/building-collection-permissions) with key restrictions. - `fromListId` is locked to the user's address for outgoing approvals - `toListId` is locked to the user's address for incoming approvals

## User Permission Structure

```typescript
const userPermissions = {
    canUpdateOutgoingApprovals: [
        {
            // fromListId: 'user-address', // Locked to user's address
            toListId: 'All', // Can specify recipients
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: FullTimeRanges,
            ownershipTimes: FullTimeRanges,
            approvalId: 'All',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // Lock forever
        },
    ],
    canUpdateIncomingApprovals: [
        {
            fromListId: 'All', // Can specify senders
            //  toListId: 'user-address', // Locked to user's address
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: FullTimeRanges,
            ownershipTimes: FullTimeRanges,
            approvalId: 'All',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // Lock forever
        },
    ],
    canUpdateAutoApproveSelfInitiatedOutgoingTransfers: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges,
        },
    ],
    canUpdateAutoApproveSelfInitiatedIncomingTransfers: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges,
    canUpdateAutoApproveAllIncomingTransfers: [
        {
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges,
        },
    ],
};
```

## Implementation

Users update their permissions via `MsgUpdateUserApprovals`:

```typescript
const updateUserApprovals = {
    creator: 'bb1...', // User's address
    collectionId: '1',
    updateUserPermissions: true,
    userPermissions,
    // ... other approval updates
};
```

## Related Examples

For permission patterns, see:

* [Freezing Mint Transferability](/token-standard/examples/permissions/freezing-mint-transferability) - Collection permission example
* [Locking Specific Approval ID](/token-standard/examples/permissions/locking-specific-approval-id) - Approval ID targeting
* [Locking Specific Token IDs](/token-standard/examples/permissions/locking-specific-token-ids) - Token ID targeting
* [Building Collection Permissions](/token-standard/examples/building-collection-permissions) - Collection-level patterns

For user approval configuration, see:

* [Building User Approvals](/token-standard/examples/building-user-approvals) - User approval setup


# Cosmos Coin Wrapper Tutorial

This tutorial walks you through setting up cosmos coin wrappers to bridge BitBadges with the broader Cosmos ecosystem. Cosmos coin wrappers automatically convert tokens to fungible Cosmos coins and vice versa.

## Prerequisites

* Understanding of [Cosmos Wrapper Paths](/token-standard/learn/cosmos-coin-wrapper-paths)
* Basic knowledge of BitBadges collections and approvals

## Step 1: Set Up Your Cosmos Denominations

First, define your cosmos coin wrapper paths. For detailed information about available options, see [Cosmos Wrapper Paths](/token-standard/learn/cosmos-coin-wrapper-paths).

```typescript
const cosmosCoinWrapperPaths = [ ... ];
```

## Step 2: Generate Your Special Address

When you create a collection with cosmos coin wrapper paths, the system automatically generates a special address for each wrapper. This address acts as the bridge between tokens and cosmos coins. This will also be available on the BitBadges site if you want to go that route.

```typescript
import { generateAliasAddressForDenom } from 'bitbadges';

const denom = 'utoken1';
const wrapperAddress = generateAliasAddressForDenom(denom);
console.log('Wrapper Address:', wrapperAddress);
```

### Dynamic Address Generation for {id} Placeholders

The {id} is actually kept for the hash preimage, so we always have one address per wrapper path regardless of the token ID.

## Step 3: Set Up Approvals for Wrapping/Unwrapping

The transfers still operate under the approval / transferability system. We will use the following examples from our examples section, but you can customize as you see fit. Note the need to override the wrapper address's approvals where necessary because the wrapper address is uncontrollable.

* [Cosmos Wrapper Approval](/token-standard/examples/approvals/cosmos-wrapper-approval)
* [Cosmos Unwrapper Approval](/token-standard/examples/approvals/cosmos-unwrapper-approval)

```typescript
const collection = {
    ...BaseCollectionDetails,
    collectionApprovals: [
        ...otherApprovals,
        wrapperApproval,
        unwrapperApproval,
    ],
};
```


# Defining and Locking Circulating Supply

This example demonstrates how circulating supply is dynamically calculated and how to control it through mint approval management.

## Overview

Unlike traditional blockchains with set-and-forget supply mechanisms, BitBadges supply is **dynamically calculated** based on the ability to use mint approvals and the ability to create new ones or edit them.

Thus, note that if the manager can create any new Mint approval, they can theoretically increase the supply by whatever the approval allows.

## Lock Supply Forever (Fixed Cap)

```typescript
const FullTimeRanges = [
    {
        start: '1',
        end: '18446744073709551615',
    },
];

const collectionPermissions = {
    // ... other permissions
    canUpdateCollectionApprovals: [
        {
            fromListId: 'Mint', // Target all mint approvals
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: FullTimeRanges,
            ownershipTimes: FullTimeRanges,
            approvalId: 'All',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // Cannot update mint approvals
        },
    ],
};
```

**Result**: All Mint approvals are final. Whatever currently possible is possible but final.

## Controlled Supply (Managed Growth)

```typescript
const collectionPermissions = {
    // ... other permissions
    canUpdateCollectionApprovals: [
        {
            fromListId: 'Mint',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: FullTimeRanges,
            ownershipTimes: FullTimeRanges,
            approvalId: 'initial-mint', // Only lock initial mint approval
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges,
        },
    ],
};
```

**Result**: "initial-mint" approval locked, but manager can add new ones.

## Dynamic Supply (Fully Flexible)

```typescript
const collectionPermissions = {
    // ... other permissions
    canUpdateCollectionApprovals: [], // Soft-enabled
    canAddMoreAliasPaths: [],
    canAddMoreCosmosCoinWrapperPaths: [],
};
```

**Result**: Manager can always modify mint approvals and adjust supply

## Lock Specific Token IDs

```typescript
const collectionPermissions = {
    // ... other permissions
    canUpdateCollectionApprovals: [
        {
            fromListId: 'Mint',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: [
                {
                    start: '1',
                    end: '100',
                },
            ],
            ownershipTimes: FullTimeRanges,
            approvalId: 'All',
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges,
        },
    ],
};
```

**Result**: The Mint approvals for tokens 1-100 are locked and final. The manager can still create new Mint approvals for other token IDs or post-mint approvals for those tokens.

## Related Examples

* [Freezing Mint Transferability](/token-standard/examples/permissions/freezing-mint-transferability) - Lock all mint approvals
* [Building Collection Approvals](/token-standard/examples/building-collection-approvals) - Create mint approvals
* [Empty Approval Criteria](/token-standard/examples/empty-approval-criteria) - Unlimited mint template


# Empty Approval Criteria Template

When creating collection approvals with empty approval criteria, you can use this template for "no additional restrictions". We reference this for simplicity in other examples.

## Template

```typescript
const EmptyApprovalCriteria = {
    approvalCriteria: {
        // No challenges to be completed
        merkleChallenges: [],
        // No specific balances to check
        predeterminedBalances: {
            manualBalances: [],
            incrementedBalances: {
                startBalances: [],
                incrementTokenIdsBy: '0',
                incrementOwnershipTimesBy: '0',
                durationFromTimestamp: '0',
                allowOverrideTimestamp: false,
                recurringOwnershipTimes: {
                    startTime: '0',
                    intervalLength: '0',
                    chargePeriodLength: '0',
                },
                allowOverrideWithAnyValidToken: false,
                allowAmountScaling: false,
                maxScalingMultiplier: '0',
            },
            orderCalculationMethod: {
                useOverallNumTransfers: false,
                usePerToAddressNumTransfers: false,
                usePerFromAddressNumTransfers: false,
                usePerInitiatedByAddressNumTransfers: false,
                useMerkleChallengeLeafIndex: false,
                challengeTrackerId: '',
            },
        },
        // No approval amounts to check (0 = unlimited)
        approvalAmounts: {
            overallApprovalAmount: '0',
            perToAddressApprovalAmount: '0',
            perFromAddressApprovalAmount: '0',
            perInitiatedByAddressApprovalAmount: '0',
            amountTrackerId:
                'a4ab9bc5e8752842a35a79238de4f627677ceae1d8fa9de44b52416e085f7f11',
            resetTimeIntervals: {
                startTime: '0',
                intervalLength: '0',
            },
        },
        // No max number of transfers to check (0 = unlimited)
        maxNumTransfers: {
            overallMaxNumTransfers: '0',
            perToAddressMaxNumTransfers: '0',
            perFromAddressMaxNumTransfers: '0',
            perInitiatedByAddressMaxNumTransfers: '0',
            amountTrackerId:
                'd711e23dbe57b786dfb2d86d4a6792fb8c9951a18223065ea0c07d424225a738',
            resetTimeIntervals: {
                startTime: '0',
                intervalLength: '0',
            },
        },
        // No coin transfers to execute
        coinTransfers: [],

        // No ETH signature challenges to be completed
        ethSignatureChallenges: [],
        // No dynamic store challenges to be completed
        dynamicStoreChallenges: [],

        // No address matching requirements
        requireToEqualsInitiatedBy: false,
        requireFromEqualsInitiatedBy: false,
        requireToDoesNotEqualInitiatedBy: false,
        requireFromDoesNotEqualInitiatedBy: false,
        // No overrides from outgoing approvals
        overridesFromOutgoingApprovals: false,
        // No overrides to incoming approvals
        overridesToIncomingApprovals: false,
        // No auto deletion options
        autoDeletionOptions: {
            afterOneUse: false,
            afterOverallMaxNumTransfers: false,
        },
        // No user royalties
        userRoyalties: {
            percentage: '0',
            payoutAddress: '',
        },
        // No tokens to check ownership of
        mustOwnTokens: [],
        // No address checks
        senderChecks: {
            mustBeEvmContract: false,
            mustNotBeEvmContract: false,
            mustBeLiquidityPool: false,
            mustNotBeLiquidityPool: false,
        },
        recipientChecks: {
            mustBeEvmContract: false,
            mustNotBeEvmContract: false,
            mustBeLiquidityPool: false,
            mustNotBeLiquidityPool: false,
        },
        initiatorChecks: {
            mustBeEvmContract: false,
            mustNotBeEvmContract: false,
            mustBeLiquidityPool: false,
            mustNotBeLiquidityPool: false,
        },
        // No alternative time checks
        altTimeChecks: {
            offlineHours: [],
            offlineDays: [],
        },
        // No priority requirement
        mustPrioritize: false,
        // No EVM query challenges
        evmQueryChallenges: [],
        // No voting challenges
        votingChallenges: [],
    },
};
```

## Related Documentation

* [Approval Criteria Overview](/token-standard/learn/approval-criteria)
* [Building Collection Approvals](/token-standard/examples/building-collection-approvals)
* [Transferability](/token-standard/learn/transferability)


# Mint All Tokens to Self - Tutorial

This tutorial walks through the process of creating a collection and minting all tokens to yourself in a single transaction. This is useful for creating collections where you want to control the initial distribution.

## Overview

This is a two-step process that can be executed as a single multi-message transaction:

1. **Create Collection** with a mint approval that allows you to mint tokens
2. **Execute Transfer** using that approval to mint tokens to yourself

## Step 1: Create Mint Approval

First, create an approval that allows you to mint tokens from the "**Mint**" address:

```typescript
// Step 1: Set up your mint approval
const mintApproval = {
    fromListId: 'Mint', // From the mint address
    toListId: 'All', // To any address
    initiatedByListId: myAddress, // Only you can initiate
    transferTimes: UintRangeArray.FullRanges(),
    tokenIds: UintRangeArray.FullRanges(), // All token IDs
    ownershipTimes: UintRangeArray.FullRanges(),
    approvalId: 'mint-approval',
    version: 0n,
    approvalCriteria: {
        // No restrictions - you can mint unlimited amounts
        ...defaultNoRestrictionsApprovalCriteria,
        overridesFromOutgoingApprovals: true, // Required for mint address
    },
};

// Step 1: Create your collection with the mint approval
const collection = {
    ...BaseCollectionDetails,
    collectionApprovals: [mintApproval, ...otherApprovals],
};

// Create the collection
```

## Step 2: Execute Mint Transfer

After creating the collection, use the mint approval to transfer tokens to yourself:

```typescript
// Step 2: Mint tokens to yourself using the approval
const transfers = [
    {
        from: 'Mint', // From mint address
        toAddresses: [myAddress], // To your address
        balances: [
            {
                tokenIds: [{ start: 1n, end: 100n }],
                ownershipTimes: UintRangeArray.FullRanges(),
                amount: 100n,
            },
        ],
        // ... other transfer details
    },
];
```


# Approvals


# Admin Override Approval

This example demonstrates how to create an approval that allows a specific address to forcefully transfer tokens, overriding all user-level approvals. This provides complete administrative control for emergency situations or management purposes.

## Overview

An admin override approval grants a specific address the power to:

* Transfer tokens from any address to any address
* Override user-level incoming and outgoing approvals
* Bypass normal approval restrictions
* Maintain complete administrative control

⚠️ **Warning**: This approval type grants significant power and should be used carefully with trusted addresses only.

## Code Example

```typescript
const approveSelfForcefully = (address: string) => {
    const id = 'complete-admin-control';

    return {
        fromListId: 'Mint',
        toListId: 'All',
        initiatedByListId: address,
        transferTimes: UintRangeArray.FullRanges(),
        tokenIds: UintRangeArray.FullRanges(),
        ownershipTimes: UintRangeArray.FullRanges(),
        approvalId: id,
        version: 0n,
        approvalCriteria: {
            ...EmptyApprovalCriteria,
            overridesFromOutgoingApprovals: true,
            overridesToIncomingApprovals: true,
        },
    };
};
```


# Burnable Approval

This example demonstrates how to create a burnable approval that allows tokens to be sent to the burn address, effectively removing them from circulation.

## Overview

A burnable approval enables tokens to be permanently destroyed by sending them to the zero address.

## Code Example

```typescript
const burnableApproval = new CollectionApproval({
    fromListId: '!Mint', // Excludes the Mint address
    toListId: 'bb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqs7gvmv', // Burn address (bb-prefixed)
    initiatedByListId: 'All',
    transferTimes: UintRangeArray.FullRanges(),
    ownershipTimes: UintRangeArray.FullRanges(),
    tokenIds: UintRangeArray.FullRanges(),
    approvalId: 'burnable-approval',
    version: 0n,
    approvalCriteria: undefined, // No additional restrictions
});
```


# Cosmos Unwrapper Approval

This example demonstrates how to create an approval that allows the Cosmos coin wrapper address to send tokens back to users, enabling conversion from Cosmos coins back to tokens (unwrapping).

You pretty much: 1) figure out your address and 2) figure out a path that users can send from this address without needing the address to control its approvals.

Full example: [Cosmos Coin Wrapper Example](/token-standard/examples/cosmos-coin-wrapper-example)

## Code Example

```typescript
export const unwrapperApproval = ({
    specialAddress,
    tokenIds,
    ownershipTimes,
    approvalId,
}: {
    specialAddress: string;
    tokenIds: iUintRange<bigint>[];
    ownershipTimes: iUintRange<bigint>[];
    approvalId: string;
}): RequiredApprovalProps => {
    const id = approvalId;
    const toSet: RequiredApprovalProps = {
        version: 0n,
        fromListId: specialAddress,
        fromList: AddressList.getReservedAddressList(specialAddress),
        toListId: 'All',
        toList: AddressList.AllAddresses(),
        initiatedByListId: 'All',
        initiatedByList: AddressList.AllAddresses(),
        transferTimes: UintRangeArray.FullRanges(),
        tokenIds: tokenIds,
        ownershipTimes: ownershipTimes,
        approvalId: id,
        approvalCriteria: {
            ...EmptyApprovalCriteria,
            allowSpecialWrapping: true, // Required for wrapper path operations
            mustPrioritize: true, // Chain-enforced: must be true for special wrapping approvals
            overridesFromOutgoingApprovals: true,
        },
    };

    return toSet;
};
```


# Cosmos Wrapper Approval

This example demonstrates how to create an approval that allows tokens to be sent to a Cosmos coin wrapper address, enabling conversion to native Cosmos SDK coins.

You pretty much: 1) figure out your address and 2) figure out a path that users can send to this address without needing the address to control its approvals. Oftentimes, you may not even need to forcefully override the incoming approvals because you default allow all incoming transfers which also applies to the wrapper address automatically.

Full example: [Cosmos Coin Wrapper Example](/token-standard/examples/cosmos-coin-wrapper-example)

## Code Example

```typescript
export const wrapperApproval = ({
    specialAddress,
    tokenIds,
    ownershipTimes,
    approvalId,
}: {
    specialAddress: string;
    tokenIds: iUintRange<bigint>[];
    ownershipTimes: iUintRange<bigint>[];
    approvalId: string;
}): RequiredApprovalProps => {
    const id = approvalId;
    const toSet: RequiredApprovalProps = {
        version: 0n,
        toListId: specialAddress,
        toList: AddressList.getReservedAddressList(specialAddress),
        fromListId: 'AllWithoutMint',
        fromList: AddressList.getReservedAddressList('AllWithoutMint'),
        initiatedByListId: 'All',
        initiatedByList: AddressList.AllAddresses(),
        transferTimes: UintRangeArray.FullRanges(),
        tokenIds: tokenIds,
        ownershipTimes: ownershipTimes,
        approvalId: id,
        approvalCriteria: {
            ...EmptyApprovalCriteria,
            allowSpecialWrapping: true, // Required for wrapper path operations
            mustPrioritize: true, // Chain-enforced: must be true for special wrapping approvals
            overridesToIncomingApprovals: true,
        },
    };

    return toSet;
};
```


# Transferable Approval

This example demonstrates how to create a basic transferable approval that allows tokens to be freely transferred between any users after minting.

## Overview

A transferable approval enables tokens to be moved between addresses without restrictions.

## Code Example

```typescript
const transferableApproval = new CollectionApproval({
    fromListId: '!Mint', // Excludes the Mint address
    toListId: 'All',
    initiatedByListId: 'All',
    transferTimes: UintRangeArray.FullRanges(),
    ownershipTimes: UintRangeArray.FullRanges(),
    tokenIds: UintRangeArray.FullRanges(),
    approvalId: 'transferable-approval',
    version: 0n,
    approvalCriteria: undefined, // No additional restrictions
});
```


# Message Transfer Examples

This section contains practical examples of token transfer messages for the BitBadges protocol.

## Examples

* [Simple Token Transfer](/token-standard/examples/msg-transfer/simple-badge-transfer) - Basic mint-to-address transfer example
* [Transfer with Precalculation](/token-standard/examples/msg-transfer/transfer-with-precalculation) - Transfer using approval-based precalculation


# Simple Token Transfer

This example demonstrates a basic token transfer from the mint to a specific address.

## Overview

This transfer creates token ID 1 from collection 20 and sends it to the creator address. The token has full ownership time range and uses collection-level approval.

## Transfer Details

* **Collection ID**: 20
* **Token ID**: 1
* **Amount**: 1
* **From**: Mint (new token creation)
* **To**: Creator address
* **Approval**: Collection-level approval (assumes user-level approvals successfully auto-scan)

## JSON Structure

```json
[
    {
        "creator": "bb18el5ug46umcws58m445ql5scgg2n3tzagfecvl",
        "collectionId": "20",
        "transfers": [
            {
                "from": "Mint",
                "toAddresses": ["bb18el5ug46umcws58m445ql5scgg2n3tzagfecvl"],
                "balances": [
                    {
                        "amount": "1",
                        "ownershipTimes": [
                            {
                                "start": "1",
                                "end": "18446744073709551615"
                            }
                        ],
                        "tokenIds": [
                            {
                                "start": "1",
                                "end": "1"
                            }
                        ]
                    }
                ],
                "precalculateBalancesFromApproval": {
                    "approvalId": "",
                    "approvalLevel": "",
                    "approverAddress": "",
                    "version": "0"
                },
                "merkleProofs": [],
                "ethSignatureProofs": [],
                "memo": "",
                "prioritizedApprovals": [
                    {
                        "approvalId": "4a1ed47db7bc0f9f7174eab12aa9b8c9b9e4e37474ca2264668cf8e1b1598dde",
                        "approvalLevel": "collection",
                        "approverAddress": "",
                        "version": "0"
                    }
                ],
                "onlyCheckPrioritizedCollectionApprovals": true,
                "onlyCheckPrioritizedIncomingApprovals": false,
                "onlyCheckPrioritizedOutgoingApprovals": false
            }
        ]
    }
]
```

## Key Components Explained

### Transfer Source

* `"from": "Mint"` - Indicates this is a new token creation from the mint

### Destination

* `"toAddresses": ["bb18el5ug46umcws58m445ql5scgg2n3tzagfecvl"]` - The recipient address

### Balance Specification

* `"amount": "1"` - Transfer 1 token
* `"ownershipTimes"` - Full ownership time range (1 to max uint64)
* `"tokenIds"` - Specific token ID range (1 to 1)

### Approval Configuration

* `"prioritizedApprovals"` - Uses collection-level approval
* `"onlyCheckPrioritizedCollectionApprovals": true` - Only check collection approvals
* `"approvalId"` - Specific approval identifier for the collection

### Additional Settings

* `"merkleProofs": []` - No merkle proofs required for this simple transfer
* `"ethSignatureProofs": []` - No ETH signature proofs required for this simple transfer
* `"memo": ""` - No memo attached

## Usage

This example can be used as a template for basic token minting operations where you want to create a new token and transfer it to a specific address using collection-level approval.


# Transfer with Precalculation

This example demonstrates a token transfer that uses precalculation from approval criteria instead of manually specifying balances.

## Overview

This transfer creates tokens from collection 20 and sends them to the creator address. Instead of manually specifying the balance amounts, it uses precalculation from the approval criteria to determine what tokens to transfer.

## Transfer Details

* **Collection ID**: 20
* **From**: Mint (new token creation)
* **To**: Creator address
* **Approval**: Collection-level approval with precalculation
* **Precalculation**: Enabled with specific approval ID

## JSON Structure

```json
[
    {
        "creator": "bb18el5ug46umcws58m445ql5scgg2n3tzagfecvl",
        "collectionId": "20",
        "transfers": [
            {
                "from": "Mint",
                "toAddresses": ["bb18el5ug46umcws58m445ql5scgg2n3tzagfecvl"],
                "balances": [],
                "precalculateBalancesFromApproval": {
                    "approvalId": "fd1cef5941fb08487ecc1038af09fb29a6d7d40a89d8e4889c9c954978aa7e41",
                    "approvalLevel": "collection",
                    "approverAddress": "",
                    "version": "0",
                    "precalculationOptions": {
                        "overrideTimestamp": "0",
                        "tokenIdsOverride": []
                    }
                },
                "merkleProofs": [],
                "ethSignatureProofs": [],
                "memo": "",
                "prioritizedApprovals": [
                    {
                        "approvalId": "fd1cef5941fb08487ecc1038af09fb29a6d7d40a89d8e4889c9c954978aa7e41",
                        "approvalLevel": "collection",
                        "approverAddress": "",
                        "version": "0"
                    }
                ],
                "onlyCheckPrioritizedCollectionApprovals": true,
                "onlyCheckPrioritizedIncomingApprovals": false,
                "onlyCheckPrioritizedOutgoingApprovals": false
        ]
    }
]
```

## Key Components Explained

### Precalculation Configuration

* `"balances": []` - Empty balances array since amounts are calculated from approval
* `"precalculateBalancesFromApproval"` - Specifies which approval to use for calculation
* `"approvalId"` - The specific approval ID that defines the transfer criteria

### Prioritized Approvals

* `"prioritizedApprovals"` - Uses the same approval ID for both precalculation and transfer
* `"onlyCheckPrioritizedCollectionApprovals": true` - Only check collection-level approvals
* `"onlyCheckPrioritizedIncomingApprovals": false` - Skip incoming approval checks
* `"onlyCheckPrioritizedOutgoingApprovals": false` - Skip outgoing approval checks

### Precalculation Options

* `"precalculationOptions.overrideTimestamp": "0"` - Use current timestamp for calculations (only applies if `allowOverrideTimestamp` is true in approval)
* `"precalculationOptions.tokenIdsOverride": []` - No token ID overrides, use approval criteria (only applies if `allowOverrideWithAnyValidToken` is true in approval)

### Non-Auto-Scan Behavior

This example demonstrates "prioritized non-auto-scan" behavior where:

* Only the specified approval is checked (no automatic scanning of other approvals)
* The system doesn't automatically look for other valid approvals
* Transfer is limited to what the specified approval allows
* Can use approvals with side effects and custom criteria like merkle challenges and ETH signature challenges
* Shows proper versioning of approvals

## Usage

This example is useful when:

* You want to transfer tokens based on approval criteria rather than manual specification
* You need precise control over which approval is used
* You want to avoid automatic approval scanning
* The approval criteria dynamically determine amounts and IDs

## Differences from Simple Transfer

| Feature               | Simple Transfer      | Precalculation Transfer   |
| --------------------- | -------------------- | ------------------------- |
| Balance Specification | Manual amounts       | Calculated from approval  |
| Approval Scanning     | Auto-scan enabled    | Only specified approval   |
| Flexibility           | Fixed amounts        | Dynamic based on criteria |
| Control               | Direct specification | Approval-driven           |

***


# Permission Examples

This directory contains practical examples of different permission configurations for collections. Each example demonstrates specific patterns and use cases for controlling collection management.

## Contents

* [Freezing Mint Transferability](/token-standard/examples/permissions/freezing-mint-transferability) - Permanently freeze minting capabilities
* [Locking Specific Approval ID](/token-standard/examples/permissions/locking-specific-approval-id) - Lock specific approval IDs with granular control
* [Locking Specific Token IDs](/token-standard/examples/permissions/locking-specific-token-ids) - Lock approvals for specific token ID ranges
* [Locking Valid Token IDs](/token-standard/examples/permissions/locking-valid-token-ids) - Control valid token ID range updates

## Permission System Overview

BitBadges permissions follow a time-based system where:

1. **Permanently Permitted Times** - Permission is always allowed
2. **Permanently Forbidden Times** - Permission is always denied
3. **Default (Empty)** - Permission is soft-enabled (manager can change)

## Common Patterns

* **No Manager** - Set manager to empty string to disable all management
* **Complete Control** - Empty permission arrays for full soft-enabled control
* **Locked Forever** - Use `permanentlyForbiddenTimes: FullTimeRanges`
* **Time-Limited** - Use specific time ranges for temporary control


# Freezing Mint Transferability

This example demonstrates how to permanently freeze minting capabilities by making mint-related collection approvals immutable.

## Overview

By setting `permanentlyForbiddenTimes` for mint approval updates, you can ensure that no new minting approvals can be added and existing ones cannot be modified.

## Permission Configuration

```typescript
const FullTimeRanges = [
    {
        start: '1',
        end: '18446744073709551615',
    },
];

const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateValidTokenIds: [],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [
        {
            // Which approvals does this permission apply to? Approvals must match ALL criteria.
            fromListId: 'Mint',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: FullTimeRanges,
            ownershipTimes: FullTimeRanges,
            approvalId: 'All',

            // What is status of this approval at any given time? (Unhandled = soft-enabled)
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges,
        },
    ],
};
```

## Implementation

```typescript
const createCollection = {
    // ... other collection fields
    collectionPermissions,
    collectionApprovals: [
        // Include any initial mint approvals here
        // These will be the ONLY mint approvals ever possible
        {
            fromListId: 'Mint',
            toListId: 'creator-address',
            // ... initial mint approval configuration
        },
    ],
};
```

## Important Notes

### ⚠️ Irreversible Action

Once set to permanently forbidden, mint permissions cannot be restored. Carefully configure initial mint approvals before freezing. Ensure all mint approvals you will ever need are set.

## Related Examples

* [Building Collection Permissions](/token-standard/examples/building-collection-permissions) - General permission patterns
* [Building Collection Approvals](/token-standard/examples/building-collection-approvals) - Approval configuration


# Locking Specific Approval ID

This example demonstrates how to permanently lock a specific approval ID while keeping other approvals updatable.

## Overview

By targeting a specific `approvalId`, you can freeze that approval permanently while allowing updates to other approvals. The `!` operator can be used to target all approvals EXCEPT a specific ID.

## Lock Specific Approval ID

```typescript
const FullTimeRanges = [
    {
        start: '1',
        end: '18446744073709551615',
    },
];

const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateValidTokenIds: [],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [
        {
            // Which approvals does this permission apply to? Approvals must match ALL criteria.
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: FullTimeRanges,
            ownershipTimes: FullTimeRanges,
            approvalId: 'abc123', // Only targets this specific approval ID

            // What is status of this approval at any given time? (Unhandled = soft-enabled)
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // Permanently locked
        },
    ],
};
```

## Lock All EXCEPT Specific Approval ID

```typescript
const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateValidTokenIds: [],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [
        {
            // Which approvals does this permission apply to? Approvals must match ALL criteria.
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: FullTimeRanges,
            ownershipTimes: FullTimeRanges,
            approvalId: '!abc123', // All approvals EXCEPT abc123

            // What is status of this approval at any given time? (Unhandled = soft-enabled)
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // All others permanently locked
        },
    ],
};
```

## Implementation

```typescript
const createCollection = {
    // ... other collection fields
    collectionPermissions,
    collectionApprovals: [
        {
            approvalId: 'abc123',
            // ... this approval will be locked/unlocked based on configuration
        },
        {
            approvalId: 'other-approval',
            // ... this approval's updateability depends on configuration
        },
    ],
};
```

## Related Examples

* [Freezing Mint Transferability](/token-standard/examples/permissions/freezing-mint-transferability) - Lock all mint approvals
* [Building Collection Permissions](/token-standard/examples/building-collection-permissions) - General permission patterns


# Locking Specific Token IDs

This example demonstrates how to permanently lock approvals for specific token IDs while keeping other approvals updatable.

## Overview

By targeting specific `tokenIds`, you can freeze approvals for those tokens permanently while allowing updates to approvals for other token IDs.

## Lock Token IDs 1-100

```typescript
const FullTimeRanges = [
    {
        start: '1',
        end: '18446744073709551615',
    },
];

const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateValidTokenIds: [],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [
        {
            // Which approvals does this permission apply to? Approvals must match ALL criteria.
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: [
                {
                    start: '1',
                    end: '100', // Only targets tokens 1-100
                },
            ],
            ownershipTimes: FullTimeRanges,
            approvalId: 'All',

            // What is status of this approval at any given time? (Unhandled = soft-enabled)
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // Permanently locked
        },
    ],
};
```

## Lock All Tokens EXCEPT 1-100

```typescript
const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateValidTokenIds: [],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [
        {
            // Which approvals does this permission apply to? Approvals must match ALL criteria.
            fromListId: 'All',
            toListId: 'All',
            initiatedByListId: 'All',
            transferTimes: FullTimeRanges,
            tokenIds: [
                {
                    start: '101',
                    end: '18446744073709551615', // All tokens except 1-100
                },
            ],
            ownershipTimes: FullTimeRanges,
            approvalId: 'All',

            // What is status of this approval at any given time? (Unhandled = soft-enabled)
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // All others permanently locked
        },
    ],
};
```

## Implementation

```typescript
const createCollection = {
    // ... other collection fields
    collectionPermissions,
    collectionApprovals: [
        {
            tokenIds: [{ start: '1', end: '50' }],
            // ... this approval will be locked if it overlaps with permission criteria
        },
        {
            tokenIds: [{ start: '150', end: '200' }],
            // ... this approval's updateability depends on configuration
        },
    ],
};
```

## Use Cases

* **Lock Founder Tokens**: Prevent modification of special token 1-100 transfer rules
* **Preserve Rare Items**: Keep limited edition tokens (1-100) immutable
* **Tier-Based Control**: Lock specific tiers while allowing others to evolve

## Important Notes

### ⚠️ ID Range Targeting

The permission only applies to approvals that overlap with the specified token ID ranges. Approvals targeting token IDs outside the range remain updatable.

## Related Examples

* [Locking Specific Approval ID](/token-standard/examples/permissions/locking-specific-approval-id) - Lock by approval ID
* [Freezing Mint Transferability](/token-standard/examples/permissions/freezing-mint-transferability) - Lock all mint approvals


# Locking Valid Token IDs

This example demonstrates how to control updates to the `validTokenIds` field, either locking it permanently or allowing controlled expansion. The `validTokenIds` field is used to control which token IDs are considered valid for the collection.

## Overview

The `canUpdateValidTokenIds` permission controls whether the valid token ID ranges can be modified.

## Lock Valid Token IDs Forever

```typescript
const FullTimeRanges = [
    {
        start: '1',
        end: '18446744073709551615',
    },
];

const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateValidTokenIds: [
        {
            // Which token IDs does this permission apply to?
            tokenIds: FullTimeRanges, // All token IDs

            // What is status of this permission at any given time?
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // Never allowed to update
        },
    ],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [],
    canAddMoreAliasPaths: [],
    canAddMoreCosmosCoinWrapperPaths: [],
};
```

## Lock Token IDs 1-100, Allow Future Expansion

```typescript
const collectionPermissions = {
    canDeleteCollection: [],
    canArchiveCollection: [],
    canUpdateStandards: [],
    canUpdateCustomData: [],
    canUpdateManager: [],
    canUpdateCollectionMetadata: [],
    canUpdateValidTokenIds: [
        {
            // Which token IDs does this permission apply to?
            tokenIds: [
                {
                    start: '1',
                    end: '100', // Only applies to tokens 1-100
                },
            ],

            // What is status of this permission at any given time?
            permanentlyPermittedTimes: [],
            permanentlyForbiddenTimes: FullTimeRanges, // Token IDs 1-100 locked forever
        },
        // Token IDs 101+ remain soft-enabled (can be updated by manager)
    ],
    canUpdateTokenMetadata: [],
    canUpdateCollectionApprovals: [],
    canAddMoreAliasPaths: [],
    canAddMoreCosmosCoinWrapperPaths: [],
};
```

## Implementation

```typescript
const createCollection = {
    // ... other collection fields
    collectionPermissions,
    validTokenIds: [
        {
            start: '1',
            end: '100', // Initial valid range
        },
    ],
};
```

## Important Notes

### ⚠️ Token ID Targeting

* Permissions only apply to the specified token ID ranges
* Unspecified ranges remain soft-enabled for manager updates
* Cannot reduce valid token IDs once locked (only expansion possible for unlocked ranges)

## Related Examples

* [Locking Specific Token IDs](/token-standard/examples/permissions/locking-specific-token-ids) - Lock approval updates for token ranges
* [Freezing Mint Transferability](/token-standard/examples/permissions/freezing-mint-transferability) - Lock mint approvals


# Transaction Examples

This directory contains complete transaction examples for the x/tokenization module.

## Contents

* [MsgCreateCollection](/token-standard/examples/txs/msgcreatecollection) - Examples for creating collections
* [MsgUpdateUserApprovals](/token-standard/examples/txs/msgupdate-user-approvals) - Examples for updating user-level approvals


# MsgCreateCollection Examples

This directory contains complete examples for creating collections using the `MsgCreateCollection` transaction.

## Contents

* [Quest Token Collection](/token-standard/examples/txs/msgcreatecollection/quest-badge-collection) - Example of creating a quest collection with Merkle proofs
* [Tradable NFT Collection](/token-standard/examples/txs/msgcreatecollection/tradable-nft-collection) - Example of creating a tradable NFT collection for marketplace trading




---

[Next Page](/llms-full.txt/1)

