feat(identity): ERC-8004 identity + reputation MCP tools#11
feat(identity): ERC-8004 identity + reputation MCP tools#11
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds IdentityConfig with testnet/mainnet chain IDs and RPC URLs, plus IdentityRegistry and ReputationRegistry ABI definitions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds IdentityRegistrationFailed, IdentityNotFound, IdentityTxFailed, and DeregisterNotConfirmed error classes following existing patterns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Creates public and wallet client builders using viem, with Injective EVM chain definitions for both testnet (1439) and mainnet (2525). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n ID note - Rename getReputation output from 'updatedAt' to 'feedbackCount' per PRD - Add comment explaining chain ID 2525 vs 1776 discrepancy for verification Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add module doc comment header - Extract walletClient.account to avoid repeated non-null assertions - Filter Transfer event logs by contract address for safer agentId parsing - Add early guard for no-op updates (no fields = throw before key decrypt) - Add chain property to test mock for realistic call shape - Add test for no-op update rejection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ist() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers the full agent lifecycle on real Injective EVM testnet: register, status, update, list, deregister. Run via npm run test:integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused IdentityRegistrationFailed error class (dead code) - Remove unused bech32 dependency (sdk-ts provides address conversion) - Remove unnecessary 'as const' from server.ts identity tool handlers - Document owner filter limitation in list() (uses mint events, not transfers) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fetches - Extract withIdentityTx() helper to eliminate repeated unlock/client/catch boilerplate - Cache PublicClient and Chain per network to avoid redundant creation - Parallelize agent metadata fetches in list() with Promise.allSettled - Over-fetch candidates when type filter is active to improve result count - Use viem's zeroAddress constant instead of magic string - Use evm.injAddressToEth() instead of direct SDK import - Wrap list() errors in IdentityTxFailed instead of bare Error - Fix try-block indentation in list() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lpers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ble ABI - register() overloads: bare, with URI, with URI+metadata tuple[] - setMetadata(id, key, bytes) replaces updateMetadata tuple - setAgentURI replaces setTokenURI - setAgentWallet requires EIP-712 signature (deadline + proof) - getMetadata(id, key) per-key instead of tuple return - getAgentWallet replaces getLinkedWallet - Registered event for agentId extraction - deregister, ownerOf, tokenURI, Transfer unchanged Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pgradeable ABI Replace assumed registerAgent/updateMetadata/setTokenURI/setLinkedWallet calls with the real contract functions: register(uri, metadata[]), setMetadata(id, key, bytes), setAgentURI, and setAgentWallet with EIP-712 signature. Add identityCfg to TxContext for chainId access. Update all tests to cover the new metadata tuple format, per-key updates, self-link wallet signing, and wallet-differs-from-signer skip. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gradeable ABI - status(): use per-key getMetadata(id, key) returning ABI-encoded bytes instead of old getMetadata(id) returning a tuple - status(): use getAgentWallet() instead of getLinkedWallet() - status(): decode name, builderCode, agentType via decodeStringMetadata() - list(): fetch per-key getMetadata for name and agentType per agent - Change agentType from number to string in StatusResult, ListEntry, ListParams - Add new test for empty metadata decoding - All 15 tests passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- agent_register: type → string, builderCode → plain string, wallet optional with self-link note - agent_update: same type/builderCode changes - agent_list: type filter → string - Updated schema tests to match Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align the integration test with the new IdentityRegistryUpgradeable contract interface: type/builderCode are now plain strings stored as metadata keys, wallet link is optional and returns walletTxHash, and status assertions check decoded metadata strings. Also updates the identity config with real testnet contract addresses and RPC URL. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deleted probe scripts (probe-contract, extract-selectors, find-abi, check-balance, try-register) that were used during ABI discovery. Updated register-test-agent.ts to use the new handler interface where type and builderCode are plain strings and wallet is optional. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tants, parallelize update receipts - Extract linkWalletIfSelf() to deduplicate EIP-712 wallet flow from register+update - Add METADATA_KEYS constants to avoid raw string metadata keys across 4 files - Separate send/wait phases in update() — serial sends then parallel receipt waits - Remove redundant identityRegistry from TxContext (use identityCfg.identityRegistry) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rom E2E test - Fix Registered event parsing: match exactly 3 topics to avoid confusing with Transfer (4 topics) - Reduce walletLinkDeadline default from 600s to 120s (contract rejects "deadline too far") - Handle missing reputation gracefully (return zeros for new agents) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…register/update handlers Add ipfsGateway to IdentityConfig and getPinataJwt() helper. Register handler now auto-generates an agent card and uploads to Pinata when no URI is provided. Update handler fetches existing card, merges changes, and re-uploads when card-level fields (description, image, services) change. Both handlers fail fast with a clear error when PINATA_JWT is not configured. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tools - agent_register: description, image (URL), services array, auto-card generation note - agent_update: description, image, services, removeServices, card re-upload note - Both tools mention PINATA_JWT requirement for card generation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Let StorageError propagate through withIdentityTx (not swallowed as IdentityTxFailed) - Extract requirePinataJwt() to deduplicate JWT guard in register/update - Remove redundant validateImageUrl calls (card.ts handles validation internally) - Add 15s timeout to fetchAgentCard fetch calls - Extract resolveIpfsUri helper with trailing-slash safety - Handle fetchAgentCard errors explicitly in update (catch + fallback to fresh card) - Extract shared serviceEntrySchema in server.ts (was duplicated inline) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ryUpgradeable Replace the stub getReputation ABI with the full ReputationRegistryUpgradeable contract interface (getSummary, giveFeedback, readAllFeedback, readFeedback, getClients, revokeFeedback, getVersion, NewFeedback event). Update agent_status to call getSummary with proper decimal conversion and rename feedbackCount to count in the StatusResult interface. Update tests accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ent_feedback_list, agent_give_feedback, agent_revoke_feedback) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rement - Fix NewFeedback event parsing: extract feedbackIndex from first data word (event has 4 topics, not 3) - Fix getSummary: requires client addresses, not empty array. Fetch getClients first, then call getSummary - Fix agent_status reputation: same getClients-first pattern - Update test mocks for new call ordering - Remove debug scripts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change AGENT_CARD_TYPE from 'https://erc8004.org/agent-card' to 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1' to match the agent-sdk and 8004scan.io expectations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify log.topics[0] matches the NewFeedback event signature hash before reading feedbackIndex from log.data, preventing mismatches if the contract emits multiple events in the same transaction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Point to the local SDK package so that identity-related tools can import AgentClient, AgentReadClient, and PinataStorage directly from @injective/agent-sdk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the identity module's 9 implementation files (~750 lines) with 2 thin adapter files (~437 lines) that delegate to the SDK's AgentClient and AgentReadClient. The adapter handles only MCP-specific concerns: keystore unlock and response formatting. Deleted: abis.ts, card.ts, client.ts, config.ts, helpers.ts, storage.ts, types.ts and their 5 test files. Rewrote identity.test.ts and read.test.ts to mock the SDK layer. server.ts is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Collapse redundant catch blocks using wrapSdkError() helper - Short-circuit cardUri fetch when uri param is supplied directly (no RPC) - Fix list() total to reflect pre-slice count (not post-slice) - Use 10 ** e.decimals, nullish-coalesce tags, catch (_err) with comment - Update tests to match corrected semantics (total=3, cardUri=uri) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Register agent with full metadata (image, description, services) and fetch status with reputation. Shows the refactored adapter working end-to-end with register → status flow.
Full lifecycle tests against Injective testnet contracts and IPFS: - Register agent with full metadata (image, description, services) - Fetch status with reputation from on-chain registry - List agents by owner - Give feedback with tags - Fetch reputation score/count after feedback - List feedback entries with filtering Tests are skipped unless TEST_ADDRESS, TEST_PASSWORD, PINATA_JWT environment variables are set. Includes comprehensive documentation (INTEGRATION.md) with: - Setup instructions (testnet wallet, INJ faucet, Pinata) - How to run tests - Expected output - Troubleshooting guide - Cost estimate (~0.06 INJ per run) Tests hit real blockchain and IPFS — no mocks.
Simplified integration test that exercises the full identity lifecycle: - Register agent with metadata and services - Fetch status - Give feedback - Fetch reputation - List feedback entries - Revoke feedback - Fetch final reputation Runs against real testnet contracts and IPFS. Note: Currently blocked by SDK bug in wallet linking (deadline too far). Workaround: SDK fix needed in setAgentWallet deadline calculation.
Wallet linking in AgentClient.register() fails with 'deadline too far' error during testnet E2E testing. Bug is in SDK's setAgentWallet implementation, not in adapter. Full E2E test suite reaches wallet linking step but fails at blockchain simulation. Other operations (status, feedback, reputation) work fine. Requires SDK PR to fix deadline calculation.
…olved - e2e-quick-test.ts: remove self-feedback step (contract rejects it; use full-flow-test.ts with ephemeral reviewer wallet instead) - KNOWN_ISSUES.md: mark wallet linking deadline as fixed in SDK SDK fix: walletLinkDeadline default 600s → 240s (contract max is 300s). Verified on testnet: agents 16 and 17 registered successfully. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…elize reads integration.test.ts: - Derive evmAddress (0x) from TEST_ADDRESS (inj1) in beforeAll via evm.injAddressToEth; rep.clients and entry.client are 0x addresses — previous inj1 assertions would always fail - Single ts = Date.now() per test for consistent run-ID across name/builderCode/image e2e-quick-test.ts: - Replace inline PRIVATE_KEY check + normalization with getTestPrivateKey() - Single ts = Date.now() at top of main() reused across name/builderCode/image/label - Fetch status, reputation, feedbackList concurrently via Promise.all Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x402-agent script - RegisterParams and UpdateParams now accept x402?: boolean - Wire x402 through to AgentClient.register() and AgentClient.update() - hasCardUpdate in update() includes x402 change (triggers card re-upload) - scripts/create-x402-agent.ts: standalone script that registers a trading agent with x402=true, mcp+rest services, and two reputation feedbacks from a funded ephemeral reviewer wallet Verified on testnet: Agent ID 18 - Card URI: ipfs://bafkreiewnbit63kl2zfan3tszro5bapw67bs2bxmsmbltygzno4xore43i - Reputation: 87.5 score / 2 reviews (reliability=90, accuracy=85) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scripts/ and docs/ were added during exploratory development and E2E testnet debugging. They are not part of the published package and contain stale state (KNOWN_ISSUES.md documents a bug fixed in the SDK). Both folders are gitignored going forward. Vitest build cache artifacts (*.timestamp-*.mjs) are also gitignored to prevent them sneaking in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntion Update serviceEntrySchema to use name (uppercase enum) and endpoint fields matching the SDK's ServiceEntry interface. Add optional version field. Replace inline duplicate schema in agent_update with shared reference. Type removeServices as ServiceName[] throughout to prevent silent no-ops from lowercase values. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pdate Adds ActionSchema Zod validation and passthrough to both MCP tools so LLM-registered agents can include callable action schemas on their card. Also fixes a gap in the local SDK where UpdateOptions was missing the actions field, preventing the MCP server from compiling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e/documentation + active flag Extends RegisterParams and UpdateParams (and their Zod schemas) with the ERC-8004 enrichment fields recently added to @injective/agent-sdk so MCP clients can set them via agent_register and agent_update. agent_update also gains an active flag for toggling visibility in 8004scan. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Written by corepack; pins pnpm@10.33.0 for reproducible installs in CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces a comprehensive ERC-8004 agent identity system featuring on-chain agent registration, status retrieval, listing, deregistration, and feedback management via a new Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (1)
src/identity/index.ts (1)
271-291:⚠️ Potential issue | 🟡 MinorRoot cause for the
valueinteger issue flagged inserver.ts.
BigInt(params.value)throwsRangeErrorfor any non-integer number. The schema fix inagent_give_feedback(z.number().int()) is the right place to enforce this; see the comment there. Noting here so it's clear why: this line is the source that makes the schema constraint necessary. Consider also defensively coercing withMath.trunc(params.value)if you want to tolerate rounding, but schema-level rejection is cleaner.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/identity/index.ts` around lines 271 - 291, The BigInt(params.value) call in giveFeedback is throwing RangeError for non-integer numbers; update the giveFeedback implementation to either assume schema-level validation or defensively coerce to an integer before BigInt: replace BigInt(params.value) with BigInt(Math.trunc(params.value)) (or otherwise assert Number.isInteger(params.value) and throw a clear error) in the giveFeedback method that calls createClient so non-integer inputs no longer cause RangeError at runtime.
🧹 Nitpick comments (5)
src/identity/integration.test.ts (1)
38-38: Nit: confirmdescribe.skipIfsemantics with a string.
SKIP_REASONisstring | null.describe.skipIfskips on truthy values, so this works in practice — but passing the reason string as the condition obscures intent. Consider passing a boolean and logging the reason separately, e.g.:const shouldSkip = !TEST_ADDRESS || !TEST_PASSWORD || !PINATA_JWT if (shouldSkip) console.log(`Skipping identity integration tests: ${SKIP_REASON}`) describe.skipIf(shouldSkip)('Identity Integration Tests (Testnet)', ...)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/identity/integration.test.ts` at line 38, The test currently passes SKIP_REASON (a string|null) directly to describe.skipIf which relies on truthiness and obscures intent; compute an explicit boolean (e.g., shouldSkip) from the required checks (TEST_ADDRESS, TEST_PASSWORD, PINATA_JWT) and pass that to describe.skipIf, and separately log SKIP_REASON when shouldSkip is true to preserve the human-readable reason; update the test declaration using shouldSkip instead of SKIP_REASON and add a console.log or test logger for SKIP_REASON when skipping.src/identity/demo.test.ts (1)
74-145: Heavyconsole.logoutput in a standard unit test will clutter everynpm testrun.This file is picked up by the default
vitest run(not gated behind an env var the wayintegration.test.tsis), so the emoji-rich log lines will print on every developer/CI run. Consider either:
- Gating the logs behind
if (process.env.DEBUG)/process.env.VITEST_VERBOSE, or- Moving this into the integration suite if it's intended as a walkthrough rather than a regression test, or
- Dropping the logs entirely and relying on the assertions (they fully cover the flow).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/identity/demo.test.ts` around lines 74 - 145, The test emits many emoji-rich console.log calls (e.g., lines around identity.register and identityRead.status printing registerResult and statusResult) which will clutter every vitest run; either remove those console.log statements, or wrap them with a runtime guard such as if (process.env.VITEST_VERBOSE || process.env.DEBUG) before the blocks that log the registration and status outputs, or move this entire test into the integration suite; update references to the mocked call assertions (mockRegister) to remain unchanged and ensure the test still asserts registerResult and statusResult values after removing/gating logs.README.md (1)
156-166: Optional: architecture tree omits the newidentity/module.Consider adding
identity/ ERC-8004 agent identity + reputation (wraps@injective/agent-sdk)to the tree so the diagram stays in sync with the added surface.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 156 - 166, Update the architecture tree in the README to include the new identity module: add a line like "├── identity/ ERC-8004 agent identity + reputation (wraps `@injective/agent-sdk`)" in the same list where other modules (config, keystore, wallets, markets, accounts, trading, evm/eip712, orders, transfers, bridges, evm) are declared so the diagram and module surface reflect the added identity/ module.src/identity/read.ts (1)
92-113: Optional: 3× over-fetch for type filtering is fragile when the requested type is rare.If only ~5% of agents on the registry match
type='analytics'and the caller asks forlimit: 20,fetchLimit = 60will likely yield fewer than 20 matches and the caller has no way to know whether they got a truncated page.totalhere is post-filter count from the over-fetched slice, not the true total matching the filter on-chain.Given the SDK doesn't index by type, this is a pragmatic approach, but consider either:
- Documenting the caveat in the tool description (results are best-effort when
typeis set), or- Paginating until
limitis reached or the SDK returns fewer thanfetchLimit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/identity/read.ts` around lines 92 - 113, The current 3x over-fetch using fetchLimit when params.type is set can underdeliver when the type is rare; change the logic in the read handler that calls sdk.getAgentsByOwner / sdk.listAgents to page and accumulate results until either you have at least limit matching agents (filtering by params.type) or the SDK returns fewer items than the page size (no more data), then slice to limit and set filteredTotal to the total matching count you accumulated (not just the sliced length); ensure you preserve the existing agents mapping (agentId, name, agentType, owner) and stop paginating when no more pages are returned to avoid infinite loops.src/mcp/server.ts (1)
985-985: Minor:ownerschema accepts arbitrary strings.
identityRead.listonly handles two shapes: a string starting withinj(converted viaevm.injAddressToEth) or anything else passed through as a0xaddress. Any other input (e.g., a typo, an ENS name, empty string) is forwarded tosdk.getAgentsByOwneras a malformed hex address and surfaces as a cryptic SDK error.Recommend validating at the schema layer to match the tool description (
inj1... or 0x...):🛡️ Proposed fix
- owner: z.string().optional().describe('Filter by owner — accepts inj1... or 0x... address.'), + owner: z.string() + .regex(/^(inj1[a-z0-9]{38}|0x[a-fA-F0-9]{40})$/, 'Must be inj1... or 0x... address') + .optional() + .describe('Filter by owner — accepts inj1... or 0x... address.'),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/mcp/server.ts` at line 985, The owner schema currently allows arbitrary strings; restrict it to only the two accepted shapes so identityRead.list won't forward invalid values to sdk.getAgentsByOwner. Replace z.string().optional() for the owner field with a union/refinement that accepts either an inj-prefixed address (string starting with "inj") or a 0x hex Ethereum address (e.g., regex /^0x[0-9a-fA-F]{40}$/), and keep it optional; mention the same symbols in the code (the owner schema field and the identityRead.list flow) so callers are validated at the schema layer before evm.injAddressToEth or sdk.getAgentsByOwner are invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@package.json`:
- Line 22: The package.json currently uses an unresolved local file dependency
"@injective/agent-sdk": "file:../injective-agent-cli/packages/sdk" which breaks
CI and npm installs; replace that entry with a machine-independent reference —
e.g. if you move both repos into a monorepo, change to "workspace:*" and add a
pnpm-workspace.yaml; or publish `@injective/agent-sdk` and pin a published version
(e.g. "0.x.y"); or point to the git URL with path
(git+https://github.com/<org>/injective-agent-cli.git#<tag>&path=/packages/sdk)
— update the "@injective/agent-sdk" value accordingly and ensure CI workflows
reflect the chosen approach.
In `@README.md`:
- Around line 59-67: Add the three missing identity tools to the Identity
(ERC-8004) table and add a blank line after the heading to satisfy MD058: insert
rows for `agent_reputation` (read-only: returns aggregated reputation — score,
count, clients), `agent_feedback_list` (read-only: lists individual feedback
entries with value, tags, revocation status; no gas), and `agent_give_feedback`
(on-chain feedback submission; costs gas), and ensure there is an empty line
between the "### Identity (ERC-8004)" heading and the table header so
markdownlint MD058 is resolved.
In `@src/identity/demo.test.ts`:
- Around line 1-8: Update the top-of-file doc comment to reference the correct
test filename: change the run command "npm test -- src/identity/demo.ts" to "npm
test -- src/identity/demo.test.ts" (edit the comment block at the top of
demo.test.ts). Ensure the example command matches the actual test filename so
the run pattern works as expected.
In `@src/identity/INTEGRATION.md`:
- Around line 91-97: Update the "Increase Timeout" section so the shown npm test
command actually increases Vitest's per-test timeout: replace or augment the
suggested flags with Vitest's timeout flag (for example use
--testTimeout=300000) or explicitly instruct readers to edit the timeout in the
test file src/identity/integration.test.ts; mention the exact test file name and
the --testTimeout=300000 flag so consumers know where to change it or how to run
tests with a longer timeout.
In `@src/identity/integration.test.ts`:
- Around line 38-89: The tests leak state and rely on cross-test shared variable
agentId set inside the "it('registers agent on testnet with full metadata')"
test; move registration into a deterministic setup and add cleanup: register the
agent in beforeAll (call identity.register with TEST_ADDRESS/TEST_PASSWORD to
produce agentId and keep the existing assertions as separate it()s if you want
reporting) or collapse into a single e2e lifecycle it(); then add an afterAll
that calls identity.deregister(agentId, { confirm: true }) (or the equivalent
deregister function) as a best-effort cleanup to remove the on-chain agent;
update imports to include afterAll from vitest if needed and reference the
agentId variable produced in setup for all subsequent tests.
In `@src/identity/read.ts`:
- Around line 115-159: Both reputation and feedbackList currently swallow all
errors; update the catch blocks in the reputation and feedbackList methods to
log the error (include agentId and error.message) and only return the zero/empty
result for the SDK's "no entries"/"not found" condition; for all other
exceptions rethrow. Locate the reputation method (calls getReputation) and
feedbackList method (calls getFeedbackEntries) and replace the broad catch(_err)
with logic that (1) captures the thrown error, (2) checks the
SDK/registry-specific "not found" or "no entries" error sentinel/class/string
and if matched returns the current zeroed result, otherwise call
processLogger.error or similar with a structured message including agentId and
the error, then rethrow the error.
In `@src/integration/identity.integration.test.ts`:
- Around line 95-103: The test is flaky because identityRead.list(config, {
limit: 50 }) can miss the newly created agent; update the call to scope results
to the test wallet by passing the owner filter (e.g. identityRead.list(config, {
limit: 50, owner: <wallet address> })), using the existing config wallet/address
field so the search reliably returns agents for this wallet; then assert found
by agentId as before (references: identityRead.list, agentId, config).
In `@src/mcp/server.ts`:
- Around line 1038-1039: The schema currently allows fractional numbers for the
rating `value` which then causes `identity.giveFeedback` to throw when it calls
BigInt(params.value); change the schema entry named `value` in the relevant Zod
object (the lines defining `value: z.number()...`) to enforce integers (e.g.,
use `.int()` like `valueDecimals` does) so non-integer inputs are rejected early
and produce a validation error before `giveFeedback` runs.
---
Duplicate comments:
In `@src/identity/index.ts`:
- Around line 271-291: The BigInt(params.value) call in giveFeedback is throwing
RangeError for non-integer numbers; update the giveFeedback implementation to
either assume schema-level validation or defensively coerce to an integer before
BigInt: replace BigInt(params.value) with BigInt(Math.trunc(params.value)) (or
otherwise assert Number.isInteger(params.value) and throw a clear error) in the
giveFeedback method that calls createClient so non-integer inputs no longer
cause RangeError at runtime.
---
Nitpick comments:
In `@README.md`:
- Around line 156-166: Update the architecture tree in the README to include the
new identity module: add a line like "├── identity/ ERC-8004 agent identity +
reputation (wraps `@injective/agent-sdk`)" in the same list where other modules
(config, keystore, wallets, markets, accounts, trading, evm/eip712, orders,
transfers, bridges, evm) are declared so the diagram and module surface reflect
the added identity/ module.
In `@src/identity/demo.test.ts`:
- Around line 74-145: The test emits many emoji-rich console.log calls (e.g.,
lines around identity.register and identityRead.status printing registerResult
and statusResult) which will clutter every vitest run; either remove those
console.log statements, or wrap them with a runtime guard such as if
(process.env.VITEST_VERBOSE || process.env.DEBUG) before the blocks that log the
registration and status outputs, or move this entire test into the integration
suite; update references to the mocked call assertions (mockRegister) to remain
unchanged and ensure the test still asserts registerResult and statusResult
values after removing/gating logs.
In `@src/identity/integration.test.ts`:
- Line 38: The test currently passes SKIP_REASON (a string|null) directly to
describe.skipIf which relies on truthiness and obscures intent; compute an
explicit boolean (e.g., shouldSkip) from the required checks (TEST_ADDRESS,
TEST_PASSWORD, PINATA_JWT) and pass that to describe.skipIf, and separately log
SKIP_REASON when shouldSkip is true to preserve the human-readable reason;
update the test declaration using shouldSkip instead of SKIP_REASON and add a
console.log or test logger for SKIP_REASON when skipping.
In `@src/identity/read.ts`:
- Around line 92-113: The current 3x over-fetch using fetchLimit when
params.type is set can underdeliver when the type is rare; change the logic in
the read handler that calls sdk.getAgentsByOwner / sdk.listAgents to page and
accumulate results until either you have at least limit matching agents
(filtering by params.type) or the SDK returns fewer items than the page size (no
more data), then slice to limit and set filteredTotal to the total matching
count you accumulated (not just the sliced length); ensure you preserve the
existing agents mapping (agentId, name, agentType, owner) and stop paginating
when no more pages are returned to avoid infinite loops.
In `@src/mcp/server.ts`:
- Line 985: The owner schema currently allows arbitrary strings; restrict it to
only the two accepted shapes so identityRead.list won't forward invalid values
to sdk.getAgentsByOwner. Replace z.string().optional() for the owner field with
a union/refinement that accepts either an inj-prefixed address (string starting
with "inj") or a 0x hex Ethereum address (e.g., regex /^0x[0-9a-fA-F]{40}$/),
and keep it optional; mention the same symbols in the code (the owner schema
field and the identityRead.list flow) so callers are validated at the schema
layer before evm.injAddressToEth or sdk.getAgentsByOwner are invoked.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 37fc7396-fe94-46bb-8aa5-9cbaa09b468a
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (15)
.gitignoreREADME.mdpackage.jsonsrc/errors/errors.test.tssrc/errors/index.tssrc/identity/INTEGRATION.mdsrc/identity/demo.test.tssrc/identity/identity.test.tssrc/identity/index.tssrc/identity/integration.test.tssrc/identity/read.test.tssrc/identity/read.tssrc/integration/identity.integration.test.tssrc/mcp/server.test.tssrc/mcp/server.ts
| "typecheck": "tsc --noEmit" | ||
| }, | ||
| "dependencies": { | ||
| "@injective/agent-sdk": "file:../injective-agent-cli/packages/sdk", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
pnpm file: protocol dependency pointing to a sibling directory outside the repo — behavior on npm publish and in CI
💡 Result:
Using a pnpm file: protocol dependency pointing to a sibling directory outside the repository (e.g., "file:../sibling-pkg") works locally for development if the sibling directory exists relative to the project root, as pnpm resolves relative file: paths from the project root/workspace root (with fixes in later versions for overrides). Changes in the sibling propagate via hard links. On npm publish: The file: specifier remains unchanged in the published package.json. Since pnpm publish packs only files specified in the "files" field or package dir (excluding node_modules), the sibling directory is not included in the tarball. The published package has an unresolvable "file:../sibling-pkg" dependency, causing install failures for consumers (ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND or similar). In CI: Fails with ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND because the sibling directory outside the repo is not checked out/cloned in CI. The relative path resolves to a non-existent location. Recommendations: Use for local dev only. For publishable packages, use version ranges or workspace: (if in monorepo). In CI, use overrides with vendored tarballs (pnpm pack the sibling and reference "file:./vendor/sibling.tgz" with vendor dir in repo). Ensure lockfile compatibility across OS (resolved in recent pnpm versions).
Citations:
- 1: https://pnpm.io/cli/link
- 2: https://pnpm.io/package-sources
- 3: https://stackoverflow.com/questions/72032028/can-pnpm-replace-npm-link-yarn-link
- 4:
readPackage/pnpm.overridesand relative paths infile:protocol dependencies pnpm/pnpm#5493 - 5: Fix (or document changes in) "file:" protocol pnpm/pnpm#8053
- 6: ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY with relative local dependency (difference between windows and linux CI?) pnpm/pnpm#7955
- 7: https://pnpm.io/cli/publish
Local file: dependency outside the repo breaks installs in CI and on npm publish.
"@injective/agent-sdk": "file:../injective-agent-cli/packages/sdk" points to a sibling directory that exists only on your machine. When published to npm or installed in CI, the sibling directory doesn't exist at that path, causing ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND failures. Relative file: paths are resolved from the project root; outside-repo paths become unresolvable across machines.
Choose one:
- pnpm workspace (preferred if repos are developed together): Merge into a monorepo with
pnpm-workspace.yamland reference as"workspace:*". - Published package: Publish
@injective/agent-sdkto npm or a private registry and pin a version. - Git dependency: Use
"git+https://github.com/<org>/injective-agent-cli.git#<tag>&path=/packages/sdk"(pnpm supports git with path) or a committed tarball for reproducibility.
Until resolved, pnpm install on a fresh clone and CI workflows will fail.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` at line 22, The package.json currently uses an unresolved local
file dependency "@injective/agent-sdk":
"file:../injective-agent-cli/packages/sdk" which breaks CI and npm installs;
replace that entry with a machine-independent reference — e.g. if you move both
repos into a monorepo, change to "workspace:*" and add a pnpm-workspace.yaml; or
publish `@injective/agent-sdk` and pin a published version (e.g. "0.x.y"); or
point to the git URL with path
(git+https://github.com/<org>/injective-agent-cli.git#<tag>&path=/packages/sdk)
— update the "@injective/agent-sdk" value accordingly and ensure CI workflows
reflect the chosen approach.
| ### Identity (ERC-8004) | ||
| | Tool | Description | | ||
| |---|---| | ||
| | `agent_register` | Register a new AI agent identity on the ERC-8004 registry. Costs gas. | | ||
| | `agent_update` | Update agent metadata, token URI, or linked wallet. Costs gas. | | ||
| | `agent_deregister` | Permanently burn an agent's identity NFT (irreversible). Costs gas. | | ||
| | `agent_status` | Get full agent details: metadata, wallet, owner, reputation. Read-only. | | ||
| | `agent_list` | Find registered agents with optional owner/type filters. Read-only. | | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Extract the exact identity tool names registered in server.ts
rg -nP '\bserver\.tool\s*\(\s*["'\'']agent_[a-z_]+' --type=ts -g 'src/mcp/server.ts'Repository: InjectiveLabs/mcp-server
Length of output: 50
🏁 Script executed:
#!/bin/bash
# Find server.ts file
fd -t f -name 'server.ts' | head -20Repository: InjectiveLabs/mcp-server
Length of output: 298
🏁 Script executed:
#!/bin/bash
# Also check the repository structure
git ls-files | grep -E '(server|mcp)' | head -20Repository: InjectiveLabs/mcp-server
Length of output: 117
🏁 Script executed:
#!/bin/bash
# Check README content at the specified lines
sed -n '59,67p' README.mdRepository: InjectiveLabs/mcp-server
Length of output: 575
🏁 Script executed:
#!/bin/bash
# Read the server.ts file to see tool registrations
cat -n src/mcp/server.ts | head -100Repository: InjectiveLabs/mcp-server
Length of output: 5378
🏁 Script executed:
#!/bin/bash
# Search for all agent tool registrations in server.ts
rg 'server\.tool|agent_' src/mcp/server.ts -A 3 | head -80Repository: InjectiveLabs/mcp-server
Length of output: 2615
🏁 Script executed:
#!/bin/bash
# Specifically search for agent_feedback and agent_reputation
rg 'agent_feedback|agent_reputation' src/mcp/server.tsRepository: InjectiveLabs/mcp-server
Length of output: 257
🏁 Script executed:
#!/bin/bash
# Get all agent_* tools in server.ts with full context
rg "server\.tool\(\s*['\"]agent_" src/mcp/server.ts -A 2 | head -120Repository: InjectiveLabs/mcp-server
Length of output: 50
🏁 Script executed:
#!/bin/bash
# Also search for agent_give_feedback since it's mentioned in the feedback_list context
rg 'agent_give|agent_feedback|agent_reputation|agent_register|agent_update|agent_deregister|agent_status|agent_list' src/mcp/server.ts -B 2 | head -150Repository: InjectiveLabs/mcp-server
Length of output: 889
🏁 Script executed:
#!/bin/bash
# Get full descriptions of the missing tools for accurate rewrite
rg "server\.tool\(\s*['\"]agent_reputation|agent_feedback_list|agent_give_feedback" src/mcp/server.ts -A 10 | head -80Repository: InjectiveLabs/mcp-server
Length of output: 2111
Identity tool table is missing three tools (agent_reputation, agent_feedback_list, agent_give_feedback), and MD058 requires a blank line before the table.
The README documents only five identity tools, but src/mcp/server.ts registers eight. The missing tools are:
agent_reputation— Get aggregated reputation (score, count, clients) for an agent. Read-only.agent_feedback_list— List individual feedback entries for an agent with value, tags, and revocation status. Read-only, no gas cost.agent_give_feedback— Submit on-chain feedback for an agent. Costs gas.
Also, markdownlint MD058 at line 60 — add a blank line after the heading and before the table column definitions.
📝 Proposed fix
### Identity (ERC-8004)
+
| Tool | Description |
|---|---|
| `agent_register` | Register a new AI agent identity on the ERC-8004 registry. Costs gas. |
| `agent_update` | Update agent metadata, token URI, or linked wallet. Costs gas. |
| `agent_deregister` | Permanently burn an agent's identity NFT (irreversible). Costs gas. |
| `agent_status` | Get full agent details: metadata, wallet, owner, reputation. Read-only. |
| `agent_list` | Find registered agents with optional owner/type filters. Read-only. |
+| `agent_reputation` | Get aggregated reputation (score, count, clients) for an agent. Read-only. |
+| `agent_feedback_list` | List individual feedback entries for an agent with value, tags, and revocation status. Read-only. |
+| `agent_give_feedback` | Submit on-chain feedback for an agent. Costs gas. |🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 60-60: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` around lines 59 - 67, Add the three missing identity tools to the
Identity (ERC-8004) table and add a blank line after the heading to satisfy
MD058: insert rows for `agent_reputation` (read-only: returns aggregated
reputation — score, count, clients), `agent_feedback_list` (read-only: lists
individual feedback entries with value, tags, revocation status; no gas), and
`agent_give_feedback` (on-chain feedback submission; costs gas), and ensure
there is an empty line between the "### Identity (ERC-8004)" heading and the
table header so markdownlint MD058 is resolved.
| /** | ||
| * End-to-end demo: register an agent with full metadata, then fetch its status with reputation. | ||
| * | ||
| * This is a Vitest test file demonstrating the full identity workflow with mocked SDK. | ||
| * | ||
| * Run with: | ||
| * npm test -- src/identity/demo.ts | ||
| */ |
There was a problem hiding this comment.
Doc comment has a stale filename.
Line 7 says npm test -- src/identity/demo.ts but the file is demo.test.ts. The pattern won't match.
✏️ Fix
- * npm test -- src/identity/demo.ts
+ * npm test -- src/identity/demo.test.ts📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * End-to-end demo: register an agent with full metadata, then fetch its status with reputation. | |
| * | |
| * This is a Vitest test file demonstrating the full identity workflow with mocked SDK. | |
| * | |
| * Run with: | |
| * npm test -- src/identity/demo.ts | |
| */ | |
| /** | |
| * End-to-end demo: register an agent with full metadata, then fetch its status with reputation. | |
| * | |
| * This is a Vitest test file demonstrating the full identity workflow with mocked SDK. | |
| * | |
| * Run with: | |
| * npm test -- src/identity/demo.test.ts | |
| */ |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/demo.test.ts` around lines 1 - 8, Update the top-of-file doc
comment to reference the correct test filename: change the run command "npm test
-- src/identity/demo.ts" to "npm test -- src/identity/demo.test.ts" (edit the
comment block at the top of demo.test.ts). Ensure the example command matches
the actual test filename so the run pattern works as expected.
| ### Increase Timeout | ||
|
|
||
| Tests have 2-min timeout for registration (blockchain is slow). If needed: | ||
|
|
||
| ```bash | ||
| npm test -- src/identity/integration.test.ts --bail 1 --reporter=verbose | ||
| ``` |
There was a problem hiding this comment.
Minor: "Increase Timeout" section shows a command that doesn't increase timeout.
--bail 1 --reporter=verbose controls failure-bail and reporter verbosity — it does not change the per-test timeout. Consider replacing with the Vitest timeout flag (e.g., --testTimeout=300000) or clarify that the timeout must be edited in the test file itself.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/INTEGRATION.md` around lines 91 - 97, Update the "Increase
Timeout" section so the shown npm test command actually increases Vitest's
per-test timeout: replace or augment the suggested flags with Vitest's timeout
flag (for example use --testTimeout=300000) or explicitly instruct readers to
edit the timeout in the test file src/identity/integration.test.ts; mention the
exact test file name and the --testTimeout=300000 flag so consumers know where
to change it or how to run tests with a longer timeout.
| describe.skipIf(SKIP_REASON)('Identity Integration Tests (Testnet)', () => { | ||
| const config = testConfig() | ||
| let agentId: string | ||
| let evmAddress: string // EVM equivalent of TEST_ADDRESS, derived in beforeAll | ||
|
|
||
| beforeAll(async () => { | ||
| console.log(`\n🔐 Verifying wallet at ${TEST_ADDRESS}...`) | ||
| wallets.unlock(TEST_ADDRESS!, TEST_PASSWORD!) | ||
| evmAddress = evm.injAddressToEth(TEST_ADDRESS!) | ||
| console.log('✅ Wallet unlocked successfully\n') | ||
| }) | ||
|
|
||
| it('registers agent on testnet with full metadata', async () => { | ||
| console.log('📝 Registering agent on testnet...') | ||
| const ts = Date.now() | ||
|
|
||
| const result = await identity.register(config, { | ||
| address: TEST_ADDRESS!, | ||
| password: TEST_PASSWORD!, | ||
| name: 'E2E Test Agent ' + ts, | ||
| type: 'trading', | ||
| builderCode: 'e2e-test-' + ts, | ||
| description: 'Full end-to-end test agent with metadata, image, and services', | ||
| image: 'https://picsum.photos/256?random=' + ts, | ||
| services: [ | ||
| { | ||
| name: 'trading', | ||
| endpoint: 'https://api.test.com/trade', | ||
| description: 'Test trading service', | ||
| }, | ||
| { | ||
| name: 'analytics', | ||
| endpoint: 'https://api.test.com/analytics', | ||
| description: 'Test analytics service', | ||
| }, | ||
| ], | ||
| }) | ||
|
|
||
| console.log('✅ Agent registered!') | ||
| console.log(` • Agent ID: ${result.agentId}`) | ||
| console.log(` • TX Hash: ${result.txHash}`) | ||
| console.log(` • Card URI: ${result.cardUri}`) | ||
| console.log(` • Owner: ${result.owner}\n`) | ||
|
|
||
| agentId = result.agentId | ||
|
|
||
| expect(result.agentId).toBeDefined() | ||
| expect(result.txHash).toBeDefined() | ||
| expect(result.cardUri).toBeDefined() | ||
| expect(result.owner).toBe(result.evmAddress) | ||
| expect(result.owner).toMatch(/^0x[a-fA-F0-9]{40}$/) | ||
| }, 120000) |
There was a problem hiding this comment.
Integration suite leaks testnet state and is fragile to test filtering / reordering.
Two related concerns that will bite you after a few CI runs:
-
No cleanup. Every successful run registers a fresh agent on testnet and never deregisters it. Gas is wasted and the registry accumulates garbage indefinitely. The PR description says deregister is covered with
confirm: true, but there’s no correspondingit(...)here. Add a final step (either anafterAllbest-effort cleanup, or anit('deregisters the agent', ...)that asserts the tx and closes the lifecycle). -
Cross-test shared state.
agentIdis assigned inside the firstitand read by every subsequentit. If a user runs a single test via-t 'fetches registered agent', or the register test is skipped/fails, the downstream tests will silently useundefinedasagentId, producing misleading failures (e.g. "newAgent is undefined" rather than "register failed"). Prefer either:- Doing the one-time setup in
beforeAll(register there, keep the assertions as separateits for reporting), or - Consolidating the full lifecycle into a single
it('e2e lifecycle', ...)so all steps share a deterministic scope.
- Doing the one-time setup in
🧹 Sketch: cleanup via afterAll
- beforeAll(async () => {
+ beforeAll(async () => {
console.log(`\n🔐 Verifying wallet at ${TEST_ADDRESS}...`)
wallets.unlock(TEST_ADDRESS!, TEST_PASSWORD!)
evmAddress = evm.injAddressToEth(TEST_ADDRESS!)
console.log('✅ Wallet unlocked successfully\n')
})
+
+ afterAll(async () => {
+ if (!agentId) return
+ try {
+ await identity.deregister(config, {
+ address: TEST_ADDRESS!,
+ password: TEST_PASSWORD!,
+ agentId,
+ confirm: true,
+ })
+ } catch (err) {
+ console.warn(`⚠️ Failed to deregister test agent ${agentId}:`, err)
+ }
+ })(Remember to add afterAll to the vitest import.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/integration.test.ts` around lines 38 - 89, The tests leak state
and rely on cross-test shared variable agentId set inside the "it('registers
agent on testnet with full metadata')" test; move registration into a
deterministic setup and add cleanup: register the agent in beforeAll (call
identity.register with TEST_ADDRESS/TEST_PASSWORD to produce agentId and keep
the existing assertions as separate it()s if you want reporting) or collapse
into a single e2e lifecycle it(); then add an afterAll that calls
identity.deregister(agentId, { confirm: true }) (or the equivalent deregister
function) as a best-effort cleanup to remove the on-chain agent; update imports
to include afterAll from vitest if needed and reference the agentId variable
produced in setup for all subsequent tests.
| async reputation(config: Config, params: ReputationParams): Promise<ReputationResult> { | ||
| const sdk = getClient(config.network) | ||
| try { | ||
| const rep = await sdk.getReputation(BigInt(params.agentId), { | ||
| clientAddresses: params.clientAddresses as `0x${string}`[] | undefined, | ||
| tag1: params.tag1, | ||
| tag2: params.tag2, | ||
| }) | ||
| return { | ||
| agentId: params.agentId, | ||
| score: rep.score, | ||
| count: rep.count, | ||
| clients: rep.clients as string[], | ||
| } | ||
| } catch (_err) { | ||
| // Reputation is best-effort — return zeros if the agent has no feedback or registry errors | ||
| return { agentId: params.agentId, score: 0, count: 0, clients: [] } | ||
| } | ||
| }, | ||
|
|
||
| async feedbackList(config: Config, params: FeedbackListParams): Promise<FeedbackListResult> { | ||
| const sdk = getClient(config.network) | ||
| try { | ||
| const entries = await sdk.getFeedbackEntries(BigInt(params.agentId), { | ||
| clientAddresses: params.clientAddresses as `0x${string}`[] | undefined, | ||
| tag1: params.tag1, | ||
| tag2: params.tag2, | ||
| includeRevoked: params.includeRevoked, | ||
| }) | ||
| return { | ||
| agentId: params.agentId, | ||
| entries: entries.map((e) => ({ | ||
| client: e.client, | ||
| feedbackIndex: Number(e.feedbackIndex), | ||
| value: Number(e.value) / 10 ** e.decimals, | ||
| tag1: e.tags[0] ?? '', | ||
| tag2: e.tags[1] ?? '', | ||
| revoked: e.revoked, | ||
| })), | ||
| } | ||
| } catch (_err) { | ||
| // Feedback list is best-effort — return empty if agent has no feedback or registry errors | ||
| return { agentId: params.agentId, entries: [] } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Minor: silent catch in reputation and feedbackList hides operational failures.
Both handlers swallow every error and return zeroed/empty results. That conflates three distinct states into one:
- Agent genuinely has no feedback yet (expected happy path).
- RPC endpoint is down or rate-limited.
- Contract address misconfigured / not deployed for this network.
Callers and operators can't distinguish these. At minimum, log the error (structured, with agentId and error message) so broken-registry incidents aren't invisible. Better: narrow the swallow to the SDK's "no entries"/"not found" error class and rethrow the rest.
♻️ Suggested shape
- } catch (_err) {
- // Reputation is best-effort — return zeros if the agent has no feedback or registry errors
- return { agentId: params.agentId, score: 0, count: 0, clients: [] }
- }
+ } catch (err) {
+ const msg = err instanceof Error ? err.message : String(err)
+ // Only treat "not found" / "no entries" as the zero-feedback case
+ if (/nonexistent|not found|no entries|invalid token/i.test(msg)) {
+ return { agentId: params.agentId, score: 0, count: 0, clients: [] }
+ }
+ throw err
+ }Same pattern applies to feedbackList at lines 155-158.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/read.ts` around lines 115 - 159, Both reputation and
feedbackList currently swallow all errors; update the catch blocks in the
reputation and feedbackList methods to log the error (include agentId and
error.message) and only return the zero/empty result for the SDK's "no
entries"/"not found" condition; for all other exceptions rethrow. Locate the
reputation method (calls getReputation) and feedbackList method (calls
getFeedbackEntries) and replace the broad catch(_err) with logic that (1)
captures the thrown error, (2) checks the SDK/registry-specific "not found" or
"no entries" error sentinel/class/string and if matched returns the current
zeroed result, otherwise call processLogger.error or similar with a structured
message including agentId and the error, then rethrow the error.
| it('lists agents and finds ours', async () => { | ||
| const result = await identityRead.list(config, { limit: 50 }) | ||
|
|
||
| expect(result.agents.length).toBeGreaterThan(0) | ||
| const found = result.agents.find(a => a.agentId === agentId) | ||
| expect(found).toBeDefined() | ||
| expect(found!.name).toBe('UpdatedTestBot') | ||
| expect(found!.agentType).toBe('trading') | ||
| }, 60_000) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm how sdk.listAgents orders results — if asc by agentId, the flakiness above is real.
fd -t f -e ts -e js 'listAgents' | head -n 20
rg -nP --type=ts -C4 '\blistAgents\s*\(' | head -n 200Repository: InjectiveLabs/mcp-server
Length of output: 1205
🏁 Script executed:
# Read the integration test to see the exact context and full test
head -n 110 src/integration/identity.integration.test.ts | tail -n 25Repository: InjectiveLabs/mcp-server
Length of output: 893
🏁 Script executed:
# Check the list() method signature in identity/read.ts
rg -nP --type=ts 'list\s*\(' src/identity/read.ts -A 10 | head -n 50Repository: InjectiveLabs/mcp-server
Length of output: 506
🏁 Script executed:
# Look for type definitions or interface for list() method
rg -nP --type=ts 'interface.*List|type.*List' src/identity/read.ts -B 2 -A 5 | head -n 60Repository: InjectiveLabs/mcp-server
Length of output: 1148
🏁 Script executed:
# Search for SDK agent ordering or sorting behavior
rg -nP --type=ts 'getAgentsByOwner|listAgents' src/ -A 5 | grep -iE 'sort|order|asc|desc' | head -n 20Repository: InjectiveLabs/mcp-server
Length of output: 50
🏁 Script executed:
# Look for other integration tests that might use owner filtering
grep -rn 'identityRead.list' src/integration/ --include="*.ts"Repository: InjectiveLabs/mcp-server
Length of output: 181
🏁 Script executed:
# Check the SDK client type for listAgents or getAgentsByOwner signatures
rg -nP 'interface.*AgentReadClient|type.*AgentReadClient' src/ -A 30 | head -n 80Repository: InjectiveLabs/mcp-server
Length of output: 50
🏁 Script executed:
# Find SDK package dependency
rg -nP 'injective|agent' package.jsonRepository: InjectiveLabs/mcp-server
Length of output: 394
🏁 Script executed:
# Find imports of AgentReadClient or SDK types
rg -nP "import.*from.*['\"]" src/identity/read.ts | head -n 10Repository: InjectiveLabs/mcp-server
Length of output: 272
🏁 Script executed:
# Check if there's any SDK documentation or type hints in the repo
find . -name "*.d.ts" -o -name "*types*" | grep -i agent | head -n 10Repository: InjectiveLabs/mcp-server
Length of output: 50
🏁 Script executed:
# Check test setup and see if there are any comments about testnet state or flakiness
head -n 50 src/integration/identity.integration.test.tsRepository: InjectiveLabs/mcp-server
Length of output: 1878
🏁 Script executed:
# Search for retry mechanisms or flakiness handling in integration tests
rg -nP 'retry|flaky|timeout|maxRetries' src/integration/ --type=tsRepository: InjectiveLabs/mcp-server
Length of output: 247
🏁 Script executed:
# Look at the beforeAll/beforeEach setup to understand test isolation
rg -nP 'beforeAll|beforeEach|describe.*identity' src/integration/identity.integration.test.ts -A 5 | head -n 40Repository: InjectiveLabs/mcp-server
Length of output: 260
Minor: unfiltered listing may become flaky if testnet accumulates agents.
The test uses identityRead.list(config, { limit: 50 }) without filtering. If the SDK orders agents by ID ascending (or does not guarantee newest-first), the just-registered agent can fall outside the first 50 as testnet grows, causing expect(found).toBeDefined() to fail.
Scope the result to this wallet using the owner parameter, which is already supported:
🧪 Proposed fix
- const result = await identityRead.list(config, { limit: 50 })
+ const result = await identityRead.list(config, { owner: testEvmAddress, limit: 50 })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('lists agents and finds ours', async () => { | |
| const result = await identityRead.list(config, { limit: 50 }) | |
| expect(result.agents.length).toBeGreaterThan(0) | |
| const found = result.agents.find(a => a.agentId === agentId) | |
| expect(found).toBeDefined() | |
| expect(found!.name).toBe('UpdatedTestBot') | |
| expect(found!.agentType).toBe('trading') | |
| }, 60_000) | |
| it('lists agents and finds ours', async () => { | |
| const result = await identityRead.list(config, { owner: testEvmAddress, limit: 50 }) | |
| expect(result.agents.length).toBeGreaterThan(0) | |
| const found = result.agents.find(a => a.agentId === agentId) | |
| expect(found).toBeDefined() | |
| expect(found!.name).toBe('UpdatedTestBot') | |
| expect(found!.agentType).toBe('trading') | |
| }, 60_000) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/integration/identity.integration.test.ts` around lines 95 - 103, The test
is flaky because identityRead.list(config, { limit: 50 }) can miss the newly
created agent; update the call to scope results to the test wallet by passing
the owner filter (e.g. identityRead.list(config, { limit: 50, owner: <wallet
address> })), using the existing config wallet/address field so the search
reliably returns agents for this wallet; then assert found by agentId as before
(references: identityRead.list, agentId, config).
| value: z.number().describe('Rating value (integer). Meaning depends on your scale.'), | ||
| valueDecimals: z.number().int().min(0).max(18).optional().describe('Decimal places for the value (default 0).'), |
There was a problem hiding this comment.
Minor: value should be .int() — BigInt conversion throws on fractional numbers.
identity.giveFeedback does BigInt(params.value) which throws RangeError: The number X cannot be converted to a BigInt because it is not an integer for any non-integer value. The description already states "integer", and neighboring valueDecimals uses .int(). Enforce it in the schema for a clean validation error:
🛡️ Proposed fix
- value: z.number().describe('Rating value (integer). Meaning depends on your scale.'),
+ value: z.number().int().describe('Rating value (integer). Meaning depends on your scale.'),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| value: z.number().describe('Rating value (integer). Meaning depends on your scale.'), | |
| valueDecimals: z.number().int().min(0).max(18).optional().describe('Decimal places for the value (default 0).'), | |
| value: z.number().int().describe('Rating value (integer). Meaning depends on your scale.'), | |
| valueDecimals: z.number().int().min(0).max(18).optional().describe('Decimal places for the value (default 0).'), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/mcp/server.ts` around lines 1038 - 1039, The schema currently allows
fractional numbers for the rating `value` which then causes
`identity.giveFeedback` to throw when it calls BigInt(params.value); change the
schema entry named `value` in the relevant Zod object (the lines defining
`value: z.number()...`) to enforce integers (e.g., use `.int()` like
`valueDecimals` does) so non-integer inputs are rejected early and produce a
validation error before `giveFeedback` runs.
ckhbtc
left a comment
There was a problem hiding this comment.
Review focused on the same funds-safety / irreversibility / LLM-trust-boundary lens as PR #10. Two blocking items, four tighten-before-merge items. No secret-leakage issues found; self-link wallet restriction and error-taxonomy additions look good.
Blocking
- Optimistic tx-hash returns on every write path — no receipt confirmation.
confirm: booleanis LLM-supplied and not a real safety barrier for an irreversible NFT burn.
Should land in the same revision
agentIdZod schema accepts any non-empty string;BigInt()throws surface as misleadingIdentityTxFailed.- Read paths silently swallow network/RPC errors as
score: 0. - URL-typed fields accept
javascript:/data:/file://. feedbackHashcast to0x${string}without hex validation.
| cardUri: r.cardUri, | ||
| ...walletLinkInfo(params.wallet, client.address, r.txHashes), | ||
| } | ||
| } catch (err) { wrapSdkError(err) } |
There was a problem hiding this comment.
Blocking — optimistic tx-hash return on all write paths.
register, update (L258), deregister (L267), giveFeedback (L290), and revokeFeedback (L302) all return whatever txHash/txHashes the SDK hands back, with no on-chain confirmation. This is the same class of bug that PR #10 fixed for bridge/close flows.
agent_deregister is particularly risky — the tool is documented as "permanently burn an NFT" but the returned txHash could be from a broadcast that was never included. An LLM caller that sees { agentId, txHash } will treat the burn as complete; if the tx is actually dropped, a subsequent agent_register could collide, or the user could believe the identity is gone when it isn't.
Suggested fix: after each write, either poll client.getStatus(id) (or confirm the NFT is gone, for deregister) before returning, or wait for the receipt and check status === 'success' + the expected event topic (the repo already has fix(identity): add event topic check to giveFeedback log extraction in history, so the pattern is available). Accepting unconfirmed SDK results as success is inconsistent with the project's stance from #10.
| address: injAddress.describe('Your inj1... address (must be in local keystore).'), | ||
| password: z.string().describe('Keystore password to decrypt the signing key.'), | ||
| agentId: z.string().min(1).describe('The numeric agent ID to deregister.'), | ||
| confirm: z.boolean().describe('Must be true to proceed. This action is irreversible.'), |
There was a problem hiding this comment.
Blocking — confirm: boolean is LLM-controlled and not a meaningful guard.
The confirm flag is supplied by the same LLM context that decided to call agent_deregister in the first place. An adversarial or hallucinating prompt can trivially pass confirm: true; there is no out-of-band human confirmation, no ownership check, and no second factor before the SDK call.
Other destructive tools in this repo (bridge withdrawals, position close) don't take a runtime confirm parameter — they rely on explicit user phrasing + a strong tool description. A runtime confirm: boolean forwarded from the LLM looks like a safety barrier but is weaker than it appears.
Suggested fix: at minimum, do a server-side ownership check before calling client.deregister — fetch status, verify params.address (or the signer derived from it) is the actual NFT owner, and throw a typed error if not. That turns the confirm guard into defense-in-depth rather than the only line of defense. Consider also removing the confirm param entirely and letting tool description + signer-ownership do the work, matching the rest of the codebase.
| { | ||
| address: injAddress.describe('Your inj1... address (must be in local keystore).'), | ||
| password: z.string().describe('Keystore password to decrypt the signing key.'), | ||
| agentId: z.string().min(1).describe('The numeric agent ID (from agent_register).'), |
There was a problem hiding this comment.
agentId: z.string().min(1) accepts any non-empty string, but downstream every use is BigInt(params.agentId). Values like "abc", "12n", "1.5" pass Zod validation then throw SyntaxError inside the try block in src/identity/index.ts, which wrapSdkError re-throws as IdentityTxFailed: Cannot convert abc to a BigInt — a misleading "tx failed" error for what is really input validation.
Same issue on every agentId schema in this file (agent_update, agent_deregister, agent_status, agent_list, agent_reputation, agent_feedback_list, agent_give_feedback, agent_revoke_feedback).
Fix: z.string().regex(/^\d+$/, 'agentId must be a non-negative integer string'). Same treatment for feedbackIndex, value (if string-typed), etc.
| } catch (_err) { | ||
| // Reputation is best-effort — return zeros if the agent has no feedback or registry errors | ||
| return { agentId: params.agentId, score: 0, count: 0, clients: [] } | ||
| } |
There was a problem hiding this comment.
reputation and feedbackList (L158) both swallow _err unconditionally and return zeros / empty arrays. This masks network outages, RPC errors, and contract reverts as "no data" — an LLM consumer gets { score: 0, count: 0 } even when the registry is temporarily unreachable.
When an agent-to-agent trust decision (or a human deciding whether to trust an agent) is based on reputation, the difference between "this agent has no reputation" and "we couldn't reach the chain" is load-bearing.
Suggested fix: narrow the catch to only handle the specific not-found / empty-results case (e.g., check error type or message), and re-throw network/RPC errors so the caller can distinguish. If the SDK doesn't surface a typed "not found" error, consider a try { getStatus } catch { IdentityNotFound } pre-check and let other errors propagate.
| type: z.string().min(1).describe('Agent type (e.g., "trading", "analytics", "data").'), | ||
| builderCode: z.string().min(1).describe('Builder identifier string.'), | ||
| description: z.string().optional().describe('Short description of what the agent does. Shown on 8004scan.'), | ||
| image: z.string().optional().describe('Image URL (https://, http://, or ipfs://). Displayed on 8004scan.'), |
There was a problem hiding this comment.
URL-typed fields are plain z.string().optional() — image (L877), uri (L881, L918), sourceCode (L886, L925), documentation (L887, L926), and the update-side image (L914) all pass javascript:, data:, file://, or any other URI scheme. These strings flow unmodified into IPFS upload and end up in a persistent on-chain record funded by the user's gas.
The descriptions say "https://, http://, or ipfs://" but that's not enforced. The service-entry schema already uses z.string().url() correctly for endpoint, so the pattern exists in this file.
Fix: z.string().url() on each. If ipfs:// needs to be accepted (URL parser rejects it in some Node versions), add an explicit allowlist regex: z.string().regex(/^(https?|ipfs):\/\//).
| tag2: z.string().optional().describe('Secondary tag.'), | ||
| endpoint: z.string().optional().describe('Service endpoint being rated.'), | ||
| feedbackURI: z.string().optional().describe('URI with detailed feedback.'), | ||
| feedbackHash: z.string().optional().describe('32-byte hex hash of feedback content.'), |
There was a problem hiding this comment.
feedbackHash is z.string().optional() but is cast to `0x${string}` in src/identity/index.ts:282 with no validation. An LLM passing arbitrary text gets a silent type cast and then a cryptic SDK-level failure when the contract rejects a non-32-byte value.
Fix: z.string().regex(/^0x[0-9a-fA-F]{64}$/, 'feedbackHash must be a 32-byte hex string').optional().
Summary
agent_register,agent_update,agent_deregister,agent_status,agent_list,agent_reputation,agent_feedback@injective/agent-sdkfor identity/reputation registry interaction; adds viem client factory, EIP-712 wallet-link helper, and error classesTest plan
pnpm typecheckcleanpnpm buildcleanpnpm test— 326 passed / 6 skipped (integration suite runs viatest:integration)scripts/create-x402-agenton testnet (reviewer)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation