Configurable Direct Components Architecture
Solves the Starknet 4MB contract size limit using the Optional Trait / NoOp Trait pattern.
Traditional token contracts exceeded Starknet's 4MB limit by including all features. This architecture lets you include only the components you need.
Single CoreTokenComponent that preserves ALL logic from original TokenComponent:
- Complex game address validation
- Dynamic interface checking with
supports_interfacecalls - Multi-game metadata lookups vs single-game validation
- Settings/objectives validation when components available
- Conditional feature execution based on availability
Six independent components:
- Minter - Minter tracking and registry
- Objectives - Token objectives management
- Context - Game context handling
- Soulbound - Soulbound token functionality
- Renderer - Custom renderer support
- Settings - Game settings management
Zero-cost trait implementations for disabled features. Contracts choose which component impls to wire up (real vs NoOp) at the contract level:
// Enable minter with real implementation
impl MinterOptionalImpl = MinterComponent::MinterOptionalImpl<ContractState>;
// Disable other features with zero-cost NoOps
impl ObjectivesOptionalImpl = NoOpObjectives<ContractState>;
impl SettingsOptionalImpl = NoOpSettings<ContractState>;
impl ContextOptionalImpl = NoOpContext<ContractState>;
impl SoulboundOptionalImpl = NoOpSoulbound<ContractState>;
impl RendererOptionalImpl = NoOpRenderer<ContractState>;#[starknet::contract]
mod MyToken {
use game_components_embeddable_game_standard::token::token_component::CoreTokenComponent;
use game_components_embeddable_game_standard::token::noop_traits::*;
component!(path: CoreTokenComponent, storage: core_token, event: CoreTokenEvent);
#[abi(embed_v0)]
impl CoreTokenImpl = CoreTokenComponent::CoreTokenImpl<ContractState>;
impl CoreTokenInternalImpl = CoreTokenComponent::InternalImpl<ContractState>;
// Use NoOp implementations for disabled features
impl MinterImpl = NoOpMinter<ContractState>;
impl ObjectivesImpl = NoOpObjectives<ContractState>;
impl ContextImpl = NoOpContext<ContractState>;
impl SoulboundImpl = NoOpSoulbound<ContractState>;
impl RendererImpl = NoOpRenderer<ContractState>;
impl SettingsImpl = NoOpSettings<ContractState>;
}#[starknet::contract]
mod MyAdvancedToken {
use game_components_embeddable_game_standard::token::token_component::CoreTokenComponent;
use game_components_embeddable_game_standard::token::extensions::{
MinterComponent, ObjectivesComponent
};
use game_components_embeddable_game_standard::token::noop_traits::*;
component!(path: CoreTokenComponent, storage: core_token, event: CoreTokenEvent);
component!(path: MinterComponent, storage: minter, event: MinterEvent);
component!(path: ObjectivesComponent, storage: objectives, event: ObjectivesEvent);
// Enable selected features
impl MinterOptionalImpl = MinterComponent::MinterOptionalImpl<ContractState>;
impl ObjectivesOptionalImpl = ObjectivesComponent::ObjectivesOptionalImpl<ContractState>;
// Disable unused features
impl ContextImpl = NoOpContext<ContractState>;
impl SoulboundImpl = NoOpSoulbound<ContractState>;
impl RendererImpl = NoOpRenderer<ContractState>;
impl SettingsImpl = NoOpSettings<ContractState>;
}// Override specific behaviors
impl CustomMinter of OptionalMinter<ContractState> {
fn on_mint_with_minter(ref self: ContractState, minter: ContractAddress) -> u64 {
// Custom minter logic
42
}
}See src/tests/examples/ for comprehensive examples:
minimal_optimized_example.cairo- Smallest possible tokenfull_token_contract.cairo- All features enabledsingle_game_token_contract.cairo- Single game mode
- Compile-time Optimization: Unused features are completely eliminated via NoOp traits
- Runtime Sophistication: Enabled features retain full complexity
- Developer Choice: Pick exactly what you need
- Zero Dependencies: Disabled features add zero overhead
- Gradual Adoption: Enable features as needed
- Full Compatibility: Works with existing game components
trait ICoreToken<TState> {
fn mint(ref self: TState, to: ContractAddress, game_address: ContractAddress, /* ... */);
fn burn(ref self: TState, token_id: u64);
fn token_uri(self: @TState, token_id: u64) -> ByteArray;
// ... standard ERC721 methods
}Each feature component provides its own interface:
IMinterComponent- Minter managementIObjectivesComponent- Objectives managementIContextComponent- Context handlingISoulboundComponent- Soulbound functionalityIRendererComponent- Custom rendering
- Features should be completely independent
- Use the
OptionalTraitpattern for core integration - Provide both enabled and NoOp implementations
- Include comprehensive examples