Recentemente comecei os estudos da ERC-8004: Trustless Agents e resolvi escrever aqui no site para compartilhar o que aprendi sobre o assunto. Na primeira parte desta série de tutoriais, falamos da problemática A2A que gerou a motivação para a criação da ERC-8004 e entramos a fundo no primeiro dos seus três pilares, que é o Registro de Identidade. Na segunda parte, falamos do Registro de Reputação, que reúne os feedbacks dos clientes que usaram os serviços do agente. Nesta terceira e última etapa, vamos aprofundar no terceiro pilar: Registro de Validação.
Vamos lá!
Registro de Validação
O terceiro e último registro previsto pela ERC-8004 é o que está mais experimental e nebuloso dentre os três: o registro de validação. De certa forma ele cumpre o mesmo papel que o Registro de Reputação, mas de uma maneira mais segura, por assim dizer. A ideia é que o Validation Registry permita que os agentes solicitem validação do seu trabalho para validadores na rede. Para isso, os agentes forneceriam dados suficientes para a reprodução e validação do resultado entregue pelo seu trabalho, algo como uma auditoria externa, interessante especialmente para serviços envolvendo altos valores, alto risco, etc.
De posse desses dados, o validador re-processará a execução da tarefa usando qualquer técnica determinística, auditável e segura e registrará on-chain uma prova de que o agente realizou a tarefa corretamente (ou não), pontuando com uma nota de 0 a 100 e fornecendo opcionalmente a URI de um arquivo com evidências da validação. Desta forma, enquanto no Registro de Reputação temos uma confiança construída com uma filosofia mais “community driven”, entre os clients, no Registro de Validação nós temos algo mais próximo de uma auditoria externa, um feedback mais profissional.
Na prática, temos uma inicialização com o endereço do registro de identidade, para vincular os dois (acoplamento fraco):
|
1 2 3 |
initialize(address identityRegistry_) |
E obtenção do endereço se necessário com outra função:
|
1 2 3 |
getIdentityRegistry() |
Já o uso do registro de validação se dá em duas etapas: Validation Request (emitida pelo agente) e Validation Response (emitida pelo validador).
Requisição de Validação
O agente (ou seu owner) que deseja ter seu trabalho validado emite um pedido de validação para um validador usando a função abaixo.
|
1 2 3 |
function validationRequest(address validatorAddress, uint256 agentId, string requestURI, bytes32 requestHash) external |
Todos os parâmetros são obrigatórios, sendo que a requestURI é um arquivo na rede IPFS (ou equivalente) com as informações necessárias para a validação, como endpoints, payloads, resultado esperado, etc. Enquanto que o requestHash é o identificador único da requisição, gerado a partir do hash do conteúdo da requestURI. Essa request é armazenada internamente e notificada na rede através de um evento.
|
1 2 3 |
event ValidationRequest(address indexed validatorAddress, uint256 indexed agentId, string requestURI, bytes32 indexed requestHash) |
Uma vez submetida, agora é esperar o validador fazer o trabalho dele.
Resposta de Validação
A outra perna do processo de validação é a resposta. Quando o validador terminar o serviço dele ele deve chamar a função abaixo para registrar sua resposta.
|
1 2 3 |
function validationResponse(bytes32 requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag) external |
Somente os parâmetros requestHash (identificador da request) e response (nota/score) são obrigatórios. A response é uma nota de 0 a 100 que pode ser entendida como booleano (0 = false, 100 = true) ou qualquer variação dentro desse range. Já responseURI e responseHash podem ser usados para fornecer evidências da validação, como provas. Tag por outro lado, é para ajudar na categorização, servindo por exemplo como status da validação, já que essa função pode ser chamada diversas vezes conforme a validação for acontecendo, mantendo atualizado um campo lastUpdate no registro de cada resposta.
Um ponto importante é que somente o validador designado pode responder a uma request aberta para ele e, quando fizer com sucesso, deve emitir um evento.
|
1 2 3 |
event ValidationResponse(address indexed validatorAddress, uint256 indexed agentId, bytes32 indexed requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag) |
Implementação
Outras funções necessárias são as de leitura, conforme especificação abaixo.
|
1 2 3 4 5 6 |
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 deve trazer os dados de request e response para um requestHash específico (lembre-se, ele é o identificador único de cada request).
A getSummary deve trazer um resumo/agregado das respostas validadas para um agentId. Se fornecido validatorAddresses e/ou tag, deve usar essas informações como filtro também.
A getAgentValidations deve retornar os request hashes de todas validações realizadas para um agentId.
A getValidatorRequests deve retornar os request hashes de todas validações feitas por um validatorAddress.
Abaixo uma sugestão de implementação que atende ao padrão acima.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
// 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, "Unknow 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]; } } |
Conclusões
E com isso terminamos esses nossos estudos iniciais da ERC-8004. Alguns pontos adicionais a se considerar:
- este ainda é um draft, muita coisa pode mudar e ainda não há uma implementação “oficial”, dei apenas sugestões;
- alguns materiais citam a necessidade de se criar um ERC8004Coordinator, ou seja, um quarto contrato que reúne as instâncias dos três registros, para facilitar a sua gestão;
- alguns materiais citam que a validação poderia ser realizada no registro de reputação pelo validador;
Quer se juntar aos builders da ERC-8004? Eles criaram um grupo no Telegram para compartilhar conhecimentos e experiências, entre aqui (em Inglês).
Dos mesmos criadores, conheça também o Ag0 SDK, que facilita a adesão ao novo padrão aqui.
Outra recomendação de estudos de um assunto ortogonal à ERC-8004 é o protocolo x402, criado pela Coinbase e que facilita o pagamento agêntico por serviços online, saiba mais no site oficial.
Até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.



