← Back to Document Hub

Esperanto Stablecoin (ESP) Smart Contract Architecture

Copyright © Christian Derler

Document Version: 1.0 Last Updated: 2026-03-30 Status: Production-Ready Specification


Table of Contents

  1. Executive Summary
  2. Architecture Overview
  3. Core Contracts
  4. Multi-Chain Strategy
  5. Security Framework
  6. Deployment Plan
  7. Gas Optimization Notes

Executive Summary

The Esperanto Stablecoin (ESP) is a MiCAR-compliant Asset-Referenced Token designed to track a 5-pillar global index with institutional-grade risk management. The architecture leverages UUPS upgradeable proxies, time-locked governance, and multi-signature controls to balance innovation with regulatory requirements.

Key Characteristics: - Type: ERC-20 + ERC-2612 (permit functionality) - Compliance: MiCAR-ready with blocklist and pause controls - Primary Chain: Ethereum L1 (Mainnet) - Multi-Chain: Arbitrum, Base, Polygon (via canonical bridges) - Governance: 3-of-5 multisig with 48-hour timelock - Oracle: 2-of-3 medianization with commit-reveal scheme - Settlement: T+1 with daily limits and per-transaction caps


Architecture Overview

System-Level Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│                     ESP ECOSYSTEM ARCHITECTURE                   │
└─────────────────────────────────────────────────────────────────┘

                         ┌──────────────────┐
                         │   ESPToken.sol   │ (ERC-20 + ERC-2612)
                         │  UUPS Upgradeable│
                         └────────┬─────────┘
                                  │
                ┌─────────────────┼─────────────────┐
                │                 │                 │
                ▼                 ▼                 ▼
        ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
        │ ESPMinter.sol│  │ESPOracle.sol │  │ESPCircuit    │
        │              │  │              │  │Breaker.sol   │
        │ Primary Mint │  │ Index Oracle │  │              │
        │ Redemption   │  │ Medianizer   │  │ Emergency    │
        │ Queue        │  │ Commit-Reveal│  │ Controls     │
        └────┬─────────┘  └──────┬───────┘  └──────┬───────┘
             │                   │                 │
             └───────────────────┼─────────────────┘
                                 │
                         ┌───────▼────────┐
                         │ESPGovernance   │
                         │                │
                         │Timelock        │
                         │Parameter Mgmt  │
                         │Role Control    │
                         └────────────────┘
                                 │
                         ┌───────▼────────────────┐
                         │ 3-of-5 Gnosis Safe     │
                         │ Admin Multisig         │
                         └────────────────────────┘

MULTI-CHAIN LAYER:
┌──────────────────────────────────────────────────────────┐
│ Canonical Bridge (Lock-and-Mint Pattern)                 │
├──────────────────────────────────────────────────────────┤
│ Ethereum L1 ◄──► Arbitrum ◄──► Base ◄──► Polygon       │
│ (Primary)        (Layer 2)     (Layer 2)  (Sidechain)   │
└──────────────────────────────────────────────────────────┘

Contract Dependency Graph

ESPToken (Core)
├── Used by: ESPMinter, ESPCircuitBreaker
├── Depends on: AccessControl, ERC-2612, Pausable, Burnable
└── Storage: Balances, Allowances, Blocklist

ESPMinter (Mint/Burn Logic)
├── Calls: ESPToken.mint(), ESPToken.burn()
├── Depends on: ESPOracle (for NAV pricing)
├── Manages: Redemption queue, Fee collection
└── Storage: Pending redeemptions, Fee vault

ESPOracle (Index Feed)
├── Inputs: Chainlink aggregators (x3), Committee signer
├── Implements: Commit-Reveal with 2-of-3 threshold
├── Manages: Historical prices, Staleness detection
└── Storage: Price history, Commit state

ESPCircuitBreaker (Emergency Controls)
├── Controlled by: 3-of-5 Gnosis Safe
├── Can pause: ESPToken, ESPMinter
├── Monitors: Oracle staleness, Price volatility
└── Triggers: Fee escalation, Emergency shutdown

ESPGovernance (Parameter Management)
├── Requires: 48-hour timelock minimum
├── Controls: All parameter updates
├── Managed by: 4-of-5 multisig (emergency override)
└── Storage: Pending updates, Execution timestamps

Core Contracts

1. ESPToken.sol — ERC-20 Token

The canonical token implementation with regulatory compliance features.

Interface Specification

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

interface IESPToken {
    /// @notice Token metadata
    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint8);

    /// @notice ERC-20 standard functions
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @notice ERC-2612 Permit (gasless approvals)
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function nonces(address owner) external view returns (uint256);
    function DOMAIN_SEPARATOR() external view returns (bytes32);

    /// @notice Burnable extension
    function burn(uint256 amount) external;
    function burnFrom(address account, uint256 amount) external;

    /// @notice Pausable functions (PAUSER_ROLE only)
    function pause() external;
    function unpause() external;
    function paused() external view returns (bool);

    /// @notice Minting (MINTER_ROLE only)
    function mint(address to, uint256 amount) external;

    /// @notice Blocklist (regulatory compliance)
    function addToBlocklist(address account) external;
    function removeFromBlocklist(address account) external;
    function isBlocklisted(address account) external view returns (bool);

    /// @notice Role management
    function grantRole(bytes32 role, address account) external;
    function revokeRole(bytes32 role, address account) external;
    function renounceRole(bytes32 role, address callerConfirmation) external;
    function hasRole(bytes32 role, address account) external view returns (bool);

    /// @notice UUPS proxy upgrade
    function upgradeToAndCall(address newImplementation, bytes calldata data) external payable;

    /// @notice Events
    event Mint(address indexed to, uint256 amount);
    event Burn(address indexed from, uint256 amount);
    event BlocklistAdded(address indexed account);
    event BlocklistRemoved(address indexed account);
    event Paused(address indexed account);
    event Unpaused(address indexed account);

    // Role constants
    bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 constant BLOCKLIST_MANAGER_ROLE = keccak256("BLOCKLIST_MANAGER_ROLE");
    bytes32 constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
}

Implementation Details

Key Features: - Pausable: Emergency pause of all token transfers (transfer, transferFrom, mint, burn) - Burnable: Token holders can burn their own tokens; MINTER_ROLE can burn on behalf - Blocklist: Regulatory-compliant blocklist preventing transfers to/from sanctioned addresses - ERC-2612 Permits: Gasless approvals enabling meta-transactions - UUPS Proxy: Upgradeable via proxy for bug fixes and feature enhancements - Access Control: Fine-grained role-based permission system

State Variables:

bool private _paused;
mapping(address => bool) private _blocklist;

Roles: - MINTER_ROLE: Can mint tokens (assigned to ESPMinter) - PAUSER_ROLE: Can pause/unpause (assigned to ESPCircuitBreaker) - BLOCKLIST_MANAGER_ROLE: Can manage blocklist (assigned to Admin multisig) - UPGRADER_ROLE: Can authorize upgrades (assigned to ESPGovernance) - DEFAULT_ADMIN_ROLE: Can manage all roles (held by ESPGovernance)


2. ESPMinter.sol — Mint/Burn Logic

Manages primary market minting with NAV-based pricing and redemption queue.

Interface Specification

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IESPMinter {
    /// @notice Redemption request structure
    struct RedemptionRequest {
        address requester;
        uint256 espAmount;
        uint256 navPerToken;
        uint256 timestamp;
        bool settled;
    }

    /// @notice Mint configuration
    struct MintConfig {
        uint256 dailyMintCap;
        uint256 perTxMintCap;
        uint256 minMintAmount;
        uint256 minterFeeBps; // 5-15 basis points
        uint256 navBandBps;   // ±10 basis points
        uint256 settlementDelay; // T+1
    }

    /// @notice Requestor must be KYC'd counterparty
    function requestMint(
        address counterparty,
        uint256 fiatAmount,
        bytes calldata kycProof
    ) external returns (uint256 mintedAmount);

    /// @notice Execute pending mint (after T+1 settlement)
    function executeMint(uint256 requestId) external;

    /// @notice Requestor must be KYC'd counterparty
    function requestRedemption(uint256 espAmount) external returns (uint256 requestId);

    /// @notice Execute pending redemption (after T+1 settlement)
    function executeRedemption(uint256 requestId) external returns (uint256 fiatAmount);

    /// @notice NAV-based pricing (index value)
    function getNavPerToken() external view returns (uint256);

    /// @notice Calculate mint output given NAV and fiat amount
    function calculateMintOutput(
        uint256 fiatAmount,
        uint256 navPerToken
    ) external view returns (uint256 espAmount, uint256 feeBps);

    /// @notice Calculate redemption output
    function calculateRedemptionOutput(
        uint256 espAmount,
        uint256 navPerToken
    ) external view returns (uint256 fiatAmount, uint256 feeBps);

    /// @notice View mint configuration
    function getMintConfig() external view returns (MintConfig memory);

    /// @notice Update configuration (GOVERNANCE only)
    function updateMintConfig(
        uint256 newDailyCap,
        uint256 newPerTxCap,
        uint256 newMinAmount,
        uint256 newFeeBps,
        uint256 newBandBps
    ) external;

    /// @notice Get daily minting stats
    function getDailyMintStats() external view returns (
        uint256 todaysMintedAmount,
        uint256 remainingCap,
        uint256 resetTime
    );

    /// @notice View redemption request
    function getRedemptionRequest(uint256 requestId)
        external view returns (RedemptionRequest memory);

    /// @notice List pending redemptions
    function getPendingRedemptions() external view returns (uint256[] memory requestIds);

    /// @notice Fee collection
    function collectFees() external returns (uint256 feeAmount);
    function getFeeVault() external view returns (uint256);

    /// @notice Events
    event MintRequested(
        uint256 indexed requestId,
        address indexed counterparty,
        uint256 fiatAmount,
        uint256 espAmount,
        uint256 navPerToken,
        uint256 feeBps
    );
    event MintExecuted(
        uint256 indexed requestId,
        address indexed counterparty,
        uint256 espAmount
    );
    event RedemptionRequested(
        uint256 indexed requestId,
        address indexed requester,
        uint256 espAmount,
        uint256 navPerToken
    );
    event RedemptionExecuted(
        uint256 indexed requestId,
        address indexed requester,
        uint256 fiatAmount
    );
    event ConfigUpdated(
        uint256 dailyMintCap,
        uint256 perTxMintCap,
        uint256 minMintAmount,
        uint256 minterFeeBps,
        uint256 navBandBps
    );
    event FeeCollected(uint256 amount);
}

Implementation Details

Mint Flow: 1. KYC’d counterparty calls requestMint(fiatAmount, kycProof) — stores pending mint request 2. Contract validates: KYC status, daily cap, per-tx cap, NAV band (±10bps) 3. Returns mintedAmount = fiatAmount × navPerToken / (1 + feeBps / 10000) 4. After T+1 settlement window, counterparty calls executeMint(requestId) 5. ESPToken mints to counterparty; fees accrue to vault

Redemption Flow: 1. Token holder calls requestRedemption(espAmount) 2. ESP is held in escrow; request enters settlement queue 3. After T+1, holder calls executeRedemption(requestId) 4. Calculate: fiatAmount = espAmount × navPerToken / (1 + feeBps / 10000) 5. Fees deducted; fiat transferred to holder; ESP burned

Key Constraints: - Daily mint cap: Prevents excessive supply expansion - Per-transaction mint cap: Reduces slippage and sandwich attack surface - Minimum mint amount: Economically viable transactions - NAV band (±10bps): Prevents arbitrage against oracle feed - Fee range (5-15 bps): Configurable by governance, collected to vault

Storage:

mapping(uint256 => RedemptionRequest) public redemptionRequests;
uint256 public requestCounter;
uint256 public lastMintResetTime;
uint256 public todaysMintedAmount;
uint256 public feeVault;
MintConfig public config;

3. ESPOracle.sol — Index Oracle

Multi-source price oracle with commit-reveal scheme and 2-of-3 medianization.

Interface Specification

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IESPOracle {
    /// @notice Price entry with timestamp
    struct PriceUpdate {
        uint256 timestamp;
        uint256 navPerToken;
        bytes32 dataHash;
    }

    /// @notice Commit-reveal state
    struct CommitState {
        bytes32 commitHash;
        uint256 commitTime;
        bool revealed;
    }

    /// @notice Get current NAV per token (2-of-3 median)
    function getNavPerToken() external view returns (uint256);

    /// @notice Get price with timestamp
    function getLatestPrice() external view returns (uint256 navPerToken, uint256 timestamp);

    /// @notice Chainlink feed price (source 1)
    function getPriceFeed1() external view returns (uint256);

    /// @notice Chainlink feed price (source 2)
    function getPriceFeed2() external view returns (uint256);

    /// @notice Chainlink feed price (source 3)
    function getPriceFeed3() external view returns (uint256);

    /// @notice Committee signer price (custom feed)
    function getCommitteeSigner() external view returns (address);

    /// @notice Commit phase: committees submit hash of price and salt
    function commitPrice(bytes32 priceHash) external;

    /// @notice Reveal phase: committees submit actual price and salt
    /// @param price The NAV per token value
    /// @param salt Random value used in hash generation
    function revealPrice(uint256 price, bytes32 salt) external;

    /// @notice Finalize pricing (2-of-3 threshold)
    function finalizePrice() external;

    /// @notice Check if price is stale (> 25 hours)
    function isPriceStale() external view returns (bool);

    /// @notice Get heartbeat interval (daily update)
    function getHeartbeatInterval() external view returns (uint256);

    /// @notice Get staleness threshold (25 hours in seconds)
    function getStalenessThreshold() external view returns (uint256);

    /// @notice Get historical prices
    function getPriceHistory(uint256 lookbackHours)
        external view returns (PriceUpdate[] memory);

    /// @notice Get specific historical price
    function getPriceAt(uint256 timestamp) external view returns (uint256);

    /// @notice Update Chainlink feed addresses (GOVERNANCE only)
    function updateChainlinkFeeds(
        address feed1,
        address feed2,
        address feed3
    ) external;

    /// @notice Update committee signer (GOVERNANCE only)
    function updateCommitteeSigner(address newSigner) external;

    /// @notice Emergency price update (CIRCUIT_BREAKER only)
    function emergencyPriceUpdate(uint256 navPerToken) external;

    /// @notice Events
    event PriceCommitted(address indexed committer, bytes32 commitHash);
    event PriceRevealed(address indexed revealer, uint256 price);
    event PriceFinalized(uint256 navPerToken, uint256 timestamp);
    event PriceEmergencyUpdated(uint256 navPerToken);
    event FeedUpdated(uint8 indexed feedIndex, address newFeedAddress);
    event CommitteeSignerUpdated(address newSigner);
}

Implementation Details

Medianization Strategy: 1. Source 1-3: Chainlink USDC/USD, EUR/USD, AUD/USD aggregators (3 independent feeds) 2. Weighting: Equal weight (1/3 each) across feeds 3. Aggregation: 2-of-3 median (requires at least 2 valid prices) 4. Committee: Off-chain signer submits custom 5-pillar index (redundancy/emergency path)

Commit-Reveal Scheme:

Week T:
  Mon 08:00 UTC: Commitment phase begins
  Mon 20:00 UTC: Reveal phase begins
  Tue 04:00 UTC: Finalization deadline

Next cycle: Tue 08:00 UTC

Staleness Detection: - Max acceptable age: 25 hours - Action: ESPCircuitBreaker escalates fees, may pause minting - Emergency: Manual override via oracle signer with timelock

Historical Storage: - Stores last 52 weeks of daily prices (52 entries × ~100 bytes = 5.2 KB) - Circular buffer in-contract; offchain indexing via events - Enables dapp calculation of 7d, 30d, 365d moving averages

Storage:

address public chainlinkFeed1; // USDC/USD
address public chainlinkFeed2; // EUR/USD
address public chainlinkFeed3; // AUD/USD
address public committeeSigner;

uint256 public lastPriceUpdate;
uint256 public currentNavPerToken;

mapping(uint256 => PriceUpdate) public priceHistory;
mapping(address => CommitState) public commitStates;

4. ESPCircuitBreaker.sol — Emergency Controls

Pause controls, oracle stall detection, and dynamic fee escalation.

Interface Specification

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IESPCircuitBreaker {
    /// @notice Circuit breaker states
    enum BreakerState {
        NORMAL,
        MINT_PAUSED,
        FULLY_PAUSED,
        EMERGENCY_SHUTDOWN
    }

    /// @notice Stress configuration
    struct StressConfig {
        uint256 baseFeeBps;         // 5 bps normal
        uint256 stressFeeBps;       // 15 bps under stress
        uint256 volatilityThreshold; // 2% price swing triggers stress
        uint256 stalePriceThreshold; // 25 hours
    }

    /// @notice Get current breaker state
    function getState() external view returns (BreakerState);

    /// @notice Get current fee multiplier (accounts for stress)
    function getCurrentFeeBps() external view returns (uint256);

    /// @notice Check if minting is paused
    function isMintPaused() external view returns (bool);

    /// @notice Check if all operations paused
    function isFullyPaused() external view returns (bool);

    /// @notice Pause only minting (allow burns/redemptions)
    /// @dev Only callable by 3-of-5 Gnosis Safe
    function pauseMinting() external;

    /// @notice Pause all token operations
    /// @dev Only callable by 3-of-5 Gnosis Safe
    function pauseAllOperations() external;

    /// @notice Resume minting (exits MINT_PAUSED state)
    function resumeMinting() external;

    /// @notice Resume all operations (exits FULLY_PAUSED state)
    function resumeAllOperations() external;

    /// @notice Emergency shutdown (irreversible until governance override)
    function triggerEmergencyShutdown() external;

    /// @notice Check oracle staleness
    function isOracleStaledDetected() external view returns (bool);

    /// @notice Auto-escalate fees if oracle is stale
    function escalateFeesOnOracleStall() external;

    /// @notice Check for price volatility (2% swing)
    function isVolatilityThresholdExceeded() external view returns (bool);

    /// @notice Stress configuration management (GOVERNANCE only)
    function updateStressConfig(
        uint256 newBaseFeeBps,
        uint256 newStressFeeBps,
        uint256 newVolatilityThreshold,
        uint256 newStalePriceThreshold
    ) external;

    /// @notice Get stress configuration
    function getStressConfig() external view returns (StressConfig memory);

    /// @notice Get multisig guard
    function getMultisig() external view returns (address);

    /// @notice Events
    event MintPaused(address indexed caller, uint256 timestamp);
    event AllOperationsPaused(address indexed caller, uint256 timestamp);
    event MintResumed(address indexed caller, uint256 timestamp);
    event AllOperationsResumed(address indexed caller, uint256 timestamp);
    event EmergencyShutdownTriggered(address indexed caller, uint256 timestamp);
    event FeesEscalated(uint256 oldFeeBps, uint256 newFeeBps, string reason);
    event OracleStalenessDetected(uint256 lastUpdateTime);
    event VolatilityThresholdExceeded(uint256 priceChange);
}

Implementation Details

State Machine:

NORMAL ──pause_mint──> MINT_PAUSED
  │                        │
  │                   resume_mint
  │                        │
  │                        ▼
  └──pause_all──────────> FULLY_PAUSED
                               │
                        resume_all
                               │
                               ▼
                           NORMAL

Emergency override from any state:
Any state ──trigger_shutdown──> EMERGENCY_SHUTDOWN
                               (only governance can recover)

Fee Escalation Triggers: 1. Oracle Stale (> 25 hours): Fees escalate from 5bps → 15bps 2. Price Volatility (> 2% swing): Fees escalate from 5bps → 15bps 3. Manual Admin Action: Explicit fee escalation (e.g., after market stress event)

Multisig Guard: - Pause operations require 3-of-5 Gnosis Safe approval - Resume operations require 3-of-5 Gnosis Safe approval - Emergency shutdown can be triggered by oracle signer (with timelock review)

Storage:

BreakerState public currentState = BreakerState.NORMAL;
address public multisigGuard; // 3-of-5 Gnosis Safe
uint256 public lastStateChange;
StressConfig public stressConfig;

5. ESPGovernance.sol — Parameter Management

Timelock-protected governance with role management and emergency override.

Interface Specification

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IESPGovernance {
    /// @notice Pending parameter update
    struct PendingUpdate {
        bytes32 updateType; // keccak256("MINT_CONFIG"), etc.
        bytes updateData;
        uint256 proposalTime;
        uint256 executionTime;
        bool executed;
        bool canceled;
    }

    /// @notice Propose parameter update (requires 48h timelock)
    function proposeUpdate(
        bytes32 updateType,
        bytes calldata updateData
    ) external returns (uint256 updateId);

    /// @notice Execute pending update after timelock
    function executeUpdate(uint256 updateId) external;

    /// @notice Cancel pending update (emergency)
    function cancelUpdate(uint256 updateId) external;

    /// @notice Get pending update details
    function getPendingUpdate(uint256 updateId)
        external view returns (PendingUpdate memory);

    /// @notice Get timelock duration (48 hours)
    function getTimelockDuration() external view returns (uint256);

    /// @notice Can execute update?
    function canExecute(uint256 updateId) external view returns (bool);

    /// @notice Grant role to account (requires timelock)
    function proposeGrantRole(bytes32 role, address account)
        external returns (uint256 updateId);

    /// @notice Revoke role from account (requires timelock)
    function proposeRevokeRole(bytes32 role, address account)
        external returns (uint256 updateId);

    /// @notice Emergency override (4-of-5 multisig only, no timelock)
    function emergencyOverride(
        bytes32 updateType,
        bytes calldata updateData
    ) external;

    /// @notice Get admin multisig
    function getAdminMultisig() external view returns (address);

    /// @notice Get emergency multisig (4-of-5)
    function getEmergencyMultisig() external view returns (address);

    /// @notice Events
    event UpdateProposed(
        uint256 indexed updateId,
        bytes32 indexed updateType,
        uint256 proposalTime,
        uint256 executionTime
    );
    event UpdateExecuted(uint256 indexed updateId, bytes32 indexed updateType);
    event UpdateCanceled(uint256 indexed updateId, bytes32 indexed updateType);
    event EmergencyOverrideExecuted(bytes32 indexed updateType);
}

Implementation Details

Timelock Protection: - All parameter changes require 48-hour minimum delay - Proposal includes: updateType, updateData, proposalTime, executionTime - Execution window: [executionTime, executionTime + 2 days] - After 2 days, update expires and must be re-proposed

Update Types: - MINT_CONFIG: Daily cap, per-tx cap, min amount, fees, NAV band - ORACLE_CONFIG: Chainlink feeds, committee signer, staleness threshold - STRESS_CONFIG: Base fees, stress fees, volatility threshold - GRANT_ROLE: Assign role to address - REVOKE_ROLE: Remove role from address - UPGRADE_TOKEN: Authorize new ESPToken implementation

Emergency Override: - 4-of-5 multisig can execute immediately (no timelock) - Use cases: Critical bug discovered, regulatory requirement, oracle failure recovery - All emergency overrides logged and reported to token holders

Storage:

uint256 public constant TIMELOCK_DURATION = 48 hours;
uint256 public updateCounter;
mapping(uint256 => PendingUpdate) public pendingUpdates;
address public adminMultisig; // 3-of-5
address public emergencyMultisig; // 4-of-5

Multi-Chain Strategy

Canonical Bridge Design: Lock-and-Mint Pattern

Architecture:

┌─────────────────────────────────────────────────────────────┐
│                    ETHEREUM L1 (Primary)                     │
│                                                               │
│  ESPToken ◄────────── ESPBridge (Lock) ──────────►          │
│    (Balance: 100M)                                            │
└────────────────────────┬──────────────────────────────────────┘
                         │
          ┌──────────────┴──────────────┐
          │                             │
    ┌─────▼──────────┐         ┌─────────▼──────────┐
    │    ARBITRUM    │         │      BASE          │
    │    (Layer 2)   │         │    (Layer 2)       │
    │                │         │                    │
    │ ◄─ Mint ESP ◄┐ │         │ ◄─ Mint ESP ◄┐   │
    │     token    │ │         │     token    │   │
    │             ┌┘ │         │             ┌┘   │
    │ (Balance: 50M) │         │ (Balance: 30M)   │
    └────────────────┘         └───────────────────┘
          │                             │
          │                      ┌──────▼─────────┐
          │                      │   POLYGON      │
          │                      │  (Sidechain)   │
          │                      │                │
          └─ ◄─ Mint ESP ◄────────►   token      │
               token      relay      (Balance:    │
                                      20M)       │
                            └────────────────────┘

Deployment Per Chain

Ethereum L1 (Primary): - ESPToken (canonical implementation) - ESPMinter (primary minting authority) - ESPOracle (authoritative price feed) - ESPCircuitBreaker (emergency controls) - ESPGovernance (parameter management) - All governance/multisigs deployed here

Arbitrum One (Layer 2): - ESPToken (bridged copy, locked on L1) - ESPBridgeAdapter (minimal: receive mint messages, emit burn events) - No minting authority (depends on L1) - Oracle price feed relayed from L1 (updated hourly via Chainlink)

Base (Layer 2): - ESPToken (bridged copy, locked on L1) - ESPBridgeAdapter (minimal) - Staking/reward contracts (future expansion)

Polygon (Sidechain): - ESPToken (bridged copy, locked on L1) - ESPBridgeAdapter (minimal) - Native AMM liquidity pools (Uniswap v3)

Cross-Chain Message Verification

Whitelist Bridge Protocols: 1. Ethereum ↔︎ Arbitrum: Arbitrum native bridge (most secure) 2. Ethereum ↔︎ Base: Optimism native bridge 3. Ethereum ↔︎ Polygon: Polygon native bridge (PoS) 4. Fallback: LayerZero (for non-native chain pairs)

Message Verification:

interface IBridgeAdapter {
    // Verify message came from authorized bridge
    function verifyBridgeMessage(
        bytes calldata message,
        bytes calldata proof,
        uint256 sourceChainId
    ) external view returns (bool);

    // Relay mint authorization from L1
    function relayMintAuthorization(
        uint256 mintAmount,
        address recipient,
        bytes calldata l1Signature
    ) external;

    // Emit burn event for L1 settlement
    function burnAndNotifyL1(
        uint256 amount,
        address burner
    ) external;
}

Chain-Specific Considerations

Ethereum L1: - Gas optimization: Batch settlement on off-peak hours - Storage: On-chain history of all transactions - Multisig: 3-of-5 Gnosis Safe (mainnet)

Arbitrum: - Compressed calldata via L1 batching (lower fees) - Update oracle feed hourly instead of daily (no additional cost) - Multisig: Same 3-of-5 Safe contract (cross-chain execution)

Base: - Optimized for Coinbase integration (KYC easier) - Lower fees → lower mint minimums acceptable - Liquidity AMM incentives from Coinbase

Polygon: - Lower security model (PoS) → lower caps initially (Phase 1: $50M) - Staking/reward contracts for community engagement - Separate staking multisig (2-of-3 higher risk tolerance)


Security Framework

Audit Plan

Phase 1: Pre-Mainnet Audits (Timeline: Months 1-3)

  1. OpenZeppelin Formal Security Review
    • Scope: ESPToken, ESPMinter, ESPOracle, ESPCircuitBreaker
    • Focus: Access control, reentrancy, overflow/underflow, upgrade path
    • Estimated cost: $75K, Duration: 2 weeks
    • Expected completion: Month 1
  2. Trail of Bits Advanced Security Audit
    • Scope: Full contract suite + multi-chain integration
    • Focus: Game theory, oracle manipulation, cross-chain message passing
    • Estimated cost: $100K, Duration: 3 weeks
    • Expected completion: Month 2
  3. Certora Formal Verification (ESPOracle, ESPMinter)
    • Scope: Mathematical correctness of pricing logic and settlement
    • Properties to verify:
      • Mint output always ≥ (fiatAmount - fiatAmount × 0.15%) / navPerToken
      • Redemption output always ≥ (espAmount × navPerToken - espAmount × navPerToken × 0.15%)
      • Oracle staleness always detected within 25 hours + 1 block
      • Multisig can never be bypassed
    • Estimated cost: $50K, Duration: 2 weeks
    • Expected completion: Month 2

Phase 2: Testnet Security (Timeline: Months 2-3)

Bug Bounty Program (Immunefi)

Tier Structure:

Severity Bounty Range Examples
Critical $50K - $100K Mint without authorization, oracle manipulation allowing $1M+ fraud, break multisig
High $10K - $50K Pause bypass, fee calculation error >100bps, reentrancy allowing token theft
Medium $1K - $10K Oracle feed manipulation (< 1%), parameter escaping timelock
Low $100 - $1K Precision loss in calculations, event log inconsistencies

Rules: - Bounties paid in stablecoin (USDC) - Submission via Immunefi (no direct emails) - 90-day responsible disclosure window - KYC required for claims > $5K

Incident Response Plan

Triggers: - Oracle feed down > 2 hours - Unauthorized mint > $100K detected - Multisig key compromise suspected - Smart contract exploit active

Escalation:

L1: Monitoring System (automated alerts)
    ↓
L2: Incident Lead (responds within 15 min)
    ↓
L3: Technical Team (investigates, deploys patch if needed)
    ↓
L4: 3-of-5 Multisig (votes on emergency pause/upgrade within 30 min)
    ↓
L5: Legal/Regulatory (notifies relevant authorities within 1 hour)
    ↓
L6: Public Communication (transparency report within 3 hours)

Response Actions (in order): 1. Pause affected operations (30 minutes max) - If oracle: ESPCircuitBreaker escalates fees - If mint leak: ESPCircuitBreaker pauses minting - If contract bug: Full pause via multisig 2. Investigate root cause (24-48 hours) - Blockchain forensics, transaction replay analysis - Third-party auditor on-call (2-hour SLA) 3. Prepare fix (48-72 hours) - Code patch + re-audit (abbreviated) - Test on Sepolia with same parameters 4. Emergency upgrade (if critical) - 4-of-5 multisig emergency override (no timelock) - Implement new implementation, proxy points to it - Maintain state integrity (no fund loss) 5. Reassessment (post-incident) - Full timeline published on governance forum - Lessons learned documented - Preventive measures (upgraded monitoring, additional circuit breakers)

Upgrade Governance

Standard Upgrade Path (48-hour timelock): 1. Propose new implementation contract (fully audited) 2. 3-of-5 multisig approves proposal 3. 48-hour public review period (community challenge) 4. Execute upgrade: UUPS proxy calls upgradeToAndCall(newImpl, data)

Emergency Upgrade Path (no timelock): 1. Critical vulnerability discovered and confirmed by 2 auditors 2. 4-of-5 emergency multisig votes (1-hour window) 3. Immediate execution if 4+ signatures collected 4. Ratification by governance within 7 days (or rollback)

Upgrade Contract Specification:

interface IESPTokenV2 {
    // All functions from V1 (backward compatible)
    // Plus new functions:

    // Data migration function (called during upgrade)
    function initializeV2(bytes calldata migrationData) external;

    // Version identifier
    function version() external pure returns (uint256);

    // Must return exact bytecode hash for relay nodes to verify
    function implCodeHash() external pure returns (bytes32);
}

Deployment Plan

Phase 1: Testnet Deployment (Sepolia, Week 1-2)

Contracts to Deploy: 1. ESPToken (UUPS proxy) 2. ESPMinter (UUPS proxy) 3. ESPOracle (with mock Chainlink feeds) 4. ESPCircuitBreaker 5. ESPGovernance (with 2-of-3 test multisig) 6. Sepolia Gnosis Safe (3-of-3: 3 test signers)

Initialization Parameters:

// ESPToken
name: "Esperanto Test"
symbol: "ESP-TEST"
decimals: 18
initialAdmin: governance.address

// ESPMinter
dailyMintCap: 1_000_000e18 // 1M tokens
perTxMintCap: 100_000e18   // 100K tokens
minMintAmount: 10_000e18   // 10K tokens
minterFeeBps: 10            // 10 bps initial
navBandBps: 10              // ±10 bps

// ESPOracle
chainlinkFeed1: 0x...       // USDC/USD Sepolia
chainlinkFeed2: 0x...       // EUR/USD Sepolia
chainlinkFeed3: 0x...       // AUD/USD Sepolia
committeeSigner: 0x...      // Test signer

// ESPCircuitBreaker
baseFeeBps: 5
stressFeeBps: 15
volatilityThreshold: 200    // 2%
stalePriceThreshold: 90000  // 25 hours

Testnet Activities (4 weeks): - Week 1: Deploy, smoke test, KYC 5 test counterparties - Week 2: Daily test mints/redeems ($10K-$100K range) - Week 3: Stress test (rapid mints, oracle feed interruptions) - Week 4: Circuit breaker tests, emergency pause/resume

Success Criteria: - All transactions confirmed on-chain - No arithmetic overflows/underflows - Multisig guards working correctly - Fee calculations accurate to 1 wei


Phase 2: Guarded Mainnet Launch (Ethereum L1, Week 3-4)

Initial Caps: - Daily mint cap: $10M - Per-transaction mint cap: $1M - Total initial supply cap: $500M (enforced by code)

Counterparties Phase 2 (5 large institutions): - Kraken custody - Fidelity prime brokerage - Bitcoin Suisse OTC - Wintermute market maker - Lido staking (liquidity provider)

Deployment: 1. Deploy production contracts (audited versions) 2. Initialize with same multisig (3-of-5 Gnosis Safe) 3. Activate oracle with live Chainlink feeds 4. Enable minting with $10M daily cap 5. Monitor continuously for 2 weeks

Monitoring Infrastructure: - Real-time price feed validation (±2% from CME) - Mint/redemption latency tracking (target: < 2 minutes) - Smart contract storage variance detection - Multisig transaction alerting (Gnosis Safe integration)

Go/No-Go Decision Points: | Week | Criteria | Action | |——|———-|——–| | 1 | >$100M minted, zero exploits | Increase daily cap to $25M | | 2 | Consistent NAV tracking within ±15bps | Increase to $50M daily cap | | 3 | Oracle uptime >99.9%, fees collected correctly | Full mainnet activation |


Phase 3: Full Mainnet + Multi-Chain Expansion (Week 5+)

Full Mainnet (Ethereum L1): - Remove daily cap (market-driven) - Per-tx cap: $5M - Activate full governance (48-hour timelock)

Multi-Chain Rollout (sequential):

Arbitrum One (Week 6): - Deploy bridged ESPToken - Activate Arbitrum bridge (native) - Initial cap: $100M total bridged - Deploy ESPBridgeAdapter to relay Chainlink prices

Base (Week 7): - Deploy bridged ESPToken - Activate Base bridge - Target: 30% of Arbitrum volume (for balance)

Polygon PoS (Week 8): - Deploy bridged ESPToken - Limited to $20M initial (PoS security model) - Partner with Uniswap v3 for DEX liquidity


Gas Optimization Notes

Solidity Version & Compiler Settings

pragma solidity ^0.8.20;
// solc: --optimize --optimize-runs=200
// (prioritize deployment gas over runtime calls)

Key Optimizations

1. ESPToken Storage Packing

// ❌ Not optimized (4 slots)
mapping(address => uint256) balances;      // slot 0
mapping(address => mapping(address => uint256)) allowances; // slot 1
bool paused;                               // slot 2
mapping(address => bool) blocklist;        // slot 3

// ✅ Optimized (3 slots)
mapping(address => uint256) balances;      // slot 0
mapping(address => mapping(address => uint256)) allowances; // slot 1
mapping(address => bool) blocklist;        // slot 2 (reuse)
bool paused;                               // packed into slot 3 with future bool

2. ESPMinter Redemption Queue (Array vs Mapping)

// ❌ Array iteration O(n), worse gas as queue grows
RedemptionRequest[] public redemptionQueue;

// ✅ Mapping + counter (O(1) lookup, O(1) removal)
mapping(uint256 => RedemptionRequest) redemptionRequests;
uint256 public requestCounter;

3. ESPOracle Price History (Circular Buffer)

// ❌ Unbounded array (storage grows forever)
PriceUpdate[] public allPriceHistory;

// ✅ Circular buffer (fixed 52 slots = 1 year daily)
uint256 constant HISTORY_SIZE = 52;
mapping(uint256 => PriceUpdate) priceHistory;
uint256 priceHistoryIndex = 0;

function recordPrice(uint256 nav) internal {
    priceHistory[priceHistoryIndex] = PriceUpdate({
        timestamp: block.timestamp,
        navPerToken: nav
    });
    priceHistoryIndex = (priceHistoryIndex + 1) % HISTORY_SIZE;
}

4. ESPMinter Fee Calculations (Integer Math)

// Avoid division, use proportional math
uint256 constant BPS_DENOMINATOR = 10_000;

// ❌ Imprecise (rounding errors)
uint256 feeAmount = (espAmount * feeBps) / BPS_DENOMINATOR;

// ✅ Precise (ceiling rounding, no loss)
uint256 feeAmount = (espAmount * feeBps + BPS_DENOMINATOR - 1) / BPS_DENOMINATOR;

5. Multi-call Batch Operations

// ❌ Separate calls (N × SLOAD + SSTORE overhead)
for (uint256 i = 0; i < users.length; i++) {
    token.burn(users[i], amounts[i]);  // 2 SSTOREs each
}

// ✅ Single batch (1 × SLOAD + 1 × SSTORE)
interface IMulticall {
    function batchBurn(address[] users, uint256[] amounts) external;
}

Gas Cost Estimates

Operation Gas Notes
Mint (request + execute) 180K Includes oracle read, settlement delay
Burn 40K Standard ERC-20
Transfer 65K With blocklist check
Oracle price update (finalize) 120K 2-of-3 median calculation
Pause (multisig) 3K Just state change; multisig overhead ~50K separate
Emergency shutdown 35K State machine transition + event logging

Layer 2 Optimizations

Arbitrum calldata compression: - Use BLS signatures for batch rollups (~100 bytes → 20 bytes) - Estimate: Arbitrum gas ~10x cheaper than Ethereum L1

Base calldata: - Similar to Arbitrum - Expected: $0.05 per transaction

Polygon native: - PoS validators (centralized), lower gas - Expected: $0.01 per transaction


Appendix: Role Management Matrix

Role Contract Granted To Permissions
DEFAULT_ADMIN ESPToken ESPGovernance Can grant/revoke all roles
MINTER_ROLE ESPToken ESPMinter Can call mint()
PAUSER_ROLE ESPToken ESPCircuitBreaker Can call pause()/unpause()
BLOCKLIST_MANAGER ESPToken Admin Multisig Can add/remove blocklist addresses
UPGRADER_ROLE ESPToken ESPGovernance Can authorize proxy upgrades
CIRCUIT_BREAKER_ADMIN ESPCircuitBreaker 3-of-5 Gnosis Safe Can trigger pause/resume
ORACLE_SIGNER ESPOracle Committee members (3) Can commit/reveal prices
GOVERNANCE_PROPOSER ESPGovernance 3-of-5 Gnosis Safe Can propose parameter updates
GOVERNANCE_EXECUTOR ESPGovernance Timelock contract Can execute after 48h delay

Summary

This document specifies a production-ready smart contract architecture for the Esperanto Stablecoin (ESP), a MiCAR-compliant Asset-Referenced Token. The system balances decentralization (DAOs, multisigs) with regulatory compliance (blocklists, pausability) through carefully engineered access controls, time-locked governance, and redundant oracle feeds.

Key design principles: - Safety First: Multiple circuit breakers, oracle staleness detection, emergency pause paths - Transparency: All state changes emit events; on-chain settlement history - Auditability: UUPS proxy pattern allows coordinated upgrades; formal verification for core logic - Scalability: Multi-chain expansion via canonical bridges; low gas via circular buffers and batch operations

The deployment plan progresses through testnet validation, guarded mainnet launch with conservative caps, and full multi-chain expansion. Security is layered: code audits, formal verification, bug bounties, and incident response procedures.

Next Steps: 1. Auditor selection (OpenZeppelin, Trail of Bits, Certora) 2. Testnet deployment and 4-week validation period 3. Mainnet launch with $10M daily cap 4. Progressive expansion to $500M supply across 4 chains


Document License: Creative Commons Attribution 4.0 International (CC BY 4.0)