Verified Address
Verified Address는 특정 지갑 주소가 신뢰할 수 있는 발행자로부터 고객확인을 받았다는 사실을 나타내는 데이터에요. 이를 통해 웹3 금융 서비스를 더욱 더 안전하게 이용할 수 있습니다.
이 문서에서는 여러분이 온체인 앱을 만들 때 참고할 수 있도록 verified address를 조회하고 활용하는 방법을 소개합니다.
Verified Address 조회하기
요구사항
아래 항목들이 설치되어 있어야해요.
개발 환경 세팅
이 문서에서는 viem을 사용해요. Viem은 Node.js 라이브러리이기 때문에 Node.js 프로젝트로 생성합니다.
Chain Client 설정
Verified Address 조회를 위해 chain client를 설정합니다.
import {createPublicClient, defineChain, http} from "viem";
import {sepolia} from "viem/chains";
// GIWA 세폴리아 체인 설정
export const giwaSepolia = defineChain({
id: 91342,
name: 'Giwa Sepolia',
nativeCurrency: { name: 'Sepolia Ether', symbol: 'ETH', decimals: 18 },
rpcUrls: {
default: {
http: ['https://sepolia-rpc.giwa.io'],
},
},
contracts: {
disputeGameFactory: {
[sepolia.id]: {
address: '0x37347caB2afaa49B776372279143D71ad1f354F6',
},
},
l2OutputOracle: {},
multicall3: {
address: '0xcA11bde05977b3631167028862bE2a173976CA11',
},
portal: {
[sepolia.id]: {
address: '0x956962C34687A954e611A83619ABaA37Ce6bC78A',
},
},
l1StandardBridge: {
[sepolia.id]: {
address: '0x77b2ffc0F57598cAe1DB76cb398059cF5d10A7E7',
},
},
},
testnet: true,
});
// GIWA 세폴리아 체인 데이터를 읽기 위한 client
export const publicClient = createPublicClient({
chain: giwaSepolia,
transport: http(),
});
컨트랙트 주소 및 ABI 설정
Verified Address는 DojangScroll 컨트랙트와 EAS 컨트랙트를 통해 조회해요. 이를 위해 사전에 컨트랙트 주소와 ABI를 정의합니다.
import {parseAbi} from "viem";
// Verified Addres 조회를 위해 필요한 컨트랙트 주소 정의
export const dojangScrollAddress = '0xd5077b67dcb56caC8b270C7788FC3E6ee03F17B9';
export const easAddress = '0x4200000000000000000000000000000000000021';
// Dojang 발행자 ID 정의
export const dojangAttesterIds = {
// keccak256("dojang.dojangattesterids.upbitkorea")
UPBIT_KOREA:
'0xd99b42e778498aa3c9c1f6a012359130252780511687a35982e8e52735453034' as const
}
// DojangScroll ABI 정의
export const dojangScrollAbi = parseAbi([
'function isVerified(address, bytes32) external view returns (bool)',
'function getVerifiedAddressAttestationUid(address, bytes32) external view returns (bytes32)'
]);
// EAS ABI 정의
// 참고: https://github.com/ethereum-attestation-service/eas-contracts/blob/master/deployments/optimism-sepolia/EAS.json
export const easAbi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessDenied","type":"error"},{"inputs":[],"name":"AlreadyRevoked","type":"error"},{"inputs":[],"name":"AlreadyRevokedOffchain","type":"error"},{"inputs":[],"name":"AlreadyTimestamped","type":"error"},{"inputs":[],"name":"InsufficientValue","type":"error"},{"inputs":[],"name":"InvalidAttestation","type":"error"},{"inputs":[],"name":"InvalidAttestations","type":"error"},{"inputs":[],"name":"InvalidExpirationTime","type":"error"},{"inputs":[],"name":"InvalidLength","type":"error"},{"inputs":[],"name":"InvalidOffset","type":"error"},{"inputs":[],"name":"InvalidRegistry","type":"error"},{"inputs":[],"name":"InvalidRevocation","type":"error"},{"inputs":[],"name":"InvalidRevocations","type":"error"},{"inputs":[],"name":"InvalidSchema","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidVerifier","type":"error"},{"inputs":[],"name":"Irrevocable","type":"error"},{"inputs":[],"name":"NotFound","type":"error"},{"inputs":[],"name":"NotPayable","type":"error"},{"inputs":[],"name":"WrongSchema","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":true,"internalType":"address","name":"attester","type":"address"},{"indexed":false,"internalType":"bytes32","name":"uid","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"schema","type":"bytes32"}],"name":"Attested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":true,"internalType":"address","name":"attester","type":"address"},{"indexed":false,"internalType":"bytes32","name":"uid","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"schema","type":"bytes32"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"revoker","type":"address"},{"indexed":true,"internalType":"bytes32","name":"data","type":"bytes32"},{"indexed":true,"internalType":"uint64","name":"timestamp","type":"uint64"}],"name":"RevokedOffchain","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"data","type":"bytes32"},{"indexed":true,"internalType":"uint64","name":"timestamp","type":"uint64"}],"name":"Timestamped","type":"event"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint64","name":"expirationTime","type":"uint64"},{"internalType":"bool","name":"revocable","type":"bool"},{"internalType":"bytes32","name":"refUID","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structAttestationRequestData","name":"data","type":"tuple"}],"internalType":"structAttestationRequest","name":"request","type":"tuple"}],"name":"attest","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint64","name":"expirationTime","type":"uint64"},{"internalType":"bool","name":"revocable","type":"bool"},{"internalType":"bytes32","name":"refUID","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structAttestationRequestData","name":"data","type":"tuple"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"structEIP712Signature","name":"signature","type":"tuple"},{"internalType":"address","name":"attester","type":"address"}],"internalType":"structDelegatedAttestationRequest","name":"delegatedRequest","type":"tuple"}],"name":"attestByDelegation","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getAttestTypeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"uid","type":"bytes32"}],"name":"getAttestation","outputs":[{"components":[{"internalType":"bytes32","name":"uid","type":"bytes32"},{"internalType":"bytes32","name":"schema","type":"bytes32"},{"internalType":"uint64","name":"time","type":"uint64"},{"internalType":"uint64","name":"expirationTime","type":"uint64"},{"internalType":"uint64","name":"revocationTime","type":"uint64"},{"internalType":"bytes32","name":"refUID","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"attester","type":"address"},{"internalType":"bool","name":"revocable","type":"bool"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"structAttestation","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDomainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getName","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"revoker","type":"address"},{"internalType":"bytes32","name":"data","type":"bytes32"}],"name":"getRevokeOffchain","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRevokeTypeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getSchemaRegistry","outputs":[{"internalType":"contractISchemaRegistry","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"data","type":"bytes32"}],"name":"getTimestamp","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"uid","type":"bytes32"}],"name":"isAttestationValid","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint64","name":"expirationTime","type":"uint64"},{"internalType":"bool","name":"revocable","type":"bool"},{"internalType":"bytes32","name":"refUID","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structAttestationRequestData[]","name":"data","type":"tuple[]"}],"internalType":"structMultiAttestationRequest[]","name":"multiRequests","type":"tuple[]"}],"name":"multiAttest","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint64","name":"expirationTime","type":"uint64"},{"internalType":"bool","name":"revocable","type":"bool"},{"internalType":"bytes32","name":"refUID","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structAttestationRequestData[]","name":"data","type":"tuple[]"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"structEIP712Signature[]","name":"signatures","type":"tuple[]"},{"internalType":"address","name":"attester","type":"address"}],"internalType":"structMultiDelegatedAttestationRequest[]","name":"multiDelegatedRequests","type":"tuple[]"}],"name":"multiAttestByDelegation","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"uid","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structRevocationRequestData[]","name":"data","type":"tuple[]"}],"internalType":"structMultiRevocationRequest[]","name":"multiRequests","type":"tuple[]"}],"name":"multiRevoke","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"uid","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structRevocationRequestData[]","name":"data","type":"tuple[]"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"structEIP712Signature[]","name":"signatures","type":"tuple[]"},{"internalType":"address","name":"revoker","type":"address"}],"internalType":"structMultiDelegatedRevocationRequest[]","name":"multiDelegatedRequests","type":"tuple[]"}],"name":"multiRevokeByDelegation","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"data","type":"bytes32[]"}],"name":"multiRevokeOffchain","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"data","type":"bytes32[]"}],"name":"multiTimestamp","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"uid","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structRevocationRequestData","name":"data","type":"tuple"}],"internalType":"structRevocationRequest","name":"request","type":"tuple"}],"name":"revoke","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"schema","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"uid","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"structRevocationRequestData","name":"data","type":"tuple"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"structEIP712Signature","name":"signature","type":"tuple"},{"internalType":"address","name":"revoker","type":"address"}],"internalType":"structDelegatedRevocationRequest","name":"delegatedRequest","type":"tuple"}],"name":"revokeByDelegation","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"data","type":"bytes32"}],"name":"revokeOffchain","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"data","type":"bytes32"}],"name":"timestamp","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}];
Verified Address 조회하기
이제 Verified Address를 조회해볼까요?
Verified Address 여부는 Dojang 서비스의 편의성 컨트랙트인 DojangScroll 컨트랙트 함수를 통해 바로 조회할 수 있어요.
추가로 만료시점 등의 메타데이터가 필요한 경우, EAS 컨트랙트를 통해 직접 Attestation 데이터를 조회해야해요.
1
코드 작성하기
import {Address} from "viem";
import {publicClient} from "./config";
import {dojangAttesterIds, dojangScrollAddress, easAddress, dojangScrollAbi, easAbi} from "./contract";
async function isVerified(address: Address) {
// DojangScroll 컨트랙트로 verified 여부를 조회해요.
return await publicClient.readContract({
address: dojangScrollAddress,
abi: dojangScrollAbi,
functionName: 'isVerified',
// 두번째 인자는 발행자 ID 에요.
args: [address, dojangAttesterIds.UPBIT_KOREA],
});
}
async function getVerifiedAddressAttestation(address: Address) {
// DojangScroll 컨트랙트로 attestation uid를 조회해요.
const attestationUid = await publicClient.readContract({
address: dojangScrollAddress,
abi: dojangScrollAbi,
functionName: 'getVerifiedAddressAttestationUid',
args: [address, dojangAttesterIds.UPBIT_KOREA],
});
// EAS 컨트랙트로 attestation 데이터를 조회해요.
return await publicClient.readContract({
address: easAddress,
abi: easAbi,
functionName: 'getAttestation',
args: [attestationUid],
})
}
async function main() {
// ⚠️ 조회할 지갑 주소를 입력하세요.
const addressToCheck = "0x...";
const verified = await isVerified(addressToCheck);
console.log(`${addressToCheck} is ${verified ? "Verified" : "Not verified"}\n`);
if (verified) {
const attestation = await getVerifiedAddressAttestation(addressToCheck);
console.log(attestation);
}
}
main().then();
Verified Address 활용하기
아래 예시처럼 특정 컨트랙트 함수를 verified address인 지갑들만 실행할 수 있게 제약할 수 있어요.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
interface IDojangScroll {
function isVerified(address addr, bytes32 attesterId) external view returns (bool);
}
// 아래 abstract 컨트랙트에 포함된 modifier를 사용해 접근통제가 가능해요.
abstract contract VerifiedAddressAccessControl {
address internal constant DOJANG_SCROLL = 0xd5077b67dcb56caC8b270C7788FC3E6ee03F17B9;
// keccak256("dojang.dojangattesterids.upbitkorea")
bytes32 internal constant ATTESTER_ID = 0xd99b42e778498aa3c9c1f6a012359130252780511687a35982e8e52735453034;
error NotVerified();
// 위 attester로부터 verified address를 발급받았는지 확인하는 modifier
modifier onlyVerified() {
if (!IDojangScroll(DOJANG_SCROLL).isVerified(msg.sender, ATTESTER_ID)) {
revert NotVerified();
}
_;
}
}
// 위에서 작성한 abstract 컨트랙트를 상속받아요.
contract VerifiedGiwa is VerifiedAddressAccessControl {
// ℹ️ 아래 함수는 verified address인 지갑들만 실행할 수 있어요.
function verifiedAddressFunction() public onlyVerified {
// ⚠️ 함수를 구현하세요.
}
}
Last updated