Overview
ERC1155Singleton is an abstract contract that implements a unique variation of the ERC1155 multi-token standard. Unlike standard ERC1155 contracts that support multiple copies of each token ID, this implementation enforces that only one token exists per ID, making it behave similarly to ERC721 while maintaining ERC1155 compatibility.
Key Differences from Standard ERC1155
Singleton Pattern
Each token ID can only have a maximum balance of 1, ensuring uniqueness.
Owner Tracking
Adds
ownerOf(uint256 id) function to directly query token ownership, similar to ERC721.Simplified Balances
balanceOf returns either 0 or 1, never higher values.ENS Integration
Extends
HCAContext for Hierarchical Call Authorization in ENS v2.Contract Details
Location:src/erc1155/ERC1155Singleton.sol
Inherits:
HCAContext- Hierarchical Call Authorization contextERC165- Interface detectionIERC1155Singleton- Extended ERC1155 interface withownerOfIERC1155Errors- Standard ERC1155 error definitionsIERC1155MetadataURI- Metadata URI support
State Variables
The contract maintains minimal storage for efficient ownership tracking:Functions
supportsInterface
The interface identifier to check
True if the interface is supported, false otherwise
IERC1155(0xd9b67a26)IERC1155Singleton(0x6352211e)IERC1155MetadataURI(0x0e89341c)- All interfaces supported by parent contracts
Example
ownerOf
The token ID to query ownership for
The address that owns the token, or
address(0) if the token doesn’t existExample
balanceOf
The address to query the balance for
The token ID to query
Returns
1 if the account owns the token, 0 otherwiseExample
balanceOfBatch
balanceOf. Queries multiple account/token pairs in a single call.
Array of addresses to query balances for
Array of token IDs to query (must be same length as accounts)
Array of balances (0 or 1) corresponding to each account/id pair
accountsandidsarrays must have the same length
ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength)if array lengths don’t match
Example
setApprovalForAll
The address to grant or revoke operator status
true to grant approval, false to revokeApprovalForAll(address indexed account, address indexed operator, bool approved)
ERC1155InvalidOperator(address(0))if operator is the zero address
Example
isApprovedForAll
The token owner’s address
The operator’s address to check
true if the operator is approved, false otherwiseExample
safeTransferFrom
The current owner of the token
The recipient address
The token ID to transfer
The amount to transfer (must be 1 for singleton tokens)
Additional data to pass to the receiver contract
- Caller must be the token owner or approved operator
tocannot be the zero addressfrommust own the tokenvaluemust be 1 (singleton constraint)- If
tois a contract, it must implementIERC1155Receiver.onERC1155Received
TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)
ERC1155MissingApprovalForAll(address operator, address owner)if caller lacks approvalERC1155InvalidReceiver(address(0))if recipient is zero addressERC1155InvalidSender(address(0))if sender is zero addressERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId)if value is not 1 or sender doesn’t own the token
Example
safeBatchTransferFrom
safeTransferFrom. Transfers multiple tokens in a single transaction.
The current owner of the tokens
The recipient address
Array of token IDs to transfer
Array of amounts to transfer (each must be 1)
Additional data to pass to the receiver contract
- Same requirements as
safeTransferFrom idsandvaluesarrays must have the same length- All values must be 1
TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)
- Same reverts as
safeTransferFrom ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength)if array lengths don’t match
Example
uri
The token ID to get the URI for
The URI string for the token’s metadata
Example
Internal Functions
The following internal functions are available for use by contracts inheriting fromERC1155Singleton:
_mint
The address to mint the token to
The token ID to mint
The amount to mint (must be 1)
Additional data for receiver contract
tocannot be the zero address- Token ID must not already exist (enforced by singleton constraint)
valuemust be 1- If
tois a contract, it must implementIERC1155Receiver.onERC1155Received
TransferSingle(address indexed operator, address(0), address indexed to, uint256 id, uint256 value)
Example
_mintBatch
_mint. Creates multiple tokens in a single transaction.
The address to mint tokens to
Array of token IDs to mint
Array of amounts to mint (each must be 1)
Additional data for receiver contract
- Same as
_mint - Arrays must have the same length
TransferBatch(address indexed operator, address(0), address indexed to, uint256[] ids, uint256[] values)
Example
_burn
The current owner of the token
The token ID to burn
The amount to burn (must be 1)
fromcannot be the zero addressfrommust own the tokenvaluemust be 1
TransferSingle(address indexed operator, address indexed from, address(0), uint256 id, uint256 value)
Example
_burnBatch
_burn. Destroys multiple tokens in a single transaction.
The current owner of the tokens
Array of token IDs to burn
Array of amounts to burn (each must be 1)
- Same as
_burn - Arrays must have the same length
TransferBatch(address indexed operator, address indexed from, address(0), uint256[] ids, uint256[] values)
Example
_update
The sender address (
address(0) for minting)The recipient address (
address(0) for burning)Array of token IDs to update
Array of amounts (each must be 1 for transfers, 0 is allowed for no-ops)
- Validates array lengths match
- For each token with value > 0:
- Verifies sender owns the token
- Enforces singleton constraint (value must be 1)
- Updates ownership mapping
- Emits
TransferSingleorTransferBatchevent
ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength)if array lengths don’t matchERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId)if value exceeds 1 or sender doesn’t own the token
This function does not perform the ERC1155 receiver acceptance check. Use
_updateWithAcceptanceCheck instead when transferring to potentially unknown addresses._updateWithAcceptanceCheck
_update that includes the ERC1155 receiver acceptance check for safe transfers.
The sender address
The recipient address
Array of token IDs
Array of amounts
Additional data for receiver contract
- Calls
_updateto perform the transfer - If recipient is a contract, calls
onERC1155ReceivedoronERC1155BatchReceived - Validates the receiver returns the correct magic value
_setApprovalForAll
The token owner granting or revoking approval
The operator receiving approval status
Whether to grant or revoke approval
ApprovalForAll(address indexed owner, address indexed operator, bool approved)
ERC1155InvalidOperator(address(0))if operator is zero address
Events
TransferSingle
The address that initiated the transfer
The sender address (
address(0) for minting)The recipient address (
address(0) for burning)The token ID that was transferred
The amount transferred (always 1 for singleton tokens)
TransferBatch
The address that initiated the transfer
The sender address (
address(0) for minting)The recipient address (
address(0) for burning)Array of token IDs that were transferred
Array of amounts transferred (all values are 1 for singleton tokens)
ApprovalForAll
The token owner granting or revoking approval
The operator receiving approval status
true if approval was granted, false if revokedApproval
ERC1155Singleton (though not actively used in current implementation).
The token owner
The approved address
The token ID
Error Reference
The contract uses standard ERC1155 errors from OpenZeppelin:ERC1155InvalidOperator
address(0)).
ERC1155MissingApprovalForAll
ERC1155InsufficientBalance
- Attempting to transfer a value greater than 1 (singleton constraint)
- Sender doesn’t own the token being transferred
ERC1155InvalidReceiver
address(0).
ERC1155InvalidSender
address(0).
ERC1155InvalidArrayLength
Interface
IERC1155Singleton
The contract implements theIERC1155Singleton interface which extends standard ERC1155:
0x6352211e
This interface adds the ownerOf function to standard ERC1155, enabling direct ownership queries similar to ERC721.
Usage Examples
Basic Token Operations
Operator Approval Pattern
Batch Operations
Security Considerations
Singleton Enforcement
Singleton Enforcement
The contract enforces that only one token exists per ID by checking in
_update that values are either 0 or 1. Attempting to transfer value > 1 will revert with ERC1155InsufficientBalance.Reentrancy Protection
Reentrancy Protection
The contract follows the check-effects-interaction pattern. State changes occur in
_update before external calls in _updateWithAcceptanceCheck. Contracts inheriting should maintain this pattern.Zero Address Checks
Zero Address Checks
The contract validates against zero addresses for operators, senders, and receivers to prevent accidental burns or invalid operations.
Approval Security
Approval Security
Operator approvals grant full control over all tokens. Users should only approve trusted operators and revoke approvals when no longer needed.
Integration with ENS v2
In ENS v2,ERC1155Singleton serves as the base for registry contracts that represent name ownership:
- Token ID represents the canonical ID of an ENS name
- Owner is the address with control over the name
- Singleton constraint ensures each name has exactly one owner
- HCAContext integration enables hierarchical permission checks
See Also
Registry Contracts
See how registries use ERC1155Singleton
OpenZeppelin ERC1155
Standard ERC1155 documentation
ERC1155 Standard
Original EIP-1155 specification