OnchainVerifiable 컨트랙트 개발하기

OnchainVerifier 컨트랙트는 이미 배포돼 있어요. 누구나 GIWA 에서 스마트컨트랙트를 작성할 때 바로 쓸 수 있어요.

isVerified

function isVerified(address addr, DojangAttesterId attesterId) external view returns (bool);

특정 지갑이 검증(Verified)됐는지 바로 확인할 수 있는 뷰 함수에요. DeFi 프로토콜, 지갑, 게이트키핑이 필요할 때 “검증된 사용자만 이용 가능” 같은 정책을 쉽게 붙일 수 있어요.

배포 된 OnchainVerifiable 컨트랙트

Network
OnchainVerifier contract Address
Verfier ID

Testnet

0xd99b42e778498aa3c9c1f6a012359130252780511687a35982e8e52735453034

Example

Verified ERC20

이 예제는 OpenZeppelin ERC20 표준을 그대로 사용하면서, 토큰 전송 시 OnchainVerifiable 컨트랙트의 isVerified 함수를 호출하여 전송을 허용/차단하는 방법을 보여줍니다.

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

type DojangAttesterId is bytes32;

interface IVerifier {
    function isVerified(address primaryAddress, DojangAttesterId attesterId) external view returns (bool);
}

contract VerifiedERC20 is ERC20 {
    IVerifier public immutable verifier;
    DojangAttesterId public immutable attesterId;
    error NotVerified(address addr);

    constructor(
        string memory name_,
        string memory symbol_,
        address verifier_,
        DojangAttesterId attesterId_,
        uint256 initialSupply
    ) ERC20(name_, symbol_) {
        require(verifier_ != address(0), "verifier is zero");
        verifier = IVerifier(verifier_);
        attesterId = attesterId_;
        _mint(msg.sender, initialSupply);
    }

    // OZ v5 단일 훅: 모든 이동(mint/burn/transfer)이 이 경로를 탑니다.
    function _update(address from, address to, uint256 value) internal override {
        // 일반 transfer(= 양쪽 모두 비제로)에서만 검증
        if (from != address(0) && to != address(0)) {
            // 토큰 보유자(from)가 검증되어야 함
            if (!verifier.isVerified(from, attesterId)) revert NotVerified(from);

            // 필요 시 호출자(transferFrom의 spender)도 검증
            if (!verifier.isVerified(_msgSender(), attesterId)) revert NotVerified(_msgSender());

            // 필요 시 수신자(to)도 검증
            if (!verifier.isVerified(to, attesterId)) revert NotVerified(to);
        }

        super._update(from, to, value);
    }
}

Verified ERC721

이 예제는 OpenZeppelin ERC721 표준을 그대로 사용하면서, NFT 전송 시 외부 검증 컨트랙트의 isVerified 함수를 호출하여 전송을 허용/차단하는 방법을 보여줍니다.

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

type DojangAttesterId is bytes32;

interface IVerifier {
    function isVerified(address addr, DojangAttesterId attesterId) external view returns (bool);
}

contract VerifiedERC721 is ERC721, Ownable {
    IVerifier public immutable verifier;

    DojangAttesterId public constant UPBIT_KOREA =
        DojangAttesterId.wrap(0xd99b42e778498aa3c9c1f6a012359130252780511687a35982e8e52735453034);

    error NotVerified(address account);

    constructor(
        string memory name_,
        string memory symbol_,
        address verifier_
    ) ERC721(name_, symbol_) Ownable(msg.sender) {
        require(verifier_ != address(0), "verifier is zero");
        verifier = IVerifier(verifier_);
    }

    function mint(address to, uint256 tokenId) external onlyOwner {
        if (!verifier.isVerified(to, UPBIT_KOREA)) revert NotVerified(to);
        _mint(to, tokenId);
    }
 
    // OpenZeppelin v5 단일 훅: 민트/번/전송 모두 이 경로를 탑니다.
    function _update(address to, uint256 tokenId, address auth)
        internal
        override
        returns (address from)
    {
        // 상태 변경 전에 "기존 소유자" 파악 (민트면 address(0))
        address prevOwner = _ownerOf(tokenId);

        // 일반 전송에서만 검증 (민트/번 제외)
        if (prevOwner != address(0) && to != address(0)) {
            if (!verifier.isVerified(prevOwner, UPBIT_KOREA)) revert NotVerified(prevOwner);
            if (!verifier.isVerified(_msgSender(), UPBIT_KOREA)) revert NotVerified(_msgSender());
            if (!verifier.isVerified(to, UPBIT_KOREA)) revert NotVerified(to);
        }

        // 실제 상태 업데이트 (from 반환값은 이전 소유자)
        return super._update(to, tokenId, auth);
    }
}

Last updated