Récemment, j’ai commencé les études autour 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 penchés de manière approfondie sur le premier des trois piliers, à savoir l’Enregistrement d’Identité. Dans cette deuxième étape, nous allons approfondir le deuxième pilier : l’Enregistrement de Réputation.
Allons-y !
Enregistrement de la Réputation
Une fois que le contrat intelligent Enregistrement d’Identité garantit que l’Agent d’IA est bien celui qu’il prétend être, qui fournit les services qu’il réalise, qui est son propriétaire et bien plus encore, c’est bien grâce à l’Enregistrement de Réputation que nous pouvons déterminer si cet agent est réellement compétent dans ce qu’il fait. Pensez à l’enregistrement de réputation comme à la rubrique des évaluations que l’on trouve sur n’importe quel site de commerce en ligne ou de prestation de services, où l’on voit généralement des étoiles ou des notes de 1 à 5, 1 à 10, etc., ainsi que les témoignages de clients satisfaits (ou non). L’idée est la même, mais en conservant un registre on-chain (auditable, immuable et décentralisé) et, si nécessaire, éventuellement d’autres informations complémentaires hors chaîne.
Ainsi, dès qu’un Agent, dûment enregistré sur la blockchain, effectue un service pour un autre agent, celui-ci l’autorise à laisser une évaluation dans le registre de réputation, qui n’est autre que un autre smart contract de façade, accroché au registre d’identité, via une fonction initialize.
function initialize(address identityRegistry_)
Plus tard, pour savoir qui est l’identité de registre, il faut utiliser une autre fonction.
function getIdentityRegistry() external view returns (address identityRegistry)
Feedback
Cette évaluation peut être n’importe quoi selon le contexte : notes de 1 à 10, étoiles de 1 à 5, etc. Le format suggéré par le brouillon de l’ERC-8004 propose que les retours soient sous le format suivant :
- valeur du score (entier);
- nombre de décimales du score (identique à ce que l’on voit pour les tokens ERC-20);
- jusqu’à deux étiquettes facultatives, pour aider à comprendre le score;
- point d’entrée de contexte (du service utilisé, optionnel);
- fichier JSON hors chaîne avec les détails (IPFS, optionnel);
- hash du fichier précédent (optionnel);
Comme vous pouvez le voir, la seule chose réellement importante ici est le score, un int128 avec la note attribuée par l’agent au service de l’autre agent et le nombre de décimales que nous allons utiliser (pouvant être 0). En plus de stocker les scores des retours individuels, l’ERC détermine que l’agrégation de ces scores, de manière simplifiée, soit réalisée on‑chain, c’est-à-dire présente dans le contrat, tandis que des agrégations et rapports plus sophistiqués peuvent être réalisés hors chaîne, si nécessaire. C’est dans ces ressources hors chaîne que les champs optionnels du feedback peuvent briller, car pour une moyenne simple, seul le score et les décimales suffisent.
Pour donner un feedback sur un service, la fonction à appeler est celle-ci :
function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string calldata tag1, string calldata tag2, string calldata endpoint, string calldata feedbackURI, bytes32 feedbackHash) external
Il est important de souligner que le propriétaire d’un agent ne peut pas laisser de feedback sur celui-ci, de même que les portefeuilles autorisés à le transférer. Si vous implémentez les agents comme des tokens soulbound, cette dernière instruction ne s’applique pas, car le transfert n’est pas possible.
Lorsqu’un feedback est émis, un événement doit être émis, comme ci-dessous.
event NewFeedback(uint256 indexed agentId, address indexed clientAddress, uint64 feedbackIndex, int128
La ERC propose également des étiquettes pour contextualiser le score (comme starred, uptime et d’autres) et un standard de métadonnées pour le fichier hors chaîne de l’évaluation.
Un point qui n’est pas évoqué concerne l’autorisation pour le feedback, puisqu’un système ouvert pourrait être facilement inondé par du spam destiné à dénigrer ou à élever artificiellement la réputation d’un agent virtuel. Cela peut être résolu en créant un mapping similaire à l’allocation (allowance) du ERC-20, afin que le propriétaire de l’agent autorise automatiquement la réception du feedback après la prestation du service.
Gestion des Feedbacks
La ERC détermine également des cas limites relatifs aux feedbacks, comme par exemple lorsqu’un client souhaite révoquer un feedback qu’il a donné à un agent. Dans ce cas, il doit appeler la fonction revokeFeedback :
function revokeFeedback(uint256 agentId, uint64 feedbackIndex) external
Et elle, à son tour, doit émettre un événement FeedbackRevoked :
event FeedbackRevoked(uint256 indexed agentId, address indexed clientAddress, uint64 indexed feedbackIndex)
On prévoit également l’ajout de réponses aux feedbacks, par exemple le propriétaire de l’agent présentant un reçu de remboursement ou le client présentant un reçu de transaction. Pour ajouter des réponses, la fonction appendResponse doit être appelée :
function appendResponse(uint256 agentId, address clientAddress, uint64 feedbackIndex, string calldata responseURI, bytes32 responseHash) external
Cette fonction utilise l’indice du feedback comme référence et permet de fournir l’URI du fichier sur le réseau IPFS. En cas d’utilisation d’autres réseaux, le responseHash est obligatoire pour garantir l’intégrité.
Enfin, nous avons les fonctions de lecture définies par le standard :
function getSummary(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2) external view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals)
function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex) external view returns (int128 value, uint8 valueDecimals, string tag1, string tag2, bool isRevoked)
function readAllFeedback(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2, bool includeRevoked) external view returns (address[] memory clients, uint64[] memory feedbackIndexes, int128[] memory values, uint8[] memory valueDecimals, string[] memory tag1s, string[] memory tag2s, bool[] memory revokedStatuses)
function getResponseCount(uint256 agentId, address clientAddress, uint64 feedbackIndex, address[] responders) external view returns (uint64 count)
function getClients(uint256 agentId) external view returns (address[] memory)
function getLastIndex(uint256 agentId, address clientAddress) external view returns (uint64)
La fonction getSummary doit retourner un résumé agrégé des feedbacks donnés à un agentId par un ensemble de clients. En d’autres termes, c’est une sous-vision de l’agrégation générale de cet agent.
La readFeedback doit apporter un feedback spécifique, en tenant compte du client et/ou de l’indice du feedback. Facultativement, on peut utiliser les étiquettes pour affiner encore le retour.
La readAllFeedback reçoit l’agentId comme entrée obligatoire et certains filtres optionnels, et renvoie la collection de clients, les indices de feedback, les notes données, les décimales, les étiquettes et l’information indiquant si chacun est révoqué ou non. Notez que les feedbacks eux-mêmes ne sont pas retournés et si des filtres sont fournis, ils doivent être respectés dans le résultat final.
La getResponseCount renvoie le nombre de réponses à un feedback et à un agent spécifiques, avec la possibilité de filtrer par client et/ou répondant.
La getClients renvoie toutes les adresses des clients qui ont laissé un feedback pour cet agent.
Et enfin, la getLastIndex doit renvoyer l’indice du dernier feedback soumis par un client pour un agentId.
Ci-dessous, un exemple minimal d’implémentation de ReputationRegistry, avec un couplage faible sur IdentityRegistry.
// 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 ReputationRegistry {
event NewFeedback(
uint256 indexed agentId,
address indexed clientAddress,
uint64 feedbackIndex,
int128 value,
uint8 valueDecimals
);
event FeedbackRevoked(uint256 indexed agentId, address indexed clientAddress, uint64 indexed feedbackIndex);
event ResponseAppended(uint256 indexed agentId, address indexed clientAddress, uint64 feedbackIndex, address indexed responder, string responseURI, bytes32 responseHash);
struct Feedback {
uint256 agentId;
address from;
int128 value;
uint8 valueDecimals;
uint256 timestamp;
bool revoked;
}
struct Response {
address responder;
string responseURI;
bytes32 responseHash;
uint256 timestamp;
}
struct Aggregate {
int256 sum;
uint256 count;
uint256 avg;
}
uint8 constant DECIMALS = 2;
IERC8004IdentityRegistry private _identityRegistry;
mapping(uint256 => Feedback[]) private _feedbacks;
mapping(uint256 => mapping(uint256 => Response[])) public responses;
mapping(uint256 => Aggregate) public aggregate;
mapping(uint256 => mapping(address => bool)) private _feedbackAuthorizations;
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 authorizeFeedback(uint256 agentId, address clientAddress) external {
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
require(_identityRegistry.ownerOf(agentId) == msg.sender, "Only agent owner can authorize");
_feedbackAuthorizations[agentId][clientAddress] = true;
}
function giveFeedback(
uint256 agentId,
int128 value,
uint8 valueDecimals
) external {
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
require(valueDecimals == DECIMALS, "Invalid decimals");
require(_feedbackAuthorizations[agentId][msg.sender] && _identityRegistry.ownerOf(agentId) != msg.sender, "Not authorized to give feedback");
_feedbackAuthorizations[agentId][msg.sender] = false; // Revoke authorization to prevent reentrancy
_feedbacks[agentId].push(
Feedback({
agentId: agentId,
from: msg.sender,
value: value,
valueDecimals: DECIMALS,
timestamp: block.timestamp,
revoked: false
})
);
Aggregate storage agg = aggregate[agentId];
agg.sum += value;
agg.count += 1;
agg.avg = agg.sum / agg.count;
emit NewFeedback(
agentId,
msg.sender,
_feedbacks[agentId].length - 1,
value,
DECIMALS
);
}
function revokeFeedback(uint256 agentId, uint64 feedbackIndex) external {
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
require(feedbackIndex < _feedbacks[agentId].length, "Invalid index");
Feedback memory feedback = _feedbacks[agentId][feedbackIndex];
require(feedback.from == msg.sender, "Not feedback author");
// Update aggregate before removing feedback
Aggregate storage agg = aggregate[agentId];
agg.sum -= feedback.value;
agg.count -= 1;
agg.avg = agg.count > 0 ? agg.sum / agg.count : int256(0);
_feedbacks[agentId][feedbackIndex].revoked = true; // Mark as revoked instead of removing to maintain indices
emit FeedbackRevoked(agentId, msg.sender, feedbackIndex);
}
function appendResponse(uint256 agentId, address clientAddress, uint64 feedbackIndex, string calldata responseURI, bytes32 responseHash) external {
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
require(feedbackIndex < _feedbacks[agentId].length, "Invalid index");
bool isAgentOwner = clientAddress == msg.sender && _identityRegistry.ownerOf(agentId) == msg.sender;
bool isClient = clientAddress == msg.sender && clientAddress == _feedbacks[agentId][feedbackIndex].from;
require(isAgentOwner || isClient, "Only agent owner or feedback author can respond");
responses[agentId][feedbackIndex].push(
Response({
responder: msg.sender,
responseURI: responseURI,
responseHash: responseHash,
timestamp: block.timestamp
})
);
emit ResponseAppended(
agentId,
clientAddress,
feedbackIndex,
msg.sender,
responseURI,
responseHash
);
}
function contains(address[] memory addrSet, address addr) private pure returns (bool){
for(uint256 i=0; i < addrSet.length; i++){
if(addr == addrSet[i]) return true;
}
return false;
}
function getSummary(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2) external view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals){
require(clientAddresses.length > 0, "clientAddresses is required");
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
count = 0;
summaryValue = 0;
summaryValueDecimals = DECIMALS;
for (uint256 i = 0; i < _feedbacks[agentId].length; i++) {
Feedback memory feedback = _feedbacks[agentId][i];
if (!feedback.revoked && contains(clientAddresses, feedback.from)) {
summaryValue += feedback.value;
count++;
}
}
summaryValue = count > 0 ? summaryValue / count : int128(0);
}
function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex) external view returns (int128 value, uint8 valueDecimals, string tag1, string tag2, bool isRevoked) {
require(feedbackIndex < _feedbacks[agentId].length, "Invalid index");
Feedback memory feedback;
if(clientAddress != address(0)) {
uint64 aux = 0;
for(uint64 i = 0; i < _feedbacks[agentId].length; i++) {
if(_feedbacks[agentId][i].from == clientAddress){
if(aux == feedbackIndex) {
feedback = _feedbacks[agentId][i];
break;
}
else aux++;
}
}
require(feedback.from == clientAddress, "Feedback not found for specified client");
} else {
feedback = _feedbacks[agentId][feedbackIndex];
}
value = feedback.value;
valueDecimals = feedback.valueDecimals;
tag1 = ""; // Placeholder, as tags are not implemented in this example
tag2 = ""; // Placeholder, as tags are not implemented in this example
isRevoked = feedback.revoked;
}
function readAllFeedback(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2, bool includeRevoked) external view returns (address[] memory clients, uint64[] memory feedbackIndexes, int128[] memory values, uint8[] memory valueDecimals, string[] memory tag1s, string[] memory tag2s, bool[] memory revokedStatuses){
require(clientAddresses.length > 0, "clientAddresses is required");
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
// For simplicity, this example does not implement tag-based filtering. In a real implementation, you would need to store tags in the Feedback struct and filter accordingly.
uint64 count = 0;
for (uint64 i = 0; i < _feedbacks[agentId].length; i++) {
Feedback memory feedback = _feedbacks[agentId][i];
if ((includeRevoked || !feedback.revoked) && (clientAddresses.length == 0 || contains(clientAddresses, feedback.from))) {
count++;
}
}
clients = new address[](count);
feedbackIndexes = new uint64[](count);
values = new int128[](count);
valueDecimals = new uint8[](count);
tag1s = new string[](count);
tag2s = new string[](count);
revokedStatuses = new bool[](count);
uint64 index = 0;
for (uint64 i = 0; i < _feedbacks[agentId].length; i++) {
Feedback memory feedback = _feedbacks[agentId][i];
if ((includeRevoked || !feedback.revoked) && (clientAddresses.length == 0 || contains(clientAddresses, feedback.from))) {
clients[index] = feedback.from;
feedbackIndexes[index] = i;
values[index] = feedback.value;
valueDecimals[index] = feedback.valueDecimals;
tag1s[index] = ""; // Placeholder, as tags are not implemented in this example
tag2s[index] = ""; // Placeholder, as tags are not implemented in this example
revokedStatuses[index] = feedback.revoked;
index++;
}
}
}
function getResponseCount(uint256 agentId, address clientAddress, uint64 feedbackIndex, address[] responders) external view returns (uint64 count){
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
count = 0;
bool clientFilter = clientAddress != address(0);
bool responderFilter = responders.length > 0;
if(clientFilter){
uint64 aux = 0;
for (uint64 i = 0; i < _feedbacks[agentId].length; i++) {
if (_feedbacks[agentId][i].from == clientAddress) {
if(aux == feedbackIndex) {
for (uint64 j = 0; j < responses[agentId][i].length; j++) {
Response memory response = responses[agentId][i][j];
if (!responderFilter || contains(responders, response.responder)) {
count++;
}
}
}
else aux++;
}
}
}
else {
for (uint64 i = 0; i < responses[agentId][feedbackIndex].length; i++) {
Response memory response = responses[agentId][feedbackIndex][i];
if (!responderFilter || contains(responders, response.responder)) {
count++;
}
}
}
}
function getClients(uint256 agentId) external view returns (address[] memory){
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
uint256 count = _feedbacks[agentId].length;
address[] memory clients = new address[](count);
for(uint256 i = 0; i < count; i++){
clients[i] = _feedbacks[agentId][i].from;
}
return clients;
}
function getLastIndex(uint256 agentId, address clientAddress) external view returns (uint256){
require(address(_identityRegistry) != address(0), "Not initialized");
require(_identityRegistry.exists(agentId), "Unknown agent");
require(clientAddress != address(0), "Invalid client address");
for (uint256 i = _feedbacks[agentId].length - 1; i >= 0; i--) {
if (_feedbacks[agentId][i].from == clientAddress && !_feedbacks[agentId][i].revoked) {
return i;
}
}
revert("No feedback from this client");
}
}
Dans l’étape suivante et dernière, nous parlerons du troisième pilier, l’Enregistrement de Validation.
À bientôt !
Auteur
Fabien Delpont
Fabien Delpont, développeur et créateur du site Python Doctor.

Python débutant
- Présentation Python
- Installer Python
- Interpréteur python
- IDE Editeurs python
- Calculs et variables
- Les listes
- Les tuples
- Les dictionnaires
- Les fonctions
- Les fonctions natives
- Conditions if elif else
- Boucle for / while
- Les modules/packages
- Les exceptions
- Les compréhensions de liste
- Programmation orientée objet
- Les décorateurs
- Les itérateurs/générateurs
- Expressions régulières
- Lire / Editer un fichier
- PEP 8 / bonnes pratiques
- Black formateur de code
Python avancé
- Différences python 2 / 3
- Encodage python
- Pip installe vos librairies
- Virtualenv
- Debug
- Python Path
- Gestion fichiers et dossiers
- Interface graphique tKinter
- Graphiques scientifiques
- Programmation asynchrone
- XML et python
- BeautifulSoup / HTML parser
- Créer un exécutable
- Dossier partagé / samba
- FTP / SFTP
- Fabric SSH
- Envoyer un mail SMTP
- Réseau / socket
- Base de données
- Créer un serveur web
- Websocket & Crossbar
- Générateur de site statique
- Numba
- Tests unitaires
- Deep Learning IA
- Deep Learning & PyTorch
- Block Chain
Django
- Présentation Django
- Installer Django
- Initialiser un projet Django
- Créer une application Django
- ORM Django
- Login Django
- Les champs des modèles
- Interface admin Django
- Queryset
- Many to many relation
- Les vues
- Les formulaires
- CSRF Token
- Les middlewares
- Templates Django
- Context Processor
- Signaux Django
- Xadmin
- Django select2
- Crispy Forms
- AngularJS et Django
- Uploader un fichier en ajax
- Django Rest Framework
- Django déploiement



