A protocol for deploying omnichain tokens with deterministic addresses across multiple blockchains. Built on Doppler Airlock technology and LayerZero V2 for secure cross-chain messaging.
Holograph Protocol enables the creation of ERC-20 tokens with deterministic addresses across supported chains. The protocol integrates with Doppler's token factory system for secure token launches and automates fee collection through LayerZero V2 cross-chain messaging.
- Deterministic Addresses: CREATE2-based deployment ensures consistent addresses across chains
- Doppler Integration: Authorized token factory for Doppler Airlock launches
- Fee Automation: Automated fee collection and cross-chain distribution
- LayerZero V2: Cross-chain fee bridging infrastructure
- HolographDeployer: Deterministic contract deployment system with salt validation
Base Chain LayerZero V2 Ethereum Chain
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ Doppler Airlock │────────▶│ │ │ │
│ ↓ │ │ Message │ │ │
│ HolographFactory│ │ Passing │ │ │
│ │ │ │ │ │
│ FeeRouter │────────▶│ (Fees) │─────────▶│ Fee Processing │
│ │ │ │ │ │
│ HolographERC20 │ │ │ │ StakingRewards │
└─────────────────┘ └─────────────┘ └─────────────────┘
Primary Chains: Base (token creation) and Ethereum (fee processing/staking)
Additional Support: Unichain deployment available for expanded reach
Note: Cross-chain token bridging is temporarily deferred. Currently, only fee bridging is supported through LayerZero V2.
The project includes a streamlined Makefile for common development tasks. All deployment operations default to dry-run mode for safety - no real transactions are sent unless explicitly enabled.
# Development
make build # Compile all contracts
make test # Run the full test suite
make fmt # Format Solidity code
make clean # Clean build artifacts
# View all available commands
make help
Dry-run mode (default - safe for testing):
# Primary chains
make deploy-base # Simulate Base deployment
make deploy-eth # Simulate Ethereum deployment
# Additional chains
make deploy-unichain # Simulate Unichain deployment
# Configuration
make configure-base # Simulate Base configuration
make configure-eth # Simulate Ethereum configuration
make configure-unichain # Simulate Unichain configuration
# Operations
make fee-ops # Simulate fee operations
Live deployment mode:
# Set environment variables
export BROADCAST=true
export DEPLOYER_PK=0x... # For deploy-* commands
export OWNER_PK=0x... # For configure-* commands
export OWNER_PK=0x... # For fee operations (owner-only)
# Required RPC URLs
export BASE_RPC_URL=https://mainnet.base.org
export ETHEREUM_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY
export UNICHAIN_RPC_URL=https://mainnet.unichain.org
# Required API keys for verification
export BASESCAN_API_KEY=your_basescan_key
export ETHERSCAN_API_KEY=your_etherscan_key
export UNISCAN_API_KEY=your_uniscan_key
# Deploy to primary chains
make deploy-base # Deploy to Base mainnet
make deploy-eth # Deploy to Ethereum mainnet
# Optionally deploy to additional chains
make deploy-unichain # Deploy to Unichain mainnet
Variable | Required For | Description |
---|---|---|
BROADCAST |
Live deployments | Set to true to send real transactions |
DEPLOYER_PK |
deploy-* commands |
Private key for contract deployment |
OWNER_PK |
configure-* commands |
Private key for contract administration |
OWNER_PK |
fee-* commands |
Private key for fee operations (owner-only) |
BASE_RPC_URL |
Base operations | RPC endpoint for Base network |
ETHEREUM_RPC_URL |
Ethereum operations | RPC endpoint for Ethereum network |
UNICHAIN_RPC_URL |
Unichain operations | RPC endpoint for Unichain network |
BASESCAN_API_KEY |
Base verification | API key for Basescan contract verification |
ETHERSCAN_API_KEY |
Ethereum verification | API key for Etherscan contract verification |
UNISCAN_API_KEY |
Unichain verification | API key for Uniscan contract verification |
-
Test everything in dry-run mode first:
# Primary chains (required) make deploy-base deploy-eth make configure-base configure-eth # Additional chains (optional) make deploy-unichain configure-unichain
-
Deploy to mainnet:
export BROADCAST=true export DEPLOYER_PK=0x... # Primary chains make deploy-base deploy-eth # Additional chains (optional) make deploy-unichain
-
Verify deployment consistency:
make verify-addresses # Check addresses match across chains
-
Configure the deployed contracts:
export OWNER_PK=0x... # Different key for admin operations # Primary chains make configure-base configure-eth # Additional chains (if deployed) make configure-unichain
-
Configure LayerZero V2 DVN security:
# Required for cross-chain fee bridging make configure-dvn-base configure-dvn-eth
-
Set up automation:
export OWNER_PK=0x... make fee-ops # Test fee operations
Contracts are automatically verified during deployment when BROADCAST=true
and the appropriate API keys are set. If verification fails during deployment, you can retry later:
# Re-run deployment with --resume --verify
forge script script/DeployBase.s.sol --rpc-url $BASE_RPC_URL --resume --verify --etherscan-api-key $BASESCAN_API_KEY
Deployed contract addresses are automatically saved to:
deployments/base/
- Base network deploymentsdeployments/ethereum/
- Ethereum network deploymentsdeployments/unichain/
- Unichain network deploymentsbroadcast/
- Complete transaction logs and artifacts
Each deployment directory contains:
deployment.json
- Complete deployment information- Individual
.txt
files for each contract address
- Dry-run by default: Scripts never broadcast without explicit
BROADCAST=true
- Environment validation: Scripts check for required environment variables
- Chain validation: Scripts warn when deploying to unexpected networks
- Role separation: Different private keys for deployment vs. administration
- Colored output: Clear visual feedback on operation status
Deterministic contract deployment system using CREATE2 for cross-chain address consistency.
function deploy(bytes memory creationCode, bytes32 salt) external returns (address deployed);
function deployAndInitialize(bytes memory creationCode, bytes32 salt, bytes memory initData) external returns (address deployed);
function computeAddress(bytes memory creationCode, bytes32 salt) external view returns (address);
Doppler-authorized token factory implementing ITokenFactory for omnichain token creation.
// Called by Doppler Airlock contracts only
function create(
uint256 initialSupply,
address recipient,
address owner,
bytes32 salt,
bytes calldata tokenData
) external returns (address token);
function setAirlockAuthorization(address airlock, bool authorized) external; // Owner only
function isTokenCreator(address token, address user) external view returns (bool);
Handles fee collection from Doppler Airlock contracts and cross-chain fee distribution.
function collectAirlockFees(address airlock, address token, uint256 amt) external; // Owner only
function bridge(uint256 minGas, uint256 minHlg) external; // Owner only
function setTrustedAirlock(address airlock, bool trusted) external; // Owner only
Single-token HLG staking with reward distribution, cooldown periods, and emergency controls.
function stake(uint256 amount) external; // Stake HLG tokens
function withdraw(uint256 amount) external; // Withdraw after cooldown (default 7 days)
function claim() external; // Claim accumulated rewards
function addRewards(uint256 amount) external; // FeeRouter only
- Create token through Doppler Airlock (which calls HolographFactory.create())
- Airlock handles auction mechanics and initial distribution
- HolographFactory deploys HolographERC20 with deterministic CREATE2 address
- FeeRouter automatically set as integrator for trading fee collection
- Token address consistent across supported chains (primarily Base and Ethereum)
- Source: Trading fees from Doppler auctions (collected by Airlock contracts)
- Protocol Split: 50% of collected fees (HOLO_FEE_BPS = 5000)
- Treasury Split: 50% of collected fees forwarded to treasury address
- HLG Distribution: Protocol fees bridged to Ethereum, swapped WETH→HLG, 50% burned / 50% staked
- Security: Trusted Airlock whitelist prevents unauthorized ETH transfers to FeeRouter
Use the provided TypeScript utility in the script/
directory to create tokens through Doppler:
# Set environment variables
export PRIVATE_KEY=0x...
export BASESCAN_API_KEY=your_api_key
# Create a token
npm run create-token
Or programmatically:
import { createToken, TokenConfig } from './script/create-token.js'
import { parseEther } from 'viem'
const config: TokenConfig = {
name: "MyToken",
symbol: "MTK",
initialSupply: parseEther("1000000"),
minProceeds: parseEther("100"),
maxProceeds: parseEther("10000"),
auctionDurationDays: 3
}
const result = await createToken(config, process.env.PRIVATE_KEY)
For authorized Airlock contracts:
// Only callable by authorized Doppler Airlock contracts
bytes memory tokenData = abi.encode(
name,
symbol,
yearlyMintCap,
vestingDuration,
recipients,
amounts,
tokenURI
);
address token = holographFactory.create(
initialSupply,
recipient,
owner,
salt,
tokenData
);
// Collect fees from Doppler Airlock (Owner only)
feeRouter.collectAirlockFees(airlockAddress, tokenAddress, amount);
// Bridge accumulated fees (Owner only)
feeRouter.bridge(minGas, minHlgOut);
- Owner: Contract administration, trusted remote management, treasury updates
- Owner-Only Operations: All fee operations now require owner permissions (no keeper role)
- FeeRouter Authorization: Only designated FeeRouter can add rewards to StakingRewards
- Airlock Authorization: Only whitelisted Doppler Airlock contracts can create tokens
- Salt Validation: HolographDeployer requires first 20 bytes of salt to match sender address
- Deterministic Addresses: CREATE2 ensures consistent addresses, preventing address confusion
- Griefing Protection: Salt validation prevents malicious actors from front-running deployments
- Signed Deployments: Support for gasless deployment with signature verification
- Trusted Remotes: Per-endpoint whitelist of authorized cross-chain message senders
- Endpoint Validation: LayerZero V2 endpoint verification for all cross-chain messages
- Trusted Airlocks: Whitelist preventing unauthorized ETH transfers to FeeRouter
- Replay Protection: Nonce-based system preventing message replay attacks
- Dust Protection: MIN_BRIDGE_VALUE (0.01 ETH) prevents uneconomical bridging
- Slippage Protection: Configurable minimum HLG output for swaps
- Cooldown Period: 7-day default withdrawal cooldown prevents staking manipulation
- Emergency Controls: Owner can pause all major contract functions
# Run all tests
forge test
# Unit tests
forge test --match-path test/unit/
# Integration tests
forge test --match-path test/integration/
# Gas reports
forge test --gas-report
Set up the following environment variables for deployment and operations:
# Network RPCs
export BASE_RPC="https://mainnet.base.org"
export ETH_RPC="https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY"
# Private Keys
export DEPLOYER_PK="0x..." # Contract deployment
export OWNER_PK="0x..." # Contract administration
export KEEPER_PK="0x..." # Automation operations
# LayerZero Endpoint IDs
export BASE_EID=30184 # Base mainnet
export ETH_EID=30101 # Ethereum mainnet
# Contract Addresses (update after deployment)
export DOPPLER_AIRLOCK="0x..."
export LZ_ENDPOINT="0x..." # LayerZero V2 endpoint
export TREASURY="0x..." # Treasury multisig
export HLG="0x..." # HLG token address
export WETH="0x..." # WETH address
export SWAP_ROUTER="0x..." # Uniswap V3 SwapRouter
export STAKING_REWARDS="0x..." # StakingRewards contract
# Addresses (set after deployment)
export FEE_ROUTER="0x..."
export HOLOGRAPH_FACTORY="0x..."
export KEEPER_ADDRESS="0x..."
All Holograph contracts are deployed through the HolographDeployer system, which ensures deterministic addresses across chains:
- CREATE2 Deployment: Contracts have identical addresses on all chains
- Salt Validation: First 20 bytes of salt must match deployer address to prevent griefing
- Batch Operations: Deploy and initialize contracts in a single transaction
- Signed Deployments: Support for gasless deployment via signed messages
💡 Quick Start: Use the simplified Makefile commands documented in the Development & Deployment section above for streamlined deployment.
# Set up environment
export BROADCAST=true
export DEPLOYER_PK=0x...
export BASE_RPC_URL=https://mainnet.base.org
export ETHEREUM_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY
export UNICHAIN_RPC_URL=https://mainnet.unichain.org
# Deploy to primary chains
make deploy-base deploy-eth
# Optionally add Unichain
make deploy-unichain
For advanced users who need more control over deployment parameters:
# Deploy using deployment script
forge script script/DeployBase.s.sol \
--rpc-url $BASE_RPC_URL \
--broadcast --private-key $DEPLOYER_PK \
--verify --etherscan-api-key $BASESCAN_API_KEY
# Deploy using deployment script
forge script script/DeployEthereum.s.sol \
--rpc-url $ETHEREUM_RPC_URL \
--broadcast --private-key $DEPLOYER_PK \
--verify --etherscan-api-key $ETHERSCAN_API_KEY
# Deploy using deployment script
forge script script/DeployUnichain.s.sol \
--rpc-url $UNICHAIN_RPC_URL \
--broadcast --private-key $DEPLOYER_PK \
--verify --etherscan-api-key $UNISCAN_API_KEY
After deployment, configure the system using the fee operations script:
# 1. Update script/FeeOperations.s.sol with actual Airlock addresses
# 2. Whitelist Airlock contracts (Owner only)
make fee-setup BROADCAST=true
# Note: All operations are owner-only (no keeper role needed)
# 4. Configure LayerZero trusted remotes
cast send $FEE_ROUTER "setTrustedRemote(uint32,bytes32)" \
$ETH_EID $(cast address-to-bytes32 $ETH_FEE_ROUTER) \
--rpc-url $BASE_RPC --private-key $OWNER_PK
cast send $FEE_ROUTER "setTrustedRemote(uint32,bytes32)" \
$BASE_EID $(cast address-to-bytes32 $BASE_FEE_ROUTER) \
--rpc-url $ETH_RPC --private-key $OWNER_PK
# Monitor system status
make fee-status
# Run fee collection and bridging (automated/cron)
make fee-ops BROADCAST=true
# Set up automated execution (example cron)
echo "*/10 * * * * cd /path/to/holograph && make fee-ops BROADCAST=true" | crontab -
# Emergency treasury redirection (Owner only)
forge script script/FeeOperations.s.sol \
--sig "emergencyRedirect(address)" $EMERGENCY_TREASURY \
--rpc-url $BASE_RPC --broadcast --private-key $OWNER_PK
# Unpause operations (Owner only)
cast send $FEE_ROUTER "unpause()" \
--rpc-url $BASE_RPC --private-key $OWNER_PK
# Update treasury address (Owner only)
cast send $FEE_ROUTER "setTreasury(address)" $NEW_TREASURY \
--rpc-url $BASE_RPC --private-key $OWNER_PK
- LayerZero V2: Cross-chain messaging protocol
- Doppler Airlock: Token launch mechanism
- OpenZeppelin: Access control and security utilities
- Uniswap V3: WETH/HLG swapping on Ethereum
# Check system status
make fee-status
# Manual fee collection
make fee-collect BROADCAST=true
# Emergency treasury redirect
forge script script/FeeOperations.s.sol --sig "emergencyRedirect(address)" $EMERGENCY_TREASURY --rpc-url $BASE_RPC --broadcast --private-key $OWNER_PK
# Check FeeRouter ETH balance
cast balance $FEE_ROUTER --rpc-url $BASE_RPC
# Check if Airlock is whitelisted
cast call $FEE_ROUTER "trustedAirlocks(address)" $AIRLOCK_ADDRESS --rpc-url $BASE_RPC
# Setup trusted Airlocks
make fee-setup BROADCAST=true
- FeeRouter Balance: Should accumulate fees between fee operations
- Trusted Airlocks: Must be whitelisted before fee collection
- LayerZero Messages: Monitor cross-chain message delivery
- HLG Distribution: Verify burn/stake operations on Ethereum
- "UntrustedSender" Error: Airlock not whitelisted - run
setupTrustedAirlocks()
- "AccessControl" Error: Address missing owner permissions (all operations are owner-only)
- Bridge Failures: Check LayerZero trusted remotes configuration
- Low HLG Output: Adjust slippage protection or check Uniswap liquidity
Additional technical documentation is available in the docs/
directory:
- Scripts Overview - Deployment and operational scripts guide
- Token Creation - TypeScript utility for creating tokens
- DVN Configuration - LayerZero V2 security setup
- Operations Guide - System monitoring and management
- Upgrade Guide - Contract upgrade procedures
MIT