Verified Address

Verified Address is data that proves a specific wallet address has been KYC-verified by a trusted issuer. With this, users can access web3 financial services more securely.

This document shows how to query and use Verified Address so you can reference it when building on-chain apps.

Querying Verified Address

Requirements

Make sure the following are installed.

Set up development environment

In this tutorial, we’ll use viem. Viem is a Node.js library, so we’ll start by creating a Node.js project.

1

Create a project folder

mkdir giwa-verified-address-tutorial
cd giwa-verified-address-tutorial
2

Initialize the project

pnpm init
3

Install dependencies

pnpm add -D tsx @types/node
pnpm add viem

Configure the Chain Client

Set up chain client for querying Verified Address.

src/config.ts
import {createPublicClient, defineChain, http} from "viem";
import {sepolia} from "viem/chains";

// GIWA Sepolia chain configuration
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,
});

// Client for reading GIWA Sepolia chain data
export const publicClient = createPublicClient({
  chain: giwaSepolia,
  transport: http(),
});

Contract addresses and ABI setup

Verified Address is queried via the DojangScroll contract and the EAS contract. To do so, you first need to define the contract addresses and ABIs.

src/contract.ts
import {parseAbi} from "viem";

// Define contract addresses needed to query Verified Address
export const dojangScrollAddress = '0xd5077b67dcb56caC8b270C7788FC3E6ee03F17B9';
export const easAddress = '0x4200000000000000000000000000000000000021';

// Define Dojang attester IDs
export const dojangAttesterIds = {
  // keccak256("dojang.dojangattesterids.upbitkorea")
  UPBIT_KOREA:
    '0xd99b42e778498aa3c9c1f6a012359130252780511687a35982e8e52735453034' as const
}

// DojangScroll ABI definition
export const dojangScrollAbi = parseAbi([
  'function isVerified(address, bytes32) external view returns (bool)',
  'function getVerifiedAddressAttestationUid(address, bytes32) external view returns (bytes32)'
]);

// EAS ABI definition
// Reference: 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"}];

Querying Verified Address

Now let’s try querying a Verified Address.

You can check whether an address is verified directly through the DojangScroll contract, a convenience contract provided by the Dojang service.

If you need additional metadata such as the expiration time, you’ll need to query the attestation data directly from the EAS contract.

1

Writing the code

src/index.ts
import {Address} from "viem";
import {publicClient} from "./config";
import {dojangAttesterIds, dojangScrollAddress, easAddress, dojangScrollAbi, easAbi} from "./contract";

async function isVerified(address: Address) {
  // Check verified status via the DojangScroll contract.
  return await publicClient.readContract({
    address: dojangScrollAddress,
    abi: dojangScrollAbi,
    functionName: 'isVerified',
    // The second argument is the attester ID.
    args: [address, dojangAttesterIds.UPBIT_KOREA],
  });
}

async function getVerifiedAddressAttestation(address: Address) {
  // Query the attestation UID from the DojangScroll contract.
  const attestationUid = await publicClient.readContract({
    address: dojangScrollAddress,
    abi: dojangScrollAbi,
    functionName: 'getVerifiedAddressAttestationUid',
    args: [address, dojangAttesterIds.UPBIT_KOREA],
  });

  // Query the attestation data from the EAS contract.
  return await publicClient.readContract({
    address: easAddress,
    abi: easAbi,
    functionName: 'getAttestation',
    args: [attestationUid],
  })
}

async function main() {
  // ⚠️ Enter the wallet address to check.
  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();
2

Running

node --import=tsx src/index.ts

Using Verified Address

As shown in the example below, you can restrict certain contract functions so that only wallets with a Verified Address are allowed to execute them.

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

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

// Use the modifier in the abstract contract below to enforce access control.
abstract contract VerifiedAddressAccessControl {
    address internal constant DOJANG_SCROLL = 0xd5077b67dcb56caC8b270C7788FC3E6ee03F17B9;

    // keccak256("dojang.dojangattesterids.upbitkorea")
    bytes32 internal constant ATTESTER_ID = 0xd99b42e778498aa3c9c1f6a012359130252780511687a35982e8e52735453034;

    error NotVerified();

    // Modifier that checks whether the caller has a verified address from the attester above
    modifier onlyVerified() {
        if (!IDojangScroll(DOJANG_SCROLL).isVerified(msg.sender, ATTESTER_ID)) {
            revert NotVerified();
        }
        _;
    }
}

// Inherit the abstract contract written above.
contract VerifiedGiwa is VerifiedAddressAccessControl {
    // ℹ️ The function below can only be executed by wallets that are verified addresses.
    function verifiedAddressFunction() public onlyVerified {
        // ⚠️ Implement the function.
    }
}

Last updated