Agent Skills: MultiversX Factory/Manager Pattern

Factory pattern for deploying and managing child contracts from a parent manager. Use when building marketplaces, launchpads, multi-tenant systems, or any protocol that deploys child contracts from a template.

UncategorizedID: multiversx/mx-ai-skills/multiversx-factory-manager

Install this agent skill to your local

pnpm dlx add-skill https://github.com/multiversx/mx-ai-skills/tree/HEAD/skills/multiversx-factory-manager

Skill Files

Browse the full folder contents for multiversx-factory-manager.

Download Skill

Loading file tree…

skills/multiversx-factory-manager/SKILL.md

Skill Metadata

Name
multiversx-factory-manager
Description
Factory pattern for deploying and managing child contracts from a parent manager. Use when building marketplaces, launchpads, multi-tenant systems, or any protocol that deploys child contracts from a template.

MultiversX Factory/Manager Pattern

Deploy, track, and upgrade child contracts from a template — the three core operations of a factory manager.

What Problem Does This Solve?

When a protocol needs to deploy multiple instances of the same contract (e.g., one per user, per market, per pool), a factory manager handles the deployment lifecycle: deploy from a template, track all instances, and upgrade them when the template changes.

When to Use

| Scenario | Use Factory? | |---|---| | Multiple instances of same contract needed | Yes | | Each user/entity gets their own contract | Yes | | Single contract serves all users | No | | Different contract types per deployment | No — use separate deploy logic |

Core Concept

Manager Contract
├── Stores template address (code to clone)
├── Deploy: creates child from template via from_source()
├── Track: registry of all deployed children
└── Upgrade: applies new template to existing children

Operation 1: Deploy

Deploy a new child contract using from_source() to clone the template:

#[endpoint(deployChild)]
fn deploy_child(
    &self,
    child_id: ManagedBuffer,
    init_arg_a: BigUint,
    init_arg_b: ManagedAddress,
) -> ManagedAddress {
    let caller = self.blockchain().get_caller();
    let template = self.template_address().get();

    let metadata = CodeMetadata::UPGRADEABLE
        | CodeMetadata::READABLE
        | CodeMetadata::PAYABLE;

    // Deploy from template
    let new_address = self.tx()
        .typed(child_proxy::ChildProxy) // ChildProxy generated from child contract's #[multiversx_sc::proxy]
        .init(init_arg_a, init_arg_b)
        .from_source(template)
        .code_metadata(metadata)
        .returns(ReturnsNewManagedAddress)
        .sync_call();

    // Register in tracking
    self.child_addresses().insert(new_address.clone());
    self.child_by_id(&child_id).set(new_address.clone());

    self.deploy_event(&caller, &child_id, &new_address);
    new_address
}

CodeMetadata Explained

| Flag | Effect | When to Use | |---|---|---| | UPGRADEABLE | Child can be upgraded later | Almost always — needed for Operation 3 | | READABLE | Other contracts can read child's storage | When using cross-contract storage reads | | PAYABLE | Child can receive EGLD directly | When child needs to accept payments | | PAYABLE_BY_SC | Only other SCs can pay child | When child should only interact with contracts |

Operation 2: Track (Registry)

#[multiversx_sc::module]
pub trait RegistryModule {
    // Template to clone from
    #[storage_mapper("templateAddress")]
    fn template_address(&self) -> SingleValueMapper<ManagedAddress>;

    // Set of all deployed child addresses
    #[storage_mapper("childAddresses")]
    fn child_addresses(&self) -> SetMapper<ManagedAddress>;

    // Lookup: ID → child address
    #[storage_mapper("childById")]
    fn child_by_id(&self, id: &ManagedBuffer) -> SingleValueMapper<ManagedAddress>;

    // Admin set (who can deploy/upgrade)
    #[storage_mapper("admins")]
    fn admins(&self) -> UnorderedSetMapper<ManagedAddress>;

    // Helper: verify address is a managed child
    fn require_managed_child(&self, address: &ManagedAddress) {
        require!(
            self.child_addresses().contains(address),
            "Not a managed child contract"
        );
    }
}

Pattern: SetMapper for the full set (iteration + contains check) + SingleValueMapper for ID-based lookup. This gives you both O(1) lookup by ID and iteration over all children.

Operation 3: Upgrade

Upgrade a single child to the current template:

#[endpoint(upgradeChild)]
fn upgrade_child(&self, child_id: ManagedBuffer) {
    self.require_admin();

    let child_address = self.child_by_id(&child_id).get();
    self.require_managed_child(&child_address);

    let metadata = CodeMetadata::UPGRADEABLE
        | CodeMetadata::READABLE
        | CodeMetadata::PAYABLE;

    self.tx()
        .to(child_address)
        .typed(child_proxy::ChildProxy) // ChildProxy generated from child contract's #[multiversx_sc::proxy]
        .upgrade()
        .code_metadata(metadata)
        .from_source(self.template_address().get())
        .upgrade_async_call_and_exit();
}

Note: When the child contract proxy is not available (e.g., template-based deployment from external code), use raw_deploy() instead of typed() for deployment.

Template Management

#[only_owner]
#[endpoint(setTemplate)]
fn set_template(&self, template_address: ManagedAddress) {
    require!(
        self.blockchain().is_smart_contract(&template_address),
        "Address is not a smart contract"
    );
    self.template_address().set(template_address);
}

Admin Hierarchy

Owner (deployer of manager)
  └── Admins (can deploy/upgrade children)
       └── Users (interact with their own child)
fn require_admin(&self) {
    let caller = self.blockchain().get_caller();
    require!(
        self.admins().contains(&caller)
            || caller == self.blockchain().get_owner_address(),
        "Not authorized"
    );
}

Anti-Patterns

1. No Registry Tracking

// WRONG — deploying without tracking makes upgrades impossible
let addr = self.tx().typed(ChildProxy).init().from_source(template).sync_call();
// addr is lost after this transaction!

2. Missing UPGRADEABLE Flag

// WRONG — child can never be upgraded
let metadata = CodeMetadata::READABLE | CodeMetadata::PAYABLE;
// Should include CodeMetadata::UPGRADEABLE

3. Upgrading Without Validation

// WRONG — no check that address is actually a managed child
fn upgrade(&self, address: ManagedAddress) {
    self.tx().to(address).typed(ChildProxy).upgrade()
        .from_source(self.template_address().get())
        .upgrade_async_call_and_exit();
}

Template

#[multiversx_sc::module]
pub trait FactoryModule {
    #[storage_mapper("templateAddress")]
    fn template_address(&self) -> SingleValueMapper<ManagedAddress>;

    #[storage_mapper("childAddresses")]
    fn child_addresses(&self) -> SetMapper<ManagedAddress>;

    #[storage_mapper("childById")]
    fn child_by_id(&self, id: &ManagedBuffer) -> SingleValueMapper<ManagedAddress>;

    #[storage_mapper("admins")]
    fn admins(&self) -> UnorderedSetMapper<ManagedAddress>;
}