A production-ready smart contract factory with built-in indexing, tagging, and graph relationships for CosmWasm 1.0+
- Overview
- Key Features
- Architecture
- Quick Start
- Core Concepts
- Usage Examples
- API Reference
- Advanced Features
- Building & Testing
- Deployment
- Performance & Security
- FAQ
- License
CosmWasm Factory is a database-on-blockchain primitive that transforms contract management into a queryable, indexed data structure. Unlike traditional factory patterns that simply instantiate contracts, this factory provides SQL-like querying, sophisticated indexing, weighted tagging, and graph-based relationships between contracts—all on-chain.
Think of it as: PostgreSQL meets Factory Pattern for CosmWasm smart contracts.
- Zero Off-Chain Dependencies: Build complete backend stacks entirely on-chain without indexers or databases
- SQL-Like Queries: Range queries, filtering, sorting, and pagination out of the box
- Custom Indexing: Add arbitrary typed indexes to contracts for efficient lookups (13+ supported types)
- Graph Relationships: Create named relationships between contracts with optional typed values
- Batch Migrations: Migrate hundreds of contracts with error handling and retry logic
- Production Ready: Well-tested patterns, comprehensive error handling, gas-optimized
┌─────────────────┐
│ Your dApp │
└────────┬────────┘
│ create(...)
↓
┌─────────────────────────────────────────┐
│ CosmWasm Factory │
│ ┌────────────┐ ┌─────────────────┐ │
│ │ Contracts │ │ Indexes │ │
│ │ Registry │←→│ • Built-in │ │
│ └────────────┘ │ • Custom │ │
│ ┌────────────┐ │ • Tags │ │
│ │ Relations │ │ • Timestamps │ │
│ │ Graph │ └─────────────────┘ │
│ └────────────┘ │
└─────────────────────────────────────────┘
│ instantiate + index
↓
┌─────────────────┐
│ Child Contracts │
└─────────────────┘
- Built-in Indexes:
created_by,created_at,updated_at,code_id,admin - Custom Indexes: Add arbitrary typed indexes with 13+ supported types (
Uint*,Int*,String,Bytes,Binary,Bool) - Efficient Pagination: Cursor-based pagination with configurable limits (up to 500)
- Range Queries: Filter by inclusive/exclusive bounds on any indexed field
- Assign multiple tags to contracts with numeric weights (0-65535)
- Query contracts by tag with weight range filtering
- Tag-based filtering with AND/OR/XOR boolean operations
- Perfect for categorization, feature flags, or priority systems
- Create bidirectional relationships:
contractA ←[name: value]→ addressB - Named relationships with optional typed values
- Query all contracts related to a specific address
- Relationship range queries by name and value
- Build complex contract dependency graphs
- Define reusable instantiation templates
- Override protection (lockable/flexible presets)
- Track usage statistics
- Simplify contract creation for common patterns
- Migrate hundreds of contracts in batches
- Session-based migration with pause/resume
- Error handling strategies: Abort or Retry
- Track success/failure rates
- Filter by source code_id
- Query contracts in ranges by any index
- Filter by tags with weight constraints
- Find all contracts related to an address
- Boolean tag tests (AND/OR/XOR)
- Metadata retrieval with timestamps
The factory uses a compound index pattern for O(1) lookups and efficient range scans:
// Primary Storage
CONTRACT_ID_2_ADDR: Map<u32, Addr> // ID → Address
CONTRACT_ADDR_2_ID: Map<Addr, u32> // Address → ID
CONTRACT_NAME_2_ID: Map<String, u32> // Name → ID (optional)
// Built-in Indexes (bytes, u32) → u8
IX_CREATED_BY: Map<(&[u8], u32), u8>
IX_CREATED_AT: Map<(&[u8], u32), u8>
IX_CODE_ID: Map<(&[u8], u32), u8>
// Custom Indexes (dynamic)
IX_CUSTOM_{name}: Map<(&[u8], u32), u8>
// Weighted Tags
IX_WEIGHTED_TAG: Map<(&[u8], u16, u32), u8> // (tag, weight, id)
IX_TAG: Map<(&[u8], u32), u8> // (tag, id)
// Graph Relations
IX_REL_CONTRACT_ADDR: Map<(u32, &[u8], &[u8]), Option<IndexValue>>
IX_REL_ADDR: Map<(&[u8], &[u8], u32), u8>1. Contract created via factory
2. Factory instantiates child contract (SubMsg)
3. Reply handler extracts contract address
4. Built-in indexes populated automatically
5. Child contract calls factory.update() via SubMsg
6. Custom indexes, tags, relations updated
- Creator: Can instantiate contracts through factory
- Manager: Full control (migrations, presets, manual updates)
- Child Contract: Can update own indexes via SubMsg callback
- Factory as Admin: Factory is admin of created contracts (enables batch migrations)
Add to your Cargo.toml:
[dependencies]
cw-factory = { git = "https://github.com/your-org/cw-factory", tag = "v1.0.0" }use cw_factory::msg::{InstantiateMsg, Config};
let msg = InstantiateMsg {
config: Config {
managed_by: Addr::unchecked("manager_address"),
default_code_id: Some(Uint64::from(123u64)),
allowed_code_ids: vec![Uint64::from(123u64), Uint64::from(456u64)],
},
};use cw_factory::msg::{ExecuteMsg, CreateMsg};
let create_msg = CreateMsg {
code_id: Some(Uint64::from(123u64)),
label: "my-contract-1".to_string(),
name: Some("my-contract".to_string()),
admin: None, // Factory becomes admin
tags: Some(vec!["production".to_string(), "verified".to_string()]),
preset: None,
instantiate_msg: json!({
"name": "My Contract",
"symbol": "MC"
}).as_object().unwrap().clone(),
};
let msg = ExecuteMsg::Create(create_msg);use cw_factory::msg::{QueryMsg, ContractSetQueryMsg, ContractsByTagQueryParams};
let query = QueryMsg::Contracts(
ContractSetQueryMsg::WithTag(ContractsByTagQueryParams {
tag: "production".to_string(),
min_weight: None,
max_weight: None,
limit: Some(50),
desc: Some(false),
cursor: None,
})
);Contracts can be referenced in three ways:
pub enum ContractSelector {
Address(Addr), // By blockchain address
Id(ContractId), // By factory internal ID (u32)
Name(String), // By optional human-readable name
}The factory supports 13 typed index values with proper byte serialization:
pub enum IndexValue {
String(String), // Max 128 bytes, padded
Bytes(Vec<u8>),
Binary(Binary),
Uint128(Uint128), Uint64(Uint64), Uint32(u32), Uint16(u16), Uint8(u8),
Int128(Int128), Int64(Int64), Int32(i32), Int16(i16), Int8(i8),
Bool(bool),
}Why typed values? Proper byte ordering ensures correct range queries and sorting.
Contracts can update their own metadata via SubMsg callbacks:
pub struct UpdateMsg {
pub contract: Option<ContractSelector>, // None = msg.sender
pub indices: Option<Vec<IndexUpdate>>,
pub tags: Option<Vec<TagUpdate>>,
pub relations: Option<Vec<RelationUpdate>>,
}Example: Child contract updating its index
// In your child contract's execute handler
let update_msg = WasmMsg::Execute {
contract_addr: factory_addr.to_string(),
msg: to_binary(&FactoryExecuteMsg::Update(UpdateMsg {
contract: None, // Self
indices: Some(vec![
IndexUpdate {
name: "total_supply".to_string(),
value: IndexValue::Uint128(Uint128::from(1000000u128)),
}
]),
tags: Some(vec![
TagUpdate {
op: UpdateOperation::Set,
tag: "fully-minted".to_string(),
weight: Some(100),
}
]),
relations: None,
}))?,
funds: vec![],
};
Ok(Response::new().add_submessage(SubMsg::new(update_msg)))Query contracts that match tag criteria with logical operations:
pub enum BooleanTest {
And, // All tags must be present
Or, // Any tag must be present
Xor, // Exactly one tag must be present
}
// Example: Find contracts with BOTH "verified" AND "production" tags
ContractHasTagsQueryParams {
address: contract_addr,
test: BooleanTest::And,
tags: vec![
TagSelector { tag: "verified".to_string(), min_weight: None, max_weight: None },
TagSelector { tag: "production".to_string(), min_weight: Some(50), max_weight: None },
],
}Create and index multiple NFT collections:
// Create NFT collection
let create_msg = CreateMsg {
code_id: Some(Uint64::from(789u64)),
label: "Cool Cats NFT".to_string(),
name: Some("cool-cats".to_string()),
admin: Some(creator.clone()),
tags: Some(vec!["nft".to_string(), "collection".to_string()]),
instantiate_msg: json!({
"name": "Cool Cats",
"symbol": "CATS",
"minter": creator.to_string(),
}).as_object().unwrap().clone(),
preset: None,
};
// Later: Find all NFT collections created by a user
let query = QueryMsg::Contracts(
ContractSetQueryMsg::InRange(ContractsInRangeQueryParams {
index: IndexSelector::CreatedBy,
start: Some(IndexRangeBound::Inclusive(
IndexValue::String(creator.to_string())
)),
stop: Some(IndexRangeBound::Inclusive(
IndexValue::String(creator.to_string())
)),
limit: Some(100),
desc: Some(true),
cursor: None,
})
);Track relationships between DAOs and their treasury contracts:
// Update DAO to link to its treasury
let update_msg = UpdateMsg {
contract: Some(ContractSelector::Name("my-dao".to_string())),
indices: None,
tags: None,
relations: Some(vec![
RelationUpdate {
op: UpdateOperation::Set,
name: "treasury".to_string(),
value: Some(IndexValue::String("primary".to_string())),
address: treasury_addr.clone(),
}
]),
};
// Query all DAOs that have a specific address as treasury
let query = QueryMsg::Contracts(
ContractSetQueryMsg::RelatedTo(ContractsRelatedToParams {
address: treasury_addr,
start: Some(RangeQueryBound::Inclusive(NameValue {
name: "treasury".to_string(),
value: None,
})),
stop: Some(RangeQueryBound::Inclusive(NameValue {
name: "treasury".to_string(),
value: None,
})),
limit: Some(50),
desc: None,
cursor: None,
})
);// Manager sets up a preset for token contracts
let preset_msg = SetPresetMsg {
name: "standard-token".to_string(),
overridable: true,
values: json!({
"decimals": 6,
"marketing": {
"project": "MyProject",
"logo": { "url": "https://example.com/logo.png" }
}
}).as_object().unwrap().clone(),
};
// Users create tokens with preset + custom values
let create_msg = CreateMsg {
code_id: None, // Use default
label: "My Token".to_string(),
name: Some("my-token".to_string()),
admin: None,
tags: Some(vec!["token".to_string()]),
preset: Some("standard-token".to_string()),
instantiate_msg: json!({
"name": "My Token",
"symbol": "MTK",
"initial_balances": [...]
// decimals and marketing come from preset
}).as_object().unwrap().clone(),
};Migrate all contracts from code_id 1 to code_id 2:
// 1. Begin migration session
let begin_msg = ExecuteMsg::Migrations(
MigrationsExecuteMsg::Session(
MigrationSessionMsg::Begin(MigrationParams {
name: "v1-to-v2".to_string(),
from_code_id: Some(Uint64::from(1u64)),
to_code_id: Uint64::from(2u64),
migrate_msg: Some(to_binary(&MyMigrateMsg {})?),
batch_size: Some(50), // Process 50 at a time
error_strategy: MigrationErrorStrategy::Retry,
})
)
);
// 2. Step through migration (call multiple times until complete)
let step_msg = ExecuteMsg::Migrations(
MigrationsExecuteMsg::Session(
MigrationSessionMsg::Step {
name: "v1-to-v2".to_string()
}
)
);
// 3. Check status
let status_query = QueryMsg::Migrations(
MigrationsQueryMsg::Session("v1-to-v2".to_string())
);
// 4. Retry failed migrations
let retry_msg = ExecuteMsg::Migrations(
MigrationsExecuteMsg::Session(
MigrationSessionMsg::Retry {
name: "v1-to-v2".to_string(),
params: None, // Or provide updated params
}
)
);// Set tags with weights for priority
let update_msg = UpdateMsg {
contract: None,
indices: None,
tags: Some(vec![
TagUpdate {
op: UpdateOperation::Set,
tag: "priority".to_string(),
weight: Some(100), // High priority
}
]),
relations: None,
};
// Query high-priority contracts (weight >= 50)
let query = QueryMsg::Contracts(
ContractSetQueryMsg::WithTag(ContractsByTagQueryParams {
tag: "priority".to_string(),
min_weight: Some(TagWeightRangeBound::Inclusive(50)),
max_weight: None,
limit: Some(100),
desc: Some(true), // Highest priority first
cursor: None,
})
);Instantiate a new contract through the factory.
ExecuteMsg::Create(CreateMsg {
code_id: Option<Uint64>, // If None, uses default_code_id
label: String, // Required: contract label
name: Option<String>, // Optional: unique name for lookup
admin: Option<Addr>, // If None, factory is admin
tags: Option<Vec<String>>, // Initial tags (weight = 0)
preset: Option<String>, // Apply preset template
instantiate_msg: Map<String, Value>, // JSON instantiate message
})Permissions: Any address
Returns: factory-create event with contract_address, code_id, admin
Update contract indexes, tags, or relationships.
ExecuteMsg::Update(UpdateMsg {
contract: Option<ContractSelector>, // None = msg.sender
indices: Option<Vec<IndexUpdate>>,
tags: Option<Vec<TagUpdate>>,
relations: Option<Vec<RelationUpdate>>,
})Permissions:
- Contract itself (when
contractis None) - Factory manager (when
contractis specified)
Sub-types:
IndexUpdate {
name: String, // Index name
value: IndexValue, // Typed value
}
TagUpdate {
op: UpdateOperation, // Set | Remove
tag: String,
weight: Option<u16>, // 0-65535, default 0
}
RelationUpdate {
op: UpdateOperation, // Set | Remove
name: String, // Relationship name
value: Option<IndexValue>, // Optional typed value
address: Addr, // Related address
}Update factory configuration (manager only).
ExecuteMsg::Configure(Config {
managed_by: Addr,
default_code_id: Option<Uint64>,
allowed_code_ids: Vec<Uint64>,
})Create or update a preset template (manager only).
ExecuteMsg::Presets(PresetsExecuteMsg::Set(SetPresetMsg {
name: String,
values: Map<String, Value>,
overridable: bool, // If true, user values override preset
}))Delete a preset template (manager only).
ExecuteMsg::Presets(PresetsExecuteMsg::Remove {
name: String
})Migrate a single contract (manager only).
ExecuteMsg::Migrations(MigrationsExecuteMsg::Migrate(
SingletonMigrationParams {
contract: Addr,
from_code_id: Option<Uint64>, // Only migrate if matches
to_code_id: Uint64,
migrate_msg: Option<Binary>,
}
))Start a batch migration session (manager only).
ExecuteMsg::Migrations(MigrationsExecuteMsg::Session(
MigrationSessionMsg::Begin(MigrationParams {
name: String, // Unique session name
from_code_id: Option<Uint64>,
to_code_id: Uint64,
migrate_msg: Option<Binary>,
batch_size: Option<u16>, // 1-100, default 50
error_strategy: MigrationErrorStrategy, // Abort | Retry
})
))Process next batch in migration session (manager only).
ExecuteMsg::Migrations(MigrationsExecuteMsg::Session(
MigrationSessionMsg::Step { name: String }
))Retry failed migrations in a session (manager only).
ExecuteMsg::Migrations(MigrationsExecuteMsg::Session(
MigrationSessionMsg::Retry {
name: String,
params: Option<MigrationParams>, // Override params
}
))Cancel and cleanup a migration session (manager only).
ExecuteMsg::Migrations(MigrationsExecuteMsg::Session(
MigrationSessionMsg::Cancel { name: String }
))Get factory configuration.
QueryMsg::Config {}Returns: ConfigResponse
Query contracts by index range.
QueryMsg::Contracts(ContractSetQueryMsg::InRange(
ContractsInRangeQueryParams {
index: IndexSelector, // Which index to query
start: Option<IndexRangeBound>,
stop: Option<IndexRangeBound>,
limit: Option<u16>, // 1-500, default 100
desc: Option<bool>, // Descending order
cursor: Option<(Vec<u8>, ContractId)>,
}
))IndexSelector variants:
CreatedBy- Query by creator addressCreatedAt- Query by creation timestampUpdatedAt- Query by last update timestampCodeId- Query by code IDAdmin- Query by admin addressTag- Query contracts with a tag (any weight)Custom(String)- Query by custom index name
Returns: ContractsByIndexResponse
Query contracts by tag with weight filtering.
QueryMsg::Contracts(ContractSetQueryMsg::WithTag(
ContractsByTagQueryParams {
tag: String,
min_weight: Option<TagWeightRangeBound>,
max_weight: Option<TagWeightRangeBound>,
limit: Option<u16>,
desc: Option<bool>,
cursor: Option<(Vec<u8>, u16, ContractId)>,
}
))Returns: ContractsByTagResponse (includes weights)
Query contracts related to an address.
QueryMsg::Contracts(ContractSetQueryMsg::RelatedTo(
ContractsRelatedToParams {
address: Addr,
start: Option<RangeQueryBound<NameValue>>,
stop: Option<RangeQueryBound<NameValue>>,
limit: Option<u16>,
desc: Option<bool>,
cursor: Option<(ContractId, Vec<u8>)>,
}
))Returns: ContractsRelatedToResponse (includes relationship values)
Get contract creation/update metadata.
QueryMsg::Contract(ContractQueryMsg::Metadata {
address: Addr
})Returns:
ContractMetadataResponse {
created_at: Timestamp,
created_by: Addr,
updated_at: Timestamp,
name: Option<String>,
code_id: Uint64,
admin: Addr,
}List all tags for a contract.
QueryMsg::Contract(ContractQueryMsg::Tags(
ContractTagsQueryParams {
contract: Addr,
start: Option<RangeQueryBound<String>>,
stop: Option<RangeQueryBound<String>>,
limit: Option<u16>,
desc: Option<bool>,
cursor: Option<String>,
}
))Returns: ContractTagsResponse
Check if contract has specific tags.
QueryMsg::Contract(ContractQueryMsg::HasTags(
ContractHasTagsQueryParams {
address: Addr,
test: BooleanTest, // And | Or | Xor
tags: Vec<TagSelector>,
}
))Returns: bool
List all relationships for a contract.
QueryMsg::Contract(ContractQueryMsg::Relations(
ContractRelationsQueryParams {
contract: Addr,
start: Option<RangeQueryBound<NameValue>>,
stop: Option<RangeQueryBound<NameValue>>,
limit: Option<u16>,
desc: Option<bool>,
cursor: Option<(Vec<u8>, Addr)>,
}
))Returns: ContractRelationsResponse
Check if contract has specific relationships.
QueryMsg::Contract(ContractQueryMsg::IsRelatedTo(
ContractHasRelationsQueryParams {
contract_address: Addr,
address: Addr,
test: BooleanTest,
relations: Vec<NameValue>,
}
))Returns: ContractIsRelatedToResponse
Get a preset by name.
QueryMsg::Presets(PresetsQueryMsg::Get {
name: String
})Returns: PresetResponse
List all presets.
QueryMsg::Presets(PresetsQueryMsg::Paginate {
cursor: Option<String>
})Returns: PresetPaginationResponse
Get migration session status.
QueryMsg::Migrations(MigrationsQueryMsg::Session(String))Returns: MigrationSessionResponse
When to use custom indexes:
- Frequent queries by a specific field (e.g.,
total_supply,price) - Range-based filtering needs (e.g., all contracts with supply > 1M)
- Sorting requirements beyond built-in indexes
Index naming conventions:
// Good: Descriptive, lowercase, underscore-separated
"total_supply", "last_trade_price", "vault_tvl"
// Avoid: Special characters, spaces, very long names
"Total Supply", "price$$$", "my_extremely_long_index_name_that_is_hard_to_remember"Performance considerations:
- Each index adds ~2 storage writes per update
- String indexes are padded to 128 bytes (use sparingly)
- Numeric types (
Uint*/Int*) are most efficient - Limit to 5-10 custom indexes per contract for optimal performance
1. Parent-Child Hierarchies
// DAO → SubDAOs
RelationUpdate {
name: "sub_dao".to_string(),
value: Some(IndexValue::Uint64(sub_dao_index)),
address: sub_dao_addr,
}2. Many-to-Many Associations
// Token ↔ Liquidity Pools
RelationUpdate {
name: "liquidity_pool".to_string(),
value: Some(IndexValue::String(pool_type)), // "stable" | "volatile"
address: pool_addr,
}3. Dependency Tracking
// Contract → Dependencies
RelationUpdate {
name: "depends_on".to_string(),
value: Some(IndexValue::String(version)), // "v1.2.3"
address: dependency_addr,
}Strategy: Retry
- Failed migrations are tracked in
MIGRATION_ERRORS - Call
Retryto re-attempt failed contracts - Useful for transient errors (gas, timeouts)
Strategy: Abort
- Entire transaction reverts on first error
- Use for critical migrations requiring atomicity
- Allows manual intervention before proceeding
Best practices:
- Test migration on devnet with small batch sizes
- Use
Retrystrategy for large production migrations - Monitor
MigrationSessionResponsefor progress - Cancel and restart if error rate > 10%
Overridable Presets (overridable: true):
Final Values = Preset ∪ User Values
(User values take precedence)
Locked Presets (overridable: false):
Final Values = User Values ∪ Preset
(Preset values take precedence)
Use cases:
- Overridable: Templates with sensible defaults (decimals, marketing)
- Locked: Enforced parameters (oracle address, protocol fee)
- Batch Operations: Group multiple updates in single transaction
- Cursor Pagination: Always use cursors for large result sets
- Limit Indexes: Only create indexes you actually query
- Numeric Types: Prefer
Uint64overStringfor indexes when possible - Tag Weights: Use sparse weights (0, 50, 100) vs dense (0, 1, 2, 3...)
- Rust 1.70+
wasm32-unknown-unknowntarget- Docker (for optimized builds)
# Development build
cargo build
# Run unit tests
cargo test
# Generate schema files
cargo schema# Using workspace optimizer (recommended)
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/workspace-optimizer:0.15.0Output: artifacts/cw_factory.wasm (~180KB optimized)
# Unit tests
cargo test
# Integration tests
cargo test --features library
# Coverage
cargo tarpaulin --out Htmlcargo schema
# Output: JSON schema files for all messages and responses# 1. Store WASM
junod tx wasm store artifacts/cw_factory.wasm \
--from <your-key> \
--gas auto \
--gas-adjustment 1.3 \
--gas-prices 0.025ujuno \
--chain-id <chain-id>
# 2. Get code ID from response
# 3. Instantiate
junod tx wasm instantiate <code-id> \
'{"config":{"managed_by":"<manager-addr>","default_code_id":null,"allowed_code_ids":[]}}' \
--from <your-key> \
--label "CW Factory v1.0" \
--admin <admin-addr> \
--gas auto- Audit contract code
- Test all features on testnet
- Verify migration paths
- Document all custom indexes
- Set up monitoring for factory events
- Configure appropriate
allowed_code_ids - Test batch migration with small batch sizes
- Set up preset templates
- Document manager key security practices
| Operation | Estimated Gas | Notes |
|---|---|---|
| Create Contract | ~500k | Includes instantiation + indexing |
| Update (1 index) | ~150k | Per index updated |
| Update (1 tag) | ~100k | Per tag |
| Update (1 relation) | ~120k | Per relation |
| Query (range, 100 items) | ~50k | Depends on result size |
| Migration (per contract) | ~200k | Varies by contract complexity |
Estimates based on average CosmWasm gas costs
Access Control
- Manager has elevated privileges (migrations, presets, manual updates)
- Use multi-sig or DAO governance for manager address
- Factory is admin of created contracts by default (required for migrations)
Migration Safety
- Always test migrations on subset of contracts first
- Use
from_code_idfilter to target specific versions - Monitor error rates during batch migrations
- Keep batch sizes reasonable (50-100) to avoid gas limits
Index Limits
- Maximum string key size: 128 bytes
- Maximum tag weight: 65535
- Maximum query limit: 500 items per page
- Cursor-based pagination prevents DoS attacks
Q: Can I use this factory with any CosmWasm contract?
A: Yes, as long as the contract's code_id is in allowed_code_ids.
Q: How do child contracts update their indexes?
A: Via SubMsg callback to ExecuteMsg::Update with contract: None.
Q: Can I query contracts created before adding a custom index?
A: Only if contracts explicitly call Update to populate that index.
Q: What happens if a migration fails mid-batch?
A: With Retry strategy, failed contracts are tracked and can be retried. With Abort, the entire batch reverts.
Q: Can I delete contracts from the factory? A: No, contracts remain indexed. Use tags (e.g., "archived") to mark inactive contracts.
Q: How much storage does the factory use? A: ~200 bytes per contract + ~50 bytes per index + ~40 bytes per tag + ~60 bytes per relation.
Q: Can multiple factories share the same code_id pool? A: Yes, each factory instance is independent.
Q: What's the difference between tags and custom indexes? A: Tags are simple string labels with optional weights, ideal for categorization. Custom indexes support typed values and are better for numerical comparisons and ranges.
Apache 2.0
Built with CosmWasm and inspired by traditional database indexing patterns.