Minter

Purpose

Minter is the bridge/orchestrator that:

  • Accepts baseAsset (e.g., WBTC) deposits and mints the synthetic token (hilSyntheticToken) 1:1.

  • Burns hilSyntheticToken and pays back the baseAsset on redemption.

  • Distributes vault yield by minting hilSyntheticToken to itself and letting the StakingVault pull it (via prior approval) into the vault, where it vests into the share price.

  • Enforces compliance (via Whitelist + IComplianceChecker), role-gated ops, and pausing.

Imports

import {IMinter} from "../interfaces/minter/IMinter.sol";
import {IStakingVault} from "../interfaces/vault/IStakingVault.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20//ERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IMintableERC20} from "../interfaces/token/IMintableERC20.sol";
import {Whitelist} from "../helpers/Whitelist.sol";
import {IVerificationSBT} from "../interfaces/galactica/IVerificationSBT.sol";
import {IComplianceChecker} from "../interfaces/galactica/IComplianceChecker.sol";

Roles

  • DEFAULT_ADMIN_ROLE – master admin; can rotate role addresses and update the compliance checker and manual whitelist.

  • OPERATOR_ROLE – allowed to move baseAsset from the Minter to custodian (transferToCustody).

  • PAUSER_ROLE – can pause() and unpause().

  • DISTRIBUTOR_ROLE – can initiate vault yield distributions.

  • CUSTODIAN_ROLE – address identity for custody (not used as a caller gate in current functions, but rotated and emitted).

Storage

Notes on storage semantics

  • Decimals check (constructor): IERC20Metadata(hilSyntheticToken).decimals() == IERC20Metadata(baseAsset).decimals() is enforced.

  • Approval: In the constructor, Minter calls

    so the vault can pull minted yield from the Minter during StakingVault.distributeYield(...).

  • totalDeposits accounting:

    • Increments on: mint, ownerMint, distributeYield.

    • Decrements on: redeem.

    • Not changed by transferToCustody. This variable reflects net issues/burns from Minter’s perspective, not the live baseAsset balance held at the contract.

Constructor

Requirements

  • All addresses must be non-zero; else AddressCantBeZero().

  • decimals(hilSyntheticToken) == decimals(baseAsset); else IncompatibleDecimals().

Effects

  • Sets immutable token refs, installs compliance checker, role holders, and stakingVault.

  • Grants roles:

    • DEFAULT_ADMIN_ROLE_initialAdmin

    • CUSTODIAN_ROLE_custodian

    • OPERATOR_ROLE_operator

    • PAUSER_ROLE_pauser

    • DISTRIBUTOR_ROLE_distributor

  • Approves the vault to spend unlimited hilSyntheticToken from the Minter (used during yield distribution).

Events (from IMinter and Whitelist)

  • event Minted(address indexed to, uint256 amount);

  • event Redeemed(address indexed user, uint256 amount);

  • event FundsTransferredToCustody(uint256 amount, address indexed custodian);

  • event AddressWhitelisted(address indexed user, bool allowed); (emitted by Whitelist)

  • event ComplianceCheckerUpdated(address indexed newChecker); (emitted by Whitelist)

Errors

  • AddressCantBeZero()

  • IncompatibleDecimals()

  • AddressNotWhitelisted() (from Whitelist.onlyWhitelisted)


Public / External

ownerMint

What it does Mints amount of hilSyntheticToken directly to addressTo. totalDeposits += amount; Emits Minted(addressTo, amount).

When to use Admin pathway for mints associated with off-chain baseAsset inflows (e.g., native BTC deposit processed by operations).


distributeYield

What it does

  1. Mints amount of hilSyntheticToken to this Minter.

  2. totalDeposits += amount;

  3. Calls stakingVault.distributeYield(amount, timestamp).

How vault pulls Inside the vault, distributeYield performs:

Because the Minter pre-approved the vault in the constructor, the vault pulls amount from the Minter during this call.

Vesting behavior in vault The vault then sets its internal vesting window (8h) starting at timestamp (see StakingVault docs). The yield is excluded from totalAssets() until it vests linearly.

Emits Minted(address(stakingVault), amount) (from Minter)


pause / unpause

  • Pausing blocks mint, redeem, and transferToCustody (via whenNotPaused).


setOperator / setPauser / setCustodian

Rotate role addresses. Validates non-zero; revokes old role; grants new role; updates stored address.


whitelistAddress (manual whitelist toggle)

Delegates to _whitelistAddress in Whitelist. Emits AddressWhitelisted(user, allowed).


setComplianceChecker

Updates the compliance backend used by Whitelist. Emits ComplianceCheckerUpdated(_complianceChecker).


transferToCustody

Transfers amount of baseAsset from the Minter to custodian. Emits FundsTransferredToCustody(amount, custodian).


mint (user deposit)

Flow

  1. Pull amount of baseAsset from msg.senderthis.

  2. Mint amount of hilSyntheticToken to to.

  3. totalDeposits += amount;

  4. emit Minted(to, amount);

Preconditions

  • Caller and to must pass Whitelist.isAddressWhitelisted:

    • true if manualWhitelist[user] is set, else complianceChecker.isCompliant(user).


redeem (user redemption)

Flow

  1. Burn amount of hilSyntheticToken from msg.sender via burnFrom.

    • Caller must have set allowance for Minter if token enforces allowance on burnFrom.

  2. Transfer amount of baseAsset to msg.sender.

  3. totalDeposits -= amount;

  4. emit Redeemed(msg.sender, amount);

Last updated

Was this helpful?