Skip to content

Added ERC-8004 agent identity support#70

Open
SergeyG-Solicy wants to merge 6 commits intomainfrom
feature/erc-8004-agent-identity
Open

Added ERC-8004 agent identity support#70
SergeyG-Solicy wants to merge 6 commits intomainfrom
feature/erc-8004-agent-identity

Conversation

@SergeyG-Solicy
Copy link
Contributor

@SergeyG-Solicy SergeyG-Solicy commented Jan 9, 2026

PR Type

Enhancement


Description

  • Added comprehensive ERC-8004 agent identity support to Demos SDK

  • Implemented agent ownership proof creation and verification methods

  • Added methods to manage agent identities (add, remove, get)

  • Extended type definitions and GCR handling for agent identities

  • Added client-side helpers for NFT ownership verification and metadata retrieval


Diagram Walkthrough

flowchart LR
  A["Agent Identity Payload"] --> B["Create Ownership Proof"]
  B --> C["Sign with Demos Wallet"]
  C --> D["Add Agent Identity"]
  D --> E["GCR Edit Handler"]
  E --> F["RPC Transaction"]
  G["Verify NFT Ownership"] --> H["Check Base Sepolia Registry"]
  I["Fetch Agent Card"] --> J["Retrieve Metadata from URI"]
Loading

File Walkthrough

Relevant files
Enhancement
Identities.ts
Implement ERC-8004 agent identity operations                         

src/abstraction/Identities.ts

  • Added "agent" context support to inferIdentity() and removeIdentity()
    methods
  • Implemented createAgentOwnershipProof() to generate signed ownership
    proofs
  • Added addAgentIdentity() method with payload validation and EVM
    address verification
  • Added removeAgentIdentity() and getAgentIdentities() methods for agent
    lifecycle management
  • Implemented verifyAgentOwnership() to check NFT ownership on Base
    Sepolia registry
  • Implemented getAgentCard() to fetch agent metadata from various URI
    formats (data URI, IPFS, HTTP)
  • Added static constants for AGENT_REGISTRY_ADDRESS and
    AGENT_CHAIN_CONFIG
  • Minor code formatting fixes in error message construction
+272/-8 
index.ts
Export agent identity types and interfaces                             

src/abstraction/index.ts

  • Exported new agent identity types: AgentIdentityPayload,
    AgentIdentityAssignPayload, AgentIdentityRemovePayload,
    AgentIdentityPayloadType
  • Exported supporting types: DemosOwnershipProof, ERC8004AgentCard,
    ERC8004Endpoint
  • Updated imports to include all new agent-related type definitions
+16/-2   
index.ts
Define ERC-8004 agent identity type structures                     

src/types/abstraction/index.ts

  • Added ERC8004Endpoint interface for agent endpoint configuration
  • Added DemosOwnershipProof interface for signature-based ownership
    verification
  • Added ERC8004AgentCard interface based on EIP-8004 specification
  • Added AgentIdentityPayload interface with agent ID, EVM address,
    chain, transaction hash, and token URI
  • Added AgentIdentityAssignPayload and AgentIdentityRemovePayload
    interfaces
  • Added AgentIdentityPayloadType union type
  • Extended IdentityPayload union to include agent identity types
  • Extended UserPoints breakdown to include agents field
  • Extended UserPoints to include linkedAgents field with agent metadata
+93/-0   
GCREdit.ts
Extend GCR edit types for agent identities                             

src/types/blockchain/GCREdit.ts

  • Added AgentGCRData type alias for agent identity GCR data
  • Updated GCREditIdentity context type to include "agent"
  • Extended GCREditIdentity data union to support agent add and remove
    operations
  • Added agent remove payload structure with agentId and chain fields
+6/-2     
GCRGeneration.ts
Handle agent identity GCR generation                                         

src/websdk/GCRGeneration.ts

  • Added handler case for "agent_identity_assign" to process agent
    identity assignments
  • Added handler case for "agent_identity_remove" to process agent
    identity removals
  • Ensured agent identity data is properly passed through GCR edit
    generation
+10/-1   

Summary by CodeRabbit

  • New Features

    • ERC-8004 agent identity: add, verify, remove, lifecycle integration, and agent-aware identity inference/removal.
    • Generate cryptographic ownership proofs, verify ownership on-chain, and fetch agent cards (base64/IPFS/HTTP).
    • New public types and agent-linked user points and tracking across chains.
  • Documentation

    • Added a comprehensive PR review documenting risks, findings, and recommended fixes for agent identity handling.

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds ERC‑8004 agent identity support: new types and constants, ownership‑proof generation, on‑chain registry interactions (verify owner, fetch tokenURI), lifecycle methods (add/remove/get/verify/getCard), integrates "agent" into identity inference/removal and GCR generation, and resolves tokenURI via base64/IPFS/HTTP.

Changes

Cohort / File(s) Summary
Core Agent Identity Implementation
src/abstraction/Identities.ts
Adds AGENT_REGISTRY_ADDRESS/AGENT_CHAIN_CONFIG; implements generateAgentOwnershipMessage, createAgentOwnershipProof, addAgentIdentity, removeAgentIdentity, getAgentIdentities, verifyAgentOwnership, getAgentCard; integrates ethers for on‑chain reads and tokenURI resolution; includes minor error-message formatting tweak.
Public Exports
src/abstraction/index.ts
Re-exports agent-related types: AgentIdentityPayload, AgentIdentityAssignPayload, AgentIdentityRemovePayload, AgentIdentityPayloadType, DemosOwnershipProof, ERC8004AgentCard, ERC8004Endpoint.
Type Definitions
src/types/abstraction/index.ts
Adds ERC‑8004 typings: BaseAgentIdentityPayload, ERC8004Endpoint, DemosOwnershipProof, ERC8004AgentCard, AgentIdentityPayload, assign/remove payloads, and AgentIdentityPayloadType; extends IdentityPayload union and augments UserPoints with totalPoints, breakdown.agents, and linkedAgents.
GCR Types & Generation
src/types/blockchain/GCREdit.ts, src/websdk/GCRGeneration.ts
Adds AgentGCRData alias; extends GCREditIdentity.context and data to include "agent"; GCR generation now handles agent_identity_assign and agent_identity_remove.
Docs / Review Guidance
PR_REVIEW.md
Adds a detailed PR review document listing security, correctness, and quality issues found in the new agent implementation with proposed fixes and prioritization.

Sequence Diagram

sequenceDiagram
    participant Client
    participant DemosSDK as Demos (Identities)
    participant Signer as Crypto/Signer
    participant Registry as ERC8004_Registry
    participant Chain as EVM_RPC

    Client->>DemosSDK: generateAgentOwnershipMessage(pubKey, agentId, evmAddr)
    DemosSDK->>Signer: format/prepare message
    Signer-->>DemosSDK: message,timestamp,nonce
    DemosSDK-->>Client: {message,timestamp,nonce}

    Client->>DemosSDK: createAgentOwnershipProof(agentId, evmAddr)
    DemosSDK->>Signer: sign(message)
    Signer-->>DemosSDK: signature
    DemosSDK-->>Client: DemosOwnershipProof

    Client->>DemosSDK: addAgentIdentity(payload, referralCode?)
    DemosSDK->>Registry: submit payload/proof (via RPC)
    Registry->>Chain: persist/verify ownership
    Chain-->>Registry: tx receipt
    Registry-->>DemosSDK: RPCResponse
    DemosSDK-->>Client: RPCResponseWithValidityData

    Client->>DemosSDK: verifyAgentOwnership(agentId, expectedOwner)
    DemosSDK->>Registry: ownerOf(agentId)
    Registry-->>DemosSDK: ownerAddress
    DemosSDK-->>Client: boolean

    Client->>DemosSDK: getAgentCard(agentId)
    DemosSDK->>Registry: tokenURI(agentId)
    Registry-->>DemosSDK: tokenURI
    DemosSDK->>Chain: resolve tokenURI (base64/ipfs/http)
    Chain-->>DemosSDK: metadata JSON
    DemosSDK-->>Client: ERC8004AgentCard | null
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Possible security concern

Suggested reviewers

  • tcsenpai

Poem

🐰 I hopped through keys and token lore,
I signed a proof and knocked on the door.
Agents now tied with token and art,
I munched on bytes and gave a happy start. 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Added ERC-8004 agent identity support' directly and clearly summarizes the main change, accurately reflecting the comprehensive agent identity feature implementation across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/erc-8004-agent-identity

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 9, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
SSRF via tokenURI

Description: Untrusted tokenUri values are fetched via axios.get(tokenUri) (and via an IPFS gateway) in
getAgentCard(), which can enable SSRF and internal network probing if this SDK is used in
server-side contexts (e.g., a crafted http://169.254.169.254/... metadata URL).
Identities.ts [1336-1373]

Referred Code
async getAgentCard(agentId: string): Promise<any | null> {
    const registryAbi = [
        "function tokenURI(uint256 tokenId) external view returns (string)",
    ]

    try {
        const provider = new ethers.JsonRpcProvider(
            Identities.AGENT_CHAIN_CONFIG.rpc,
        )
        const contract = new ethers.Contract(
            Identities.AGENT_REGISTRY_ADDRESS,
            registryAbi,
            provider,
        )

        const tokenUri = await contract.tokenURI(agentId)

        // Handle different URI formats
        if (tokenUri.startsWith("data:application/json;base64,")) {
            // Base64 encoded JSON (data URI)
            const base64 = tokenUri.replace(


 ... (clipped 17 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: 🏷️
Silent error swallowing: Network/contract/parse failures in verifyAgentOwnership() and getAgentCard() are silently
swallowed (returning false/null) without any actionable context, making failures
indistinguishable from legitimate “not found/not owner” outcomes.

Referred Code
async verifyAgentOwnership(
    agentId: string,
    expectedOwner: string,
): Promise<boolean> {
    const registryAbi = [
        "function ownerOf(uint256 tokenId) external view returns (address)",
    ]

    try {
        const provider = new ethers.JsonRpcProvider(
            Identities.AGENT_CHAIN_CONFIG.rpc,
        )
        const contract = new ethers.Contract(
            Identities.AGENT_REGISTRY_ADDRESS,
            registryAbi,
            provider,
        )

        const owner = await contract.ownerOf(agentId)
        return owner.toLowerCase() === expectedOwner.toLowerCase()
    } catch (error: any) {


 ... (clipped 54 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: 🏷️
Unvalidated external URI: getAgentCard() fetches arbitrary tokenUri values over HTTP(S) without
allowlisting/validation, creating a risk of unsafe external requests (e.g., SSRF or
unexpected protocols) and lacks validation of key agent payload fields beyond basic
presence/EVM regex checks.

Referred Code
async addAgentIdentity(
    demos: Demos,
    payload: AgentIdentityPayload,
    referralCode?: string,
): Promise<RPCResponseWithValidityData> {
    // Validate the payload
    if (!payload.agentId) {
        throw new Error("Agent ID is required")
    }
    if (!payload.evmAddress) {
        throw new Error("EVM address is required")
    }
    if (!payload.proof) {
        throw new Error("Ownership proof is required")
    }

    // Verify the EVM address format
    const evmPattern = /^0x[0-9a-fA-F]{40}$/
    if (!evmPattern.test(payload.evmAddress)) {
        throw new Error(
            `Invalid EVM address format: ${payload.evmAddress}`,


 ... (clipped 127 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: 🏷️
No audit logging: New critical identity-management actions (agent identity add/remove and ownership proof
creation) do not emit any audit log entries with actor/timestamp/outcome, and it is
unclear if logging is handled elsewhere.

Referred Code
async createAgentOwnershipProof(
    demos: Demos,
    evmAddress: string,
): Promise<DemosOwnershipProof> {
    const demosPublicKey = await demos.getEd25519Address()
    const timestamp = Date.now()
    const message = `I authorize EVM address ${evmAddress} to register an ERC-8004 agent for Demos identity ${demosPublicKey}. Timestamp: ${timestamp}`

    const signature = await demos.crypto.sign(
        demos.algorithm,
        new TextEncoder().encode(message),
    )

    return {
        type: "demos-signature",
        message,
        signature: {
            type: demos.algorithm,
            data: uint8ArrayToHex(signature.signature),
        },
        demosPublicKey,


 ... (clipped 111 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: 🏷️
Logging not evident: The new agent identity flows do not add any structured logging, so it cannot be verified
from this diff whether secure, structured logging is implemented elsewhere for these
operations.

Referred Code
 * @param demos A Demos instance to communicate with the RPC
 * @param address The address to get identities for. Defaults to the connected wallet's address.
 *
 * @returns The identities associated with the address.
 */
async getUDIdentities(demos: Demos, address?: string) {
    return await this.getIdentities(demos, "getUDIdentities", address)
}

// SECTION: ERC-8004 Agent Identities

/**
 * ERC-8004 IdentityRegistry contract address on Base Sepolia
 */
static readonly AGENT_REGISTRY_ADDRESS =
    "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb"

/**
 * Base Sepolia chain configuration for agent identity
 */
static readonly AGENT_CHAIN_CONFIG = {


 ... (clipped 250 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 9, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Externalize hardcoded blockchain configuration

The agent registry address and RPC URL are hardcoded in Identities.ts. This
configuration should be externalized to improve flexibility and support for
other networks.

Examples:

src/abstraction/Identities.ts [1123-1134]
    static readonly AGENT_REGISTRY_ADDRESS =
        "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb"

    /**
     * Base Sepolia chain configuration for agent identity
     */
    static readonly AGENT_CHAIN_CONFIG = {
        chainId: 84532,
        chain: "base",
        subchain: "sepolia",

 ... (clipped 2 lines)

Solution Walkthrough:

Before:

// src/abstraction/Identities.ts
export class Identities {
    static readonly AGENT_REGISTRY_ADDRESS =
        "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb";

    static readonly AGENT_CHAIN_CONFIG = {
        chainId: 84532,
        rpc: "https://sepolia.base.org",
    };

    async verifyAgentOwnership(agentId: string, expectedOwner: string): Promise<boolean> {
        const provider = new ethers.JsonRpcProvider(
            Identities.AGENT_CHAIN_CONFIG.rpc,
        );
        const contract = new ethers.Contract(
            Identities.AGENT_REGISTRY_ADDRESS,
            // ...
        );
        // ...
    }
}

After:

// e.g., in SDK config
const demosConfig = {
  agent: {
    registryAddress: "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb",
    rpcUrl: "https://sepolia.base.org"
  }
}

// src/abstraction/Identities.ts
export class Identities {
    // No more static hardcoded values

    async verifyAgentOwnership(
        demos: Demos, // Config is accessible via Demos instance
        agentId: string,
        expectedOwner: string
    ): Promise<boolean> {
        const provider = new ethers.JsonRpcProvider(demos.config.agent.rpcUrl);
        const contract = new ethers.Contract(
            demos.config.agent.registryAddress,
            // ...
        );
        // ...
    }
}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant design flaw where blockchain configuration is hardcoded, which limits the feature's adaptability to other networks (e.g., mainnet) and is against SDK best practices.

Medium
Possible issue
Use Buffer for base64 decoding
Suggestion Impact:Updated getAgentCard to decode base64 JSON using Buffer.from(..., "base64").toString("utf-8") instead of atob(), improving Node.js compatibility.

code diff:

@@ -1357,7 +1431,7 @@
                     "data:application/json;base64,",
                     "",
                 )
-                const json = atob(base64)
+                const json = Buffer.from(base64, "base64").toString("utf-8")
                 return JSON.parse(json)

Replace the browser-specific atob() with Buffer.from(...,
'base64').toString('utf-8') in getAgentCard to ensure the code runs correctly in
Node.js environments.

src/abstraction/Identities.ts [1336-1379]

 async getAgentCard(agentId: string): Promise<any | null> {
     const registryAbi = [
         "function tokenURI(uint256 tokenId) external view returns (string)",
     ]
 
     try {
         const provider = new ethers.JsonRpcProvider(
             Identities.AGENT_CHAIN_CONFIG.rpc,
         )
         const contract = new ethers.Contract(
             Identities.AGENT_REGISTRY_ADDRESS,
             registryAbi,
             provider,
         )
 
         const tokenUri = await contract.tokenURI(agentId)
 
         // Handle different URI formats
         if (tokenUri.startsWith("data:application/json;base64,")) {
             // Base64 encoded JSON (data URI)
             const base64 = tokenUri.replace(
                 "data:application/json;base64,",
                 "",
             )
-            const json = atob(base64)
+            const json = Buffer.from(base64, "base64").toString("utf-8")
             return JSON.parse(json)
         } else if (tokenUri.startsWith("ipfs://")) {
             // IPFS URI - fetch via gateway
             const ipfsHash = tokenUri.replace("ipfs://", "")
             const response = await axios.get(
                 `https://ipfs.io/ipfs/${ipfsHash}`,
             )
             return response.data
         } else if (tokenUri.startsWith("http")) {
             // HTTP(S) URI
             const response = await axios.get(tokenUri)
             return response.data
         }
 
         return null
     } catch (error) {
         return null
     }
 }

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that atob() is not available in Node.js environments, and replacing it with Buffer ensures cross-environment compatibility, which is a significant improvement for an SDK.

Medium
General
Refactor to remove duplicated code
Suggestion Impact:createAgentOwnershipProof was updated to call generateAgentOwnershipMessage and use its returned message/timestamp instead of duplicating message/timestamp construction (with additional related changes to include agentId in both methods and the returned proof).

code diff:

@@ -1140,31 +1205,39 @@
      * linking an EVM address (which owns the agent NFT) to their Demos identity.
      *
      * @param demosPublicKey The user's Demos public key (hex string)
+     * @param agentId The ERC-8004 token ID being claimed
      * @param evmAddress The EVM address that owns the agent NFT
-     * @returns The message to be signed
+     * @returns Object containing the message and timestamp
      */
     generateAgentOwnershipMessage(
         demosPublicKey: string,
+        agentId: string,
         evmAddress: string,
-    ): string {
+    ): { message: string; timestamp: number } {
         const timestamp = Date.now()
-        return `I authorize EVM address ${evmAddress} to register an ERC-8004 agent for Demos identity ${demosPublicKey}. Timestamp: ${timestamp}`
+        const message = `I authorize EVM address ${evmAddress} to register ERC-8004 agent #${agentId} for Demos identity ${demosPublicKey}. Timestamp: ${timestamp}`
+        return { message, timestamp }
     }
 
     /**
      * Create an ownership proof by signing the ownership message with the Demos wallet.
      *
      * @param demos A Demos instance to sign the message
+     * @param agentId The ERC-8004 token ID being claimed
      * @param evmAddress The EVM address that owns the agent NFT
      * @returns The complete ownership proof object
      */
     async createAgentOwnershipProof(
         demos: Demos,
+        agentId: string,
         evmAddress: string,
     ): Promise<DemosOwnershipProof> {
         const demosPublicKey = await demos.getEd25519Address()
-        const timestamp = Date.now()
-        const message = `I authorize EVM address ${evmAddress} to register an ERC-8004 agent for Demos identity ${demosPublicKey}. Timestamp: ${timestamp}`
+        const { message, timestamp } = this.generateAgentOwnershipMessage(
+            demosPublicKey,
+            agentId,
+            evmAddress,
+        )
 
         const signature = await demos.crypto.sign(
             demos.algorithm,
@@ -1179,6 +1252,7 @@
                 data: uint8ArrayToHex(signature.signature),
             },
             demosPublicKey,
+            agentId,
             evmAddress,
             timestamp,
         }

Refactor createAgentOwnershipProof to use generateAgentOwnershipMessage for
creating the ownership message, thus removing duplicated code and ensuring a
single source of truth.

src/abstraction/Identities.ts [1161-1185]

 async createAgentOwnershipProof(
     demos: Demos,
     evmAddress: string,
 ): Promise<DemosOwnershipProof> {
     const demosPublicKey = await demos.getEd25519Address()
-    const timestamp = Date.now()
-    const message = `I authorize EVM address ${evmAddress} to register an ERC-8004 agent for Demos identity ${demosPublicKey}. Timestamp: ${timestamp}`
+    const { message, timestamp } = this.generateAgentOwnershipMessage(
+        demosPublicKey,
+        evmAddress,
+    )
 
     const signature = await demos.crypto.sign(
         demos.algorithm,
         new TextEncoder().encode(message),
     )
 
     return {
         type: "demos-signature",
         message,
         signature: {
             type: demos.algorithm,
             data: uint8ArrayToHex(signature.signature),
         },
         demosPublicKey,
         evmAddress,
         timestamp,
     }
 }

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies duplicated logic for message creation between createAgentOwnershipProof and generateAgentOwnershipMessage, which could lead to inconsistencies. Consolidating this logic improves maintainability.

Low
Log errors in catch block

Add console.error in the catch block of verifyAgentOwnership to log the error
details, which will aid in debugging ownership verification failures.

src/abstraction/Identities.ts [1304-1328]

 async verifyAgentOwnership(
     agentId: string,
     expectedOwner: string,
 ): Promise<boolean> {
     const registryAbi = [
         "function ownerOf(uint256 tokenId) external view returns (address)",
     ]
 
     try {
         const provider = new ethers.JsonRpcProvider(
             Identities.AGENT_CHAIN_CONFIG.rpc,
         )
         const contract = new ethers.Contract(
             Identities.AGENT_REGISTRY_ADDRESS,
             registryAbi,
             provider,
         )
 
         const owner = await contract.ownerOf(agentId)
         return owner.toLowerCase() === expectedOwner.toLowerCase()
     } catch (error: any) {
         // Token doesn't exist or other error
+        console.error(`Failed to verify agent ownership for agentId ${agentId}:`, error)
         return false
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion to log errors in the catch block is a good practice for improving debuggability, as it provides more context on failures without changing the function's behavior.

Low
  • Update

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/abstraction/Identities.ts:
- Around line 1146-1152: generateAgentOwnershipMessage currently creates its own
timestamp which diverges from the timestamp generated in
createAgentOwnershipProof, causing mismatch during verification; update
generateAgentOwnershipMessage to accept a timestamp argument (or return the
timestamp alongside the string) and have createAgentOwnershipProof call
generateAgentOwnershipMessage with the same timestamp it uses for the proof so
both the message string and proof.timestamp are identical, or if
generateAgentOwnershipMessage is unused, remove it and build the message only
inside createAgentOwnershipProof to guarantee a single source of truth.
🧹 Nitpick comments (4)
src/websdk/GCRGeneration.ts (1)

366-372: Avoid as any type assertion for edit.data.

The as any on line 370 bypasses TypeScript's type checking. Since AgentGCRData is now a valid type in the GCREditIdentity.data union (per src/types/blockchain/GCREdit.ts), you can remove this assertion or use a proper type.

♻️ Suggested improvement
             case "agent_identity_assign": {
                 edit.data = {
                     ...identityPayload.payload,
                     timestamp: tx.content.timestamp,
-                } as any
+                }
                 break
             }
src/abstraction/Identities.ts (3)

1304-1328: Consider distinguishing between "not owner" and "network/contract error" cases.

The current implementation returns false for any error, making it impossible for callers to distinguish between "user doesn't own the agent" vs "network error / contract issue". This could lead to confusing user experiences.

♻️ Suggested improvement
     async verifyAgentOwnership(
         agentId: string,
         expectedOwner: string,
-    ): Promise<boolean> {
+    ): Promise<{ owned: boolean; error?: string }> {
         const registryAbi = [
             "function ownerOf(uint256 tokenId) external view returns (address)",
         ]

         try {
             const provider = new ethers.JsonRpcProvider(
                 Identities.AGENT_CHAIN_CONFIG.rpc,
             )
             const contract = new ethers.Contract(
                 Identities.AGENT_REGISTRY_ADDRESS,
                 registryAbi,
                 provider,
             )

             const owner = await contract.ownerOf(agentId)
-            return owner.toLowerCase() === expectedOwner.toLowerCase()
-        } catch (error: any) {
-            // Token doesn't exist or other error
-            return false
+            return { owned: owner.toLowerCase() === expectedOwner.toLowerCase() }
+        } catch (error) {
+            // Check if token doesn't exist vs network error
+            const errorMessage = error instanceof Error ? error.message : String(error)
+            if (errorMessage.includes("nonexistent token") || errorMessage.includes("ERC721")) {
+                return { owned: false, error: "Token does not exist" }
+            }
+            return { owned: false, error: `Verification failed: ${errorMessage}` }
         }
     }

1362-1368: Consider IPFS gateway reliability and adding request timeout.

The public ipfs.io gateway may be unreliable or rate-limited. Consider making the gateway configurable or adding a timeout to prevent hanging requests.

♻️ Suggested improvement
             } else if (tokenUri.startsWith("ipfs://")) {
                 // IPFS URI - fetch via gateway
                 const ipfsHash = tokenUri.replace("ipfs://", "")
                 const response = await axios.get(
                     `https://ipfs.io/ipfs/${ipfsHash}`,
+                    { timeout: 10000 }
                 )
                 return response.data
             } else if (tokenUri.startsWith("http")) {
                 // HTTP(S) URI
-                const response = await axios.get(tokenUri)
+                const response = await axios.get(tokenUri, { timeout: 10000 })
                 return response.data
             }

1120-1134: Consider environment-based configuration for registry address and RPC endpoint.

The hardcoded registry address and RPC endpoint are currently scoped to Base Sepolia testnet. The codebase already uses process.env.NODE_ENV for environment-based switching in other parts (e.g., nativeBridge.ts). Adopting a similar pattern here—or using a configuration object passed at initialization—would simplify future deployments to Base mainnet or other chains once registry contracts are deployed.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5154833 and f07f8ab.

📒 Files selected for processing (5)
  • src/abstraction/Identities.ts
  • src/abstraction/index.ts
  • src/types/abstraction/index.ts
  • src/types/blockchain/GCREdit.ts
  • src/websdk/GCRGeneration.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Ensure full TypeScript type coverage
Run type checks after changes
Use @/ path aliases instead of relative imports
Use JSDoc format for all new methods and functions
Add // REVIEW: comments before newly added features
Provide clear, actionable error messages
Use descriptive names that clearly express intent

Files:

  • src/abstraction/index.ts
  • src/websdk/GCRGeneration.ts
  • src/types/blockchain/GCREdit.ts
  • src/abstraction/Identities.ts
  • src/types/abstraction/index.ts
🧬 Code graph analysis (3)
src/types/blockchain/GCREdit.ts (2)
src/abstraction/index.ts (1)
  • AgentIdentityPayload (65-65)
src/types/abstraction/index.ts (1)
  • AgentIdentityPayload (315-330)
src/abstraction/Identities.ts (4)
src/websdk/demosclass.ts (1)
  • Demos (48-977)
src/types/abstraction/index.ts (2)
  • DemosOwnershipProof (285-292)
  • AgentIdentityPayload (315-330)
src/encryption/index.ts (1)
  • uint8ArrayToHex (14-14)
src/types/index.ts (1)
  • RPCResponseWithValidityData (152-152)
src/types/abstraction/index.ts (1)
src/abstraction/index.ts (8)
  • ERC8004Endpoint (71-71)
  • DemosOwnershipProof (69-69)
  • ERC8004AgentCard (70-70)
  • AgentIdentityPayload (65-65)
  • AgentIdentityAssignPayload (66-66)
  • AgentIdentityRemovePayload (67-67)
  • AgentIdentityPayloadType (68-68)
  • IdentityPayload (53-53)
🔇 Additional comments (9)
src/websdk/GCRGeneration.ts (1)

374-382: LGTM!

The agent_identity_remove case correctly extends the existing removal pattern and follows the established passthrough behavior for identity removal payloads.

src/abstraction/index.ts (1)

31-39: LGTM!

The new agent identity types are properly imported and re-exported, following the established pattern for other identity types. The public API surface expansion is consistent with the ERC-8004 agent identity support.

Also applies to: 64-72

src/types/blockchain/GCREdit.ts (1)

84-85: LGTM!

The type additions for agent identity support are well-structured:

  • AgentGCRData alias provides a clear naming convention consistent with other GCR data types
  • The context union extension and data union properly cover both add and remove operations
  • Types align correctly with the AgentIdentityPayload definition

Also applies to: 90-90, 100-101

src/types/abstraction/index.ts (3)

285-292: Verify the signature field flexibility is intentional.

The signature field in DemosOwnershipProof accepts both string and { type: string; data: string }. Looking at createAgentOwnershipProof in Identities.ts (line 1177-1180), it creates the object form. Ensure consumers handle both formats consistently.


315-330: LGTM! Well-documented agent identity payload.

The AgentIdentityPayload interface has comprehensive JSDoc documentation for each field and captures all necessary data for ERC-8004 agent identity binding. The structure aligns well with the ownership proof workflow.


370-370: LGTM!

The UserPoints extensions for agent tracking follow the established pattern used for UD domains, with proper optional fields for backward compatibility and appropriate data structures for the agent metadata.

Also applies to: 379-386

src/abstraction/Identities.ts (3)

1226-1264: LGTM! Good client-side validation.

The validation logic properly checks for required fields, validates EVM address format, and ensures the proof's EVM address matches the payload. The case-insensitive comparison for address matching is correct for EVM addresses.


77-80: LGTM!

The context union extension to include "agent" in both inferIdentity and removeIdentity methods follows the established pattern for other identity types and enables the agent identity workflow.

Also applies to: 135-138


1187-1225: LGTM! Excellent documentation.

The new agent identity methods have comprehensive JSDoc documentation with clear descriptions, parameter documentation, and practical usage examples. This follows the coding guidelines for JSDoc format on new methods.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @src/abstraction/Identities.ts:
- Around line 1118-1135: Add a `// REVIEW:` marker comment immediately before
the "SECTION: ERC-8004 Agent Identities" block so the new ERC-8004 feature is
flagged per guidelines; locate the block containing the static symbols
AGENT_REGISTRY_ADDRESS and AGENT_CHAIN_CONFIG (and the "SECTION: ERC-8004 Agent
Identities" comment) and insert the `// REVIEW:` line directly above it.
- Around line 1345-1387: getAgentCard currently uses Buffer.from (breaks in
browsers), performs axios.get without timeouts/size limits, and accepts
unvalidated http:// URIs (SSRF risk). Fix by: (1) replace Buffer.from(...) with
a runtime-safe base64 decode fallback used in getAgentCard (e.g., use Buffer
when available via typeof Buffer !== 'undefined' else use atob + Uint8Array/
TextDecoder to produce a UTF-8 string) for the data: branch; (2) add axios
options { timeout: 10000, responseType: 'json', maxContentLength: 1024 * 100,
maxBodyLength: 1024 * 100 } to all axios.get calls in getAgentCard; and (3)
reject or normalize URIs so only data:application/json;base64:, ipfs://, and
https:// schemes are allowed (do not accept plain http://); return null or throw
a clear error when an unsupported scheme is encountered.
🧹 Nitpick comments (5)
src/abstraction/Identities.ts (5)

23-25: Tighten agent-related types instead of widening with any.

You import AgentIdentityPayload / DemosOwnershipProof, but getAgentCard() returns any | null. If ERC8004AgentCard exists (per PR summary), import and return it to keep TS coverage strong. As per coding guidelines, ensure full TypeScript type coverage.

Proposed diff
 import {
@@
     UDIdentityPayload,
     AgentIdentityPayload,
     DemosOwnershipProof,
+    ERC8004AgentCard,
 } from "@/types/abstraction"
@@
-    async getAgentCard(agentId: string): Promise<any | null> {
+    async getAgentCard(agentId: string): Promise<ERC8004AgentCard | null> {

77-101: inferIdentity() payload typing is getting riskier as contexts expand.

Now that "agent" is supported, payload: any + method: (context + "_identity_assign") as any increases the chance of shipping an invalid payload/method pairing without compile-time feedback. Consider a context→payload mapping type (or overloads) so each context can only accept its correct payload type. As per coding guidelines, ensure full TypeScript type coverage.


135-165: Same typing concern in removeIdentity() as inferIdentity() (now includes "agent").

payload: any makes it easy to send malformed remove payloads (e.g., { agentId } vs { agentId, chain }) with no TS help. Suggest mirroring the same context→payload typing approach here too. As per coding guidelines, ensure full TypeScript type coverage.


1235-1273: addAgentIdentity() should validate more proof/payload invariants.

Right now it checks agentId, evmAddress, proof, EVM address format, and that proof.evmAddress matches payload.evmAddress. Consider also checking:

  • payload.proof.agentId === payload.agentId
  • payload.proof.demosPublicKey === demos.getEd25519Address() (or at least non-empty + hex format)
  • agentId is numeric / BigInt-safe (to avoid accidental garbage values)
  • payload.chain is present and matches supported chains (or restrict the type)

As per coding guidelines, ensure full TypeScript type coverage + actionable errors.


1313-1337: verifyAgentOwnership() should not silently swallow all errors.

Returning false for any RPC/ABI/chain error makes debugging hard (rate limits, misconfigured RPC, wrong registry address, etc.). Suggest at least logging (behind a debug flag) or returning { ok: boolean; reason?: string } for SDK consumers. Also validate expectedOwner format before calling the chain.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f07f8ab and 59ae089.

📒 Files selected for processing (2)
  • src/abstraction/Identities.ts
  • src/types/abstraction/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/abstraction/index.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Ensure full TypeScript type coverage
Run type checks after changes
Use @/ path aliases instead of relative imports
Use JSDoc format for all new methods and functions
Add // REVIEW: comments before newly added features
Provide clear, actionable error messages
Use descriptive names that clearly express intent

Files:

  • src/abstraction/Identities.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
src/abstraction/Identities.ts (2)

94-99: Nice: actionable web2 proof-format error message.

The supported-formats list is a good UX improvement and should reduce support churn.


944-963: DetectSignatureType error string improvement is good.

Clear “expected formats” messaging will help upstream callers quickly fix inputs.

…re/erc-8004-agent-identity

# Conflicts:
#	src/abstraction/Identities.ts
#	src/abstraction/index.ts
#	src/types/abstraction/index.ts
#	src/types/blockchain/GCREdit.ts
#	src/websdk/GCRGeneration.ts
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/types/abstraction/index.ts (2)

465-467: AgentIdentityPayloadType breaks the established payload-union naming convention.

Every other payload union type follows <Context>IdentityPayload (e.g., XmIdentityPayload, Web2IdentityPayload, UdIdentityPayload). The Type suffix is unique to this entry. The name clash with AgentIdentityPayload (the inner data interface) is the root cause; consider renaming the inner data interface to AgentIdentityData to free up AgentIdentityPayload for the union, keeping it consistent with the rest.

♻️ Sketch of the rename
-export interface AgentIdentityPayload { … }          // data
+export interface AgentIdentityData { … }              // data

-export interface AgentIdentityAssignPayload … {
-    payload: AgentIdentityPayload
+export interface AgentIdentityAssignPayload … {
+    payload: AgentIdentityData

-export type AgentIdentityPayloadType =
-    | AgentIdentityAssignPayload
-    | AgentIdentityRemovePayload
+export type AgentIdentityPayload =
+    | AgentIdentityAssignPayload
+    | AgentIdentityRemovePayload
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/abstraction/index.ts` around lines 465 - 467, The union type name
AgentIdentityPayloadType is inconsistent; rename the inner interface
AgentIdentityPayload to AgentIdentityData, then change the union type name from
AgentIdentityPayloadType to AgentIdentityPayload and update all references
accordingly (e.g., adjust any usage sites that referenced AgentIdentityPayload
to now use AgentIdentityData, and ensure AgentIdentityAssignPayload and
AgentIdentityRemovePayload remain in the union). Also update any exports/imports
that mention these symbols so the project compiles with the new
AgentIdentityPayload (union) and AgentIdentityData (inner) names.

403-411: signature: string | { type: string; data: string } — document or remove the unused string variant.

createAgentOwnershipProof always produces the object form. The string variant is never emitted, so either document the scenario in which a raw string signature is valid, or narrow the type to only { type: string; data: string } to avoid misleading callers.

♻️ Proposed narrowing
 export interface DemosOwnershipProof {
     type: "demos-signature"
     message: string
-    signature: string | { type: string; data: string }
+    signature: { type: string; data: string }
     demosPublicKey: string
     agentId: string
     evmAddress: string
     timestamp: number
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/abstraction/index.ts` around lines 403 - 411, The
DemosOwnershipProof.signature union includes an unused string variant; update
the interface DemosOwnershipProof to narrow signature to only the object form {
type: string; data: string } (since createAgentOwnershipProof only emits that
shape), and adjust any dependent types or usages expecting a raw string; if you
instead intend to support raw string signatures, add documentation and ensure
createAgentOwnershipProof can emit that variant. Update references to
DemosOwnershipProof and createAgentOwnershipProof accordingly so callers and
type checks remain consistent.
src/websdk/GCRGeneration.ts (1)

382-388: as any cast for agent_identity_assign data signals a missing AgentGCRData type.

The ud_identity_assign case (Lines 374–380) spreads its payload directly with no cast. The as any here indicates that GCREditIdentity.data in GCREdit.ts doesn't yet include a typed shape for agent identity edits. Adding an AgentGCRData type (analogous to Web2GCRData / Web2TLSNGCRData) would close this type-coverage gap, keep the data field typed end-to-end, and satisfy the full TypeScript type-coverage guideline.

♻️ Sketch of the fix (in GCREdit.ts + here)

In src/types/blockchain/GCREdit.ts, add:

// REVIEW: AgentGCRData — ERC-8004 agent identity GCR data shape
export interface AgentGCRData {
    agentId: string
    evmAddress: string
    chain: string
    txHash: string
    tokenUri: string
    proof: DemosOwnershipProof
    resolverUrl?: string
    timestamp: number
}

Then in GCRGeneration.ts:

             case "agent_identity_assign": {
                 edit.data = {
                     ...identityPayload.payload,
                     timestamp: tx.content.timestamp,
-                } as any
+                } as AgentGCRData
                 break
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websdk/GCRGeneration.ts` around lines 382 - 388, Add a concrete
AgentGCRData type and wire it into the GCREdit identity union, then replace the
unsafe as any cast: declare an interface AgentGCRData (fields: agentId,
evmAddress, chain, txHash, tokenUri, proof: DemosOwnershipProof, optional
resolverUrl, timestamp: number) in the GCREdit type file and update the
GCREditIdentity.data union to include AgentGCRData, then in the
agent_identity_assign case replace the as any cast with the AgentGCRData type so
edit.data is strongly typed.
src/abstraction/Identities.ts (1)

1410-1410: Return type any | null should be ERC8004AgentCard | null.

any | null collapses to any, defeating TypeScript type coverage. ERC8004AgentCard is already defined in this codebase and reflects the correct return structure. This is required by the full TypeScript type coverage guideline.

♻️ Proposed fix
-    async getAgentCard(agentId: string): Promise<any | null> {
+    async getAgentCard(agentId: string): Promise<ERC8004AgentCard | null> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/abstraction/Identities.ts` at line 1410, Change the getAgentCard
signature to return ERC8004AgentCard | null instead of any | null and update any
internal variables/return points to match that type; ensure ERC8004AgentCard is
imported or referenced in this file and adapt the function's returned object
shape (or cast only where unavoidable) so the TypeScript compiler recognizes the
correct ERC8004AgentCard structure for getAgentCard.
🤖 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/abstraction/Identities.ts`:
- Around line 1282-1298: The JSDoc example for addAgentIdentity shows an
incorrect call to createAgentOwnershipProof missing the agentId parameter;
update the example so createAgentOwnershipProof is invoked with (demos, agentId,
evmAddress) to match the actual signature on the Identities class, and ensure
the example uses the same agentId value later when calling addAgentIdentity so
the sample is consistent with the createAgentOwnershipProof and addAgentIdentity
usages.

In `@src/types/abstraction/index.ts`:
- Around line 385-388: Add a `// REVIEW:` comment immediately before the new
ERC-8004 section header so it follows the project's coding guidelines;
specifically insert `// REVIEW:` directly above the `// SECTION ERC-8004 Agent
Identities` line in the file where the `BaseAgentIdentityPayload` interface is
declared to mark this new feature for review.

---

Duplicate comments:
In `@src/abstraction/Identities.ts`:
- Around line 1212-1220: The ownership message generated by
generateAgentOwnershipMessage currently only includes a timestamp and is
vulnerable to replay; update generateAgentOwnershipMessage(demosPublicKey,
agentId, evmAddress) to include a cryptographic nonce and domain-binding info
(chainId and registryAddress) in the human-readable message and return object.
Generate a secure random nonce (e.g., crypto.randomBytes or equivalent), accept
or fetch chainId and registryAddress (add params if needed), embed "chainId:
<chainId>, registry: <registryAddress>, nonce: <nonce>" in the message string
alongside the timestamp, and return { message, timestamp, nonce, chainId,
registryAddress } so callers can verify one-time, domain-scoped proofs. Ensure
existing callers are updated to handle the new return fields.
- Around line 1183-1184: The new section comment "// SECTION: ERC-8004 Agent
Identities" needs the required review marker; update the comment to prepend "//
REVIEW:" so it reads "// REVIEW: // SECTION: ERC-8004 Agent Identities" (or "//
REVIEW: SECTION: ERC-8004 Agent Identities" per project style) to comply with
coding guidelines and indicate this is a new feature area.
- Around line 1428-1446: The tokenUri parsing currently uses Node-only
Buffer.from, makes axios requests without timeouts, and accepts plain http which
violates ERC-8004; update the tokenUri handling block so: replace
Buffer.from(base64, "base64") with a cross-platform base64 decode that uses
Buffer when available and falls back to browser-safe APIs (e.g., globalThis.atob
+ TextDecoder) to produce UTF‑8 JSON; add a sensible request timeout option to
all axios.get calls that fetch IPFS/remote metadata (e.g., pass { timeout: <ms>
} when calling axios.get in the ipfs and http(s) branches); and tighten the URI
check to only allow "https://" (change tokenUri.startsWith("http") to
tokenUri.startsWith("https://")) and otherwise throw or return an error so only
data:application/json;base64, ipfs:// and https:// are accepted. Ensure these
changes are applied to the same tokenUri handling code paths where Buffer.from
and axios.get are referenced.

---

Nitpick comments:
In `@src/abstraction/Identities.ts`:
- Line 1410: Change the getAgentCard signature to return ERC8004AgentCard | null
instead of any | null and update any internal variables/return points to match
that type; ensure ERC8004AgentCard is imported or referenced in this file and
adapt the function's returned object shape (or cast only where unavoidable) so
the TypeScript compiler recognizes the correct ERC8004AgentCard structure for
getAgentCard.

In `@src/types/abstraction/index.ts`:
- Around line 465-467: The union type name AgentIdentityPayloadType is
inconsistent; rename the inner interface AgentIdentityPayload to
AgentIdentityData, then change the union type name from AgentIdentityPayloadType
to AgentIdentityPayload and update all references accordingly (e.g., adjust any
usage sites that referenced AgentIdentityPayload to now use AgentIdentityData,
and ensure AgentIdentityAssignPayload and AgentIdentityRemovePayload remain in
the union). Also update any exports/imports that mention these symbols so the
project compiles with the new AgentIdentityPayload (union) and AgentIdentityData
(inner) names.
- Around line 403-411: The DemosOwnershipProof.signature union includes an
unused string variant; update the interface DemosOwnershipProof to narrow
signature to only the object form { type: string; data: string } (since
createAgentOwnershipProof only emits that shape), and adjust any dependent types
or usages expecting a raw string; if you instead intend to support raw string
signatures, add documentation and ensure createAgentOwnershipProof can emit that
variant. Update references to DemosOwnershipProof and createAgentOwnershipProof
accordingly so callers and type checks remain consistent.

In `@src/websdk/GCRGeneration.ts`:
- Around line 382-388: Add a concrete AgentGCRData type and wire it into the
GCREdit identity union, then replace the unsafe as any cast: declare an
interface AgentGCRData (fields: agentId, evmAddress, chain, txHash, tokenUri,
proof: DemosOwnershipProof, optional resolverUrl, timestamp: number) in the
GCREdit type file and update the GCREditIdentity.data union to include
AgentGCRData, then in the agent_identity_assign case replace the as any cast
with the AgentGCRData type so edit.data is strongly typed.

Critical:
- getAgentCard: cross-platform base64 decoding, axios timeouts, https-only
- Ownership message: add nonce + chainId + registry for replay protection

High:
- getAgentCard return type: any|null → ERC8004AgentCard|null
- Fix JSDoc example missing agentId param
- Remove unnecessary `as any` cast in GCR generation
- Add // REVIEW: markers per coding guidelines

Type changes:
- DemosOwnershipProof.signature narrowed to object form only
- DemosOwnershipProof now includes nonce field
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/types/abstraction/index.ts (1)

467-469: Naming convention differs from other identity payload unions.

Other identity unions follow the pattern XmIdentityPayload, Web2IdentityPayload, PqcIdentityPayload, etc., but this uses AgentIdentityPayloadType. The inner data interface AgentIdentityPayload (line 436) occupies the typical union name.

Consider renaming for consistency in a future refactor:

  • AgentIdentityPayloadAgentIdentityData
  • AgentIdentityPayloadTypeAgentIdentityPayload

Not blocking for this PR since it's functional and changing it would require updates across multiple files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/abstraction/index.ts` around lines 467 - 469, Rename the
inconsistent union and inner interface to match existing conventions by changing
AgentIdentityPayload (the inner data interface) to AgentIdentityData and
AgentIdentityPayloadType (the union) to AgentIdentityPayload; update all
references to AgentIdentityPayloadType -> AgentIdentityPayload and
AgentIdentityPayload -> AgentIdentityData, and ensure the union still composes
AgentIdentityAssignPayload and AgentIdentityRemovePayload so code using those
types continues to compile.
src/abstraction/Identities.ts (2)

1187-1201: Hardcoded testnet configuration is acceptable for now.

The registry address and chain config are currently scoped to Base Sepolia. For mainnet deployment, consider making these configurable (e.g., via constructor options or environment variables). Not blocking for testnet phase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/abstraction/Identities.ts` around lines 1187 - 1201, The
AGENT_REGISTRY_ADDRESS and AGENT_CHAIN_CONFIG constants in Identities.ts are
hardcoded to Base Sepolia; make them configurable so mainnet values can be
injected at runtime by replacing the static constants with configurable options:
add constructor or factory overloads that accept registryAddress and chainConfig
(or read from environment vars) and default to the existing
AGENT_REGISTRY_ADDRESS and AGENT_CHAIN_CONFIG when not provided; update any
references that use AGENT_REGISTRY_ADDRESS and AGENT_CHAIN_CONFIG to use the
instance properties (e.g., this.agentRegistryAddress, this.agentChainConfig) so
tests keep current defaults but deployments can supply mainnet values.

1412-1415: Consider distinguishing ownership failure from errors.

The catch block returns false for any error (network issues, rate limits, RPC failures), making it indistinguishable from "address doesn't own token". This complicates debugging.

Since this is a client-side convenience helper and the node also verifies ownership, this isn't blocking. However, for better developer experience, consider either logging the error or returning a richer result:

💡 Optional improvement
-        } catch (error: any) {
-            // Token doesn't exist or other error
-            return false
+        } catch (error: any) {
+            // Log for debugging - token may not exist, or network/RPC error occurred
+            console.warn(`verifyAgentOwnership failed for agentId ${agentId}:`, error?.message || error)
+            return false
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/abstraction/Identities.ts` around lines 1412 - 1415, The catch in
src/abstraction/Identities.ts currently swallows all errors and returns false,
conflating "doesn't own token" with runtime failures; update the helper (the
ownership-check function in Identities.ts) to either log the caught error (e.g.,
console.error or your logger) before returning false or change the function's
return shape to a richer union (e.g., { owns: boolean, error?: Error } or throw
the error) so callers can distinguish ownership=false from transient errors;
adjust the function signature and call sites accordingly to handle the new
return/throw behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@PR_REVIEW.md`:
- Around line 7-28: The PR_REVIEW.md is stale — it lists "NOT FIXED" for issues
that are already addressed; update or remove those entries: mark the
getAgentCard-related items as fixed (reference decodeBase64ToUtf8 replacing
Buffer.from, axios calls updated with { timeout: 10_000 }, and URLs validated
with startsWith("https://")), and mark the ownership-proof item as fixed
(reference generateAgentOwnershipMessage now including nonce via
ethers.randomBytes(16) and embedding chainId + registry, and updates to
DemosOwnershipProof and createAgentOwnershipProof), or delete the outdated
"CRITICAL — Must Fix" block entirely to avoid confusion.
- Around line 32-68: The PR_REVIEW.md HIGH-items list is stale: update or remove
the entries that are already fixed in code (getAgentCard return type now
Promise<ERC8004AgentCard | null> — ref: getAgentCard, JSDoc example corrected to
include agentId — ref: createAgentOwnershipProof, and missing REVIEW markers
added — ref: the // REVIEW: ERC-8004 Agent Identity markers); also confirm the
GCRGeneration cast issue was handled (remove unnecessary "as any" or cast to
AgentGCRData in GCRGeneration.ts if still present) and then either delete the
resolved rows or mark them as fixed so the document matches the actual code
state before merging.

---

Nitpick comments:
In `@src/abstraction/Identities.ts`:
- Around line 1187-1201: The AGENT_REGISTRY_ADDRESS and AGENT_CHAIN_CONFIG
constants in Identities.ts are hardcoded to Base Sepolia; make them configurable
so mainnet values can be injected at runtime by replacing the static constants
with configurable options: add constructor or factory overloads that accept
registryAddress and chainConfig (or read from environment vars) and default to
the existing AGENT_REGISTRY_ADDRESS and AGENT_CHAIN_CONFIG when not provided;
update any references that use AGENT_REGISTRY_ADDRESS and AGENT_CHAIN_CONFIG to
use the instance properties (e.g., this.agentRegistryAddress,
this.agentChainConfig) so tests keep current defaults but deployments can supply
mainnet values.
- Around line 1412-1415: The catch in src/abstraction/Identities.ts currently
swallows all errors and returns false, conflating "doesn't own token" with
runtime failures; update the helper (the ownership-check function in
Identities.ts) to either log the caught error (e.g., console.error or your
logger) before returning false or change the function's return shape to a richer
union (e.g., { owns: boolean, error?: Error } or throw the error) so callers can
distinguish ownership=false from transient errors; adjust the function signature
and call sites accordingly to handle the new return/throw behavior.

In `@src/types/abstraction/index.ts`:
- Around line 467-469: Rename the inconsistent union and inner interface to
match existing conventions by changing AgentIdentityPayload (the inner data
interface) to AgentIdentityData and AgentIdentityPayloadType (the union) to
AgentIdentityPayload; update all references to AgentIdentityPayloadType ->
AgentIdentityPayload and AgentIdentityPayload -> AgentIdentityData, and ensure
the union still composes AgentIdentityAssignPayload and
AgentIdentityRemovePayload so code using those types continues to compile.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between db50d83 and 80de323.

📒 Files selected for processing (4)
  • PR_REVIEW.md
  • src/abstraction/Identities.ts
  • src/types/abstraction/index.ts
  • src/websdk/GCRGeneration.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/websdk/GCRGeneration.ts

Comment on lines +7 to +28
## CRITICAL — Must Fix

### 1. `getAgentCard()`: Buffer.from breaks in browsers + no timeouts + accepts `http://`
**File:** `src/abstraction/Identities.ts:1428-1447`
**Status:** NOT FIXED

Three issues in one method:
- **`Buffer.from(base64, "base64")`** (line 1434) — crashes in browser environments. This SDK exports from `src/abstraction/` which has no Buffer polyfill.
- **No timeout on `axios.get`** — IPFS gateway (line 1439) and HTTP (line 1445) calls can hang indefinitely.
- **Accepts plain `http://`** (line 1443: `startsWith("http")`) — SSRF risk in server contexts; should restrict to `https://` only.

**Fix:** Add a `decodeBase64ToUtf8` helper with Buffer/atob fallback, add `{ timeout: 10_000 }` to all axios calls, change `"http"` check to `"https://"`.

### 2. Ownership message lacks replay protection / domain separation
**File:** `src/abstraction/Identities.ts:1212-1219`
**Status:** NOT FIXED

`generateAgentOwnershipMessage` only includes a timestamp. No nonce, no chainId, no registry address in the signed payload. Signatures are more reusable than they should be.

**Fix:** Add a cryptographic nonce (e.g. `ethers.hexlify(ethers.randomBytes(16))`), embed chainId + registry address in the message, return `{ message, timestamp, nonce }`. Update `DemosOwnershipProof` type and `createAgentOwnershipProof` accordingly.

> **Note:** If the node enforces strict TTL + one-time nonce server-side, this is lower priority. Confirm with backend team whether the node handles replay prevention. If yes, demote to MEDIUM.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

This review document appears stale — critical issues listed as "NOT FIXED" have been addressed in the code.

Cross-referencing with the actual code in src/abstraction/Identities.ts:

Claimed Issue Document Status Actual Code State
Buffer.from breaks in browsers NOT FIXED ✅ Fixed: decodeBase64ToUtf8 helper at lines 1427-1437
No axios timeouts NOT FIXED ✅ Fixed: { timeout: 10_000 } at lines 1470, 1476
Accepts plain http:// NOT FIXED ✅ Fixed: startsWith("https://") at line 1473
No replay protection/nonce NOT FIXED ✅ Fixed: nonce via ethers.randomBytes(16) + chainId + registry at lines 1218-1230

Either update this document to reflect the current code state or remove it to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PR_REVIEW.md` around lines 7 - 28, The PR_REVIEW.md is stale — it lists "NOT
FIXED" for issues that are already addressed; update or remove those entries:
mark the getAgentCard-related items as fixed (reference decodeBase64ToUtf8
replacing Buffer.from, axios calls updated with { timeout: 10_000 }, and URLs
validated with startsWith("https://")), and mark the ownership-proof item as
fixed (reference generateAgentOwnershipMessage now including nonce via
ethers.randomBytes(16) and embedding chainId + registry, and updates to
DemosOwnershipProof and createAgentOwnershipProof), or delete the outdated
"CRITICAL — Must Fix" block entirely to avoid confusion.

Comment on lines +32 to +68
## HIGH — Should Fix

### 3. `getAgentCard` return type is `any | null`
**File:** `src/abstraction/Identities.ts:1410`
**Status:** NOT FIXED

`any | null` collapses to `any`. `ERC8004AgentCard` is already defined in `src/types/abstraction/index.ts:417-424` and is the correct return type. Also not imported in Identities.ts.

**Fix:** Import `ERC8004AgentCard`, change return to `Promise<ERC8004AgentCard | null>`.

### 4. JSDoc example for `addAgentIdentity` has wrong signature
**File:** `src/abstraction/Identities.ts:1287`
**Status:** NOT FIXED

Example shows:
```ts
const proof = await identities.createAgentOwnershipProof(demos, evmAddress)
```
But actual signature (line 1230) is `createAgentOwnershipProof(demos, agentId, evmAddress)` — `agentId` is missing.

**Fix:** Update example to `createAgentOwnershipProof(demos, "123", "0x...")`.

### 5. `as any` cast in GCR generation for agent_identity_assign
**File:** `src/websdk/GCRGeneration.ts:386`
**Status:** NOT FIXED

The `as any` is unnecessary — `AgentGCRData` (alias for `AgentIdentityPayload`) already exists in `GCREdit.ts:106` and is included in the `GCREditIdentity.data` union at line 123.

**Fix:** Remove `as any` — the spread + timestamp should be assignable directly, or use `as AgentGCRData` if needed.

### 6. Missing `// REVIEW:` markers on new feature sections
**Files:** `src/abstraction/Identities.ts:1183`, `src/types/abstraction/index.ts:385`
**Status:** NOT FIXED

Per project coding guidelines (`.github/copilot-instructions.md`), new features need `// REVIEW:` markers.

**Fix:** Add `// REVIEW: ERC-8004 Agent Identity — new feature` above both section headers.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

HIGH priority items also appear to be resolved in the code.

Claimed Issue Document Status Actual Code State
JSDoc example wrong signature NOT FIXED ✅ Fixed: Line 1301 shows createAgentOwnershipProof(demos, "123", "0x...") with 3 params
Missing // REVIEW: markers NOT FIXED ✅ Fixed: Present at Identities.ts:1184 and types/abstraction/index.ts:385
getAgentCard return type NOT FIXED ✅ Fixed: Return type is Promise<ERC8004AgentCard | null> at line 1439

This document should be updated or removed before merge to avoid misleading future reviewers.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~66-~66: The official name of this software platform is spelled with a capital “H”.
Context: ...T FIXED Per project coding guidelines (.github/copilot-instructions.md), new features...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PR_REVIEW.md` around lines 32 - 68, The PR_REVIEW.md HIGH-items list is
stale: update or remove the entries that are already fixed in code (getAgentCard
return type now Promise<ERC8004AgentCard | null> — ref: getAgentCard, JSDoc
example corrected to include agentId — ref: createAgentOwnershipProof, and
missing REVIEW markers added — ref: the // REVIEW: ERC-8004 Agent Identity
markers); also confirm the GCRGeneration cast issue was handled (remove
unnecessary "as any" or cast to AgentGCRData in GCRGeneration.ts if still
present) and then either delete the resolved rows or mark them as fixed so the
document matches the actual code state before merging.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 4, 2026

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/types/abstraction/index.ts (1)

446-450: Tighten DemosOwnershipProof.signature.type to the algorithm union.

Using string here weakens type safety. Prefer SigningAlgorithm to align with the rest of the abstraction types.

Suggested fix
 export interface DemosOwnershipProof {
@@
-    signature: { type: string; data: string }
+    signature: { type: SigningAlgorithm; data: string }
@@
 }

As per coding guidelines "Ensure full TypeScript type coverage".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/abstraction/index.ts` around lines 446 - 450, The
DemosOwnershipProof interface currently types signature.type as string which
weakens safety; change signature.type to the SigningAlgorithm union used
elsewhere so the interface DemosOwnershipProof (and its signature field)
references SigningAlgorithm instead of string, updating the signature property
type in DemosOwnershipProof to { type: SigningAlgorithm; data: string } to align
with other abstraction types.
src/abstraction/Identities.ts (1)

1420-1441: JSDoc for getAgentCard is currently orphaned.

The block at Line 1420-Line 1425 is separated from getAgentCard by decodeBase64ToUtf8, so the new method lacks attached JSDoc.

Suggested fix
-    /**
-     * Fetch the agent card metadata from the token URI.
-     *
-     * `@param` agentId The ERC-8004 token ID
-     * `@returns` The agent card metadata or null if not found
-     */
     /**
      * Decode a base64 string to UTF-8, with cross-platform support.
      */
     private decodeBase64ToUtf8(base64: string): string {
@@
     }
 
+    /**
+     * Fetch the agent card metadata from the token URI.
+     *
+     * `@param` agentId The ERC-8004 token ID
+     * `@returns` The agent card metadata or null if not found
+     */
     async getAgentCard(agentId: string): Promise<ERC8004AgentCard | null> {

As per coding guidelines "Use JSDoc format for all new methods and functions".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/abstraction/Identities.ts` around lines 1420 - 1441, The JSDoc intended
for getAgentCard is orphaned because decodeBase64ToUtf8 sits between the comment
block and the method; move or duplicate the JSDoc so it directly precedes the
async getAgentCard(agentId: string): Promise<ERC8004AgentCard | null>
declaration (remove the intermediate decodeBase64ToUtf8 placement or place its
own JSDoc above decodeBase64ToUtf8) so the getAgentCard JSDoc is attached to
getAgentCard; ensure the JSDoc describes parameters and return type consistent
with ERC8004AgentCard and matches project JSDoc style.
🤖 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/abstraction/Identities.ts`:
- Around line 1402-1417: The current try/catch around creating the
JsonRpcProvider and calling contract.ownerOf(agentId) swallows all errors and
returns false for transient RPC/network issues; instead, narrow the catch: call
new ethers.JsonRpcProvider(...) and contract.ownerOf(agentId) as before, but in
the catch inspect the error (e.g., error.code or error.message) and only return
false for the specific ERC-721 "nonexistent token" error (or a deterministic
owner-query-not-found message), while rethrowing other errors so callers can
handle RPC/network failures; reference the symbols provider, contract, ownerOf,
Identities.AGENT_REGISTRY_ADDRESS and Identities.AGENT_CHAIN_CONFIG.rpc when
updating the logic.
- Around line 1467-1473: The IPFS gateway handling in the tokenUri branch should
normalize various ipfs:// forms (e.g., ipfs://ipfs/<cid>, ipfs:///<cid>) before
building the gateway URL; instead of using tokenUri.replace("ipfs://", ""),
parse tokenUri to strip the leading scheme and any leading "ipfs/" segment or
extra slashes to produce a clean ipfsHash/path, then use that normalized value
when calling axios.get; update the logic around tokenUri and the ipfsHash
variable in the Identites.ts block handling tokenUri.startsWith("ipfs://") so
the gateway URL is always valid for common IPFS URI variants.
- Around line 1321-1348: The addAgentIdentity payload validation currently only
checks evmAddress; update addAgentIdentity to also assert that
payload.proof.agentId === payload.agentId (throw a clear Error if mismatched)
and, if available, validate payload.proof.demosPublicKey against the connected
identity's public key (e.g. compare payload.proof.demosPublicKey to
connectedIdentity.publicKey or payload.demosPublicKey) to prevent mismatched
proof/payload combos; place these checks alongside the existing
proof/evenAddress validation and include descriptive error messages referencing
agentId and demosPublicKey.

In `@src/types/abstraction/index.ts`:
- Around line 478-493: The AgentIdentityPayload interface does not include the
runtime timestamp added by the agent GCR add path; update the
AgentIdentityPayload type to include an optional timestamp (e.g., timestamp?:
number) so it matches the shape produced by GCRGeneration when adding agents, or
alternatively introduce two types (e.g., AgentIdentityRequest vs
AgentIdentityStored) and use the request type in GCRGeneration to keep persisted
types unchanged; ensure references to AgentIdentityPayload (and usages in
GCRGeneration) are updated to the chosen type so TypeScript reflects the runtime
shape.

In `@src/websdk/GCRGeneration.ts`:
- Around line 382-388: In the GCRGeneration code inside the case
"agent_identity_assign", the generated edit object doesn't carry over optional
referral data; set edit.referralCode to the referral value from the identity
payload (e.g., edit.referralCode = identityPayload.payload.referralCode or
assign conditionally if present) so the referral code is propagated along with
the existing fields like timestamp.

---

Nitpick comments:
In `@src/abstraction/Identities.ts`:
- Around line 1420-1441: The JSDoc intended for getAgentCard is orphaned because
decodeBase64ToUtf8 sits between the comment block and the method; move or
duplicate the JSDoc so it directly precedes the async getAgentCard(agentId:
string): Promise<ERC8004AgentCard | null> declaration (remove the intermediate
decodeBase64ToUtf8 placement or place its own JSDoc above decodeBase64ToUtf8) so
the getAgentCard JSDoc is attached to getAgentCard; ensure the JSDoc describes
parameters and return type consistent with ERC8004AgentCard and matches project
JSDoc style.

In `@src/types/abstraction/index.ts`:
- Around line 446-450: The DemosOwnershipProof interface currently types
signature.type as string which weakens safety; change signature.type to the
SigningAlgorithm union used elsewhere so the interface DemosOwnershipProof (and
its signature field) references SigningAlgorithm instead of string, updating the
signature property type in DemosOwnershipProof to { type: SigningAlgorithm;
data: string } to align with other abstraction types.

ℹ️ Review info
Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3455162e-8686-43ed-ac69-1c0cedf906f5

📥 Commits

Reviewing files that changed from the base of the PR and between 80de323 and 243dce3.

📒 Files selected for processing (5)
  • src/abstraction/Identities.ts
  • src/abstraction/index.ts
  • src/types/abstraction/index.ts
  • src/types/blockchain/GCREdit.ts
  • src/websdk/GCRGeneration.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/abstraction/index.ts
  • src/types/blockchain/GCREdit.ts

Comment on lines +1321 to +1348
// Validate the payload
if (!payload.agentId) {
throw new Error("Agent ID is required")
}
if (!payload.evmAddress) {
throw new Error("EVM address is required")
}
if (!payload.proof) {
throw new Error("Ownership proof is required")
}

// Verify the EVM address format
const evmPattern = /^0x[0-9a-fA-F]{40}$/
if (!evmPattern.test(payload.evmAddress)) {
throw new Error(
`Invalid EVM address format: ${payload.evmAddress}`,
)
}

// Verify the proof contains the correct EVM address
if (
payload.proof.evmAddress.toLowerCase() !==
payload.evmAddress.toLowerCase()
) {
throw new Error(
"Ownership proof EVM address doesn't match payload EVM address",
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate full proof-to-payload binding in addAgentIdentity.

Current checks only bind evmAddress. Add at least proof.agentId === payload.agentId (and ideally proof.demosPublicKey against the connected identity) to prevent inconsistent proof/payload combinations from passing local validation.

Suggested fix
         // Verify the proof contains the correct EVM address
         if (
             payload.proof.evmAddress.toLowerCase() !==
             payload.evmAddress.toLowerCase()
         ) {
             throw new Error(
                 "Ownership proof EVM address doesn't match payload EVM address",
             )
         }
+
+        if (payload.proof.agentId !== payload.agentId) {
+            throw new Error(
+                "Ownership proof agentId doesn't match payload agentId",
+            )
+        }
+
+        const connectedDemosKey = await demos.getEd25519Address()
+        if (payload.proof.demosPublicKey !== connectedDemosKey) {
+            throw new Error(
+                "Ownership proof demosPublicKey doesn't match connected identity",
+            )
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/abstraction/Identities.ts` around lines 1321 - 1348, The addAgentIdentity
payload validation currently only checks evmAddress; update addAgentIdentity to
also assert that payload.proof.agentId === payload.agentId (throw a clear Error
if mismatched) and, if available, validate payload.proof.demosPublicKey against
the connected identity's public key (e.g. compare payload.proof.demosPublicKey
to connectedIdentity.publicKey or payload.demosPublicKey) to prevent mismatched
proof/payload combos; place these checks alongside the existing
proof/evenAddress validation and include descriptive error messages referencing
agentId and demosPublicKey.

Comment on lines +1402 to +1417
try {
const provider = new ethers.JsonRpcProvider(
Identities.AGENT_CHAIN_CONFIG.rpc,
)
const contract = new ethers.Contract(
Identities.AGENT_REGISTRY_ADDRESS,
registryAbi,
provider,
)

const owner = await contract.ownerOf(agentId)
return owner.toLowerCase() === expectedOwner.toLowerCase()
} catch (error: any) {
// Token doesn't exist or other error
return false
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not collapse all verification errors into false.

Returning false for every exception makes transient RPC/network issues indistinguishable from real non-ownership, which can mislead callers and UI flows.

Suggested fix
         try {
@@
             const owner = await contract.ownerOf(agentId)
             return owner.toLowerCase() === expectedOwner.toLowerCase()
         } catch (error: any) {
-            // Token doesn't exist or other error
-            return false
+            const message = String(error?.message || "")
+            if (
+                message.toLowerCase().includes("nonexistent token") ||
+                message.toLowerCase().includes("erc721")
+            ) {
+                return false
+            }
+            throw new Error(`Failed to verify agent ownership: ${message}`)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/abstraction/Identities.ts` around lines 1402 - 1417, The current
try/catch around creating the JsonRpcProvider and calling
contract.ownerOf(agentId) swallows all errors and returns false for transient
RPC/network issues; instead, narrow the catch: call new
ethers.JsonRpcProvider(...) and contract.ownerOf(agentId) as before, but in the
catch inspect the error (e.g., error.code or error.message) and only return
false for the specific ERC-721 "nonexistent token" error (or a deterministic
owner-query-not-found message), while rethrowing other errors so callers can
handle RPC/network failures; reference the symbols provider, contract, ownerOf,
Identities.AGENT_REGISTRY_ADDRESS and Identities.AGENT_CHAIN_CONFIG.rpc when
updating the logic.

Comment on lines +1467 to +1473
} else if (tokenUri.startsWith("ipfs://")) {
// IPFS URI - fetch via gateway
const ipfsHash = tokenUri.replace("ipfs://", "")
const response = await axios.get(
`https://ipfs.io/ipfs/${ipfsHash}`,
{ timeout: 10_000 },
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize IPFS URIs before gateway resolution.

replace("ipfs://", "") fails for common ipfs://ipfs/<cid> URIs and can produce invalid gateway URLs.

Suggested fix
             } else if (tokenUri.startsWith("ipfs://")) {
                 // IPFS URI - fetch via gateway
-                const ipfsHash = tokenUri.replace("ipfs://", "")
+                const ipfsPath = tokenUri.replace(/^ipfs:\/\//, "")
+                const normalizedIpfsPath = ipfsPath.replace(/^ipfs\//, "")
                 const response = await axios.get(
-                    `https://ipfs.io/ipfs/${ipfsHash}`,
+                    `https://ipfs.io/ipfs/${normalizedIpfsPath}`,
                     { timeout: 10_000 },
                 )
                 return response.data
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/abstraction/Identities.ts` around lines 1467 - 1473, The IPFS gateway
handling in the tokenUri branch should normalize various ipfs:// forms (e.g.,
ipfs://ipfs/<cid>, ipfs:///<cid>) before building the gateway URL; instead of
using tokenUri.replace("ipfs://", ""), parse tokenUri to strip the leading
scheme and any leading "ipfs/" segment or extra slashes to produce a clean
ipfsHash/path, then use that normalized value when calling axios.get; update the
logic around tokenUri and the ipfsHash variable in the Identites.ts block
handling tokenUri.startsWith("ipfs://") so the gateway URL is always valid for
common IPFS URI variants.

Comment on lines +478 to +493
export interface AgentIdentityPayload {
/** ERC-8004 agent ID (NFT token ID) */
agentId: string
/** EVM address that owns the agent NFT */
evmAddress: string
/** The chain where the agent is registered (e.g., "base.sepolia") */
chain: string
/** Transaction hash of the agent registration */
txHash: string
/** Token URI pointing to the agent card metadata */
tokenUri: string
/** Ownership proof signed by Demos wallet */
proof: DemosOwnershipProof
/** Optional resolver URL for the agent */
resolverUrl?: string
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

AgentIdentityPayload is missing the timestamp field used by the agent GCR add path.

src/websdk/GCRGeneration.ts (Line 382-Line 386) adds timestamp to agent identity data, but this interface does not represent that runtime shape. Please add an optional timestamp (or split request vs persisted payload types) to keep typing accurate.

Suggested fix
 export interface AgentIdentityPayload {
@@
     /** Optional resolver URL for the agent */
     resolverUrl?: string
+    /** Auto-populated during GCR generation */
+    timestamp?: number
 }

As per coding guidelines "Ensure full TypeScript type coverage".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/abstraction/index.ts` around lines 478 - 493, The
AgentIdentityPayload interface does not include the runtime timestamp added by
the agent GCR add path; update the AgentIdentityPayload type to include an
optional timestamp (e.g., timestamp?: number) so it matches the shape produced
by GCRGeneration when adding agents, or alternatively introduce two types (e.g.,
AgentIdentityRequest vs AgentIdentityStored) and use the request type in
GCRGeneration to keep persisted types unchanged; ensure references to
AgentIdentityPayload (and usages in GCRGeneration) are updated to the chosen
type so TypeScript reflects the runtime shape.

Comment on lines +382 to +388
case "agent_identity_assign": {
edit.data = {
...identityPayload.payload,
timestamp: tx.content.timestamp,
}
break
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Propagate agent referral codes into the generated identity edit.

In the agent_identity_assign branch, edit.referralCode is never set, so optional referral data is dropped for this flow.

Suggested fix
             case "agent_identity_assign": {
                 edit.data = {
                     ...identityPayload.payload,
                     timestamp: tx.content.timestamp,
                 }
+                edit.referralCode = identityPayload.referralCode ?? null
                 break
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websdk/GCRGeneration.ts` around lines 382 - 388, In the GCRGeneration
code inside the case "agent_identity_assign", the generated edit object doesn't
carry over optional referral data; set edit.referralCode to the referral value
from the identity payload (e.g., edit.referralCode =
identityPayload.payload.referralCode or assign conditionally if present) so the
referral code is propagated along with the existing fields like timestamp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants