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>
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>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds an ERC-8004 identity subsystem: new identity read/write adapters backed by Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant MCP_Server
participant IdentityModule
participant Keystore
participant Pinata
participant AgentSDK
participant Chain
Client->>MCP_Server: call agent_register(params, address, password)
MCP_Server->>IdentityModule: validate & forward params
IdentityModule->>Keystore: unlock(address, password)
Keystore-->>IdentityModule: signer
alt upload metadata to IPFS
IdentityModule->>Pinata: upload metadata
Pinata-->>IdentityModule: cardUri
end
IdentityModule->>AgentSDK: register(agentMeta, signer)
AgentSDK->>Chain: submit tx
Chain-->>AgentSDK: txHash / receipt
AgentSDK-->>IdentityModule: agentId, txHash
IdentityModule-->>MCP_Server: formatted result (agentId, txHash, cardUri)
MCP_Server-->>Client: JSON-stringified response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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
| /** | ||
| * 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).
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.
There was a problem hiding this comment.
Pushed back on the framing in 4cf1a97 — the SDK already calls publicClient.waitForTransactionReceipt on every write path (register client.ts:202+282, update :494, deregister :549, giveFeedback :619, revokeFeedback :689), and register/giveFeedback additionally extract the expected event topic from the receipt (line 205-209 / 622-626). So "the txHash could be from a broadcast that was never included" doesn't hold for this stack — the SDK blocks until the receipt is mined.
The narrower legitimate concern is that the SDK doesn't check receipt.status === 'success', so a tx that reverts post-simulation would still return a hash. For the irreversible deregister path I added a post-burn client.getStatus(id) re-read that throws DeregisterNotApplied if the agent still exists — that catches both "broadcast not applied" and "tx reverted post-sim". For update/revokeFeedback (recoverable on retry) I left it to the SDK's existing receipt wait. Happy to extend if you want belt-and-suspenders there too.
Filed as a follow-up: SDK should add a receipt.status === 'success' assertion at the source so all consumers benefit.
There was a problem hiding this comment.
Follow-up on this thread — extended the receipt-status check to every write path via SDK 0.2.1 (commit b13f153 here, SDK commit 381a2708).
The SDK now exposes assertReceiptSuccess(receipt, methodName, hash) and calls it after every waitForTransactionReceipt:
register(initial register tx)registerfollow-up loop:setUrirevert →onWarning(matches the existing two-phase URI semantics — registration already succeeded),walletLinkrevert → throwupdate(per planned write, named by function)giveFeedback,revokeFeedback
So the gap that was left after the first round (only deregister had a defense-in-depth post-check, and the SDK silently passed reverted-receipt hashes through) is now closed at the source. Consumers of any version ≥ 0.2.1 get this automatically — no MCP-side post-conditions needed.
Concrete behavior change: if a tx simulates clean but reverts post-broadcast (rare — usually only happens under racing state changes), the SDK now throws ContractError instead of returning the reverted hash as success.
| 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.
There was a problem hiding this comment.
Partial accept in 4cf1a97 — added a server-side ownership pre-check before invoking client.deregister: fetches getStatus(id) and throws a typed NotAgentOwner if status.owner !== client.address (case-insensitive). The contract enforces this too, but failing here yields a clean typed error rather than a contract revert wrapped as IdentityTxFailed, and turns confirm: boolean into defense-in-depth instead of the only barrier.
Kept the confirm flag — it matches the --yes/--confirm pattern used by the agent-cli and gives the LLM a clear "this is destructive" signal in the schema. Removing it would lose that signal without adding safety, since the ownership check is the actual gate now.
Edge case from your prior review (PR #10): even the ownership pre-check could be defeated by a hallucinated address that happens to match a key in the keystore. The keystore-unlock requires the password, so an LLM still can't deregister without the user supplying both address and password in the same call.
| { | ||
| 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.
There was a problem hiding this comment.
Fixed in 4cf1a97. Added shared agentIdString = z.string().regex(/^\d+$/, 'agentId must be a non-negative integer string') at server.ts:867 and applied to all 8 identity tools (agent_update, agent_deregister, agent_status, agent_list is owner-side, agent_reputation, agent_feedback_list, agent_give_feedback, agent_revoke_feedback).
Also tightened value in agent_give_feedback to z.number().int() to fix the same class of bug for the rating field — that was the root cause of BigInt(params.value) throwing RangeError on fractional inputs (CodeRabbit caught it on the same line). Non-integer ratings now fail at the schema layer.
feedbackIndex was already z.number().int().min(0) so no change needed there.
| } 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.
There was a problem hiding this comment.
Fixed in 4cf1a97. Removed the broad catch (_err) { return zeros } in both reputation and feedbackList. RPC errors and contract reverts now propagate as-is — an LLM consumer (or human) trying to make a trust decision will see "execution reverted" / "fetch failed" instead of a confidently-wrong score: 0.
The "no feedback" case is still represented cleanly: the SDK's getReputation returns { score: 0, count: 0, clients } when active.length === 0 (read-client.ts:248) without throwing, so genuinely-empty reputation still surfaces as zeros rather than as an error. The catch was only ever swallowing real errors.
Updated the two unit tests that asserted on the swallow behavior to assert re-throw instead.
Did not pre-check getStatus → IdentityNotFound for nonexistent agents — the SDK contract calls return cleanly for that case, no exception path. If that turns out not to hold in practice we can add the pre-check.
| 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):\/\//).
There was a problem hiding this comment.
Fixed in 4cf1a97. Added shared metadataUrl = z.string().regex(/^(https?|ipfs):\/\//, ...) at server.ts:874 and applied to every URL-typed field across both agent_register and agent_update:
imageurisourceCodedocumentationfeedbackURI(inagent_give_feedback— same class of issue you flagged, also tightened)
Used the regex form rather than z.string().url() because Node's URL parser rejects ipfs:// on some versions and we genuinely need that scheme. The regex enforces exactly the three schemes that the IPFS upload + on-chain card storage paths can handle: https://, http://, ipfs://. javascript:, data:, file:// and bare strings now fail at the schema layer before any network or chain interaction.
| 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().
There was a problem hiding this comment.
Fixed in 4cf1a97. Added feedbackHashHex = z.string().regex(/^0x[0-9a-fA-F]{64}$/, 'feedbackHash must be a 32-byte hex string (0x + 64 hex chars)') at server.ts:881 and applied to feedbackHash in agent_give_feedback.
Now any non-32-byte input (wrong length, missing 0x, non-hex chars) fails at the Zod layer with a clear message rather than getting silently cast to `0x${string}` and surfacing as a cryptic SDK / contract revert. Pairs with the value: z.number().int() tightening that fixed the related BigInt() RangeError issue.
- agent_deregister: add server-side ownership pre-check (NotAgentOwner)
and post-burn getStatus verification (DeregisterNotApplied) so the burn
is confirmed before reporting success — defense-in-depth for the LLM-
controlled confirm flag and the SDK's missing receipt.status check.
- Tighten Zod schemas across the 8 identity tools:
- agentIdString: /^\d+$/ (rejects non-integer strings before BigInt)
- metadataUrl: /^(https?|ipfs):\/\// (rejects javascript:/data:/file://)
- feedbackHashHex: /^0x[0-9a-fA-F]{64}$/ (32-byte hex)
- agentOwnerFilter: union(injAddress, ethAddress)
- value: z.number().int() (was unconstrained number)
- read.ts: stop swallowing all errors as score:0 / empty entries —
RPC failures and contract reverts now propagate so callers can
distinguish "no feedback" from "registry unreachable".
- Dedupe ERC721-nonexistent detection into shared isAgentNotFoundError.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/identity/index.ts (2)
283-289: Map "agent not found" toIdentityNotFoundin the ownership pre-check.When
getStatus(id)throws because the token doesn't exist, the catch wraps it as a genericIdentityTxFailed: ERC721 .... The read adapter (src/identity/read.ts:72-75) already translates this case to the typedIdentityNotFoundusing the sharedisAgentNotFoundErrorhelper —deregistershould do the same so callers get a consistent, actionable error instead of a transaction-failed wrapper for what is really a missing-agent case.♻️ Proposed fix
import { IdentityTxFailed, + IdentityNotFound, DeregisterNotConfirmed, NotAgentOwner, DeregisterNotApplied, } from '../errors/index.js'let status try { status = await client.getStatus(id) - } catch (err) { wrapSdkError(err) } + } catch (err) { + if (isAgentNotFoundError(err)) throw new IdentityNotFound(params.agentId) + wrapSdkError(err) + } if (status.owner.toLowerCase() !== client.address.toLowerCase()) { throw new NotAgentOwner(params.agentId, client.address, status.owner) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/identity/index.ts` around lines 283 - 289, The ownership pre-check in deregister uses client.getStatus(id) and currently swallows a missing-token error by calling wrapSdkError(err); change this to detect the "agent not found" case using the shared isAgentNotFoundError helper and throw IdentityNotFound(params.agentId) when true, otherwise call wrapSdkError(err). Update the catch around client.getStatus(id) in the deregister flow (the block that assigns status and then checks status.owner) to perform this conditional error mapping so callers see IdentityNotFound instead of a generic wrapped SDK error.
291-295: DeadDeregisterNotConfirmedpassthrough.
DeregisterNotConfirmedis a local precondition error thrown at line 275 — the SDK'sclient.deregister()cannot raise it, so passing it as a passthrough class towrapSdkErroris unreachable. Either drop it or replace with a class the SDK actually surfaces (e.g.,NotAgentOwnerif the contract revert can be typed).♻️ Proposed fix
let txHash: `0x${string}` try { const r = await client.deregister(id) txHash = r.txHash - } catch (err) { wrapSdkError(err, DeregisterNotConfirmed) } + } catch (err) { wrapSdkError(err) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/identity/index.ts` around lines 291 - 295, The catch block after calling client.deregister(id) uses the local precondition error class DeregisterNotConfirmed as the passthrough to wrapSdkError even though client.deregister cannot throw that; update the handler to either remove the unreachable DeregisterNotConfirmed passthrough or replace it with an SDK-visible error class (for example NotAgentOwner) that the contract revert can actually produce, so change the wrapSdkError call in the client.deregister error handler to use an appropriate SDK error class (or no passthrough) and keep the rest of the logic in the same catch block referencing client.deregister, wrapSdkError, and DeregisterNotConfirmed/NotAgentOwner as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/identity/index.ts`:
- Around line 283-289: The ownership pre-check in deregister uses
client.getStatus(id) and currently swallows a missing-token error by calling
wrapSdkError(err); change this to detect the "agent not found" case using the
shared isAgentNotFoundError helper and throw IdentityNotFound(params.agentId)
when true, otherwise call wrapSdkError(err). Update the catch around
client.getStatus(id) in the deregister flow (the block that assigns status and
then checks status.owner) to perform this conditional error mapping so callers
see IdentityNotFound instead of a generic wrapped SDK error.
- Around line 291-295: The catch block after calling client.deregister(id) uses
the local precondition error class DeregisterNotConfirmed as the passthrough to
wrapSdkError even though client.deregister cannot throw that; update the handler
to either remove the unreachable DeregisterNotConfirmed passthrough or replace
it with an SDK-visible error class (for example NotAgentOwner) that the contract
revert can actually produce, so change the wrapSdkError call in the
client.deregister error handler to use an appropriate SDK error class (or no
passthrough) and keep the rest of the logic in the same catch block referencing
client.deregister, wrapSdkError, and DeregisterNotConfirmed/NotAgentOwner as
needed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 72c33a27-3b76-42bc-b380-91124adf36f2
📒 Files selected for processing (6)
src/errors/index.tssrc/identity/identity.test.tssrc/identity/index.tssrc/identity/read.test.tssrc/identity/read.tssrc/mcp/server.ts
✅ Files skipped from review due to trivial changes (1)
- src/identity/identity.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/mcp/server.ts
- src/identity/read.ts
ckhbtc
left a comment
There was a problem hiding this comment.
Re-review on top of 4cf1a97. Tightening from the previous round (agentId regex, metadataUrl scheme allowlist, feedbackHashHex, read-path error propagation, deregister ownership pre-check + post-burn DeregisterNotApplied) all look good. New blocker introduced by the SDK convergence, plus a few should-land-with-it items.
🔴 Blocking
-
@injective/agent-sdkisfile:../injective-agent-cli/packages/sdk— not installable. The package isn't on npm (npm view @injective/agent-sdk→ 404), and a freshnpm installagainst this PR'spackage.jsonsilently leavesnode_modules/@injective/agent-sdkempty — npm printsadded 236 packagesbut the directory doesn't exist.tsc --noEmitthen fails with 4 ×TS2307: Cannot find module '@injective/agent-sdk'onsrc/identity/index.ts:9-19andsrc/identity/read.ts:7.The README's setup is
npm install && npm run build— that's currently broken for anyone without a siblinginjective-agent-cli/checkout (i.e. everyone except the author). Whatever the long-term plan is — publish the SDK to npm, vendor it, monorepo it — this needs to land before merge or the PR ships an unbuildable package. The PR's own test-plan claim of "326 passed / 6 skipped" can't be reproduced from a clean clone.
🟡 Should land in the same revision
-
README identity table is missing 4 of 9 tools. server.ts:889-1089 registers
agent_register,agent_update,agent_deregister,agent_status,agent_list,agent_reputation,agent_feedback_list,agent_give_feedback,agent_revoke_feedback— but README.md:60-66 only lists the first five. The PR description also says "7 ERC-8004 identity MCP tools" which doesn't match the code. Update both to reflect all 9. -
PINATA_JWTnot documented. identity/index.ts:133-141 makes it required forregister(nouri) and any card-affectingupdate, but the README setup section and the env table at README.md:212-219 don't mention it. The error message at register-time is clear, but users hitting this for the first time should see it in setup docs too — at minimum next toINJECTIVE_NETWORK. -
Test plan smoke script isn't in the repo. PR description points reviewers at
scripts/create-x402-agentbut .gitignore now excludesscripts/. Either commit a sanitized version of the script (or move it into a test) or drop that line from the test plan — as written, no reviewer can run it.
🟢 Nits (non-blocking)
- Optimistic tx-hash returns on
update/giveFeedback/revokeFeedback.deregistergotDeregisterNotApplied(good), but the other writes still returnr.txHashstraight from the SDK without verifying the receipt didn't revert.registeris implicitly safe becauser.agentIdonly populates from a successful Transfer event; the others have no analogous signal. Lower-stakes than burn (writes are idempotent / re-runnable), but the same SDK gap noted in the deregister comment applies. walletLinkInfosilently returns{}whenwallet === signerbuttxHashes.length <= 1(index.ts:177-181) — the caller can't distinguish "link was already in place" from "SDK skipped the link tx for some other reason". AwalletLinkSkippedreason here would help LLM consumers.list()over-fetches 3x withtypefilter (read.ts:91) and reports the filtered count astotal. If a registry has > limit×3 agents of an unfiltered type, the LLM sees a misleadingly small total. Consider documenting this in the tool description, or paginating until exhausted.isAgentNotFoundErrorsubstring matching (index.ts:164-167) — comment acknowledges fragility. Worth a follow-up to ask the SDK to surface a typed code (e.g.ContractError.code === 'NONEXISTENT_TOKEN').
Addresses ckhbtc's re-review on PR #11. SDK dependency: - Pin @injective/agent-sdk to github:InjectiveLabs/injective-agent-sdk #07a9bf95&path:/packages/sdk (was file:../injective-agent-cli/...). The git URL works for any consumer with the new prepare:tsc script added in 07a9bf95 SDK-side, fixing the TS2307 a fresh clone hit. - Add pnpm.onlyBuiltDependencies allowlist so the SDK's prepare hook can run during pnpm install. - Remove stale package-lock.json (project pins pnpm via packageManager; npm cannot resolve the &path: subpath syntax anyway), commit pnpm-lock.yaml, gitignore package-lock.json to prevent re-creation. Drop agent_deregister: - SDK 0.2.0 (commit 85a01161) removed AgentClient.deregister to align with deployed IdentityRegistry v2, which has no burn function. - Remove the agent_deregister MCP tool, identity.deregister handler, related types/tests, the DeregisterParams/DeregisterResult/ DeregisterNotConfirmed/NotAgentOwner/DeregisterNotApplied surface, and the integration test step. - v2 retirement path is "transfer NFT to burn address or clear agentURI" — out of scope for this PR; can land separately as agent_retire if needed. README: - List all 8 identity tools (was 5, missing reputation/feedback ones). - Document PINATA_JWT in a new Environment section. - Switch setup instructions from npm to pnpm and explain the onlyBuiltDependencies requirement. Tests: 321 passed / 6 skipped (was 328 — dropped 7 deregister tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
README.md (1)
59-60:⚠️ Potential issue | 🟡 MinorAdd the missing blank line before the Identity table.
MD058 is still firing here because the table starts immediately after the
### Identity (ERC-8004)heading.📝 Proposed fix
### Identity (ERC-8004) + | Tool | Description | |---|---|🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 59 - 60, Add a single blank line between the "### Identity (ERC-8004)" heading and the following table (the line starting with "| Tool | Description |") so the Markdown heading is separated from the table and MD058 stops firing; update the README.md by inserting one empty line immediately after the "### Identity (ERC-8004)" heading.src/mcp/server.ts (1)
64-83:⚠️ Potential issue | 🟠 MajorValidate
action.urlbefore persisting it in agent metadata.Unlike
image,uri,sourceCode,documentation, andfeedbackURI, this field still accepts any string. That reopens the same validation gap for persisted URLs here: garbage values and unsafe schemes can pass straight into the on-chain card payload.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/mcp/server.ts` around lines 64 - 83, The actionSchema currently allows any string for the url field; update actionSchema to validate URLs the same way other fields (image, uri, sourceCode, documentation, feedbackURI) are validated — either replace z.string().optional() for url with the shared safe URL schema (e.g., safeUrlSchema or uri/url validator used elsewhere) or use z.string().url().optional() with allowed schemes, and keep the .describe(...) call; ensure this validation is applied before persisting agent metadata so garbage or unsafe schemes cannot be stored.src/integration/identity.integration.test.ts (1)
95-96:⚠️ Potential issue | 🟡 MinorFilter the list call to this wallet to avoid testnet-size flakiness.
Once testnet accumulates enough agents,
identityRead.list(config, { limit: 50 })can miss the newly created one and make this assertion nondeterministic.🧪 Proposed fix
- const result = await identityRead.list(config, { limit: 50 }) + const result = await identityRead.list(config, { owner: testEvmAddress, limit: 50 })🤖 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 - 96, The list call is too broad and can miss the newly created agent on busy testnet; narrow it to this test's wallet by adding the wallet filter to the identityRead.list call. Replace identityRead.list(config, { limit: 50 }) with identityRead.list(config, { limit: 50, wallet: <the test wallet identifier> }) (e.g., wallet.address or createdWallet.address depending on the variable used earlier in this test) so the query only returns agents owned by this wallet.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/identity/index.ts`:
- Around line 11-12: The try blocks that call createClient() are catching
keystore errors (e.g., WalletNotFound, WrongPassword) and rethrowing them as
IdentityTxFailed; instead use wrapSdkError's passthrough behavior so keystore
errors are preserved. Update the catch handlers around createClient() in this
file (and the other affected ranges) to call wrapSdkError(err, { passthrough:
true }) or rethrow the original keystore errors rather than wrapping them in
IdentityTxFailed; keep IdentityTxFailed for genuine transaction failures after a
chain write, and reference the existing symbols createClient, wrapSdkError,
WalletNotFound, WrongPassword, and IdentityTxFailed when making the change.
- Around line 151-163: walletLinkInfo currently only annotates the response but
the SDK call in register() and update() still forwards params.wallet (or
client.address) causing writes to differ from the returned JSON; before calling
the adapter/client, decide whether to attempt a wallet link and omit the wallet
from the request when it should be skipped (i.e., when wallet is undefined or
wallet.toLowerCase() !== signerAddress.toLowerCase()), update callers of
walletLinkInfo (register, update) to call a pre-check (or extend walletLinkInfo
to return a decision flag like shouldLink) and branch to not include the wallet
in the SDK request when shouldLink is false, and keep walletLinkInfo only for
post-response metadata (including fixing tx hash selection if needed by using
the correct txHashes index). Ensure you reference walletLinkInfo, register, and
update when making the changes.
In `@src/mcp/server.ts`:
- Around line 927-948: The agent_update endpoint description currently says card
rebuilds happen only for description, image, and services, but identity.update()
also treats actions, active, supportedTrust, tags, version, license, sourceCode,
documentation (and other card-affecting fields such as x402) as triggers; update
the descriptive string for the agent_update handler to enumerate all fields that
cause a card rebuild (include description, image, services, actions, active,
supportedTrust, tags, version, license, sourceCode, documentation, and x402) and
note that PINATA_JWT is required when these fields change so callers won’t
inadvertently hit PINATA_JWT failures (look for the agent_update docstring / API
description and the identity.update() references to align wording).
---
Duplicate comments:
In `@README.md`:
- Around line 59-60: Add a single blank line between the "### Identity
(ERC-8004)" heading and the following table (the line starting with "| Tool |
Description |") so the Markdown heading is separated from the table and MD058
stops firing; update the README.md by inserting one empty line immediately after
the "### Identity (ERC-8004)" heading.
In `@src/integration/identity.integration.test.ts`:
- Around line 95-96: The list call is too broad and can miss the newly created
agent on busy testnet; narrow it to this test's wallet by adding the wallet
filter to the identityRead.list call. Replace identityRead.list(config, { limit:
50 }) with identityRead.list(config, { limit: 50, wallet: <the test wallet
identifier> }) (e.g., wallet.address or createdWallet.address depending on the
variable used earlier in this test) so the query only returns agents owned by
this wallet.
In `@src/mcp/server.ts`:
- Around line 64-83: The actionSchema currently allows any string for the url
field; update actionSchema to validate URLs the same way other fields (image,
uri, sourceCode, documentation, feedbackURI) are validated — either replace
z.string().optional() for url with the shared safe URL schema (e.g.,
safeUrlSchema or uri/url validator used elsewhere) or use
z.string().url().optional() with allowed schemes, and keep the .describe(...)
call; ensure this validation is applied before persisting agent metadata so
garbage or unsafe schemes cannot be stored.
🪄 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: 52e00f79-df9c-460c-b837-8d587a5c4ac6
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
.gitignoreREADME.mdpackage.jsonsrc/errors/errors.test.tssrc/errors/index.tssrc/identity/identity.test.tssrc/identity/index.tssrc/integration/identity.integration.test.tssrc/mcp/server.ts
✅ Files skipped from review due to trivial changes (1)
- package.json
🚧 Files skipped from review as they are similar to previous changes (3)
- src/errors/errors.test.ts
- .gitignore
- src/errors/index.ts
| import { wallets } from '../wallets/index.js' | ||
| import { IdentityTxFailed } from '../errors/index.js' |
There was a problem hiding this comment.
Preserve keystore errors instead of wrapping them as IdentityTxFailed.
createClient() runs inside these try blocks, so WalletNotFound and WrongPassword get relabeled as transaction failures even though no chain write happened. wrapSdkError() already has passthrough support; these callers should use it.
🛠️ Proposed fix
-import { IdentityTxFailed } from '../errors/index.js'
+import { IdentityTxFailed, WalletNotFound, WrongPassword } from '../errors/index.js'
@@
- } catch (err) { wrapSdkError(err) }
+ } catch (err) { wrapSdkError(err, WalletNotFound, WrongPassword) }
@@
- } catch (err) { wrapSdkError(err) }
+ } catch (err) { wrapSdkError(err, WalletNotFound, WrongPassword) }
@@
- } catch (err) { wrapSdkError(err) }
+ } catch (err) { wrapSdkError(err, WalletNotFound, WrongPassword) }
@@
- } catch (err) { wrapSdkError(err) }
+ } catch (err) { wrapSdkError(err, WalletNotFound, WrongPassword) }Also applies to: 138-142, 168-201, 204-288
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/index.ts` around lines 11 - 12, The try blocks that call
createClient() are catching keystore errors (e.g., WalletNotFound,
WrongPassword) and rethrowing them as IdentityTxFailed; instead use
wrapSdkError's passthrough behavior so keystore errors are preserved. Update the
catch handlers around createClient() in this file (and the other affected
ranges) to call wrapSdkError(err, { passthrough: true }) or rethrow the original
keystore errors rather than wrapping them in IdentityTxFailed; keep
IdentityTxFailed for genuine transaction failures after a chain write, and
reference the existing symbols createClient, wrapSdkError, WalletNotFound,
WrongPassword, and IdentityTxFailed when making the change.
| function walletLinkInfo(wallet: string | undefined, signerAddress: string, txHashes: `0x${string}`[]): WalletLinkInfo { | ||
| if (!wallet) return {} | ||
| if (wallet.toLowerCase() !== signerAddress.toLowerCase()) { | ||
| return { | ||
| walletLinkSkipped: true, | ||
| walletLinkReason: `Wallet ${wallet} does not match signer ${signerAddress} — only self-links supported`, | ||
| } | ||
| } | ||
| if (txHashes.length > 1) { | ||
| return { walletTxHash: txHashes[1] } | ||
| } | ||
| return {} | ||
| } |
There was a problem hiding this comment.
Decide wallet linking before the SDK call, not only in the returned JSON.
walletLinkInfo() only post-processes the response. register() always forwards a wallet (params.wallet ?? client.address), so omitting wallet still self-links, and update() still forwards a mismatched wallet even when the result says linking was skipped. That makes the adapter's output inconsistent with the write it actually requested.
🛠️ Proposed fix
function walletLinkInfo(wallet: string | undefined, signerAddress: string, txHashes: `0x${string}`[]): WalletLinkInfo {
if (!wallet) return {}
if (wallet.toLowerCase() !== signerAddress.toLowerCase()) {
return {
walletLinkSkipped: true,
walletLinkReason: `Wallet ${wallet} does not match signer ${signerAddress} — only self-links supported`,
@@
async register(config: Config, params: RegisterParams): Promise<RegisterResult> {
@@
try {
const client = createClient(config, params.address, params.password, storage)
+ const wallet = params.wallet?.toLowerCase() === client.address.toLowerCase()
+ ? params.wallet as `0x${string}`
+ : undefined
const r = await client.register({
name: params.name,
type: params.type as AgentType,
builderCode: params.builderCode,
- wallet: (params.wallet ?? client.address) as `0x${string}`,
+ wallet,
uri: params.uri,
@@
async update(config: Config, params: UpdateParams): Promise<UpdateResult> {
@@
try {
const client = createClient(config, params.address, params.password, storage)
const id = BigInt(params.agentId)
+ const wallet = params.wallet?.toLowerCase() === client.address.toLowerCase()
+ ? params.wallet as `0x${string}`
+ : undefined
const r = await client.update(id, {
@@
- wallet: params.wallet as `0x${string}` | undefined,
+ wallet,Also applies to: 168-200, 204-253
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/index.ts` around lines 151 - 163, walletLinkInfo currently only
annotates the response but the SDK call in register() and update() still
forwards params.wallet (or client.address) causing writes to differ from the
returned JSON; before calling the adapter/client, decide whether to attempt a
wallet link and omit the wallet from the request when it should be skipped
(i.e., when wallet is undefined or wallet.toLowerCase() !==
signerAddress.toLowerCase()), update callers of walletLinkInfo (register,
update) to call a pre-check (or extend walletLinkInfo to return a decision flag
like shouldLink) and branch to not include the wallet in the SDK request when
shouldLink is false, and keep walletLinkInfo only for post-response metadata
(including fixing tx hash selection if needed by using the correct txHashes
index). Ensure you reference walletLinkInfo, register, and update when making
the changes.
| 'Update an existing agent\'s metadata, description, image, services, or wallet. Card-level changes (description, image, services) auto-rebuild and re-upload the Agent Card to IPFS. Requires PINATA_JWT for card updates.', | ||
| { | ||
| address: injAddress.describe('Your inj1... address (must be in local keystore).'), | ||
| password: z.string().describe('Keystore password to decrypt the signing key.'), | ||
| agentId: agentIdString.describe('The numeric agent ID (from agent_register).'), | ||
| name: z.string().min(1).optional().describe('New agent name.'), | ||
| type: z.string().min(1).optional().describe('New agent type (e.g., "trading", "analytics").'), | ||
| builderCode: z.string().min(1).optional().describe('New builder identifier string.'), | ||
| description: z.string().optional().describe('New agent description.'), | ||
| image: metadataUrl.optional().describe('New image URL (https://, http://, or ipfs://).'), | ||
| services: z.array(serviceEntrySchema).optional().describe('New service endpoints (replaces existing).'), | ||
| removeServices: z.array(serviceEntrySchema.shape.name).optional().describe('Service names to remove from the card (uppercase: "MCP", "A2A", "OASF").'), | ||
| actions: z.array(actionSchema).optional().describe('New action schemas (replaces all existing actions). Pass empty array to clear.'), | ||
| uri: metadataUrl.optional().describe('Pre-built token URI (https://, http://, or ipfs://). Skips card generation if provided.'), | ||
| wallet: ethAddress.optional().describe('New linked EVM wallet. Only works if it matches the keystore address.'), | ||
| active: z.boolean().optional().describe('Toggle the agent\'s active flag. When false, the agent is hidden from 8004scan discovery.'), | ||
| supportedTrust: z.array(z.string()).optional().describe('Replace the agent\'s declared trust models (e.g., ["reputation", "crypto-economic", "tee-attestation"]).'), | ||
| tags: z.array(z.string()).optional().describe('Replace the agent\'s discovery tags (e.g., ["defi", "trading"]).'), | ||
| version: z.string().optional().describe('New semantic version string (e.g., "1.1.0").'), | ||
| license: z.string().optional().describe('New SPDX license identifier (e.g., "MIT", "Apache-2.0").'), | ||
| sourceCode: metadataUrl.optional().describe('New source code URL (https://, http://, or ipfs://).'), | ||
| documentation: metadataUrl.optional().describe('New documentation URL (https://, http://, or ipfs://).'), |
There was a problem hiding this comment.
Broaden the agent_update description to match the real card-rebuild triggers.
This prompt says card rebuilds are only for description, image, and services, but identity.update() also treats actions, x402, active, supportedTrust, tags, version, license, sourceCode, and documentation as card-affecting. Right now the tool description will lead callers into avoidable PINATA_JWT failures.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/mcp/server.ts` around lines 927 - 948, The agent_update endpoint
description currently says card rebuilds happen only for description, image, and
services, but identity.update() also treats actions, active, supportedTrust,
tags, version, license, sourceCode, documentation (and other card-affecting
fields such as x402) as triggers; update the descriptive string for the
agent_update handler to enumerate all fields that cause a card rebuild (include
description, image, services, actions, active, supportedTrust, tags, version,
license, sourceCode, documentation, and x402) and note that PINATA_JWT is
required when these fields change so callers won’t inadvertently hit PINATA_JWT
failures (look for the agent_update docstring / API description and the
identity.update() references to align wording).
Re-pins @injective/agent-sdk to 381a2708 (0.2.1) and rewrites the
mcp-server-side adapters to consume the new SDK surface:
- walletLinkInfo: stop guessing the wallet-link tx by index in
txHashes[] (txHashes[1] could be either setUriHash or walletHash
depending on whether the two-phase URI re-upload ran). Now reads the
SDK's named result.walletTxHash directly. The "wallet === signer but
SDK emitted no link tx" branch — previously a silent {} — now returns
walletLinkSkipped with a reason.
- isAgentNotFoundError: prefer typed
ContractError.revertReason === 'ERC721NonexistentToken' over
substring matching. SDK 0.2.1 decodes the revert via
formatContractError; the substring branch is kept as a fallback for
errors that don't pass through the formatter.
- agent_list tool: document the over-fetch caveat in the description so
LLM consumers know that `total` undercounts when matching agents of
the requested type exist beyond the limit*3 over-fetch window.
The optimistic-txHash concern (nit #1) is fixed in the SDK itself —
0.2.1 now asserts receipt.status === 'success' on every write path via
a new assertReceiptSuccess helper, so update / giveFeedback /
revokeFeedback no longer silently return hashes for reverted txs. In
register's followup-receipts loop, setUri reverts emit onWarning
(matches the existing two-phase URI semantics) while wallet-link
reverts hard-fail.
Tests: 322 passed / 6 skipped (was 321; added 1 test for the
SDK-emitted-no-walletTxHash case). Mocks for @injective/agent-sdk
extended to expose the ContractError class so isAgentNotFoundError's
typed check works under vi.mock.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/identity/index.ts`:
- Around line 148-152: The write paths update, giveFeedback, and revokeFeedback
are not using isAgentNotFoundError(err), so a nonexistent agent surfaces as
IDENTITY_TX_FAILED; modify each of the three functions' catch blocks to detect
agent-not-found by calling isAgentNotFoundError(err) and rethrow or return the
specific IDENTITY_NOT_FOUND error/result instead of mapping every failure to
IDENTITY_TX_FAILED (match the same catch/throw pattern used elsewhere in this
file that checks isAgentNotFoundError and returns IDENTITY_NOT_FOUND). Ensure
you reference the same error constant (IDENTITY_NOT_FOUND) and the helper
isAgentNotFoundError when updating the catch logic in functions update,
giveFeedback, and revokeFeedback.
- Around line 84-89: The GiveFeedbackParams.value and
RevokeFeedbackParams.feedbackIndex use unsafe JS number types and are later
converted to BigInt (in the methods that perform the BigInt conversions),
risking precision loss for integers > 2^53-1; update the parameter types to
string (or bigint) in the GiveFeedbackParams and RevokeFeedbackParams
interfaces, and adjust the call sites and conversion logic to parse BigInt from
the string, or alternatively add a runtime guard using Number.isSafeInteger()
before converting the incoming number to BigInt and throw a clear error if the
check fails so values cannot silently corrupt on-chain data.
In `@src/identity/read.test.ts`:
- Around line 108-134: Add a test that explicitly exercises the ContractError
path used by isAgentNotFoundError: have mockGetEnrichedAgent reject with an
instance of ContractError whose revertReason === 'ERC721NonexistentToken' and
then assert that identityRead.status(config, { agentId: '###' }) rejects with
IdentityNotFound (and optional message check). This ensures the code path in
isAgentNotFoundError (the instanceof ContractError + revertReason check) is
covered alongside the existing string-substring error tests.
- Around line 361-376: Add a new test in src/identity/read.test.ts that mocks
mockGetFeedbackEntries returning an entry whose value is larger than
Number.MAX_SAFE_INTEGER (e.g., 9007199254740992n or 10n ** 18n + 1n) with an
appropriate decimals field, call identityRead.feedbackList(config, { agentId:
AGENT_ID }) and compute the exact expected normalized decimal string using
BigInt arithmetic (divide and format using BigInt to produce the exact decimal
representation); then assert that result.entries[0]!.value matches that exact
representation (or fail if it loses precision) so this boundary case will catch
silent precision loss in the normalization logic (see conversion logic in
feedbackList / Number(e.value) / 10 ** e.decimals).
🪄 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: 911ddd69-80fa-4b32-88e0-fa4555882913
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
package.jsonsrc/identity/identity.test.tssrc/identity/index.tssrc/identity/read.test.tssrc/mcp/server.ts
✅ Files skipped from review due to trivial changes (2)
- package.json
- src/mcp/server.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/identity/identity.test.ts
| export interface GiveFeedbackParams { | ||
| address: string | ||
| password: string | ||
| agentId: string | ||
| value: number | ||
| valueDecimals?: number |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is JavaScript's Number.MAX_SAFE_INTEGER, and why can converting a Number larger than that to BigInt still produce the wrong integer?
💡 Result:
JavaScript's Number.MAX_SAFE_INTEGER is 9007199254740991 (2^53 - 1), the largest integer that can be exactly represented and safely used for arithmetic in the Number type, which uses IEEE 754 double-precision floating-point format with a 53-bit mantissa. Converting a Number larger than this to BigInt produces the wrong integer because Numbers larger than Number.MAX_SAFE_INTEGER cannot represent all integers exactly due to limited precision. The floating-point representation rounds or loses lower-order bits, so BigInt(n) where n > Number.MAX_SAFE_INTEGER captures only the imprecise approximation stored in the Number, not the original exact integer value. For example: let n = 90071992547409946; // Intended value, but stored imprecisely BigInt(n); // Returns 90071992547409952n or similar rounded value To preserve exact values, use BigInt from strings or literals with 'n' suffix instead of converting from imprecise Numbers.
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
- 2: https://stackoverflow.com/questions/60982404/convert-number-to-bigint-or-string-when-its-value-exceeds-number-max-safe-integ
- 3: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/bigint
🏁 Script executed:
cat -n src/identity/index.ts | head -100Repository: InjectiveLabs/mcp-server
Length of output: 3121
🏁 Script executed:
cat -n src/identity/index.ts | sed -n '100,115p'Repository: InjectiveLabs/mcp-server
Length of output: 486
🏁 Script executed:
cat -n src/identity/index.ts | sed -n '265,295p'Repository: InjectiveLabs/mcp-server
Length of output: 1364
🏁 Script executed:
cat -n src/identity/index.ts | sed -n '260,285p'Repository: InjectiveLabs/mcp-server
Length of output: 1043
🏁 Script executed:
rg 'BigInt\(' src/identity/index.ts -B 2 -A 2Repository: InjectiveLabs/mcp-server
Length of output: 771
Unsafe number type for on-chain integers causes silent data corruption.
value (GiveFeedbackParams, line 88) and feedbackIndex (RevokeFeedbackParams, line 107) are typed as number but converted to BigInt in the respective methods (lines 269 and 291). Any integer larger than 2^53 - 1 (9007199254740991) loses precision in JavaScript's floating-point representation before the BigInt conversion, causing wrong reputation values or feedback entries to be submitted on-chain.
Change parameter types to string or add Number.isSafeInteger() validation before conversion:
🛠️ Minimal guard if you keep the current API shape
+function assertSafeInteger(value: number, field: string): void {
+ if (!Number.isSafeInteger(value) || value < 0) {
+ throw new IdentityTxFailed(`${field} must be a non-negative safe integer`)
+ }
+}
@@
async giveFeedback(config: Config, params: GiveFeedbackParams): Promise<GiveFeedbackResult> {
try {
const client = createClient(config, params.address, params.password)
+ assertSafeInteger(params.value, 'value')
const r = await client.giveFeedback({
@@
async revokeFeedback(config: Config, params: RevokeFeedbackParams): Promise<RevokeFeedbackResult> {
try {
const client = createClient(config, params.address, params.password)
+ assertSafeInteger(params.feedbackIndex, 'feedbackIndex')
const r = await client.revokeFeedback({📝 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.
| export interface GiveFeedbackParams { | |
| address: string | |
| password: string | |
| agentId: string | |
| value: number | |
| valueDecimals?: number | |
| export interface GiveFeedbackParams { | |
| address: string | |
| password: string | |
| agentId: string | |
| value: number | |
| valueDecimals?: number |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/index.ts` around lines 84 - 89, The GiveFeedbackParams.value and
RevokeFeedbackParams.feedbackIndex use unsafe JS number types and are later
converted to BigInt (in the methods that perform the BigInt conversions),
risking precision loss for integers > 2^53-1; update the parameter types to
string (or bigint) in the GiveFeedbackParams and RevokeFeedbackParams
interfaces, and adjust the call sites and conversion logic to parse BigInt from
the string, or alternatively add a runtime guard using Number.isSafeInteger()
before converting the incoming number to BigInt and throw a clear error if the
check fails so values cannot silently corrupt on-chain data.
| export function isAgentNotFoundError(err: unknown): boolean { | ||
| if (err instanceof ContractError && err.revertReason === 'ERC721NonexistentToken') return true | ||
| const msg = err instanceof Error ? err.message : String(err) | ||
| return msg.includes('ERC721') || msg.includes('nonexistent') || msg.includes('invalid token') | ||
| } |
There was a problem hiding this comment.
Preserve IDENTITY_NOT_FOUND on the write paths.
isAgentNotFoundError() is defined, but update, giveFeedback, and revokeFeedback never use it. A nonexistent agent will therefore surface as IDENTITY_TX_FAILED, which makes a bad agentId indistinguishable from a retryable transaction failure.
🛠️ Proposed fix
-import { IdentityTxFailed } from '../errors/index.js'
+import { IdentityNotFound, IdentityTxFailed } from '../errors/index.js'
@@
- } catch (err) { wrapSdkError(err) }
+ } catch (err) {
+ if (isAgentNotFoundError(err)) throw new IdentityNotFound(params.agentId)
+ wrapSdkError(err)
+ }Apply the same catch pattern to update, giveFeedback, and revokeFeedback.
Also applies to: 223-261, 264-295
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/index.ts` around lines 148 - 152, The write paths update,
giveFeedback, and revokeFeedback are not using isAgentNotFoundError(err), so a
nonexistent agent surfaces as IDENTITY_TX_FAILED; modify each of the three
functions' catch blocks to detect agent-not-found by calling
isAgentNotFoundError(err) and rethrow or return the specific IDENTITY_NOT_FOUND
error/result instead of mapping every failure to IDENTITY_TX_FAILED (match the
same catch/throw pattern used elsewhere in this file that checks
isAgentNotFoundError and returns IDENTITY_NOT_FOUND). Ensure you reference the
same error constant (IDENTITY_NOT_FOUND) and the helper isAgentNotFoundError
when updating the catch logic in functions update, giveFeedback, and
revokeFeedback.
| it('normalizes values by decimals', async () => { | ||
| mockGetFeedbackEntries.mockResolvedValue([ | ||
| { | ||
| client: '0x' + 'aa'.repeat(20), | ||
| feedbackIndex: 0n, | ||
| value: 12345n, | ||
| decimals: 3, | ||
| tags: ['tag', ''], | ||
| revoked: false, | ||
| }, | ||
| ]) | ||
|
|
||
| const result = await identityRead.feedbackList(config, { agentId: AGENT_ID }) | ||
|
|
||
| expect(result.entries[0]!.value).toBeCloseTo(12.345, 3) | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm conversion strategy and current test coverage for precision boundaries.
rg -n "feedbackList|Number\\(|10 \\*\\*|decimals" src/identity/read.ts
rg -n "MAX_SAFE_INTEGER|9007199254740991|toBeCloseTo|bigint" src/identity/read.test.tsRepository: InjectiveLabs/mcp-server
Length of output: 425
🏁 Script executed:
# Search for other tests involving feedbackList or value normalization
rg -B2 -A5 "feedbackList" src/identity/read.test.ts | head -100
# Check for any comments or safeguards around large values
rg -B3 -A3 "feedbackIndex|feedbackList.*value" src/identity/read.ts
# Look for any tests with larger bigint values
rg -n "value.*[0-9]{6,}n|MAX_SAFE" src/identity/read.test.tsRepository: InjectiveLabs/mcp-server
Length of output: 1841
🏁 Script executed:
# Check the full feedbackList implementation for any comments about precision
rg -B5 -A10 "async feedbackList" src/identity/read.ts
# Look for any comments in the file about bigint, precision, or MAX_SAFE_INTEGER
rg -i "precision|safe|overflow|bigint.*number|number.*conversion" src/identity/read.tsRepository: InjectiveLabs/mcp-server
Length of output: 700
🏁 Script executed:
# Get complete entries mapping to see the full context
sed -n '131,150p' src/identity/read.tsRepository: InjectiveLabs/mcp-server
Length of output: 779
Add a test for bigint normalization precision boundaries.
The current test at lines 361–376 validates decimal normalization with a small value (12345n), but doesn't test behavior when value exceeds Number.MAX_SAFE_INTEGER (9007199254740991). The conversion at line 144 of src/identity/read.ts (Number(e.value) / 10 ** e.decimals) will silently lose precision with large bigints. A boundary test would prevent regression.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/identity/read.test.ts` around lines 361 - 376, Add a new test in
src/identity/read.test.ts that mocks mockGetFeedbackEntries returning an entry
whose value is larger than Number.MAX_SAFE_INTEGER (e.g., 9007199254740992n or
10n ** 18n + 1n) with an appropriate decimals field, call
identityRead.feedbackList(config, { agentId: AGENT_ID }) and compute the exact
expected normalized decimal string using BigInt arithmetic (divide and format
using BigInt to produce the exact decimal representation); then assert that
result.entries[0]!.value matches that exact representation (or fail if it loses
precision) so this boundary case will catch silent precision loss in the
normalization logic (see conversion logic in feedbackList / Number(e.value) / 10
** e.decimals).
|
@ckhbtc — pushed SDK 0.2.1 (
mcp-server (
Tests: 322 passed / 6 skipped (was 321; +1 new test for the SDK-emitted-no-walletTxHash case). Ready for re-review. |
Two follow-ups surfaced by a cold-install validation
(rm -rf node_modules + pnpm store prune + pnpm install):
1) Dead-code removal in src/identity/index.ts:
- walletLinkInfo: dropped the 'wallet === signer but SDK emitted no
walletTxHash → walletLinkSkipped' branch. Unreachable in current
SDK — when wallet matches signer the link tx is always pushed and
any failure throws. The branch perpetuated mental-model noise.
- isAgentNotFoundError: dropped the substring fallback. SDK 0.2.1
decodes ERC721NonexistentToken via formatContractError into a
typed ContractError.revertReason; the substring branch was the
exact fragility ckhbtc warned about.
- Updated identity.test.ts (removed unreachable-branch test) and
read.test.ts (rewrote the 3 substring-shaped tests as 2 typed
ContractError tests with explicit revertReason).
2) Pre-existing latent missing direct deps (caught by cold install,
masked previously by npm-style hoisting of pnpm's virtual store):
- @injectivelabs/core-proto-ts-v2 — used by src/evm/index.ts
- @injectivelabs/ts-types — used by src/evm/index.ts
- ethers — used by src/evm/eip712.ts and its tests
None of these were declared as direct dependencies; pnpm's strict
isolation correctly broke the cold install. Adding them surfaces
the implicit dep contract.
Tests: 320 passed / 6 skipped under a fully cold install (was 322,
net -2 from removing the substring/unreachable test cases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ckhbtc
left a comment
There was a problem hiding this comment.
Three blocking items below — mostly concrete bugs/docs errors. Architecture, Zod validation, and error wrapping all look solid; this is close.
Blocking
1. npm install fails cold — @injective/agent-sdk uses a pnpm-only specifier
package.json line 23:
"github:InjectiveLabs/injective-agent-sdk#<sha>&path:/packages/sdk"
npm's github: shorthand does not support &path: — only #ref. pnpm resolves this via codeload tarballs (visible in pnpm-lock.yaml), so pnpm works, but any contributor who reflexively runs npm install (the repo's prior workflow) gets a broken install. The packageManager field only emits a warning, it doesn't hard-block npm.
Fix options:
- Publish
@injective/agent-sdkto npm with a real version, or - Add a
preinstallscript that exits with a clear message ifnpm_config_user_agentdoesn't start withpnpm, and make the README's "pnpm only" note prominent (currently buried).
2. agent_register silently triggers a wallet-link tx when wallet is omitted — contradicts its own tool description
src/identity/index.ts line 181:
wallet: (params.wallet ?? client.address) as \`0x\${string}\`,The agent_register tool description says "Omit to skip" wallet linking, but the code substitutes client.address and the SDK then submits a self-link transaction. agent_update does not apply this default — it passes params.wallet through as undefined — so the two write tools behave inconsistently for the same input.
This is an LLM foot-gun: a model following the documented "omit to skip" semantics will unknowingly send a second on-chain tx every time it registers without an explicit wallet.
Fix: pass params.wallet through as \0x${string}` | undefined(matchingagent_update`), or change the description and schema to make the auto-link behavior explicit.
3. README documents the wrong default network
README line 78:
INJECTIVE_NETWORK...mainnet(default) ortestnet.
But src/mcp/server.ts line 92:
const NETWORK = validateNetwork(process.env['INJECTIVE_NETWORK'] ?? 'testnet')Default is testnet. A user who trusts the README, skips the env var, and runs agent_register will register on testnet then look for their agent on mainnet and find nothing. Either change the README to testnet (default) or change the code default — but they need to agree.
Non-blocking
typeis free-formz.string().min(1)in bothagent_registerandagent_update. The ERC-8004 registry presumably has a fixed set of valid types — should bez.enum([...]), likeserviceEntrySchemaalready is. Otherwise an LLM can registertype: \"my-custom-type\"and either get a confusing on-chain rejection or pollute the registry with garbage types.agent_listtotalis misleading when a type filter is active. It reflects matches inside the over-fetch window, not the registry total. The tool description documents the over-fetch caveat but the field name doesn't. Rename tomatchedInWindowor addmayBeIncomplete: truewhen a type filter narrows the window — otherwise a caller seeingtotal: 3will conclude only 3 such agents exist.
What looks good
wrapSdkError/isAgentNotFoundErrorsplit is clean:IdentityNotFoundfor ERC-721 nonexistent token, everything else wrapped asIdentityTxFailed. No silent catches inread.ts— RPC errors propagate.- Zod schemas are otherwise tight:
agentIdStringrejects non-integer strings beforeBigInt(),metadataUrlblocksjavascript:/data:,feedbackHashHexenforces 32-byte length,agentOwnerFilterhandles bothinj1...and0x.... - Pinata flow fails cleanly (
IdentityTxFailedwith a clear message) before any on-chain tx if neitherPINATA_JWTnor a pre-uploadeduriis provided — no half-success state. - pnpm lockfile pins the SDK to a specific commit, so reproducible for pnpm users.
Summary
agent_register,agent_update,agent_status,agent_list,agent_reputation,agent_feedback_list,agent_give_feedback,agent_revoke_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— 321 passed / 6 skipped (integration suite runs viatest:integration)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores