diff --git a/PR_REVIEW.md b/PR_REVIEW.md new file mode 100644 index 0000000..7336764 --- /dev/null +++ b/PR_REVIEW.md @@ -0,0 +1,139 @@ +# PR #70 Review — ERC-8004 Agent Identity + +Reviewed against current code on branch `feature/erc-8004-agent-identity`. + +--- + +## 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. + +--- + +## 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`. + +### 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. + +--- + +## MEDIUM — Nice to Have + +### 7. `verifyAgentOwnership` silently swallows all errors +**File:** `src/abstraction/Identities.ts:1398-1401` + +Returns `false` for any error (network, rate limit, wrong RPC, etc.) — indistinguishable from "not owner". Consider at minimum a `console.error` or returning `{ owned: boolean; error?: string }`. + +**Decision needed:** Changing return type is breaking. If keeping `boolean`, at least add `console.error` for debugging. If this is a client-side convenience helper (node also verifies), lower priority. + +### 8. `DemosOwnershipProof.signature` union has unused `string` variant +**File:** `src/types/abstraction/index.ts:406` + +Type is `string | { type: string; data: string }` but `createAgentOwnershipProof` (line 1250) always produces the object form. The `string` variant is never emitted. + +**Fix:** Narrow to `{ type: string; data: string }` only, OR document when string variant is valid. + +### 9. `AgentIdentityPayloadType` naming inconsistency +**File:** `src/types/abstraction/index.ts:465-467` + +Every other identity union follows the pattern `XmIdentityPayload`, `Web2IdentityPayload`, etc. This one uses `AgentIdentityPayloadType` with a unique `Type` suffix, clashing with `AgentIdentityPayload` (the inner data interface). + +**Ideal fix:** Rename inner `AgentIdentityPayload` → `AgentIdentityData`, then rename `AgentIdentityPayloadType` → `AgentIdentityPayload`. This is a larger refactor touching imports across files. + +**Pragmatic fix:** Leave as-is if not worth the churn, but note the inconsistency. + +--- + +## LOW / DEFERRED — Won't fix now + +### 10. Hardcoded registry address and RPC +**File:** `src/abstraction/Identities.ts:1188-1199` + +Currently scoped to Base Sepolia testnet. Should eventually be configurable for mainnet deployment. The codebase already uses `process.env.NODE_ENV` elsewhere. Not blocking for testnet phase. + +### 11. `inferIdentity` / `removeIdentity` use `payload: any` +**File:** `src/abstraction/Identities.ts:84, 142` + +Expanding `context` union to include `"agent"` further weakens type safety with `any` payloads. A context→payload discriminated union map would be ideal but is a cross-cutting refactor affecting all identity types, not just agent. + +### 12. Additional validation in `addAgentIdentity` +Could also verify `payload.proof.agentId === payload.agentId`, check `demosPublicKey` format, validate `agentId` is numeric/BigInt-safe. Current validation is sufficient for MVP; node validates server-side. + +--- + +## FALSE POSITIVES — Already Fixed or Not Applicable + +| Review Comment | Status | Reason | +|---|---|---| +| Timestamp inconsistency between `generateAgentOwnershipMessage` and `createAgentOwnershipProof` | **FIXED** in commit 59ae089 | `createAgentOwnershipProof` now calls `generateAgentOwnershipMessage` and shares timestamp (line 1236) | +| Refactor to remove duplicated message code | **FIXED** in commit 59ae089 | Same fix as above — single source of truth now | +| `AgentGCRData` type missing from `GCREdit.ts` | **NOT AN ISSUE** | `AgentGCRData` already exists at `GCREdit.ts:106` and is in the data union at line 123 | +| `atob()` → `Buffer.from()` suggestion (qodo) | **ALREADY APPLIED** | Code already uses `Buffer.from` (line 1434). The real issue is Buffer itself not being browser-safe | +| No audit logging | **NOT APPLICABLE** | SDK is a client library; audit logging belongs in the node/server, not the SDK | +| No structured logging | **NOT APPLICABLE** | Same — SDK consumers handle their own logging | +| Ticket compliance (no ticket provided) | **NOISE** | Automated check, not a code issue | +| Codebase duplication compliance | **NOISE** | Not configured, automated check | + +--- + +## Summary + +| Priority | Count | Items | +|---|---|---| +| CRITICAL | 2 | getAgentCard safety (#1), replay protection (#2) | +| HIGH | 4 | Return type (#3), JSDoc (#4), as any (#5), REVIEW markers (#6) | +| MEDIUM | 3 | Error swallowing (#7), signature type (#8), naming (#9) | +| LOW/DEFERRED | 3 | Config (#10), payload typing (#11), extra validation (#12) | +| FALSE POSITIVE | 8 | Already fixed or not applicable | diff --git a/src/abstraction/Identities.ts b/src/abstraction/Identities.ts index 0985c04..5cefb97 100644 --- a/src/abstraction/Identities.ts +++ b/src/abstraction/Identities.ts @@ -2,6 +2,7 @@ // This should be able to query and set the GCR identities for a Demos address import axios from "axios" +import { ethers } from "ethers" import { XMCoreTargetIdentityPayload, Web2CoreTargetIdentityPayload, @@ -19,6 +20,9 @@ import { FindDemosIdByWeb2IdentityQuery, FindDemosIdByWeb3IdentityQuery, UDIdentityPayload, + AgentIdentityPayload, + DemosOwnershipProof, + ERC8004AgentCard, NomisWalletIdentity, EthosWalletIdentity, EthosIdentityRemoveData, @@ -79,7 +83,7 @@ export class Identities { */ private async inferIdentity( demos: Demos, - context: "xm" | "web2" | "pqc" | "ud" | "nomis" | "ethos" | "tlsn", + context: "xm" | "web2" | "pqc" | "ud" | "agent" | "nomis" | "ethos" | "tlsn", payload: any, ): Promise { if (context === "web2") { @@ -95,9 +99,9 @@ export class Identities { ) { // construct informative error message const errorMessage = `Invalid ${payload.context - } proof format. Supported formats are: ${this.formats.web2[ - payload.context - ].join(", ")}` + } proof format. Supported formats are: ${this.formats.web2[ + payload.context + ].join(", ")}` throw new Error(errorMessage) } } @@ -137,7 +141,7 @@ export class Identities { */ private async removeIdentity( demos: Demos, - context: "xm" | "web2" | "pqc" | "ud" | "nomis" | "ethos" | "tlsn", + context: "xm" | "web2" | "pqc" | "ud" | "agent" | "nomis" | "ethos" | "tlsn", payload: any, ): Promise { const tx = DemosTransactions.empty() @@ -1022,7 +1026,7 @@ export class Identities { throw new Error( `Unrecognized address format: ${address}. ` + - `Expected EVM (0x...) or Solana (base58) address.`, + `Expected EVM (0x...) or Solana (base58) address.`, ) } @@ -1179,6 +1183,309 @@ export class Identities { return await this.getIdentities(demos, "getUDIdentities", address) } + // REVIEW: ERC-8004 Agent Identity — new feature + // 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 = { + chainId: 84532, + chain: "base", + subchain: "sepolia", + rpc: "https://sepolia.base.org", + } + + /** + * Generate the ownership proof message for ERC-8004 agent registration. + * + * This message must be signed by the user's Demos wallet to authorize + * 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 Object containing the message and timestamp + */ + generateAgentOwnershipMessage( + demosPublicKey: string, + agentId: string, + evmAddress: string, + ): { message: string; timestamp: number; nonce: string } { + const timestamp = Date.now() + const nonce = ethers.hexlify(ethers.randomBytes(16)) + const message = [ + "Demos GCR: link ERC-8004 agent identity", + `ChainId: ${Identities.AGENT_CHAIN_CONFIG.chainId}`, + `Registry: ${Identities.AGENT_REGISTRY_ADDRESS}`, + `AgentId: ${agentId}`, + `EvmOwner: ${evmAddress}`, + `DemosPublicKey: ${demosPublicKey}`, + `TimestampMs: ${timestamp}`, + `Nonce: ${nonce}`, + ].join("\n") + return { message, timestamp, nonce } + } + + /** + * 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 { + const demosPublicKey = await demos.getEd25519Address() + const { message, timestamp, nonce } = + this.generateAgentOwnershipMessage( + demosPublicKey, + agentId, + 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, + agentId, + evmAddress, + timestamp, + nonce, + } + } + + /** + * Add an ERC-8004 agent identity to the GCR. + * + * This links an ERC-8004 agent NFT (registered on Base Sepolia) to a Demos identity. + * + * Requirements: + * - User must have an EVM wallet linked to their Demos identity + * - That EVM wallet must own the agent NFT + * - User must sign an ownership proof with their Demos wallet + * + * @param demos A Demos instance to communicate with the RPC + * @param payload The agent identity payload containing: + * - agentId: The ERC-8004 token ID + * - evmAddress: The EVM address owning the agent + * - chain: The chain where agent is registered (e.g., "base.sepolia") + * - txHash: The registration transaction hash + * - tokenUri: The token URI pointing to agent card metadata + * - proof: The ownership proof signed by Demos wallet + * @param referralCode Optional referral code for points + * @returns The validity data of the identity transaction + * + * @example + * ```typescript + * const identities = new Identities() + * + * // Create ownership proof + * const proof = await identities.createAgentOwnershipProof(demos, "123", "0x...") + * + * // Add agent identity + * await identities.addAgentIdentity(demos, { + * agentId: "123", + * evmAddress: "0x...", + * chain: "base.sepolia", + * txHash: "0x...", + * tokenUri: "ipfs://...", + * proof + * }) + * ``` + */ + async addAgentIdentity( + demos: Demos, + payload: AgentIdentityPayload, + referralCode?: string, + ): Promise { + // 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", + ) + } + + return await this.inferIdentity(demos, "agent", { + ...payload, + referralCode, + }) + } + + /** + * Remove an ERC-8004 agent identity from the GCR. + * + * @param demos A Demos instance to communicate with the RPC + * @param agentId The ERC-8004 token ID to remove + * @param chain The chain where the agent is registered (e.g., "base.sepolia") + * @returns The validity data response from the RPC + */ + async removeAgentIdentity( + demos: Demos, + agentId: string, + chain: string = "base.sepolia", + ): Promise { + return await this.removeIdentity(demos, "agent", { agentId, chain }) + } + + /** + * Get the ERC-8004 agent identities associated with an address. + * + * @param demos A Demos instance to communicate with the RPC + * @param address The Demos address to get agent identities for. + * Defaults to the connected wallet's address. + * @returns The agent identities associated with the address. + */ + async getAgentIdentities(demos: Demos, address?: string) { + return await this.getIdentities(demos, "getAgentIdentities", address) + } + + /** + * Verify that an EVM address owns a specific ERC-8004 agent NFT. + * + * This is a client-side helper to verify ownership before submitting + * the identity to the network. The node will also verify this. + * + * @param agentId The ERC-8004 token ID + * @param expectedOwner The expected EVM address owner + * @returns True if the address owns the agent, false otherwise + */ + async verifyAgentOwnership( + agentId: string, + expectedOwner: string, + ): Promise { + 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 + } + } + + /** + * 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 { + if (typeof Buffer !== "undefined") { + return Buffer.from(base64, "base64").toString("utf-8") + } + if (typeof atob !== "undefined") { + const bin = atob(base64) + const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0)) + return new TextDecoder().decode(bytes) + } + throw new Error("Base64 decoding not supported in this runtime") + } + + async getAgentCard(agentId: string): Promise { + 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 = this.decodeBase64ToUtf8(base64) + 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}`, + { timeout: 10_000 }, + ) + return response.data + } else if (tokenUri.startsWith("https://")) { + // HTTPS URI only — plain http is rejected for security + const response = await axios.get(tokenUri, { + timeout: 10_000, + }) + return response.data + } + + return null + } catch (error) { + return null + } + } + /** * Fetch a Nomis score for a wallet. * diff --git a/src/abstraction/index.ts b/src/abstraction/index.ts index d8dbc3d..51b64ec 100644 --- a/src/abstraction/index.ts +++ b/src/abstraction/index.ts @@ -29,6 +29,13 @@ import { TelegramAttestationPayload, TelegramSignedAttestation, DiscordProof, + AgentIdentityPayload, + AgentIdentityAssignPayload, + AgentIdentityRemovePayload, + AgentIdentityPayloadType, + DemosOwnershipProof, + ERC8004AgentCard, + ERC8004Endpoint, NomisWalletIdentity, EthosWalletIdentity, EthosIdentityRemoveData, @@ -60,6 +67,13 @@ export { TelegramAttestationPayload, TelegramSignedAttestation, DiscordProof, + AgentIdentityPayload, + AgentIdentityAssignPayload, + AgentIdentityRemovePayload, + AgentIdentityPayloadType, + DemosOwnershipProof, + ERC8004AgentCard, + ERC8004Endpoint, NomisWalletIdentity, EthosWalletIdentity, EthosIdentityRemoveData, diff --git a/src/types/abstraction/index.ts b/src/types/abstraction/index.ts index 97ec9a8..99aadae 100644 --- a/src/types/abstraction/index.ts +++ b/src/types/abstraction/index.ts @@ -424,12 +424,99 @@ export type TLSNIdentityPayload = | TLSNIdentityAssignPayload | TLSNIdentityRemovePayload +// REVIEW: ERC-8004 Agent Identity types — new feature +// SECTION ERC-8004 Agent Identities +export interface BaseAgentIdentityPayload { + context: "agent" +} + +/** + * ERC-8004 Agent Identity endpoint configuration + */ +export interface ERC8004Endpoint { + name: string + endpoint: string + version?: string +} + +/** + * Demos ownership proof for linking EVM address to Demos identity + * This proves the EVM address owner authorized the agent registration + */ +export interface DemosOwnershipProof { + type: "demos-signature" + message: string + signature: { type: string; data: string } + demosPublicKey: string + agentId: string + evmAddress: string + timestamp: number + nonce: string +} + +/** + * ERC-8004 Agent Card data structure + * Based on https://eips.ethereum.org/EIPS/eip-8004 + */ +export interface ERC8004AgentCard { + type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1" + name: string + description?: string + endpoints: ERC8004Endpoint[] + supportedTrust?: string[] + proof?: DemosOwnershipProof +} + +/** + * Agent identity payload for assigning an ERC-8004 agent to a Demos identity + * + * Requirements: + * - User must have an EVM identity linked to their Demos account + * - The EVM wallet must own the ERC-8004 agent NFT on Base Sepolia + * - User must sign ownership proof with their Demos wallet + */ +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 +} + +export interface AgentIdentityAssignPayload extends BaseAgentIdentityPayload { + method: "agent_identity_assign" + payload: AgentIdentityPayload + referralCode?: string +} + +export interface AgentIdentityRemovePayload extends BaseAgentIdentityPayload { + method: "agent_identity_remove" + payload: { + agentId: string + chain: string + } +} + +export type AgentIdentityPayloadType = + | AgentIdentityAssignPayload + | AgentIdentityRemovePayload + // SECTION Final payload type export type IdentityPayload = | XmIdentityPayload | Web2IdentityPayload | PqcIdentityPayload | UdIdentityPayload + | AgentIdentityPayloadType | NomisIdentityPayload | EthosIdentityPayload | TLSNIdentityPayload @@ -446,6 +533,7 @@ export interface UserPoints { telegram?: number } udDomains?: { [domain: string]: number } + agents?: { [agentId: string]: number } nomisScores?: { [chain: string]: number } ethosScores?: { [chain: string]: number } referrals: number @@ -456,6 +544,14 @@ export interface UserPoints { linkedUDDomains?: { [network: string]: string[] } + linkedAgents?: { + [chain: string]: Array<{ + agentId: string + evmAddress: string + tokenUri?: string + timestamp: number + }> + } linkedNomisIdentities: NomisWalletIdentity[] linkedEthosIdentities?: EthosWalletIdentity[] lastUpdated: Date diff --git a/src/types/blockchain/GCREdit.ts b/src/types/blockchain/GCREdit.ts index 85bd0bd..5f0f0f3 100644 --- a/src/types/blockchain/GCREdit.ts +++ b/src/types/blockchain/GCREdit.ts @@ -5,6 +5,7 @@ import { PqcIdentityRemovePayload, UDIdentityPayload, XMCoreTargetIdentityPayload, + AgentIdentityPayload, NomisWalletIdentity, EthosWalletIdentity, TLSNIdentityContext, @@ -103,11 +104,13 @@ export interface PQCIdentityGCREditData { export type UdGCRData = UDIdentityPayload +export type AgentGCRData = AgentIdentityPayload + export interface GCREditIdentity { type: "identity" isRollback: boolean account: string - context: "xm" | "web2" | "pqc" | "nomis" | "ud" | "ethos" | "tlsn" + context: "xm" | "web2" | "pqc" | "ud" | "agent" | "nomis" | "ethos" | "tlsn" operation: "add" | "remove" data: | Web2GCRData // web2 add or remove identity @@ -118,6 +121,8 @@ export interface GCREditIdentity { | PqcIdentityRemovePayload["payload"] // pqc remove identity | UdGCRData // ud add identity | { domain: string } // ud remove identity + | AgentGCRData // agent add identity + | { agentId: string; chain: string } // agent remove identity | NomisWalletIdentity // nomis add/remove identity | EthosWalletIdentity // ethos add/remove identity txhash: string diff --git a/src/websdk/GCRGeneration.ts b/src/websdk/GCRGeneration.ts index 90509a3..0aab600 100644 --- a/src/websdk/GCRGeneration.ts +++ b/src/websdk/GCRGeneration.ts @@ -379,6 +379,14 @@ export class HandleIdentityOperations { break } + case "agent_identity_assign": { + edit.data = { + ...identityPayload.payload, + timestamp: tx.content.timestamp, + } + break + } + case "nomis_identity_assign": { edit.data = identityPayload.payload break @@ -419,6 +427,7 @@ export class HandleIdentityOperations { case "web2_identity_remove": case "pqc_identity_remove": case "ud_identity_remove": + case "agent_identity_remove": case "nomis_identity_remove": case "ethos_identity_remove": case "tlsn_identity_remove": {