| 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:
- Verify a specific agent — Check whether a given agent is verified on-chain, retrieve its credentials, reputation score, and freshness status.
- 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:
- Timestamp validation — Reject if the timestamp is older than 5 minutes (replay protection).
- 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.
- On-chain proof check — Derive the agent key from the address and call
registry.isVerifiedAgent(agentKey).
- Provider check — Verify
registry.getProofProvider(agentId) matches Self Protocol's provider address.
- 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:
-
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.
-
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")
.requireAge(18)
.requireOFAC()
.requireSelfProvider()
.sybilLimit(3)
.rateLimit({ perMinute: 100 })
.build();
As Express Middleware
app.use("/api", verifier.auth());
app.get("/api/profile", (req, res) => {
const agent = req.verifiedAgent;
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) {
} else {
}
Verification Pipeline
The verifier executes checks in this order:
- Header extraction — Extract
x-self-agent-address, x-self-agent-signature, x-self-agent-timestamp from the request. Missing headers result in immediate rejection.
- Timestamp window — Reject if the timestamp is more than 5 minutes old (configurable).
- Signature recovery — Reconstruct the signed message, recover the signer via ECDSA, compare to the claimed address.
- Rate limiting — If configured, check per-agent request rate.
- On-chain proof — Call
registry.isVerifiedAgent(agentKey).
- Provider check — If
.requireSelfProvider() is set, verify getProofProvider(agentId) matches Self Protocol.
- Credential checks — If
.requireAge(n) or .requireOFAC() is set, read getAgentCredentials(agentId) and verify.
- 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.