Creating a Verifier Spec
A Verifier Spec defines the complete specification for a type of verification — "what this type of transaction needs to verify, how to construct signatures, how to verify them." Immutable after deployment; multiple Verifier instances share the same Spec to form a competitive market.
This document uses XQuoteVerifierSpec as a reference implementation.
Relationship Between Spec and Verifier Instance
XQuoteVerifierSpec (1)
├── XQuoteVerifier A (Instance 1, operated by Team A)
├── XQuoteVerifier B (Instance 2, operated by Team B)
└── XQuoteVerifier C (Instance 3, auto-operated by an Agent)
- Spec defines the specification — Parameter format, signature structure,
check()verification logic - Instances provide the service — Each independently operates off-chain services, sets own prices, competes for business
- Deal Contract references Spec —
getRequiredSpecs()returns the Spec address required for each verification slot in the deal flow; Traders use this to search for matching Verifier instances
Step 1: Implement Metadata
contract XQuoteVerifierSpec is IVerifierSpec {
function name() external pure override returns (string memory) {
return "XQuoteVerifierSpec";
}
function version() external pure override returns (string memory) {
return "1.0";
}
function description() external pure override returns (string memory) {
return
"Verifies X/Twitter quote tweets. "
"check params: string tweet_id, string quoter_username. "
"specParams (for verification): abi.encode(string tweet_id, string quoter_username, string quote_tweet_id).";
}
}
Importance of description(): It's not just human-readable documentation — off-chain services need to decode on-chain parameters based on the specParams format declared in the description. It must be precise down to parameter types and order.
Note: The
IVerifierSpecbase interface only defines three methods:name(),version(),description().check()is not in the base interface because each Spec's signature parameters differ — you need to define thecheck()signature yourself. Deal Contracts cast the Spec address to your specific type when calling it.
Step 2: Define the EIP-712 Signature Structure
bytes32 public constant VERIFY_TYPEHASH = keccak256(
"Verify(string tweetId,string quoterUsername,uint256 fee,uint256 deadline)"
);
This typehash defines the content structure for the Verifier's quote signature. The Verifier signs according to this structure during request_sign, and the contract verifies the signature according to this structure during createDeal.
Design Considerations
The signature includes: business parameters (solidifying verification content), fee (solidifying the price), deadline (solidifying the validity period). It does not include dealIndex (the deal hasn't been created at signing time) or partyA/partyB (the Verifier only cares about verification content).
EIP-712 is used instead of plain signatures because structured signatures allow wallets to display semantic signing content rather than a hex string.
Step 3: Implement check()
function check(
address verifierInstance,
string calldata tweet_id,
string calldata quoter_username,
uint256 fee,
uint256 deadline,
bytes calldata sig
) external view returns (bool) {
if (block.timestamp > deadline) revert SignatureExpired();
bytes32 structHash = keccak256(abi.encode(
VERIFY_TYPEHASH,
keccak256(bytes(tweet_id)),
keccak256(bytes(quoter_username)),
fee,
deadline
));
bytes32 domainSeparator = IVerifier(verifierInstance).DOMAIN_SEPARATOR();
address owner_ = IVerifier(verifierInstance).owner();
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signer = _recoverSigner(digest, sig);
return signer == owner_;
}
Key Design Points
- Call timing —
check()is called by the Deal Contract duringcreateDealto verify the signature was indeed issued by the specified Verifier's owner - DOMAIN_SEPARATOR — Read from
verifierInstance; each instance has an independent domain to prevent cross-instance signature replay - Owner verification — signer must equal
IVerifier(verifierInstance).owner(); owner must be an EOA (transferOwnershiprejects contract addresses) - EIP-2 low-s value —
_recoverSignerenforces low-s value to prevent signature malleability attacks
Step 4: Define the specParams Format
specParams is the parameter package passed to the Verifier's off-chain service during the verification phase. Note it's not identical to check()'s signature parameters:
check() signature parameters (at createDeal stage):
tweet_id, quoter_username
specParams (at verification stage):
abi.encode(tweet_id, quoter_username, quote_tweet_id)
The additional quote_tweet_id — this field doesn't exist at createDeal time (B hasn't executed the task yet); it's written when B calls claimDone. The verifier needs it to check the specific quote tweet B posted.
Constructing specParams in the Deal Contract
// XQuoteDealContract.getVerificationParams()
specParams = abi.encode(d.tweet_id, d.quoter_username, d.quote_tweet_id);
Key point: specParams is constructed and returned by the Deal Contract's getVerificationParams(). The Verifier's off-chain service reads and decodes it from on-chain. The entire chain passes through no off-chain intermediary.
Step 5 (Optional): Create a Sample Verifier Contract Instance and Corresponding Off-Chain Verification Service
The Spec author doesn't necessarily need to operate a Verifier themselves; anyone can deploy an instance based on an existing Spec (see Deployment & Operations). The instance contract is lightweight:
contract XQuoteVerifier is VerifierBase {
address public immutable SPEC;
constructor(address specAddress) VerifierBase("XQuoteVerifier", "1") {
require(specAddress != address(0), "spec cannot be zero");
SPEC = specAddress;
}
function description() external pure override(VerifierBase) returns (string memory) {
return "X Quote Tweet Verification: Verifies that a user has quoted a specific tweet on X.";
}
function spec() external view override(VerifierBase) returns (address) {
return SPEC;
}
}
Full VerifierBase API in Developer Reference.
The focus for Verifiers is the off-chain verification service implementation; see Deployment & Operations.
Reference Implementation
- Spec contract:
contracts/verifier-specs/XQuoteVerifierSpec.sol - Verifier instance:
examples/x-quote-verifier/XQuoteVerifier.sol