Skip to content本文へ移動

HyperEVM Development · L1 Precompiles & Interop · 5/8

Building on HyperEVM

Common Patterns

ERC-20 Tokens on HyperEVM

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract HypeToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("HypeToken", "HTKN") {
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }
}

Vault Contract Pattern

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract SimpleVault is ERC20, Ownable {
    using SafeERC20 for IERC20;

    IERC20 public immutable asset;  // e.g. a USDC token on HyperEVM

    constructor(address _asset)
        ERC20("Vault Share", "vUSDC")
        Ownable(msg.sender)
    {
        asset = IERC20(_asset);
    }

    function deposit(uint256 amount) external returns (uint256 shares) {
        uint256 assetsBefore = asset.balanceOf(address(this));
        uint256 totalShares = totalSupply();

        if (totalShares == 0) {
            shares = amount;
        } else {
            shares = (amount * totalShares) / assetsBefore;
        }
        require(shares > 0, "zero shares");

        asset.safeTransferFrom(msg.sender, address(this), amount);
        _mint(msg.sender, shares);
    }

    function withdraw(uint256 shares) external returns (uint256 amount) {
        uint256 assetsHeld = asset.balanceOf(address(this));
        uint256 totalShares = totalSupply();

        amount = (shares * assetsHeld) / totalShares;

        _burn(msg.sender, shares);
        asset.safeTransfer(msg.sender, amount);
    }

    function totalAssets() public view returns (uint256) {
        return asset.balanceOf(address(this));
    }
}

This shows the share accounting behind ERC-4626 tokenized vaults, but it is not ERC-4626 compliant and is vulnerable to the first-depositor "inflation" attack (someone donates tokens to the empty vault to skew the share price). For production, build on OpenZeppelin's ERC4626, which includes virtual-share protection.

Reading HyperCore Prices in a Vault

contract PriceAwareVault is SimpleVault {
    // Mark price read precompile (see the previous lesson)
    address constant MARK_PX = 0x0000000000000000000000000000000000000806;

    constructor(address _asset) SimpleVault(_asset) {}

    // Raw price: divide by 10^(6 - szDecimals) for a human-readable value
    function getMarkPx(uint32 perpIndex) public view returns (uint64) {
        (bool ok, bytes memory res) = MARK_PX.staticcall(abi.encode(perpIndex));
        require(ok, "markPx failed");
        return abi.decode(res, (uint64));
    }
}

Development Workflow

  1. Write contracts in Solidity (Hardhat or Foundry)
  2. Test locally with Hardhat/Foundry test framework
  3. Deploy to testnet — verify behavior with real HyperCore data (precompiles do not exist on a local node)
  4. Verify on explorer — publish source code
  5. Deploy to mainnet — after thorough testing
  6. Build frontend — ethers.js / viem for contract interaction

Security Considerations

HyperEVM 開発 · L1 の precompile と連携 · 5/8

HyperEVM で作る

よく使う形

HyperEVM の ERC-20 トークン

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract HypeToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("HypeToken", "HTKN") {
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }
}

vault コントラクトの形

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract SimpleVault is ERC20, Ownable {
    using SafeERC20 for IERC20;

    IERC20 public immutable asset;  // e.g. a USDC token on HyperEVM

    constructor(address _asset)
        ERC20("Vault Share", "vUSDC")
        Ownable(msg.sender)
    {
        asset = IERC20(_asset);
    }

    function deposit(uint256 amount) external returns (uint256 shares) {
        uint256 assetsBefore = asset.balanceOf(address(this));
        uint256 totalShares = totalSupply();

        if (totalShares == 0) {
            shares = amount;
        } else {
            shares = (amount * totalShares) / assetsBefore;
        }
        require(shares > 0, "zero shares");

        asset.safeTransferFrom(msg.sender, address(this), amount);
        _mint(msg.sender, shares);
    }

    function withdraw(uint256 shares) external returns (uint256 amount) {
        uint256 assetsHeld = asset.balanceOf(address(this));
        uint256 totalShares = totalSupply();

        amount = (shares * assetsHeld) / totalShares;

        _burn(msg.sender, shares);
        asset.safeTransfer(msg.sender, amount);
    }

    function totalAssets() public view returns (uint256) {
        return asset.balanceOf(address(this));
    }
}

これは ERC-4626 のトークン化 vault の裏にある持ち分の計算を示しています。ただし ERC-4626 には準拠していません。また、最初の預け入れを狙う「inflation」攻撃(空の vault にトークンを贈って持ち分の価格をゆがめる攻撃)に弱いです。本番では OpenZeppelin の ERC4626 を土台にしてください。仮想の持ち分による防御が入っています。

vault で HyperCore の価格を読む

contract PriceAwareVault is SimpleVault {
    // Mark price read precompile (see the previous lesson)
    address constant MARK_PX = 0x0000000000000000000000000000000000000806;

    constructor(address _asset) SimpleVault(_asset) {}

    // Raw price: divide by 10^(6 - szDecimals) for a human-readable value
    function getMarkPx(uint32 perpIndex) public view returns (uint64) {
        (bool ok, bytes memory res) = MARK_PX.staticcall(abi.encode(perpIndex));
        require(ok, "markPx failed");
        return abi.decode(res, (uint64));
    }
}

開発の流れ

  1. Solidity でコントラクトを書く(Hardhat か Foundry)
  2. Hardhat / Foundry のテストの仕組みで手元でテストする
  3. テストネットにデプロイする — 本物の HyperCore のデータで動きを確かめます(precompile は手元のノードにはありません)
  4. エクスプローラーで検証する — ソースコードを公開します
  5. メインネットにデプロイする — 十分にテストしてから
  6. フロントエンドを作る — コントラクトとのやりとりには ethers.js / viem を使います

安全面で気をつけること