IdentityAttestationVerifierV2

0xd9623a82df084f00c7b53a91190ebf86188ccab4

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
6,766 bytes
ABI entries
38
14 read · 5 write
License
none

Contract information

Address
0xd9623a82df084f00c7b53a91190ebf86188ccab4
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x9c47a8787b…f13bcf6785

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (14)

ATTESTATION_TYPEHASH()bytes32
DEFAULT_ADMIN_ROLE()bytes32
IDENTITY_SIGNER_ROLE()bytes32
ORIGINALITY_CLEAR()uint8
ORIGINALITY_PROTECTED_APPROVED()uint8
ORIGINALITY_WARNING()uint8
VERIFIER_VERSION()uint32
eip712Domain()bytes1, string, string, uint256, address, bytes32, uint256[]
factory()address
getRoleAdmin(bytes32)bytes32
hasRole(bytes32, address)bool
hashAttestation(address, string, string, bytes32, tuple)bytes32
nonces(address)uint256
supportsInterface(bytes4)bool

Events (6)

EIP712DomainChangedFactoryConfiguredIdentityAttestationConsumedRoleAdminChangedRoleGrantedRoleRevoked

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "identitySigner",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AccessControlBadConfirmation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "neededRole",
        "type": "bytes32"
      }
    ],
    "name": "AccessControlUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AlreadyConfigured",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AttestationExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AttestationReplay",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAttestation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidOriginality",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidShortString",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSigner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFactory",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "str",
        "type": "string"
      }
    ],
    "name": "StringTooLong",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "EIP712DomainChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "factory",
        "type": "address"
      }
    ],
    "name": "FactoryConfigured",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "launchParametersHash",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "signer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "originality",
        "type": "uint8"
      }
    ],
    "name": "IdentityAttestationConsumed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "previousAdminRole",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "newAdminRole",
        "type": "bytes32"
      }
    ],
    "name": "RoleAdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleGranted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleRevoked",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "ATTESTATION_TYPEHASH",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "IDENTITY_SIGNER_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ORIGINALITY_CLEAR",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ORIGINALITY_PROTECTED_APPROVED",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ORIGINALITY_WARNING",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "VERIFIER_VERSION",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "bytes32",
        "name": "launchParametersHash",
        "type": "bytes32"
      },
      {
        "internalType": "bytes",
        "name": "attestation",
        "type": "bytes"
      }
    ],
    "name": "consumeAttestation",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "normalizedNameHash",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "normalizedSymbolHash",
        "type": "bytes32"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eip712Domain",
    "outputs": [
      {
        "internalType": "bytes1",
        "name": "fields",
        "type": "bytes1"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "version",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "chainId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "verifyingContract",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256[]",
        "name": "extensions",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      }
    ],
    "name": "getRoleAdmin",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "grantRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "hasRole",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "bytes32",
        "name": "launchParametersHash",
        "type": "bytes32"
      },
      {
        "components": [
          {
            "internalType": "bytes32",
            "name": "normalizedNameHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "normalizedSymbolHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "confusableNameSkeletonHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "confusableSymbolSkeletonHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "mediaMetadataHash",
            "type": "bytes32"
          },
          {
            "internalType": "uint256",
            "name": "nonce",
            "type": "uint256"
          },
          {
            "internalType": "uint48",
            "name": "expiry",
            "type": "uint48"
          },
          {
            "internalType": "uint8",
            "name": "originality",
            "type": "uint8"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct IdentityAttestationVerifierV2.AttestationPayload",
        "name": "payload",
        "type": "tuple"
      }
    ],
    "name": "hashAttestation",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      }
    ],
    "name": "nonces",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "callerConfirmation",
        "type": "address"
      }
    ],
    "name": "renounceRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "revokeRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "factory_",
        "type": "address"
      }
    ],
    "name": "setFactory",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { IIdentityAttestationVerifier } from "../interfaces/IIdentityAttestationVerifier.sol";

/// @title Equifold EIP-712 identity attestation verifier (gen4)
/// @author Equifold
/// @notice Venue-agnostic gen4 attestation verifier. A byte-faithful behavioral copy of
///         {IdentityAttestationVerifier} with two deltas: (1) it has NO {TokenIdentityRegistry}
///         dependency and performs NO append-only registry write when consuming an attestation — the
///         signature gate and per-creator nonce replay protection are the entire trust surface; and
///         (2) the signed attestation payload is extended with a final `bytes32 salt` field, bound
///         into the EIP-712 digest, so an attestation authorizes a specific deterministic deploy
///         salt. Removing the registry means the same normalized name/symbol may be launched more
///         than once (there is no cross-launch identity uniqueness here), which is the intended gen4
///         behavior: identity uniqueness is enforced off-chain / in the UI, not on-chain.
/// @dev Reused unchanged by every gen4 vertical (Sushi V3, v4-stock, …); it is deliberately kept
///      free of any venue-specific coupling.
contract IdentityAttestationVerifierV2 is IIdentityAttestationVerifier, AccessControl, EIP712 {
    error AttestationExpired();
    error AttestationReplay();
    error AlreadyConfigured();
    error InvalidAddress();
    error InvalidAttestation();
    error InvalidOriginality();
    error InvalidSigner();
    error NotFactory();

    uint32 public constant VERIFIER_VERSION = 2;
    uint8 public constant ORIGINALITY_CLEAR = 0;
    uint8 public constant ORIGINALITY_WARNING = 1;
    uint8 public constant ORIGINALITY_PROTECTED_APPROVED = 2;
    bytes32 public constant IDENTITY_SIGNER_ROLE = keccak256("IDENTITY_SIGNER_ROLE");
    // solhint-disable max-line-length,gas-small-strings
    bytes32 public constant ATTESTATION_TYPEHASH = keccak256(
        "IdentityAttestation(address creator,address factory,bytes32 rawNameHash,bytes32 rawSymbolHash,bytes32 normalizedNameHash,bytes32 normalizedSymbolHash,bytes32 confusableNameSkeletonHash,bytes32 confusableSymbolSkeletonHash,bytes32 mediaMetadataHash,bytes32 launchParametersHash,uint256 nonce,uint48 expiry,uint8 originality,bytes32 salt)"
    );

    // solhint-enable max-line-length,gas-small-strings

    /// @notice The signed attestation payload. Identical to v1's plus the trailing `salt` field.
    struct AttestationPayload {
        bytes32 normalizedNameHash;
        bytes32 normalizedSymbolHash;
        bytes32 confusableNameSkeletonHash;
        bytes32 confusableSymbolSkeletonHash;
        bytes32 mediaMetadataHash;
        uint256 nonce;
        uint48 expiry;
        uint8 originality;
        bytes32 salt;
    }

    struct VerificationContext {
        address creator;
        bytes32 rawNameHash;
        bytes32 rawSymbolHash;
        bytes32 launchParametersHash;
    }

    address public factory;
    mapping(address creator => uint256 nonce) public nonces;

    event FactoryConfigured(address indexed factory);
    event IdentityAttestationConsumed(
        address indexed creator,
        bytes32 indexed launchParametersHash,
        address indexed signer,
        uint256 nonce,
        uint8 originality
    );

    constructor(
        address admin,
        address identitySigner
    ) EIP712("Equifold Identity", "1") {
        if (admin == address(0) || identitySigner == address(0)) revert InvalidAddress();
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(IDENTITY_SIGNER_ROLE, identitySigner);
    }

    function setFactory(
        address factory_
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (factory != address(0)) revert AlreadyConfigured();
        if (factory_ == address(0) || factory_.code.length == 0) revert InvalidAddress();
        factory = factory_;
        emit FactoryConfigured(factory_);
    }

    /// @inheritdoc IIdentityAttestationVerifier
    /// @dev Verifies the signature + per-creator nonce and returns the normalized hashes. Unlike v1
    ///      it writes NOTHING to any registry: the append-only identity ledger is gone in gen4.
    function consumeAttestation(
        address creator,
        string calldata name,
        string calldata symbol,
        bytes32 launchParametersHash,
        bytes calldata attestation
    ) external returns (bytes32 normalizedNameHash, bytes32 normalizedSymbolHash) {
        if (msg.sender != factory) revert NotFactory();
        VerificationContext memory context = VerificationContext({
            creator: creator,
            rawNameHash: keccak256(bytes(name)),
            rawSymbolHash: keccak256(bytes(symbol)),
            launchParametersHash: launchParametersHash
        });
        (AttestationPayload memory payload, address signer) =
            _verifyAttestation(context, attestation);
        nonces[creator] = payload.nonce + 1;
        emit IdentityAttestationConsumed(
            creator, launchParametersHash, signer, payload.nonce, payload.originality
        );
        return (payload.normalizedNameHash, payload.normalizedSymbolHash);
    }

    function _verifyAttestation(
        VerificationContext memory context,
        bytes calldata attestation
    ) private view returns (AttestationPayload memory payload, address signer) {
        bytes memory signature;
        (payload, signer, signature) = abi.decode(attestation, (AttestationPayload, address, bytes));
        if (payload.expiry < block.timestamp) revert AttestationExpired();
        if (payload.nonce != nonces[context.creator]) revert AttestationReplay();
        if (payload.originality > ORIGINALITY_PROTECTED_APPROVED) revert InvalidOriginality();
        if (!hasRole(IDENTITY_SIGNER_ROLE, signer)) revert InvalidSigner();

        bytes32 digest = _hashTypedDataV4(_hashAttestation(context, payload));
        if (!SignatureChecker.isValidSignatureNow(signer, digest, signature)) {
            revert InvalidAttestation();
        }
    }

    /// @notice EIP-712 digest builder exposed for off-chain signers / tests.
    function hashAttestation(
        address creator,
        string calldata name,
        string calldata symbol,
        bytes32 launchParametersHash,
        AttestationPayload calldata payload
    ) external view returns (bytes32) {
        VerificationContext memory context = VerificationContext({
            creator: creator,
            rawNameHash: keccak256(bytes(name)),
            rawSymbolHash: keccak256(bytes(symbol)),
            launchParametersHash: launchParametersHash
        });
        return _hashTypedDataV4(_hashAttestation(context, payload));
    }

    /// @dev The EIP-712 struct hash. The `abi.encode` is split into two concatenated groups purely to
    ///      stay under the stack-depth limit with the added `salt` field; because every field is a
    ///      static 32-byte word, `bytes.concat` of the two groups is byte-identical to a single
    ///      `abi.encode` of all fields in order.
    function _hashAttestation(
        VerificationContext memory context,
        AttestationPayload memory payload
    ) private view returns (bytes32) {
        return keccak256(
            bytes.concat(
                abi.encode(
                    ATTESTATION_TYPEHASH,
                    context.creator,
                    factory,
                    context.rawNameHash,
                    context.rawSymbolHash,
                    payload.normalizedNameHash,
                    payload.normalizedSymbolHash,
                    payload.confusableNameSkeletonHash
                ),
                abi.encode(
                    payload.confusableSymbolSkeletonHash,
                    payload.mediaMetadataHash,
                    context.launchParametersHash,
                    payload.nonce,
                    payload.expiry,
                    payload.originality,
                    payload.salt
                )
            )
        );
    }
}
Chain explorer3556msChain node72ms