UNPKG

@agentic-trust/8004-sdk

Version:

ERC-8004 Trustless Agents SDK - A TypeScript SDK for interacting with ERC-8004 compliant implementations

221 lines 10.5 kB
/** * Reputation Client for ERC-8004 * Handles feedback submission and reputation queries */ import ReputationRegistryABI from './abis/ReputationRegistry.json'; import { ethers } from 'ethers'; export class ReputationClient { adapter; contractAddress; identityRegistryAddress; constructor(adapter, contractAddress, identityRegistryAddress) { this.adapter = adapter; this.contractAddress = contractAddress; this.identityRegistryAddress = identityRegistryAddress; } /** * Create a feedbackAuth structure to be signed * Spec: tuple (agentId, clientAddress, indexLimit, expiry, chainId, identityRegistry, signerAddress) * * @param agentId - The agent ID * @param clientAddress - Address authorized to give feedback * @param indexLimit - Must be > last feedback index from this client (typically lastIndex + 1) * @param expiry - Unix timestamp when authorization expires * @param chainId - Chain ID where feedback will be submitted * @param signerAddress - Address of the signer (agent owner/operator) */ createFeedbackAuth(agentId, clientAddress, indexLimit, expiry, chainId, signerAddress) { return { agentId, clientAddress, indexLimit, expiry, chainId, identityRegistry: this.identityRegistryAddress, signerAddress, }; } /** * Sign a feedbackAuth using EIP-191 * The agent owner/operator signs to authorize a client to give feedback * * @param auth - The feedbackAuth structure * @returns Signed authorization as bytes (encoded tuple + signature) */ async signFeedbackAuth(auth) { // Encode the feedbackAuth tuple // Spec: (agentId, clientAddress, indexLimit, expiry, chainId, identityRegistry, signerAddress) const encoded = ethers.AbiCoder.defaultAbiCoder().encode(['uint256', 'address', 'uint256', 'uint256', 'uint256', 'address', 'address'], [ auth.agentId, auth.clientAddress, auth.indexLimit, auth.expiry, auth.chainId, auth.identityRegistry, auth.signerAddress, ]); // Hash the encoded data const messageHash = ethers.keccak256(encoded); // Sign using EIP-191 (personal_sign) // This prefixes the message with "\x19Ethereum Signed Message:\n32" const signature = await this.adapter.signMessage(ethers.getBytes(messageHash)); // Return encoded tuple + signature concatenated // Contract will decode the tuple and verify the signature return ethers.concat([encoded, signature]); } /** * Submit feedback for an agent * Spec: function giveFeedback(uint256 agentId, uint8 score, bytes32 tag1, bytes32 tag2, string calldata feedbackUri, bytes32 calldata feedbackHash, bytes memory feedbackAuth) * * @param params - Feedback parameters (score is MUST, others are OPTIONAL) * @returns Transaction result */ async giveFeedback(params) { // Validate score is 0-100 (MUST per spec) if (params.score < 0 || params.score > 100) { throw new Error('Score MUST be between 0 and 100'); } // Convert optional string parameters to bytes32 (or empty bytes32 if not provided) const tag1 = params.tag1 ? ethers.id(params.tag1).slice(0, 66) : ethers.ZeroHash; const tag2 = params.tag2 ? ethers.id(params.tag2).slice(0, 66) : ethers.ZeroHash; const feedbackHash = params.feedbackHash || ethers.ZeroHash; const feedbackUri = params.feedbackUri || ''; const result = await this.adapter.send(this.contractAddress, ReputationRegistryABI, 'giveFeedback', [ params.agentId, params.score, tag1, tag2, feedbackUri, feedbackHash, params.feedbackAuth, ]); return { txHash: result.hash || result.txHash }; } /** * Revoke previously submitted feedback * Spec: function revokeFeedback(uint256 agentId, uint64 feedbackIndex) * * @param agentId - The agent ID * @param feedbackIndex - Index of feedback to revoke */ async revokeFeedback(agentId, feedbackIndex) { const result = await this.adapter.send(this.contractAddress, ReputationRegistryABI, 'revokeFeedback', [agentId, feedbackIndex]); return { txHash: result.hash || result.txHash }; } /** * Append a response to existing feedback * Spec: function appendResponse(uint256 agentId, address clientAddress, uint64 feedbackIndex, string calldata responseUri, bytes32 calldata responseHash) * * @param agentId - The agent ID * @param clientAddress - Client who gave the feedback * @param feedbackIndex - Index of the feedback * @param responseUri - URI to response content * @param responseHash - OPTIONAL hash of response content (KECCAK-256) */ async appendResponse(agentId, clientAddress, feedbackIndex, responseUri, responseHash) { const hash = responseHash || ethers.ZeroHash; const result = await this.adapter.send(this.contractAddress, ReputationRegistryABI, 'appendResponse', [agentId, clientAddress, feedbackIndex, responseUri, hash]); return { txHash: result.hash || result.txHash }; } /** * Get the identity registry address * Spec: function getIdentityRegistry() external view returns (address identityRegistry) */ async getIdentityRegistry() { return await this.adapter.call(this.contractAddress, ReputationRegistryABI, 'getIdentityRegistry', []); } /** * Get reputation summary for an agent * Spec: function getSummary(uint256 agentId, address[] calldata clientAddresses, bytes32 tag1, bytes32 tag2) returns (uint64 count, uint8 averageScore) * Note: agentId is ONLY mandatory parameter, others are OPTIONAL filters * * @param agentId - The agent ID (MANDATORY) * @param clientAddresses - OPTIONAL filter by specific clients * @param tag1 - OPTIONAL filter by tag1 * @param tag2 - OPTIONAL filter by tag2 */ async getSummary(agentId, clientAddresses, tag1, tag2) { const clients = clientAddresses || []; const t1 = tag1 ? ethers.id(tag1).slice(0, 66) : ethers.ZeroHash; const t2 = tag2 ? ethers.id(tag2).slice(0, 66) : ethers.ZeroHash; const result = await this.adapter.call(this.contractAddress, ReputationRegistryABI, 'getSummary', [agentId, clients, t1, t2]); return { count: BigInt(result.count || result[0]), averageScore: Number(result.averageScore || result[1]), }; } /** * Read a specific feedback entry * Spec: function readFeedback(uint256 agentId, address clientAddress, uint64 index) returns (uint8 score, bytes32 tag1, bytes32 tag2, bool isRevoked) * * @param agentId - The agent ID * @param clientAddress - Client who gave feedback * @param index - Feedback index */ async readFeedback(agentId, clientAddress, index) { const result = await this.adapter.call(this.contractAddress, ReputationRegistryABI, 'readFeedback', [agentId, clientAddress, index]); return { score: Number(result.score || result[0]), tag1: result.tag1 || result[1], tag2: result.tag2 || result[2], isRevoked: Boolean(result.isRevoked || result[3]), }; } /** * Read all feedback for an agent with optional filters * Spec: function readAllFeedback(uint256 agentId, address[] calldata clientAddresses, bytes32 tag1, bytes32 tag2, bool includeRevoked) returns arrays * Note: agentId is ONLY mandatory parameter * * @param agentId - The agent ID (MANDATORY) * @param clientAddresses - OPTIONAL filter by clients * @param tag1 - OPTIONAL filter by tag1 * @param tag2 - OPTIONAL filter by tag2 * @param includeRevoked - OPTIONAL include revoked feedback */ async readAllFeedback(agentId, clientAddresses, tag1, tag2, includeRevoked) { const clients = clientAddresses || []; const t1 = tag1 ? ethers.id(tag1).slice(0, 66) : ethers.ZeroHash; const t2 = tag2 ? ethers.id(tag2).slice(0, 66) : ethers.ZeroHash; const includeRev = includeRevoked || false; const result = await this.adapter.call(this.contractAddress, ReputationRegistryABI, 'readAllFeedback', [agentId, clients, t1, t2, includeRev]); return { clientAddresses: result.clientAddresses || result[0], scores: (result.scores || result[1]).map(Number), tag1s: result.tag1s || result[2], tag2s: result.tag2s || result[3], revokedStatuses: (result.revokedStatuses || result[4]).map(Boolean), }; } /** * Get response count for a feedback entry * Spec: function getResponseCount(uint256 agentId, address clientAddress, uint64 feedbackIndex, address[] responders) returns (uint64) * Note: agentId is ONLY mandatory parameter */ async getResponseCount(agentId, clientAddress, feedbackIndex, responders) { const client = clientAddress || ethers.ZeroAddress; const index = feedbackIndex || BigInt(0); const resp = responders || []; const result = await this.adapter.call(this.contractAddress, ReputationRegistryABI, 'getResponseCount', [agentId, client, index, resp]); return BigInt(result); } /** * Get all clients who have given feedback to an agent * Spec: function getClients(uint256 agentId) returns (address[] memory) */ async getClients(agentId) { return await this.adapter.call(this.contractAddress, ReputationRegistryABI, 'getClients', [agentId]); } /** * Get the last feedback index from a client for an agent * Spec: function getLastIndex(uint256 agentId, address clientAddress) returns (uint64) * * @param agentId - The agent ID * @param clientAddress - Client address * @returns Last feedback index (0 if no feedback yet) */ async getLastIndex(agentId, clientAddress) { const result = await this.adapter.call(this.contractAddress, ReputationRegistryABI, 'getLastIndex', [agentId, clientAddress]); return BigInt(result); } } //# sourceMappingURL=ReputationClient.js.map