# ERC-1155, Signatures and Merkle Allowlists — Blockchain & Smart Contracts (Solidity)

Source: https://www.geekswithgeeks.com/en/solidity/t-erc1155

> Use multi-token contracts and gas-efficient allowlists.

## Multi-tokens and efficient distribution

**ERC-1155** is a **multi-token standard**: one contract manages many token types, each identified by an id, which may be fungible (many copies, like game gold) or non-fungible (supply of one). It supports **batch transfers** and batch balance queries, saving gas for games and collections with many item types. Metadata uses a single URI template with `{id}` substitution. For distributing tokens to a known list of addresses, storing every address on chain is expensive. A **Merkle tree** solves this: compute a tree off chain over all eligible entries, store only the 32-byte **Merkle root** in the contract, and let each user submit a **proof** (a list of sibling hashes) showing their entry is in the tree; OpenZeppelin's **`MerkleProof.verify`** checks it. Alternatively, a trusted signer can issue **signed vouchers** (EIP-712 typed data) that users redeem, which is flexible but relies on the signer key. In both cases, track **claimed** status to prevent double claims and include the contract address and chain id in what is signed or hashed to prevent replay on other deployments.

## A Merkle allowlist claim

Only the root is stored on chain; users submit proofs.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract EventBadges is ERC1155 {
    uint256 public constant ATTENDEE = 1;
    uint256 public constant SPEAKER = 2;

    bytes32 public immutable merkleRoot;
    mapping(address => bool) public claimed;

    error AlreadyClaimed();
    error InvalidProof();

    constructor(bytes32 root) ERC1155("ipfs://bafy.../{id}.json") {
        merkleRoot = root;
    }

    /// Leaves are keccak256(bytes.concat(keccak256(abi.encode(account, badgeId))))
    function claim(uint256 badgeId, bytes32[] calldata proof) external {
        if (claimed[msg.sender]) revert AlreadyClaimed();
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, badgeId))));
        if (!MerkleProof.verify(proof, merkleRoot, leaf)) revert InvalidProof();
        claimed[msg.sender] = true;
        _mint(msg.sender, badgeId, 1, "");
    }
}
// The double hash matches OpenZeppelin's standard Merkle tree JavaScript library
// and prevents second-preimage attacks on 64-byte leaves.
```

## Use the matching off-chain library

Leaf encoding must match exactly between the script that builds the tree and the contract. OpenZeppelin's `merkle-tree` JavaScript package produces double-hashed leaves compatible with the pattern above.

**Quiz:** What does a Merkle allowlist store on chain?

- [x] Only the Merkle root; users provide proofs of inclusion
- [ ] Every eligible address
- [ ] The private keys of users
- [ ] Nothing at all

*Answer:* Only the Merkle root; users provide proofs of inclusion. A single 32-byte root commits to the whole list.
