verify-agents

star 3

Verify Self Agent ID identities and add verification middleware to APIs. Covers one-off agent verification, SelfAgentVerifier builder pattern, reputation scoring, freshness validation, sybil detection, and the critical provider check. Use when the user asks to "verify an agent", "check agent identity", "add verification", "agent verification middleware", "is this agent verified", "reputation score", "check proof of human", or "validate agent freshness".

selfxyz By selfxyz schedule Updated 3/11/2026

name: verify-agents description: > Verify Self Agent ID identities and add verification middleware to APIs. Covers one-off agent verification, SelfAgentVerifier builder pattern, reputation scoring, freshness validation, sybil detection, and the critical provider check. Use when the user asks to "verify an agent", "check agent identity", "add verification", "agent verification middleware", "is this agent verified", "reputation score", "check proof of human", or "validate agent freshness". license: MIT metadata: author: Self Protocol version: 1.0.0 mcp-server: self-agent-id

Verify Agents

Two Workflows

This skill covers two distinct verification workflows:

  1. Verify a specific agent — Check whether a given agent is verified on-chain, retrieve its credentials, reputation score, and freshness status.
  2. Add verification to an API — Integrate middleware into a service that verifies incoming HTTP requests from Self Agent ID-authenticated agents.

Both workflows rely on the same on-chain state: the SelfAgentRegistry contract, the SelfReputationProvider, and the SelfValidationProvider. The difference is context — one-off lookups versus continuous request authentication.


CRITICAL: Provider Verification

This is the single most important security check in the entire verification pipeline.

Every verification flow MUST confirm that the agent's proof provider is the Self Protocol provider address — not a third-party or fake provider. Without this check, an attacker could deploy a malicious IHumanProofProvider contract that always returns true, register agents through it, and bypass all identity guarantees.

Provider addresses:

Network SelfHumanProofProvider Address
Mainnet (42220) 0x4b036aFD959B457A208F676cf44Ea3ef73Ea3E3d
Testnet (11142220) 0x5E61c3051Bf4115F90AacEAE6212bc419f8aBB6c

How to check:

  • MCP tool: Set require_self_provider: true — the tool handles the check automatically.
  • SDK: Call registry.getProofProvider(agentId) and compare the returned address against the known Self Protocol provider address. The SelfAgentVerifier builder's .requireSelfProvider() method adds this check to the verification pipeline.
  • On-chain (Solidity): require(registry.getProofProvider(agentId) == SELF_PROVIDER_ADDRESS, "Wrong provider");

SDK default behavior: The SelfAgentVerifier checks the proof provider by default (requireSelfProvider defaults to true). Do not disable this check unless intentionally accepting agents verified by third-party providers. To explicitly disable: pass requireSelfProvider: false in the verifier config. Omitting the provider check is the most common and most dangerous integration mistake.


Verify a Specific Agent Using MCP

Use the self_verify_agent tool to perform a one-off verification of any agent.

Input Parameters

Parameter Required Type Description
agent_address Yes string Ethereum address of the agent to verify
network No string "mainnet" or "testnet" (defaults to "mainnet")
require_age No 0 | 18 | 21 Minimum age threshold to require
require_ofac No bool Require OFAC sanctions screening clearance
require_self_provider No bool Require the proof provider to be Self Protocol

Output

verified           — bool: whether the agent passes all specified checks
agent_id           — uint256: the on-chain agent ID (0 if not registered)
credentials        — object: { nationality, older_than, ofac_clear }
sybil_count        — uint256: number of agents registered by this human
verification_strength — uint8: 0-100 score from the proof provider
registered_at      — uint256: block number of registration
reason             — string: explanation if verified is false (e.g., "No human proof", "Wrong provider", "Age requirement not met")

Example Usage

To verify an agent at address 0x83fa...ff00 is a Self Protocol-verified adult:

Tool: self_verify_agent
Input:
  agent_address: "0x83fa4380903fecb801F4e123835664973001ff00"
  network: "testnet"
  require_age: 18
  require_ofac: true
  require_self_provider: true

If any check fails, verified returns false and reason explains which check failed. Always set require_self_provider: true in production.


Verify Incoming HTTP Requests Using MCP

Use the self_verify_request tool to validate an incoming HTTP request signed by a Self Agent.

Input Parameters

Parameter Required Type Description
agent_address Yes string From x-self-agent-address header
agent_signature Yes string From x-self-agent-signature header
agent_timestamp Yes string From x-self-agent-timestamp header
method Yes string HTTP method (e.g., "POST")
path Yes string Request path (e.g., "/api/data")
body No string Request body (empty string if no body)

Output

valid              — bool: whether the request is authenticated and the agent is verified
agent_address      — string: recovered signer address
agent_id           — uint256: on-chain agent ID
agent_count        — uint256: number of agents for this human (sybil indicator)
credentials        — object: { nationality, older_than, ofac_clear }

Verification Pipeline

The tool performs these checks in sequence:

  1. Timestamp validation — Reject if the timestamp is older than 5 minutes (replay protection).
  2. Signature recovery — Reconstruct the signed message by concatenating timestamp + METHOD + path + keccak256(body), hash with Keccak-256, recover the signer via EIP-191 ECDSA, and compare to the claimed agent_address.
  3. On-chain proof check — Derive the agent key from the address and call registry.isVerifiedAgent(agentKey).
  4. Provider check — Verify registry.getProofProvider(agentId) matches Self Protocol's provider address.
  5. Credential retrieval — Fetch ZK-attested credentials from the registry.

Note on replay protection: the 5-minute timestamp window means a signed request can be replayed within that window. For state-changing operations, implement additional replay protection (e.g., nonces, idempotency keys) at the application layer. The timestamp window is a trade-off between clock skew tolerance and replay risk.


Reputation Scoring (SelfReputationProvider)

The SelfReputationProvider contract provides standardized reputation scores for agents based on the verification strength of their proof provider.

getReputationScore

function getReputationScore(uint256 agentId) external view returns (uint8);

Returns a score from 0 to 100. The score is derived directly from the proof provider's verificationStrength() value.

Score tiers:

Score Meaning Provider Type
0 No human proof or unverified agent None / unknown
40 Video liveness check Video verification provider
60 Government ID without NFC chip (e.g., Aadhaar scan) Document-based provider
100 Passport NFC chip + biometric verification Self Protocol

Self Protocol agents always score 100 because the SelfHumanProofProvider reports verificationStrength() = 100.

getReputation

function getReputation(uint256 agentId) external view returns (
    uint8 score,
    string memory providerName,
    bool hasProof,
    uint256 registeredAtBlock
);

Returns full reputation details:

  • score — The 0-100 reputation score
  • providerName — The provider's self-reported name (e.g., "self")
  • hasProof — Whether the agent has an active human proof
  • registeredAtBlock — The block number at which the agent was registered

getReputationBatch

function getReputationBatch(uint256[] calldata agentIds) external view returns (uint8[] memory);

Batch query for multiple agents. Returns an array of scores in the same order as the input IDs. Use this for leaderboards, dashboards, or any UI that displays multiple agents.


Freshness Validation (SelfValidationProvider)

The SelfValidationProvider contract checks whether an agent's proof is still considered "fresh" — that is, whether enough time has passed since registration that re-verification might be warranted.

isValidAgent

function isValidAgent(uint256 agentId) external view returns (bool);

Quick boolean check. Returns true only if the agent has a valid human proof AND the proof is still within the freshness threshold. Returns false if the agent is unregistered, has no proof, or the proof has expired.

validateAgent

function validateAgent(uint256 agentId) external view returns (
    bool valid,
    bool fresh,
    uint256 registeredAt,
    uint256 blockAge,
    address proofProvider
);

Full validation details:

  • valid — Whether the agent has a human proof at all
  • fresh — Whether the proof is within the freshness window
  • registeredAt — Block number of registration
  • blockAge — Number of blocks since registration
  • proofProvider — Address of the proof provider that verified this agent

Freshness Threshold

  • Default: 6,307,200 blocks (~1 year on Celo at 5 seconds per block)
  • Configuration: The contract owner can call setFreshnessThreshold(blocks) to adjust
  • Disabling: Setting the threshold to 0 disables freshness checking entirely (all valid agents are considered fresh)

Freshness is a policy decision. A financial service might set a 30-day threshold requiring frequent re-verification. A social platform might accept the default 1-year window. A development environment might disable it entirely.


Sybil Detection

The registry supports sybil detection through nullifier-based identity linking. Each human produces a deterministic nullifier scoped to the registry — the same human always generates the same nullifier regardless of which agent they register.

sameHuman

function sameHuman(uint256 agentIdA, uint256 agentIdB) external view returns (bool);

Check if two agent IDs are controlled by the same human. Returns true if both agents have active human proofs and share the same non-zero nullifier. Returns false if either agent lacks a proof or if the nullifiers differ.

getAgentCountForHuman

function getAgentCountForHuman(uint256 nullifier) external view returns (uint256);

Returns the number of currently active agents associated with a given nullifier. To get the nullifier for an agent, first call getHumanNullifier(agentId).

Enforcement Layers

Sybil enforcement operates at two layers:

  1. Contract layer: The registry enforces maxAgentsPerHuman at registration time (default: 1). If the limit is reached, registration reverts with TooManyAgentsForHuman. The owner can adjust this via setMaxAgentsPerHuman(n). Setting to 0 means unlimited.

  2. Application layer: The SDK's SelfAgentVerifier provides a sybilLimit(n) configuration that rejects requests from agents whose human has more than n active agents. This is independent of the contract-level limit and can be more restrictive. Default is 1.

The contract-level limit restricts how many agents a human can register. The application-level limit restricts how many of those agents a single service will accept. Both are important — the contract prevents mass registration, while the application prevents a single human from overwhelming a specific service with multiple agents.


Using SDK — SelfAgentVerifier Builder

The SelfAgentVerifier class provides a builder pattern for configuring verification rules. It is the primary tool for server-side verification in TypeScript, Python, and Rust applications.

Builder Configuration

import { SelfAgentVerifier } from "@selfxyz/agent-sdk";

const verifier = SelfAgentVerifier.create()
  .network("mainnet") // Select network (sets chain ID, RPC, contract addresses)
  .requireAge(18) // Reject agents without age 18+ credential
  .requireOFAC() // Reject agents without OFAC clearance
  .requireSelfProvider() // CRITICAL: reject agents not verified by Self Protocol
  .sybilLimit(3) // Max 3 agents per human
  .rateLimit({ perMinute: 100 }) // 100 requests per minute per agent
  .build();

As Express Middleware

// Protect all routes under /api
app.use("/api", verifier.auth());

// The middleware sets req.verifiedAgent on successful verification
app.get("/api/profile", (req, res) => {
  const agent = req.verifiedAgent;
  // { agentId, address, credentials, agentCount }
  res.json({
    agentId: agent.agentId,
    nationality: agent.credentials.nationality,
  });
});

Manual Verification

const result = await verifier.verify({
  address: req.headers["x-self-agent-address"],
  signature: req.headers["x-self-agent-signature"],
  timestamp: req.headers["x-self-agent-timestamp"],
  method: req.method,
  path: req.path,
  body: JSON.stringify(req.body),
});

if (result.valid) {
  // result.agentId — on-chain agent ID
  // result.credentials — { nationality, olderThan, ofac }
  // result.agentCount — number of agents for this human
} else {
  // result.error — reason for rejection
}

Verification Pipeline

The verifier executes checks in this order:

  1. Header extraction — Extract x-self-agent-address, x-self-agent-signature, x-self-agent-timestamp from the request. Missing headers result in immediate rejection.
  2. Timestamp window — Reject if the timestamp is more than 5 minutes old (configurable).
  3. Signature recovery — Reconstruct the signed message, recover the signer via ECDSA, compare to the claimed address.
  4. Rate limiting — If configured, check per-agent request rate.
  5. On-chain proof — Call registry.isVerifiedAgent(agentKey).
  6. Provider check — If .requireSelfProvider() is set, verify getProofProvider(agentId) matches Self Protocol.
  7. Credential checks — If .requireAge(n) or .requireOFAC() is set, read getAgentCredentials(agentId) and verify.
  8. Sybil check — If .sybilLimit(n) is set, check getAgentCountForHuman(nullifier) <= n.

Each check short-circuits on failure, returning { valid: false, error: "..." }.

Python and Rust Equivalents

The same API surface exists in Python and Rust:

Python:

from self_agent_sdk import SelfAgentVerifier

verifier = (SelfAgentVerifier.create()
    .network("mainnet")
    .require_age(18)
    .require_ofac()
    .require_self_provider()
    .sybil_limit(3)
    .build())

Rust:

use self_agent_sdk::SelfAgentVerifier;

let verifier = SelfAgentVerifier::builder()
    .network("mainnet")
    .require_age(18)
    .require_ofac()
    .require_self_provider()
    .sybil_limit(3)
    .build()?;

Contract Addresses for Verification

Contract Mainnet (42220) Testnet (11142220)
SelfAgentRegistry 0xaC3DF9ABf80d0F5c020C06B04Cced27763355944 0x043DaCac8b0771DD5b444bCC88f2f8BBDBEdd379
SelfHumanProofProvider 0x4b036aFD959B457A208F676cf44Ea3ef73Ea3E3d 0x5E61c3051Bf4115F90AacEAE6212bc419f8aBB6c
SelfReputationProvider 0x69Da18CF4Ac27121FD99cEB06e38c3DC78F363f4 0x3Bb0A898C1C0918763afC22ff624131b8F420CC2
SelfValidationProvider 0x71a025e0e338EAbcB45154F8b8CA50b41e7A0577 0x84cA20B8A1559F136dA03913dbe6A7F68B6B240B
AgentDemoVerifier 0xD8ec054FD869A762bC977AC328385142303c7def 0xc31BAe8f2d7FCd19f737876892f05d9bDB294241
AgentGate 0x26e05bF632fb5bACB665ab014240EAC1413dAE35 0x86Af07e30Aa42367cbcA7f2B1764Be346598bbc2

Troubleshooting

Symptom Cause Resolution
Verification returns "Wrong provider" Agent was verified by a non-Self provider Confirm the agent registered through Self Protocol. Check getProofProvider(agentId) against the known Self provider address.
Verification returns "No human proof" Agent is registered but has no active proof The agent may have been deregistered or the proof revoked. Re-register the agent.
isValidAgent returns false but agent is registered Freshness threshold exceeded The agent's registration is older than the freshness window (~1 year default). Re-register or adjust the freshness threshold.
Middleware rejects all requests with 401 Missing or incorrect header extraction Verify the middleware reads x-self-agent-address, x-self-agent-signature, and x-self-agent-timestamp (case-insensitive). Some frameworks lowercase headers.
Sybil check fails unexpectedly sybilLimit set too low If the human legitimately has multiple agents, increase the sybilLimit() value in the verifier config.
RPC errors during on-chain verification Wrong network or RPC endpoint Ensure the verifier's network matches the network the agent registered on. Check RPC URL connectivity.

Reference Documentation

For complete code examples across multiple frameworks (Express, FastAPI, Axum, Hono) and Solidity integration patterns, see references/verification-patterns.md.

Install via CLI
npx skills add https://github.com/selfxyz/self-agent-id --skill verify-agents
Repository Details
star Stars 3
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator