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