Deployment Guide

Deploy SMART USD to Testnet & Mainnet

Complete step-by-step guide for deploying the full SMART USD protocol stack to Ethereum Sepolia (testnet) and Ethereum Mainnet.

Step 1

Testnet Deployment (Sepolia)

Prerequisites

Required
1

Install Node.js & Hardhat

npm install -g hardhat
npx hardhat --version
# Node.js 18+ required
2

Clone Repository & Install

git clone https://github.com/smartusd/protocol
cd smart-usd-protocol
npm install
3

Configure Environment

cp .env.example .env
# Edit .env:
SEPOLIA_RPC_URL=https://rpc.sepolia.org
PRIVATE_KEY=0x_your_deployer_private_key
ETHERSCAN_API_KEY=your_etherscan_api_key
4

Get Sepolia ETH

# Use faucets:
# https://sepoliafaucet.com
# https://faucet.sepolia.dev
# https://www.alchemy.com/faucets/ethereum-sepolia
# Minimum 0.5 Sepolia ETH for deployment

Deploy to Sepolia

Testnet
5

Compile Contracts

npx hardhat compile
# Compiles all Solidity contracts
# Check artifacts/ directory
6

Run Tests

npx hardhat test
# Run: npx hardhat coverage
# Target: 95%+ coverage
7

Deploy Full Protocol

npx hardhat run scripts/deploy-all.js --network sepolia
# Deploys in order:
# 1. MockUSDC (test collateral)
# 2. SGOV token
# 3. SmartUSD token  
# 4. Treasury
# 5. AI Controller
# 6. AMM Pool
# Saves addresses to deployments/sepolia.json
8

Verify on Etherscan

npx hardhat verify --network sepolia DEPLOYED_ADDRESS
# Auto-verifies all contracts
# npm run verify:sepolia
Step 2

Mainnet Deployment

⚠ Mainnet Safety Checklist

Critical
✓

Professional Audit

All contracts audited by Trail of Bits / OpenZeppelin / Halborn. Audit report published before launch.

✓

Bug Bounty Program

$500K Immunefi bug bounty live for 2+ weeks before mainnet launch. Critical vulnerabilities resolved.

✓

Multi-sig Governance

Gnosis Safe 4-of-7 multi-sig for admin functions. Timelock of 48h on all parameter changes.

✓

Configure Mainnet .env

MAINNET_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/KEY
PRIVATE_KEY=0x_hardware_wallet_key
ETHERSCAN_API_KEY=your_key
# NEVER use hot wallet for mainnet!
✓

Deploy to Mainnet

npx hardhat run scripts/deploy-mainnet.js --network mainnet
# Uses real USDC (0xA0b86991c6...)
# Chainlink price feeds (LIVE)
# Uniswap V3 integration (LIVE)
✓

Post-Deploy Checks

npm run post-deploy:mainnet
# Verifies all contract states
# Checks oracle connectivity  
# Tests mint/burn with 1 USDC
# Confirms CR = 115%
Smart Contracts

Solidity Source Code

SmartUSD.sol — Core Stablecoin

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

interface ITreasury {
    function collateralRatio() external view returns (uint256);
    function depositCollateral(address token, uint256 amount) external;
    function withdrawCollateral(address token, uint256 amount) external;
}

interface ISGov {
    function mint(address to, uint256 amount) external;
    function burn(address from, uint256 amount) external;
}

interface IAIController {
    function targetCR() external view returns (uint256);
    function isPaused() external view returns (bool);
}

/**
 * @title SmartUSD
 * @notice AI-powered algorithmic stablecoin pegged to $1.00
 * @dev Dual-token model with SGOV volatility absorber
 */
contract SmartUSD is ERC20, AccessControl, ReentrancyGuard, Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant AI_CONTROLLER_ROLE = keccak256("AI_CONTROLLER_ROLE");
    
    ITreasury public treasury;
    ISGov public sgov;
    IAIController public aiController;
    IERC20 public usdc;
    
    uint256 public constant PRECISION = 1e18;
    uint256 public constant MIN_CR = 105; // 105% minimum
    uint256 public constant MINT_FEE = 10; // 0.1% = 10 bps
    uint256 public constant BURN_FEE = 30; // 0.3% = 30 bps
    uint256 public constant SGOV_RATIO = 100; // 1% of collateral
    
    uint256 public totalMinted;
    uint256 public totalBurned;
    
    mapping(address => uint256) public lastMintBlock;
    mapping(address => uint256) public userCollateral;
    
    event Minted(address indexed user, uint256 susdAmount, uint256 collateral, uint256 sgov);
    event Burned(address indexed user, uint256 susdAmount, uint256 usdcReturned, uint256 sgov);
    event AIControllerUpdated(address newController);
    event CircuitBreakerTriggered(string reason);
    
    constructor(
        address _usdc,
        address _treasury,
        address _sgov,
        address _aiController
    ) ERC20("SMART USD", "SUSD") {
        usdc = IERC20(_usdc);
        treasury = ITreasury(_treasury);
        sgov = ISGov(_sgov);
        aiController = IAIController(_aiController);
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
    }
    
    /**
     * @notice Mint SUSD by depositing USDC collateral
     * @param usdcAmount Amount of USDC to deposit as collateral
     */
    function mint(uint256 usdcAmount) external nonReentrant whenNotPaused {
        require(!aiController.isPaused(), "AI: circuit breaker active");
        require(usdcAmount >= 1e6, "Min mint: 1 USDC");
        require(lastMintBlock[msg.sender] < block.number, "One mint per block");
        
        uint256 targetCR = aiController.targetCR(); // e.g. 115
        require(targetCR >= MIN_CR, "CR below minimum");
        
        // Calculate SUSD to mint: collateral / CR * 100
        uint256 susdToMint = (usdcAmount * 1e12 * 100) / targetCR;
        
        // Calculate fee
        uint256 fee = (susdToMint * MINT_FEE) / 10000;
        uint256 netSusd = susdToMint - fee;
        
        // Calculate SGOV to co-mint (1% of collateral value)
        uint256 sgovToMint = (usdcAmount * SGOV_RATIO) / 10000;
        
        // Transfer collateral to treasury
        require(usdc.transferFrom(msg.sender, address(treasury), usdcAmount), "USDC transfer failed");
        
        // Mint SUSD to user
        _mint(msg.sender, netSusd);
        
        // Co-mint SGOV to user
        sgov.mint(msg.sender, sgovToMint * 1e12); // scale to 18 decimals
        
        userCollateral[msg.sender] += usdcAmount;
        totalMinted += netSusd;
        lastMintBlock[msg.sender] = block.number;
        
        emit Minted(msg.sender, netSusd, usdcAmount, sgovToMint * 1e12);
    }
    
    /**
     * @notice Burn SUSD to redeem USDC collateral
     * @param susdAmount Amount of SUSD to burn
     */
    function burn(uint256 susdAmount) external nonReentrant whenNotPaused {
        require(balanceOf(msg.sender) >= susdAmount, "Insufficient SUSD");
        require(susdAmount >= 1e15, "Min burn: 0.001 SUSD");
        
        uint256 targetCR = aiController.targetCR();
        
        // Calculate USDC to return: susd * CR / 100, minus fee
        uint256 usdcToReturn = (susdAmount / 1e12 * targetCR) / 100;
        uint256 fee = (usdcToReturn * BURN_FEE) / 10000;
        uint256 netUsdc = usdcToReturn - fee;
        
        // Calculate SGOV to co-burn
        uint256 sgovToBurn = (susdAmount * SGOV_RATIO) / 10000;
        
        // Burn SUSD from user
        _burn(msg.sender, susdAmount);
        
        // Co-burn SGOV from user
        sgov.burn(msg.sender, sgovToBurn);
        
        // Release collateral from treasury
        treasury.withdrawCollateral(address(usdc), netUsdc);
        require(usdc.transfer(msg.sender, netUsdc), "USDC transfer failed");
        
        totalBurned += susdAmount;
        
        emit Burned(msg.sender, susdAmount, netUsdc, sgovToBurn);
    }
    
    // Admin functions
    function triggerCircuitBreaker(string memory reason) external onlyRole(AI_CONTROLLER_ROLE) {
        _pause();
        emit CircuitBreakerTriggered(reason);
    }
    
    function resume() external onlyRole(DEFAULT_ADMIN_ROLE) {
        _unpause();
    }
    
    function updateAIController(address newController) external onlyRole(DEFAULT_ADMIN_ROLE) {
        aiController = IAIController(newController);
        emit AIControllerUpdated(newController);
    }
}

SGov.sol — Governance & Volatility Absorber

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * @title SGov
 * @notice Governance token and volatility absorber for SMART USD protocol
 * @dev Stakers earn protocol fees and act as backstop insurance
 */
contract SGov is ERC20, AccessControl, ReentrancyGuard {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    
    uint256 public constant APY_BASE = 1840; // 18.4% APY in bps
    uint256 public constant LOCK_PERIOD = 0; // flexible staking
    
    struct StakeInfo {
        uint256 amount;
        uint256 timestamp;
        uint256 rewardDebt;
    }
    
    mapping(address => StakeInfo) public stakes;
    uint256 public totalStaked;
    uint256 public accRewardPerShare;
    uint256 public lastRewardBlock;
    uint256 public rewardPerBlock; // USDC rewards per block
    
    IERC20 public rewardToken; // USDC
    
    event Staked(address indexed user, uint256 amount);
    event Unstaked(address indexed user, uint256 amount);
    event YieldClaimed(address indexed user, uint256 amount);
    event VoteCast(address indexed voter, uint256 proposalId, bool support, uint256 weight);
    
    constructor(address _rewardToken) ERC20("SMART GOV", "SGOV") {
        rewardToken = IERC20(_rewardToken);
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
    }
    
    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }
    
    function burn(address from, uint256 amount) external onlyRole(MINTER_ROLE) {
        _burn(from, amount);
    }
    
    function stake(uint256 amount) external nonReentrant {
        require(amount > 0, "Cannot stake 0");
        require(balanceOf(msg.sender) >= amount, "Insufficient SGOV");
        
        _updateRewards(msg.sender);
        
        _transfer(msg.sender, address(this), amount);
        stakes[msg.sender].amount += amount;
        stakes[msg.sender].timestamp = block.timestamp;
        totalStaked += amount;
        
        emit Staked(msg.sender, amount);
    }
    
    function unstake(uint256 amount) external nonReentrant {
        require(stakes[msg.sender].amount >= amount, "Insufficient staked");
        
        _updateRewards(msg.sender);
        claimYield();
        
        stakes[msg.sender].amount -= amount;
        totalStaked -= amount;
        _transfer(address(this), msg.sender, amount);
        
        emit Unstaked(msg.sender, amount);
    }
    
    function claimYield() public nonReentrant {
        _updateRewards(msg.sender);
        uint256 pending = _pendingReward(msg.sender);
        if (pending > 0) {
            stakes[msg.sender].rewardDebt = stakes[msg.sender].amount * accRewardPerShare / 1e12;
            rewardToken.transfer(msg.sender, pending);
            emit YieldClaimed(msg.sender, pending);
        }
    }
    
    function _updateRewards(address user) internal {
        if (totalStaked > 0) {
            uint256 reward = (block.number - lastRewardBlock) * rewardPerBlock;
            accRewardPerShare += reward * 1e12 / totalStaked;
        }
        lastRewardBlock = block.number;
        stakes[user].rewardDebt = stakes[user].amount * accRewardPerShare / 1e12;
    }
    
    function _pendingReward(address user) internal view returns (uint256) {
        if (stakes[user].amount == 0) return 0;
        return (stakes[user].amount * accRewardPerShare / 1e12) - stakes[user].rewardDebt;
    }
    
    function stakedBalance(address user) external view returns (uint256) {
        return stakes[user].amount;
    }
    
    function pendingYield(address user) external view returns (uint256) {
        return _pendingReward(user);
    }
    
    // Governance voting weight = balance + staked
    function votingPower(address user) external view returns (uint256) {
        return balanceOf(user) + stakes[user].amount;
    }
}
Configuration

Hardhat & Deploy Scripts

hardhat.config.js

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

module.exports = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      chainId: 11155111
    },
    mainnet: {
      url: process.env.MAINNET_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      chainId: 1,
      gasPrice: "auto"
    },
    // Local testing
    hardhat: {
      chainId: 31337,
      forking: {
        url: process.env.MAINNET_RPC_URL,
        blockNumber: 19000000
      }
    }
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY
  },
  gasReporter: {
    enabled: true,
    currency: "USD"
  }
};

scripts/deploy-all.js

const { ethers } = require("hardhat");
const fs = require("fs");

async function main() {
  const [deployer] = await ethers.getSigners();
  console.log("Deploying with:", deployer.address);
  
  const balance = await ethers.provider.getBalance(deployer.address);
  console.log("Balance:", ethers.formatEther(balance), "ETH");
  
  // 1. Deploy MockUSDC (testnet only)
  const MockUSDC = await ethers.getContractFactory("MockUSDC");
  const usdc = await MockUSDC.deploy();
  await usdc.waitForDeployment();
  console.log("MockUSDC:", await usdc.getAddress());
  
  // 2. Deploy SGov
  const SGov = await ethers.getContractFactory("SGov");
  const sgov = await SGov.deploy(await usdc.getAddress());
  await sgov.waitForDeployment();
  console.log("SGov:", await sgov.getAddress());
  
  // 3. Deploy Treasury
  const Treasury = await ethers.getContractFactory("Treasury");
  const treasury = await Treasury.deploy(await usdc.getAddress());
  await treasury.waitForDeployment();
  console.log("Treasury:", await treasury.getAddress());
  
  // 4. Deploy AI Controller
  const AIController = await ethers.getContractFactory("AIController");
  const controller = await AIController.deploy(115); // 115% initial CR
  await controller.waitForDeployment();
  console.log("AIController:", await controller.getAddress());
  
  // 5. Deploy SmartUSD
  const SmartUSD = await ethers.getContractFactory("SmartUSD");
  const smartUSD = await SmartUSD.deploy(
    await usdc.getAddress(),
    await treasury.getAddress(),
    await sgov.getAddress(),
    await controller.getAddress()
  );
  await smartUSD.waitForDeployment();
  console.log("SmartUSD:", await smartUSD.getAddress());
  
  // 6. Setup roles
  await sgov.grantRole(await sgov.MINTER_ROLE(), await smartUSD.getAddress());
  await treasury.grantRole(await treasury.MANAGER_ROLE(), await smartUSD.getAddress());
  
  // 7. Save deployment
  const deployments = {
    network: hre.network.name,
    chainId: hre.network.config.chainId,
    timestamp: new Date().toISOString(),
    contracts: {
      MockUSDC: await usdc.getAddress(),
      SGov: await sgov.getAddress(),
      Treasury: await treasury.getAddress(),
      AIController: await controller.getAddress(),
      SmartUSD: await smartUSD.getAddress()
    }
  };
  
  fs.writeFileSync(
    `deployments/${hre.network.name}.json`,
    JSON.stringify(deployments, null, 2)
  );
  
  console.log("\n✅ Deployment complete!");
  console.log("Run: npx hardhat verify --network", hre.network.name);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

🌐 Deploy This Webapp to Cloudflare Pages

Live in 2 min
1

Build

npm run build
2

Deploy

npx wrangler pages deploy dist --project-name smart-usd
3

Your App is Live

https://smart-usd.pages.dev
# Custom domain: 
# npx wrangler pages project create smart-usd
# Add CNAME: app.yourdomain.com → pages.dev
← Back to SMART USD App