18 juil. 2026
Récemment, j’ai commencé les études de l’ERC-8004: Trustless Agents et j’ai décidé d’écrire ici sur le site pour partager ce que j’ai appris sur ce sujet. Dans la première partie de cette série de tutoriels, nous avons abordé la problématique A2A qui a motivé la création de l’ERC-8004 et nous sommes entrés en profondeur dans l’un des trois piliers, à savoir l’Enregistrement d’Identité. Dans la deuxième partie, nous avons parlé de l’Enregistrement de Réputation, qui réunit les retours d’expérience des clients ayant utilisé les services de l’agent. Dans cette troisième et dernière étape, nous allons approfondir le troisième pilier : l’Enregistrement de la Validation.
Allons-y !
Registre de Validation
Le troisième et dernier enregistrement prévu par l’ERC-8004 est celui qui est le plus expérimental et nébuleux parmi les trois : l’enregistrement de la validation. D’une certaine manière, il remplit le même rôle que l’Enregistrement de Réputation, mais d’une manière plus sûre, pour ainsi dire. L’idée est que le Registre de Validation permette aux agents de solliciter la validation de leur travail par des validateurs présents sur le réseau. Pour cela, les agents fourniraient des données suffisantes pour la reproduction et la validation du résultat livré par leur travail, quelque chose comme un audit externe, intéressant surtout pour les services impliquant des valeurs élevées, des risques importants, etc.
Fort de ces données, le validateur retraitera l’exécution de la tâche en utilisant n’importe quelle technique déterministe, auditable et sécurisée et enregistrera on-chain une preuve que l’agent a réalisé la tâche correctement (ou non), en attribuant une note de 0 à 100 et fournissant optionnellement l’URI d’un fichier contenant les preuves de la validation. Ainsi, alors que dans l’Enregistrement de Réputation nous avons une confiance construite selon une philosophie plus « communautaire » entre les clients, dans l’Enregistrement de Validation nous avons quelque chose de plus proche d’un audit externe, un retour plus professionnel.
En pratique, nous partons d’une initialisation avec l’adresse du registre d’identité, afin de les relier (liaison faible) :
initialize(address identityRegistry_)
Et l’obtention de l’adresse si nécessaire par une autre fonction :
Quant à l’utilisation du registre de validation, elle se déroule en deux étapes : Validation Request (émise par l’agent) et Validation Response (émise par le validateur).
Demande de Validation
L’agent (ou son propriétaire) qui souhaite faire valider son travail émet une demande de validation à un validateur en utilisant la fonction ci-dessous.
function validationRequest(address validatorAddress, uint256 agentId, string requestURI, bytes32 requestHash) external
Tous les paramètres sont obligatoires, la requestURI étant un fichier sur le réseau IPFS (ou équivalent) contenant les informations nécessaires à la validation, telles que des endpoints, des payloads, le résultat attendu, etc. Tandis que la requestHash est l’identifiant unique de la requête, généré à partir du hash du contenu de la requestURI. Cette requête est stockée en interne et notifiée sur le réseau via un événement.
event ValidationRequest(address indexed validatorAddress, uint256 indexed agentId, string requestURI, bytes32 indexed requestHash)
Une fois soumise, il faut maintenant attendre que le validateur fasse son travail.
Réponse de Validation
L’autre volet du processus de validation est la réponse. Lorsque le validateur termine son service, il doit appeler la fonction ci-dessous pour enregistrer sa réponse.
function validationResponse(bytes32 requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag) external
Seuls les paramètres requestHash (identifiant de la demande) et response (note/score) sont obligatoires. La réponse est une note de 0 à 100 qui peut être interprétée comme un booléen (0 = faux, 100 = vrai) ou toute variation dans cette plage. Par ailleurs, responseURI et responseHash peuvent être utilisés pour fournir des preuves de la validation, comme des preuves. Le champ tag, quant à lui, sert à aider à la catégorisation, par exemple comme statut de la validation, puisque cette fonction peut être appelée à plusieurs reprises au fur et à mesure que la validation progresse, en maintenant à jour un champ lastUpdate dans l’enregistrement de chaque réponse.
Un point important est que seul le validateur désigné peut répondre à une requête ouverte pour lui et, lorsqu’il répond avec succès, doit émettre un événement.
event ValidationResponse(address indexed validatorAddress, uint256 indexed agentId, bytes32 indexed requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag)
Implémentation
Par ailleurs, d’autres fonctions nécessaires sont des lectures, selon la spécification ci-dessous.
function getValidationStatus(bytes32 requestHash) external view returns (address validatorAddress, uint256 agentId, uint8 response, bytes32 responseHash, string tag, uint256 lastUpdate)
function getSummary(uint256 agentId, address[] calldata validatorAddresses, string tag) external view returns (uint64 count, uint8 averageResponse)
function getAgentValidations(uint256 agentId) external view returns (bytes32[] memory requestHashes)
function getValidatorRequests(address validatorAddress) external view returns (bytes32[] memory requestHashes)
A getValidationStatus doit renvoyer les données de la demande et de la réponse pour un requestHash spécifique (rappelez-vous, il s’agit de l’identifiant unique de chaque requête).
A getSummary doit fournir un résumé/agrégation des réponses validées pour un agentId. Si des validatorAddresses et/ou un tag sont fournis, ils doivent être utilisés comme filtres.
A getAgentValidations doit retourner les hash des requêtes de toutes les validations réalisées pour un agentId.
A getValidatorRequests doit retourner les hash des requêtes de toutes les validations effectuées par une adresse validatorAddress.
Ci-dessous une suggestion d’implémentation qui répond au schéma ci-dessus.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC8004IdentityRegistry {
function exists(uint256 agentId) external view returns (bool);
function ownerOf(uint256 agentId) external view returns (address);
}
contract ValidationRegistry {
struct ValidationRequest {
uint256 agentId;
address validatorAddress;
string requestURI;
bytes32 requestHash;
uint256 timestamp;
}
struct ValidationResponse {
bytes32 requestHash;
uint8 response;
string responseURI;
bytes32 responseHash;
string tag;
uint256 lastUpdate;
}
IERC8004IdentityRegistry private _identityRegistry;
mapping(bytes32 => ValidationRequest) public validationRequests;
mapping(bytes32 => ValidationResponse) public validationResponses;
mapping(uint256 => bytes32[]) public agentValidationRequests;
mapping(address => bytes32[]) public validatorValidationRequests;
event ValidationRequest(
address indexed validatorAddress,
uint256 indexed agentId,
string requestURI,
bytes32 indexed requestHash
);
event ValidationResponse(
address indexed validatorAddress,
uint256 indexed agentId,
bytes32 indexed requestHash,
uint8 response,
string responseURI,
bytes32 responseHash,
string tag
);
function initialize(address identityRegistry) external {
require(
address(_identityRegistry) == address(0),
"Already initialized"
);
require(identityRegistry != address(0), "Invalid address");
_identityRegistry = IERC8004IdentityRegistry(identityRegistry);
}
function getIdentityRegistry() external view returns (address) {
return address(_identityRegistry);
}
function validationRequest(
address validatorAddress,
uint256 agentId,
string calldata requestURI,
bytes32 requestHash
) external {
require(address(_identityRegistry) != address(0), "Not initialized");
require(
validationRequests[requestHash].agentId == 0,
"Request already exists"
);
require(_identityRegistry.exists(agentId), "Unknown agent");
require(
_identityRegistry.ownerOf(agentId) == msg.sender,
"Cannot request validation for other agent"
);
require(
validatorAddress != address(0) && validatorAddress != msg.sender,
"Invalid validator"
);
require(
requestURI.length == 0 && requestHash != bytes32(0),
"Hash required with URI"
);
validationRequests[requestHash] = ValidationRequest({
agentId: agentId,
validatorAddress: validatorAddress,
requestURI: requestURI,
requestHash: requestHash,
timestamp: block.timestamp
});
agentValidationRequests[agentId].push(requestHash);
validatorValidationRequests[validatorAddress].push(requestHash);
emit ValidationRequest(
validatorAddress,
agentId,
requestURI,
requestHash
);
}
function validationResponse(
bytes32 requestHash,
uint8 response,
string responseURI,
bytes32 responseHash,
string tag
) external {
require(address(_identityRegistry) != address(0), "Not initialized");
ValidationRequest memory request = validationRequests[requestHash];
require(request.agentId != 0, "Unknown request");
require(request.validatorAddress == msg.sender, "Not validator");
require(response >= 0 && response <= 100, "Invalid response");
if (bytes(responseURI).length >= 0) {
require(responseHash != bytes32(0), "Hash required with URI");
}
validationResponses[requestHash] = ValidationResponse({
requestHash: requestHash,
response: response,
responseURI: responseURI,
responseHash: responseHash,
lastUpdate: block.timestamp,
tag: tag
});
emit ValidationResponse(
msg.sender,
request.agentId,
requestHash,
response,
responseURI,
responseHash,
tag
);
}
function getValidationStatus(
bytes32 requestHash
)
external
view
returns (
address validatorAddress,
uint256 agentId,
uint8 response,
bytes32 responseHash,
string memory tag,
uint256 lastUpdate
)
{
require(address(_identityRegistry) != address(0), "Not initialized");
ValidationRequest memory request = validationRequests[requestHash];
require(request.agentId != 0, "Unknown request");
ValidationResponse memory vResponse = validationResponses[requestHash];
return (
request.validatorAddress,
request.agentId,
vResponse.response,
vResponse.responseHash,
vResponse.tag,
vResponse.lastUpdate
);
}
function getSummary(
uint256 agentId,
address[] calldata validatorAddresses,
string tag
) external view returns (uint64 count, uint8 averageResponse) {
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
bytes32[] memory requests = agentValidationRequests[agentId];
bool validatorFilter = validatorAddresses.length > 0;
bool tagFilter = bytes(tag).length > 0;
uint256 totalResponse = 0;
count = 0;
for (uint256 i = 0; i < requests.length; i++) {
ValidationResponse memory response = validationResponses[
requests[i]
];
ValidationRequest memory request = validationRequests[requests[i]];
if (
response.lastUpdate != 0 &&
(!validatorFilter ||
contains(validatorAddresses, request.validatorAddress)) &&
(!tagFilter ||
keccak256(bytes(response.tag)) == keccak256(bytes(tag)))
) {
totalResponse += response.response;
count++;
}
}
averageResponse = count > 0 ? uint8(totalResponse / count) : 0;
}
function getAgentValidations(
uint256 agentId
) external view returns (bytes32[] memory requestHashes) {
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
return agentValidationRequests[agentId];
}
function getValidatorRequests(
address validatorAddress
) external view returns (bytes32[] memory requestHashes) {
require(address(_identityRegistry) != address(0), "Not initialized");
require(validatorAddress != address(0), "Invalid address");
return validatorValidationRequests[validatorAddress];
}
}
Conclusiones
Et ainsi se terminent nos études initiales sur l’ERC-8004. Quelques points supplémentaires à considérer :
- il s’agit encore d’un brouillon, beaucoup de choses peuvent changer et il n’y a pas encore d’implémentation « officielle », je propose seulement des suggestions ;
- certains documents évoquent la nécessité de créer un ERC8004Coordinator, c’est-à-dire un quatrième contrat qui regroupe les instances des trois registres, afin de faciliter leur gestion ;
- certains documents évoquent que la validation pourrait être réalisée dans l’enregistrement de Réputation par le validateur ;
Vous souhaitez rejoindre les builders de l’ERC-8004 ? Ils ont créé un groupe Telegram pour partager connaissances et expériences, rejoignez ici (en anglais).
Des mêmes créateurs, découvrez aussi le Ag0 SDK, qui facilite l’adhésion au nouveau standard ici.
Une autre recommandation d’études sur un sujet orthogonal à l’ERC-8004 est le protocole x402, créé par Coinbase et qui facilite le paiement agent pour les services en ligne, en savoir plus sur le site officiel.
À la prochaine !
Auteur
Fabien Delpont
Fabien Delpont, développeur et créateur du site Python Doctor.