Skip to content

ERC1155 Multi-Token Standard

A complete implementation of the ERC-1155 Multi-Token standard optimized for Stylus. ERC1155 is a versatile token standard that allows you to create both fungible and non-fungible tokens in a single contract - imagine a game where you can have both collectible items (NFTs) and in-game currency (fungible tokens) managed by the same contract.

Note

This implementation uses OpenZeppelin's Stylus contracts which are currently in development. Some features like export-abi have limitations that will be addressed in v0.2.0. For more information, see this issue.

Features

Learn and implement advanced multi-token functionality:

🎮 Multi-Token Support

  • Create fungible and non-fungible tokens
  • Manage multiple token types in one contract
  • Flexible token ID system

🔄 Batch Operations

  • Transfer multiple tokens at once
  • Mint batches of tokens efficiently
  • Burn multiple tokens in one transaction

🏭 Token Creation

  • Mint single tokens with custom data
  • Create token batches with different amounts
  • Define token properties and metadata

🔐 Access Control

  • Operator approval system
  • Batch approval management
  • Secure transfer authorization

📦 Asset Management

  • Track balances per token ID
  • Handle multiple token types
  • Query token information

🛡️ Safety Features

  • Emergency pause mechanism
  • Safe transfer checks
  • Secure batch operations

Gas Optimization

  • Efficient batch processing
  • Optimized storage layout
  • Minimal operation costs

Implementation

rust
#![cfg_attr(not(test), no_main)]
extern crate alloc;

use alloc::vec::Vec;

use alloy_primitives::{Address, FixedBytes, U256};
use openzeppelin_stylus::{
    token::erc1155::{extensions::IErc1155Burnable, Erc1155, IErc1155},
    utils::{introspection::erc165::IErc165, Pausable},
};
use stylus_sdk::{
    abi::Bytes,
    prelude::{entrypoint, public, storage},
};

#[entrypoint]
#[storage]
struct Erc1155Example {
    #[borrow]
    pub erc1155: Erc1155,
    #[borrow]
    pub pausable: Pausable,
}

#[public]
#[inherit(Erc1155, Pausable)]
impl Erc1155Example {
    fn mint(
        &mut self,
        to: Address,
        token_id: U256,
        amount: U256,
        data: Bytes,
    ) -> Result<(), Vec<u8>> {
        self.pausable.when_not_paused()?;
        self.erc1155._mint(to, token_id, amount, &data)?;
        Ok(())
    }

    fn mint_batch(
        &mut self,
        to: Address,
        token_ids: Vec<U256>,
        amounts: Vec<U256>,
        data: Bytes,
    ) -> Result<(), Vec<u8>> {
        self.pausable.when_not_paused()?;
        self.erc1155._mint_batch(to, token_ids, amounts, &data)?;
        Ok(())
    }

    fn burn(
        &mut self,
        account: Address,
        token_id: U256,
        value: U256,
    ) -> Result<(), Vec<u8>> {
        self.pausable.when_not_paused()?;
        self.erc1155.burn(account, token_id, value)?;
        Ok(())
    }

    fn burn_batch(
        &mut self,
        account: Address,
        token_ids: Vec<U256>,
        values: Vec<U256>,
    ) -> Result<(), Vec<u8>> {
        self.pausable.when_not_paused()?;
        self.erc1155.burn_batch(account, token_ids, values)?;
        Ok(())
    }

    fn safe_transfer_from(
        &mut self,
        from: Address,
        to: Address,
        id: U256,
        value: U256,
        data: Bytes,
    ) -> Result<(), Vec<u8>> {
        self.pausable.when_not_paused()?;
        self.erc1155.safe_transfer_from(from, to, id, value, data)?;
        Ok(())
    }

    fn safe_batch_transfer_from(
        &mut self,
        from: Address,
        to: Address,
        ids: Vec<U256>,
        values: Vec<U256>,
        data: Bytes,
    ) -> Result<(), Vec<u8>> {
        self.pausable.when_not_paused()?;
        self.erc1155.safe_batch_transfer_from(from, to, ids, values, data)?;
        Ok(())
    }

    fn supports_interface(interface_id: FixedBytes<4>) -> bool {
        Erc1155::supports_interface(interface_id)
    }

    /// WARNING: These functions are intended for **testing purposes** only. In
    /// **production**, ensure strict access control to prevent unauthorized
    /// pausing or unpausing, which can disrupt contract functionality. Remove
    /// or secure these functions before deployment.
    fn pause(&mut self) -> Result<(), Vec<u8>> {
        self.pausable.pause().map_err(|e| e.into())
    }

    fn unpause(&mut self) -> Result<(), Vec<u8>> {
        self.pausable.unpause().map_err(|e| e.into())
    }
}

Released under the MIT License.