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
- Creator
- 0xd03543d97B…F446Cd1418
- 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