Skip to main content

Facets & Libraries

Facets & Libraries

Understanding the core architecture: how facets and libraries work together through shared storage to build powerful, composable smart contract systems.

Overview

Compose uses a dual-component architecture where facets provide complete implementations and libraries provide reusable helper functions. Both work with the same shared storage, enabling seamless integration.

Key Concept

Facets are complete implementations. Libraries are helper functions. Both access the same storage.

Architecture Comparison

ComponentPurposeUse Case
FacetsComplete, self-contained implementationsUse as-is in your diamond for standard functionality
LibrariesHelper functions for custom facetsImport into your custom facets to extend Compose

Facets: Complete Implementations

Facets are fully functional smart contracts that can be added to your diamond:

Self-Contained

Complete implementations ready to use out of the box.

Plug & Play

Add to your diamond via the diamond cut function.

Verified

Audited, tested, and deployed on multiple chains.

Example: ERC721Facet

// ERC721Facet provides standard NFT functionality
contract ERC721Facet {
function balanceOf(address owner) external view returns (uint256) {
return LibERC721.balanceOf(owner);
}

function ownerOf(uint256 tokenId) external view returns (address) {
return LibERC721.ownerOf(tokenId);
}

function transferFrom(
address from,
address to,
uint256 tokenId
) external {
LibERC721.transferFrom(from, to, tokenId);
}

// ... more standard ERC721 functions
}
Ready to Use

You can add ERC721Facet to your diamond and immediately have full ERC721 functionality!

Libraries: Helper Functions

Libraries provide internal functions that your custom facets can use:

Building Blocks

Reusable functions for your custom logic.

Shared Storage

Access the same data as standard facets.

Flexible

Combine with your custom logic however you need.

Example: LibERC721

// LibERC721 provides helper functions for custom facets
library LibERC721 {
function mint(address to, uint256 tokenId) internal {
// Modifies storage that ERC721Facet reads
ERC721Storage storage s = erc721Storage();
s.owners[tokenId] = to;
s.balances[to]++;
// ... emit events, etc.
}

function burn(uint256 tokenId) internal {
// Modifies same storage
ERC721Storage storage s = erc721Storage();
address owner = s.owners[tokenId];
delete s.owners[tokenId];
s.balances[owner]--;
// ... emit events, etc.
}

// ... more helper functions
}

The Magic: Shared Storage

Both facets and libraries access the same storage location in your diamond:

// Your custom facet
import {LibERC721} from "compose/LibERC721.sol";

contract GameNFTFacet {
function mintWithGameLogic(
address player,
uint256 tokenId
) external {
// Your custom game logic
require(
playerHasEnoughPoints(player),
"Not enough points"
);

// Use LibERC721 to mint
// This modifies the SAME storage that
// ERC721Facet uses!
LibERC721.mint(player, tokenId);

// Now ERC721Facet.ownerOf(tokenId)
// returns player!
updatePlayerStats(player);
}
}
The Power of Composition

You don't need to inherit from anything. You don't need to override functions. Just use the library functions and everything works together!

Visual Diagram

┌─────────────────────────────────────────┐
│ Diamond Contract │
├─────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ ERC721Facet │ │ GameNFTFacet │ │
│ │ │ │ │ │
│ │ - balanceOf()│ │ - mint with │ │
│ │ - ownerOf() │ │ game logic │ │
│ │ - transfer() │ │ │ │
│ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ │ ┌─────────────────┘ │
│ ▼ ▼ │
│ ┌─────────────┐ │
│ │ LibERC721 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Storage │ │
│ │ (Shared) │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────┘

When to Use Each

Use Facets When...

You want standard, complete functionality with no customization needed.

  • Implementing standard token interfaces
  • Adding access control
  • Using governance systems

Use Libraries When...

You're building custom facets that need to integrate with Compose.

  • Creating custom minting logic
  • Building game mechanics
  • Implementing custom rules

Real-World Example

Let's build a game where players earn NFTs by completing quests:

import {LibERC721} from "compose/LibERC721.sol";
import {LibAccessControl} from "compose/LibAccessControl.sol";

contract QuestRewardsFacet {
bytes32 public constant GAME_MASTER = keccak256("GAME_MASTER");

mapping(uint256 => bool) public questCompleted;

function completeQuest(
address player,
uint256 questId,
uint256 tokenId
) external {
// Use LibAccessControl to check permissions
require(
LibAccessControl.hasRole(GAME_MASTER, msg.sender),
"Not authorized"
);

// Your game logic
require(!questCompleted[questId], "Quest already claimed");
questCompleted[questId] = true;

// Use LibERC721 to mint reward NFT
LibERC721.mint(player, tokenId);

// The standard ERC721Facet.ownerOf(tokenId) now works!
// The player can transfer using ERC721Facet.transferFrom()!
}
}
Seamless Integration

Your QuestRewardsFacet mints NFTs that work with all standard ERC721 functions. No inheritance, no complexity!

Best Practices

Do: Use Libraries in Custom Facets

Import and use library functions to integrate with Compose functionality.

Do: Add Standard Facets As-Is

Use complete facets when you don't need customization.

Don't: Modify Standard Facets

Create custom facets instead. Keep standard facets unchanged.

Don't: Call Facets from Facets

Use libraries for facet-to-facet communication, not external calls.

Available Facets & Libraries

ERC20

Fungible token standard with mint, burn, and transfer functionality.

ERC721

Non-fungible token (NFT) standard with full metadata support.

ERC1155

Multi-token standard supporting both fungible and non-fungible tokens.

Access Control

Role-based permission system for managing contract access.

Next Steps