Architecture Overview

The Opals Protocol implements a sophisticated template-based architecture that enables gas-efficient deployment of complete project ecosystems. This document provides a technical deep dive into system design, component relationships, and architectural patterns.

Core Design Principles

1. Template Factory Pattern (EIP-1167)

Problem Solved: Traditional contract deployment costs 4M+ gas per contract. Launching a complete project with 10+ contracts becomes prohibitively expensive.

Solution: The OpalsFactory deploys lightweight clones using EIP-1167 minimal proxies. Each clone is ~200 gas to deploy versus 4M for full contract deployment.

Implementation:

// OpalsFactory.sol - createClone function
function createClone(address target) internal returns (address result) {
    bytes20 targetBytes = bytes20(target);
    assembly {
        let clone := mload(0x40)
        mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
        mstore(add(clone, 0x14), targetBytes)
        mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
        result := create(0, clone, 0x37)
    }
}

Gas Comparison:

  • Full Contract Deploy: ~4,000,000 gas

  • Minimal Proxy Deploy: ~200,000 gas

  • Savings: 3,800,000 gas (74.7% reduction)

For a complete project with 10 contracts:

  • Traditional: 40,000,000 gas (~$4,000 at 100 gwei)

  • With Opals: 2,000,000 gas (~$200 at 100 gwei)

2. Single Initialization Pattern

Security Guarantee: Each template can only be initialized once, preventing re-initialization attacks.

Implementation Pattern:

// BaseTemplate.sol
contract BaseTemplate {
    address public project;
    bool private initialized;

    function init(address _project) public virtual {
        require(!initialized, "Already initialized");
        require(_project != address(0), "Invalid project");
        project = _project;
        initialized = true;
    }
}

Benefits:

  • Prevents malicious re-initialization after deployment

  • Ensures immutable project associations

  • Protects against state reset attacks

  • Standard pattern across all templates

3. Project-Centric Coordination

Architecture: The Project contract serves as the central coordination hub with bidirectional mappings for all relationships.

Key Relationships:

contract Project {
    address public operator;      // Access control
    address public token;         // Project token
    address public launcher;      // Liquidity launcher
    address public distributor;   // Fee distributor
    address[] public markets;     // NFT markets
    address[] public claims;      // Token claims
    address[] public cards;       // NFT cards

    // Bidirectional mappings maintain consistency
    mapping(address => address) public claimToCard;
    mapping(address => address) public cardToClaim;
    mapping(address => address) public cardToMarket;
    mapping(address => address) public marketToCard;
}

Why Bidirectional?

  • O(1) lookups in both directions (card→claim and claim→card)

  • Prevents orphaned relationships

  • Enables efficient validation of component relationships

  • Simplifies integration with external systems

System Architecture

Component Hierarchy

┌─────────────────────────────────────────────────────────┐
│                     OpalsFactory                        │
│  (EIP-1167 Deployment + Template Registry)             │
└────────────────┬────────────────────────────────────────┘
                 │ deploys

         ┌───────────────┐
         │    Project    │────────┐
         │ (Coordination │        │
         │      Hub)     │        │
         └───┬───────────┘        │
             │ manages            │ controls
    ┌────────┼────────┬───────────┼──────┐
    │        │        │           │      │
    ▼        ▼        ▼           ▼      ▼
┌────────┐ ┌─────┐ ┌──────┐  ┌────────┐ ┌────────┐
│Markets │ │Cards│ │Claims│  │Launcher│ │Operator│
└────────┘ └─────┘ └──────┘  └────────┘ └────────┘
    │         │        │          │
    └─────────┴────────┴──────────┘


        [Protocol Fees]


      ┌──────────────┐
      │ Distributor  │
      │  (Pro-Rata)  │
      └──────┬───────┘
             │ distributes
      ┌──────┴────────┐
      ▼               ▼
┌────────────┐  ┌────────────┐
│PatronClaim │  │VaultClaim  │
│   (10x)    │  │   (0-5x)   │
└────────────┘  └────────────┘

Template Types and Identifiers

Each template has a unique identifier used by the factory:

// Core Templates
bytes32 PROJECT = keccak256("PROJECT");
bytes32 TOKEN = keccak256("TOKEN");
bytes32 OPERATOR = keccak256("OPERATOR");
bytes32 CARD = keccak256("CARD");

// Market Templates
bytes32 STEPPED_MARKET = keccak256("STEPPED_MARKET");
bytes32 FIXED_MARKET = keccak256("FIXED_MARKET");
bytes32 MEMBERS_MARKET = keccak256("MEMBERS_MARKET");

// Claim Templates
bytes32 PATRON_CLAIM = keccak256("PATRON_CLAIM");
bytes32 VAULT_CLAIM = keccak256("VAULT_CLAIM");
bytes32 DIAMOND_CLAIM = keccak256("DIAMOND_CLAIM");

// Launcher Templates
bytes32 LIQUIDITY_LAUNCHER = keccak256("LIQUIDITY_LAUNCHER");

// Vault Templates
bytes32 WORKLOCK = keccak256("WORKLOCK");
bytes32 DISTRIBUTOR = keccak256("DISTRIBUTOR");

Data Flow Architecture

1. Project Launch Flow

User Purchase (Market)

    ├─→ 98% to Market
    │      │
    │      ├─→ 50% to LiquidityLauncher (for LP)
    │      └─→ 50% to Treasury

    └─→ 2% Platform Fee

           └─→ EthRewarder (permissionless distribution)

Market Success Trigger

    └─→ LiquidityLauncher.launch()

           ├─→ Create Uniswap V2 Pair
           ├─→ Add Liquidity (Token + ETH)
           └─→ Transfer LP to PatronClaim

                  └─→ Weight-Based Allocation

                         └─→ LP locked permanently

Key Points:

  • Market collects funds until success threshold met

  • LiquidityLauncher creates Uniswap pair atomically

  • LP tokens sent to PatronClaim (irreversible)

  • PatronClaim allocates based on NFT weights

  • No function exists to withdraw LP from PatronClaim

2. Protocol Fee Distribution Flow

Market Generates Fees (2% of sales)

    ├─→ 1% Creator Share
    ├─→ 0.4% Protocol Share
    ├─→ 0.3% Platform Referrer
    └─→ 0.3% Order Referrer

Protocol Share Routing

    ├─→ EthRewarder (market fee rewards)
    └─→ Distributor (ongoing protocol fees)

           ├─→ Query PatronClaim.totalPatronPower()
           ├─→ Query VaultClaim.totalPatronPower()
           └─→ Distribute pro-rata

                  ├─→ PatronClaim (based on 10x multiplier)
                  └─→ VaultClaim (based on 0-5x multiplier)

Implementation:

// Distributor.sol - distribute function
function distribute(address token) external nonReentrant {
    uint256 amount = IERC20(token).balanceOf(address(this));

    // Get patron powers from both claims
    uint256 patronPower = IPatronClaim(patronClaim).totalPatronPower();
    uint256 vaultPower = IVaultClaim(vaultClaim).totalPatronPower();
    uint256 totalPower = patronPower + vaultPower;

    // Calculate shares
    uint256 patronShare = (amount * patronPower) / totalPower;
    uint256 vaultShare = amount - patronShare;

    // Transfer to claims
    IERC20(token).safeTransfer(patronClaim, patronShare);
    IERC20(token).safeTransfer(vaultClaim, vaultShare);
}

3. WorkLock Yield Generation Flow

User Stakes ETH

    └─→ WorkLock.stake()

           ├─→ Wrap ETH to WETH
           ├─→ Deposit WETH to Aave V3
           ├─→ Receive aWETH (interest-bearing)
           └─→ Mint Card NFT to user

Background Yield Accrual

    └─→ Aave generates yield on WETH

           └─→ aWETH balance increases

User Claims Interest (permissionless)

    └─→ WorkLock.zapInterest(tokenId)

           ├─→ Claim interest from Aave
           ├─→ Take 30% treasury fee
           ├─→ Remaining 70% to ZapEth
           │      │
           │      ├─→ Swap 50% ETH for Token
           │      ├─→ Add liquidity (Token + ETH)
           │      └─→ Receive LP tokens

           └─→ LP tokens → VaultClaim

                  └─→ Permanent lock (10x multiplier)

Benefits:

  • ETH generates yield while supporting liquidity

  • Interest converted to permanent LP

  • 30% treasury fee sustains project operations

  • Permissionless zapping enables community participation

PatronPower Mathematics

PatronClaim: Permanent Lock (10x)

// PatronClaim.sol - getPatronPower function
function getPatronPower(uint256 tokenId) public view returns (uint256) {
    uint256 lpAmount = getStakedAmount(tokenId);
    return lpAmount * 10; // 10x multiplier for permanent lock
}

Formula: patronPower = lpAmount × 10

Example:

  • User receives 1000 LP tokens

  • PatronPower = 1000 × 10 = 10,000

  • Permanent lock (cannot unstake)

  • Highest reward multiplier in system

VaultClaim: Time-Based Lock (0-5x)

// VaultClaim.sol - getPatronPower function
function getPatronPower(uint256 tokenId) public view returns (uint256) {
    StakeInfo memory stake = stakes[tokenId];

    // Permanent lock case
    if (stake.lockEndTime == PERMANENT_LOCK_FLAG) {
        return stake.lpAmount * 10; // 10x for permanent
    }

    // Time-based multiplier (0-5x)
    uint256 lockDuration = stake.lockEndTime - stake.lockStartTime;
    uint256 timeRatio = (lockDuration * 1e18) / MAX_LOCKUP_PERIOD;
    uint256 multiplier = (5e18 * timeRatio) / 1e18;

    return (stake.lpAmount * multiplier) / 1e18;
}

Formula: patronPower = lpAmount × min(5, lockDuration / 4years × 5)

Examples:

  • 7 days lock: multiplier = (7 / 1460.97) × 5 = 0.024x

  • 1 year lock: multiplier = (365 / 1460.97) × 5 = 1.25x

  • 2 years lock: multiplier = (730 / 1460.97) × 5 = 2.5x

  • 4 years lock: multiplier = (1460.97 / 1460.97) × 5 = 5x

  • Permanent lock: multiplier = 10x

OVL Early Exit Penalty

// VaultClaim.sol - earlyExit function
function earlyExit(uint256 tokenId) external nonReentrant {
    StakeInfo memory stake = stakes[tokenId];
    require(block.timestamp < stake.lockEndTime, "Lock expired");

    // Calculate penalty (0-50% based on remaining time)
    uint256 lockDuration = stake.lockEndTime - stake.lockStartTime;
    uint256 remainingTime = stake.lockEndTime - block.timestamp;
    uint256 penaltyBps = (remainingTime * MAX_PENALTY_BPS) / lockDuration;

    // Apply penalty
    uint256 penalty = (stake.lpAmount * penaltyBps) / 10000;
    uint256 returnAmount = stake.lpAmount - penalty;

    // Redistribute penalty to remaining stakers
    _redistributePenalty(tokenId, penalty);

    // Return remaining LP to user
    IERC20(lpToken).safeTransfer(msg.sender, returnAmount);
}

Formula: penalty = lpAmount × (remainingTime / totalDuration) × 50%

Examples:

  • Exit immediately: 50% penalty (100% time remaining)

  • Exit at 25% complete: 37.5% penalty (75% time remaining)

  • Exit at 50% complete: 25% penalty (50% time remaining)

  • Exit at 75% complete: 12.5% penalty (25% time remaining)

  • Exit at 99% complete: 0.5% penalty (1% time remaining)

  • Exit at 100%: 0% penalty (normal unstake)

Event System Architecture

Event-Driven Integration

Opals contracts emit comprehensive events for off-chain systems to track state changes:

// Project events
event MarketAdded(address indexed market);
event ClaimAdded(address indexed claim);
event CardAdded(address indexed card);
event ProjectWired(address patronClaim, address vaultClaim,
                   address patronCard, address vaultCard, uint256 timestamp);

// Market events
event Collected(address indexed buyer, uint256 indexed tokenId,
                uint256 price, address referrer);
event EmergencyBurned(address indexed owner, uint256 indexed tokenId,
                      uint256 refundAmount);

// Claim events
event TokensDeposited(address indexed token, uint256 amount);
event TokensClaimed(address indexed claimer, address indexed token,
                    uint256 amount, uint256[] tokenIds);
event LPStaked(address indexed staker, uint256 indexed tokenId,
               uint256 amount, uint256 lockEndTime);
event EarlyExit(address indexed user, uint256 indexed tokenId,
                uint256 lpReturned, uint256 penalty);

// Launcher events
event LiquidityLaunched(address indexed pair, uint256 tokenAmount,
                        uint256 ethAmount, uint256 lpTokens);

Integration Pattern:

// Listen for project creation
factory.on("ContractCreated", (owner, addr, template) => {
  if (template === PROJECT_TEMPLATE) {
    console.log(`New project created: ${addr}`);
    trackProject(addr, owner);
  }
});

// Listen for market sales
market.on("Collected", (buyer, tokenId, price, referrer) => {
  console.log(`NFT #${tokenId} sold to ${buyer} for ${price}`);
  updateAnalytics(market, buyer, tokenId, price);
});

// Listen for liquidity launches
launcher.on("LiquidityLaunched", (pair, tokenAmount, ethAmount, lpTokens) => {
  console.log(`Liquidity launched: ${pair}`);
  enableTrading(pair, tokenAmount, ethAmount);
});

Security Architecture

1. CEI Pattern (Checks-Effects-Interactions)

Enforcement: All state-changing functions follow the CEI pattern to prevent reentrancy.

// SteppedMarket.sol - collect function
function collect(address referrer) external payable nonReentrant {
    // ===== CHECKS =====
    require(msg.value >= getCurrentPrice(), "Insufficient payment");
    require(totalSold < maxSupply, "Sold out");

    // ===== EFFECTS =====
    uint256 tokenId = totalSold;
    totalSold++;
    nftPrices[tokenId] = getCurrentPrice();

    // ===== INTERACTIONS =====
    ICard(card).mint(msg.sender, tokenId);
    _disperseFees(msg.value, referrer);
}

Benefits:

  • Prevents reentrancy attacks

  • Clear code structure for auditing

  • Consistent pattern across all contracts

  • Explicit separation of concerns

2. Access Control Patterns

Three-Tier System:

// 1. Project-Based Access
modifier onlyOperator() {
    require(IProject(project).isOperator(msg.sender), "Not operator");
    _;
}

// 2. Role-Based Access (OpenZeppelin)
modifier onlyMinter() {
    require(hasRole(MINTER_ROLE, msg.sender), "Not minter");
    _;
}

// 3. Social Layer (Clubs, Members)
modifier onlyMember() {
    require(IMembers(members).isMember(msg.sender), "Not member");
    _;
}

Access Control Matrix:

Function
Admin
Operator
Minter
Public

addMarket

addClaim

transferTokens

setOperator

collect

claimTokens

distribute

3. Reentrancy Protection

Implementation: All external state-changing functions use nonReentrant modifier.

// Inherited from ReentrancyGuard
contract VaultClaim is ReentrancyGuard {
    function stakeLP(uint256 amount, uint256 lockDuration)
        external
        nonReentrant  // Prevents reentrancy
    {
        // Implementation
    }
}

Protected Functions:

  • All token transfers (collect, stake, unstake, claim)

  • All ETH transfers (deposits, withdrawals, distributions)

  • All state-changing operations (launch, distribute, zap)

Gas Optimization Techniques

1. Batch Operations

Pattern: Combine multiple operations into single transactions.

// Project.sol - batchWireProject
function batchWireProject(
    address patronClaim,
    address vaultClaim,
    address patronCard,
    address vaultCard
) external {
    // Single transaction wires all relationships
    // Gas savings: ~500,000 gas (17% reduction)
    addClaim(patronClaim, patronCard);
    addClaim(vaultClaim, vaultCard);
    addCard(patronCard);
    addCard(vaultCard);
}

Savings: 500,000 gas per project (~17% reduction)

2. Packed Storage

Pattern: Pack multiple values into single storage slots.

struct Template {
    uint64 currentTemplateId;    // 8 bytes
    uint128 minimumFee;           // 16 bytes
    uint32 integratorFeePct;      // 4 bytes
    bool locked;                  // 1 byte
    address feeAddress;           // 20 bytes (next slot)
    uint64[] contractIds;         // dynamic array (separate slots)
}

Savings: 20,000 gas per SSTORE operation

3. EIP-1167 Minimal Proxies

Implementation: Clone contracts instead of deploying new ones.

// Gas comparison per deployment:
// Full contract: 4,000,000 gas
// Minimal proxy: 200,000 gas
// Savings: 3,800,000 gas (74.7%)

System-Wide Savings:

  • 10 contracts per project

  • Traditional: 40M gas

  • With proxies: 2M gas

  • Total savings: 38M gas (74.7%)

Conclusion

The Opals architecture combines battle-tested patterns (EIP-1167, CEI, reentrancy guards) with novel economics (PatronPower, OVL) to create a production-ready platform for sovereign startups.

Key Innovations:

  • 74.7% gas savings through minimal proxies

  • Mathematically enforced reward multipliers

  • Cryptographically guaranteed liquidity locking

  • Permissionless protocol fee distribution

  • Flexible staking with diamond hands rewards

Production Ready:

  • 375/375 tests passing

  • Comprehensive security audits completed

  • Battle-tested on multiple testnets

  • Ready for mainnet deployment

Next: Integration Guide for deployment patterns and best practices.

Last updated