Skip to content

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Dec 8, 2025

Description of change

send messages on eCurrency sent or received

Issue Number

closes #519

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added messaging system with message creation, updates, and archival capabilities
    • Integrated transaction notifications that notify users of transfer activity
    • Added eVault platform integration for enhanced identity and profile management
    • Enhanced group management with direct messaging and system message support
  • Chores

    • Added graphql-request dependency for API integration
    • Updated database schema with new message and user mapping tables

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

This PR introduces messaging capabilities and eVault integration to the eCurrency platform. It adds new Message and UserEVaultMapping entities, implements MessageService and PlatformEVaultService for data management, integrates message handling into WebhookController, adds transaction notification dispatch, and extends database schema with corresponding migration and mapping configurations.

Changes

Cohort / File(s) Summary
Dependencies
platforms/eCurrency-api/package.json
Added graphql-request ^6.1.0 dependency for GraphQL client support
Core Entities
platforms/eCurrency-api/src/database/entities/Message.ts, platforms/eCurrency-api/src/database/entities/UserEVaultMapping.ts
Introduced new Message entity with sender, text, group, isSystemMessage, voteId, and archive/timestamp fields; introduced UserEVaultMapping entity for local-to-eVault user mappings with profile data storage
Entity Relationships
platforms/eCurrency-api/src/database/entities/Group.ts, platforms/eCurrency-api/src/database/data-source.ts
Added one-to-many relationship from Group to Message; registered Message and UserEVaultMapping entities in data source
Database
platforms/eCurrency-api/src/database/migrations/1765208128946-migration.ts
Created migration to establish messages and user_evault_mappings tables with foreign key constraints to users and groups
Message Service
platforms/eCurrency-api/src/services/MessageService.ts
New service encapsulating message CRUD operations, system message creation, message archival, and retrieval by user/group with relation loading
eVault Integration
platforms/eCurrency-api/src/services/PlatformEVaultService.ts
New singleton service managing platform eVault existence checks, creation flow with entropy/provisioning, endpoint resolution, and profile updates; integrates GraphQL client for eVault communication
Notification Service
platforms/eCurrency-api/src/services/TransactionNotificationService.ts
New service orchestrating transaction notifications via eCurrency platform user, mutual chat creation, and recipient dispatch to both direct users and group admins
Webhook Handler
platforms/eCurrency-api/src/controllers/WebhookController.ts
Extended to handle messages table events: creates/updates Message entities, validates sender and group presence, distinguishes system vs. regular messages, manages local-to-global ID mappings
Service Updates
platforms/eCurrency-api/src/services/GroupService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Enhanced GroupService with findGroupByMembers helper and DM-deduplication logic; modified createGroup signature to accept memberIds and group properties; updated LedgerService to invoke transaction notifications post-transfer
Web3 Adapter
platforms/eCurrency-api/src/web3adapter/mappings/message.mapping.json
New mapping configuration for messages table, defining local-to-universal field mappings (text→content, sender→users, group→groups, etc.)
Subscriber Enrichment
platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts
Added enrichMessageEntity method to hydrate Message entities with full Group and Sender relations before emission; extended relation loading for messages table
Initialization
platforms/eCurrency-api/src/index.ts
Added PlatformEVaultService integration: checks and creates platform eVault on startup with logged confirmation

Sequence Diagram(s)

sequenceDiagram
    participant Web3 as Web3 Adapter
    participant WH as WebhookController
    participant MS as MessageService
    participant DB as Database
    participant Sub as Subscriber
    participant DS as Downstream

    Web3->>WH: webhook event<br/>(tableName: messages)
    activate WH
    WH->>MS: extract sender & group<br/>from payload
    WH->>MS: createMessage() or<br/>updateMessage()
    activate MS
    MS->>DB: validate sender exists
    MS->>DB: validate group exists
    MS->>DB: save Message entity
    MS-->>WH: Message (with ID mapping)
    deactivate MS
    WH->>DB: store local→global<br/>ID mapping
    deactivate WH
    
    Sub->>DB: listen for INSERT<br/>on messages
    activate Sub
    Sub->>MS: enrichMessageEntity()
    MS->>DB: load Message with<br/>sender & group relations
    MS-->>Sub: fully hydrated Message
    Sub->>DS: emit enriched<br/>message payload
    deactivate Sub
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Multiple new services with inter-dependencies: MessageService, PlatformEVaultService, and TransactionNotificationService require careful validation of error handling and data consistency across repositories
  • Database schema and entity relationships: New entities, migrations, and TypeORM relations (particularly Group↔Message and User↔Message) need verification for correctness and cascading constraints
  • Webhook integration logic: Message creation, update, and ID mapping pathways in WebhookController span multiple conditional branches and system message handling variations
  • Service signature changes: GroupService.createGroup parameter refactoring (participantIds → memberIds + new properties) and DM-deduplication logic may have downstream impacts
  • eVault integration complexity: PlatformEVaultService singleton pattern, GraphQL client management, exponential backoff retry, and profile updates introduce nuanced state management

Possibly related PRs

  • Feat/ereputation adapter #439: Directly overlaps on Message entity, MessageService, message mapping, webhook handling, and database migrations—same message infrastructure
  • chore: move eCurrency #471: Shares modifications to WebhookController, data-source entities, Group entity updates, and web3adapter subscriber logic; this PR builds upon that scaffolding with message/eVault specifics
  • Fix/group chat fix #300: Parallel implementation of message entities, MessageService, message mappings, and system message webhook handling with shared entity/database patterns

Suggested reviewers

  • sosweetham
  • xPathin

Poem

🐰 A rabbit hops through messages bright,
eVaults now mapped, transactions in flight,
System notes flutter, DMs take shape,
With webhooks and services—no data escape!
The platform now chats, let the eCurrency sing! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning While most changes align with sending transaction messages, several additions appear out-of-scope: UserEVaultMapping entity, PlatformEVaultService, and graphql-request dependency seem unrelated to the core transaction messaging objective and lack clear connection to issue #519. Clarify the relationship between eVault integration and transaction messaging, or move eVault-related changes (UserEVaultMapping, PlatformEVaultService) to a separate PR focused on eVault infrastructure.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat/ecurrency transaction messages' clearly describes the primary feature being added: transaction messaging for eCurrency, directly reflecting the main change in the changeset.
Description check ✅ Passed The PR description follows the template with all required sections completed: change description, linked issue (#519), type of change (New), tested state, and a complete checklist with all items marked as done.
Linked Issues check ✅ Passed The PR implements the core objective from issue #519 by adding transaction message functionality via WebhookController, MessageService, and TransactionNotificationService to send eCurrency debit/credit messages.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ecurrency-transaction-messages

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.

@coodos coodos marked this pull request as ready for review December 8, 2025 16:11
Copy link
Contributor

@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: 12

🧹 Nitpick comments (14)
platforms/eCurrency-api/src/services/GroupService.ts (3)

58-77: Performance concern: fallback loads all private groups into memory.

For non-DM groups, this query fetches every private group and filters in-memory. This will degrade as the number of private groups grows.

Consider applying the same subquery approach used for DMs:

-        // Fallback to general search for other group sizes
-        const groups = await this.groupRepository
-            .createQueryBuilder("group")
-            .leftJoinAndSelect("group.members", "members")
-            .where("group.isPrivate = :isPrivate", { isPrivate: true })
-            .getMany();
+        // Use subquery approach for any group size
+        const groups = await this.groupRepository
+            .createQueryBuilder("group")
+            .leftJoinAndSelect("group.members", "members")
+            .where("group.isPrivate = :isPrivate", { isPrivate: true })
+            .andWhere((qb) => {
+                const subQuery = qb.subQuery()
+                    .select("gm.group_id")
+                    .from("group_members", "gm")
+                    .where("gm.user_id IN (:...memberIds)", { memberIds: sortedMemberIds })
+                    .groupBy("gm.group_id")
+                    .having("COUNT(DISTINCT gm.user_id) = :memberCount", { 
+                        memberCount: sortedMemberIds.length 
+                    })
+                    .getQuery();
+                return "group.id IN " + subQuery;
+            })
+            .getMany();

119-124: Handle empty adminIds to avoid potential query issues.

When adminIds is empty (the default), In([]) can produce unexpected SQL behavior in some TypeORM versions. The subsequent length check would also incorrectly throw "One or more admins not found" since 0 !== 0 is false, but this is fragile.

+        let admins: User[] = [];
+        if (adminIds.length > 0) {
-        const admins = await this.userRepository.findBy({
-            id: In(adminIds),
-        });
-        if (admins.length !== adminIds.length) {
-            throw new Error("One or more admins not found");
-        }
+            admins = await this.userRepository.findBy({
+                id: In(adminIds),
+            });
+            if (admins.length !== adminIds.length) {
+                throw new Error("One or more admins not found");
+            }
+        }

104-104: Simplify redundant condition.

name.startsWith("eCurrency Chat") is a subset of name.includes("eCurrency Chat"), making the startsWith check redundant.

-        if (isPrivate && (name.startsWith("eCurrency Chat") || name.includes("eCurrency Chat")) && memberIds.length === 2) {
+        if (isPrivate && name.includes("eCurrency Chat") && memberIds.length === 2) {
platforms/eCurrency-api/src/database/entities/Message.ts (1)

12-40: LGTM! Entity structure is well-designed.

The Message entity properly handles system messages with nullable sender, and the timestamps and archival flag are appropriately configured.

One minor suggestion: consider adding an explicit @JoinColumn() on the group relation for clarity, though TypeORM will infer it correctly.

     @ManyToOne(() => Group, (group) => group.messages)
+    @JoinColumn({ name: "groupId" })
     group!: Group;

Don't forget to import JoinColumn from typeorm if you apply this.

platforms/eCurrency-api/src/services/LedgerService.ts (1)

131-147: Good defensive pattern with try/catch, but consider service reuse.

The try/catch ensures notification failures don't break transfers—this is the right approach for a non-critical side effect.

However, instantiating TransactionNotificationService on every transfer is inefficient since it creates multiple service instances (UserService, GroupService, MessageService) each time.

Consider injecting or caching the service:

 export class LedgerService {
     ledgerRepository: Repository<Ledger>;
     currencyRepository: Repository<Currency>;
+    private notificationService: TransactionNotificationService;

     constructor() {
         this.ledgerRepository = AppDataSource.getRepository(Ledger);
         this.currencyRepository = AppDataSource.getRepository(Currency);
+        this.notificationService = new TransactionNotificationService();
     }

Then in transfer:

-            const notificationService = new TransactionNotificationService();
-            await notificationService.sendTransactionNotifications(
+            await this.notificationService.sendTransactionNotifications(
platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts (2)

60-97: Consider consolidating enrichment logic.

enrichMessageEntity duplicates relation-loading logic that could be handled by extending enrichEntity. Currently, afterInsert calls enrichEntity first (lines 105-109) and then enrichMessageEntity (line 114) for messages, resulting in multiple database queries for the same entity.

Consider either:

  1. Extending enrichEntity to handle Message-specific relations
  2. Or skipping enrichEntity for messages and only calling enrichMessageEntity

112-117: Consider using entity metadata instead of hardcoded table name.

Using event.metadata.tableName === "messages" works but is fragile if the table name changes. Consider comparing against the entity class instead:

-        if (event.metadata.tableName === "messages" && entity) {
+        if (event.metadata.target === Message && entity) {

This requires importing the Message entity but provides type safety.

platforms/eCurrency-api/src/services/TransactionNotificationService.ts (2)

335-355: Sequential notification sending can be parallelized.

Notifications to sender and receiver users are sent sequentially. For group accounts with multiple admins, this compounds delays. Consider using Promise.allSettled to parallelize:

-            for (const senderUser of senderUsers) {
-                console.log(`📤 Sending transaction notification to sender: ${senderUser.id} (${senderAccountType}:${senderId})`);
-                await this.sendNotificationToUser(senderUser.id, {
-                    ...transactionDetails,
-                    isSender: true,
-                    accountId: senderId,
-                    accountType: senderAccountType,
-                });
-            }
-
-            for (const receiverUser of receiverUsers) {
-                console.log(`📥 Sending transaction notification to receiver: ${receiverUser.id} (${receiverAccountType}:${receiverId})`);
-                await this.sendNotificationToUser(receiverUser.id, {
-                    ...transactionDetails,
-                    isSender: false,
-                    accountId: receiverId,
-                    accountType: receiverAccountType,
-                });
-            }
+            const notificationPromises = [
+                ...senderUsers.map(user => 
+                    this.sendNotificationToUser(user.id, { ...transactionDetails, isSender: true, accountId: senderId, accountType: senderAccountType })
+                ),
+                ...receiverUsers.map(user => 
+                    this.sendNotificationToUser(user.id, { ...transactionDetails, isSender: false, accountId: receiverId, accountType: receiverAccountType })
+                )
+            ];
+            await Promise.allSettled(notificationPromises);

227-233: Consider specifying timezone for transaction timestamps.

toLocaleString without a timeZone option uses the server's local timezone, which may confuse users in different timezones. For financial transaction records, consider:

         const formattedTime = timestamp.toLocaleString('en-US', {
             year: 'numeric',
             month: 'short',
             day: 'numeric',
             hour: '2-digit',
-            minute: '2-digit'
+            minute: '2-digit',
+            timeZone: 'UTC',
+            timeZoneName: 'short'
         });
platforms/eCurrency-api/src/database/entities/UserEVaultMapping.ts (1)

26-27: Type safety concern: Consider defining an interface for userProfileData.

Using any for userProfileData bypasses TypeScript's type checking. While common for JSONB columns, it can lead to runtime errors if the stored structure doesn't match expectations.

Consider defining an interface:

interface UserProfileData {
    platformName?: string;
    displayName?: string;
    description?: string;
    version?: string;
    [key: string]: any; // Allow additional properties
}

Then update the column:

-    @Column({ type: "jsonb", nullable: true })
-    userProfileData!: any; // Store the UserProfile data
+    @Column({ type: "jsonb", nullable: true })
+    userProfileData!: UserProfileData | null;
platforms/eCurrency-api/src/controllers/WebhookController.ts (1)

188-188: Consider structured logging for better observability.

The code uses multiple console.log statements for debugging. Consider using a structured logging library (e.g., winston, pino) for better log management, filtering, and correlation.

Example with a structured logger:

// At the top of the file
import logger from '../utils/logger';

// Then replace console.log calls:
logger.info('Processing message', { 
    localData: local.data,
    isSystemMessage,
    hasGroup: !!group,
    hasSender: !!sender
});

Also applies to: 209-209, 220-220, 223-223, 239-239, 242-242, 258-258, 264-264

platforms/eCurrency-api/src/services/PlatformEVaultService.ts (3)

189-257: Review retry strategy for production readiness.

The retry logic attempts up to 20 times with exponential backoff (max ~20 seconds between attempts). This could take several minutes to fail, potentially blocking application startup.

Consider:

  1. Making maxRetries configurable via environment variable
  2. Adding a total timeout limit in addition to retry count
  3. Documenting the expected startup behavior if eVault is unavailable
private async createPlatformProfileInEVault(
    w3id: string,
    uri: string,
    maxRetries = parseInt(process.env.EVAULT_MAX_RETRIES || '20', 10),
): Promise<string> {
    // ... existing code
}

222-222: Document the hardcoded ontology ID.

Lines 222 and 310 use the hardcoded UUID "550e8400-e29b-41d4-a716-446655440000" as the UserProfile ontology identifier. Consider adding a constant with documentation explaining what this ID represents.

// At the top of the file
const USER_PROFILE_ONTOLOGY_ID = "550e8400-e29b-41d4-a716-446655440000";

// Then use it:
input: {
    ontology: USER_PROFILE_ONTOLOGY_ID,
    payload: platformProfile,
    acl: ["*"],
},

Also applies to: 310-310


86-97: Validate required environment variables for production.

The code provides localhost fallbacks for critical URLs and uses a demo verification code. This may mask configuration errors in production environments.

Consider validating environment variables at startup:

private validateEnvironment(): void {
    const required = ['PUBLIC_REGISTRY_URL', 'PUBLIC_PROVISIONER_URL'];
    const missing = required.filter(key => !process.env[key]);
    
    if (missing.length > 0 && process.env.NODE_ENV === 'production') {
        throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
    }
    
    if (!process.env.DEMO_VERIFICATION_CODE && process.env.NODE_ENV === 'production') {
        console.warn('Using demo verification code in production - this should be configured properly');
    }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ea05b5 and 7ef9760.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • platforms/eCurrency-api/package.json (1 hunks)
  • platforms/eCurrency-api/src/controllers/WebhookController.ts (2 hunks)
  • platforms/eCurrency-api/src/database/data-source.ts (2 hunks)
  • platforms/eCurrency-api/src/database/entities/Group.ts (2 hunks)
  • platforms/eCurrency-api/src/database/entities/Message.ts (1 hunks)
  • platforms/eCurrency-api/src/database/entities/UserEVaultMapping.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765208128946-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (2 hunks)
  • platforms/eCurrency-api/src/services/GroupService.ts (2 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (2 hunks)
  • platforms/eCurrency-api/src/services/MessageService.ts (1 hunks)
  • platforms/eCurrency-api/src/services/PlatformEVaultService.ts (1 hunks)
  • platforms/eCurrency-api/src/services/TransactionNotificationService.ts (1 hunks)
  • platforms/eCurrency-api/src/web3adapter/mappings/message.mapping.json (1 hunks)
  • platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-20T12:00:02.322Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 453
File: platforms/eReputation-api/src/controllers/WebhookController.ts:6-7
Timestamp: 2025-11-20T12:00:02.322Z
Learning: In platforms/eReputation-api/src/controllers/WebhookController.ts, the eager instantiation of VotingReputationService in the constructor (which requires OPENAI_API_KEY at startup) is intentional behavior and works as intended.

Applied to files:

  • platforms/eCurrency-api/src/controllers/WebhookController.ts
🧬 Code graph analysis (7)
platforms/eCurrency-api/src/services/LedgerService.ts (1)
platforms/eCurrency-api/src/services/TransactionNotificationService.ts (1)
  • TransactionNotificationService (11-362)
platforms/eCurrency-api/src/database/entities/UserEVaultMapping.ts (1)
platforms/dreamsync-api/src/database/entities/UserEVaultMapping.ts (1)
  • UserEVaultMapping (10-34)
platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts (1)
platforms/eCurrency-api/src/database/data-source.ts (1)
  • AppDataSource (27-27)
platforms/eCurrency-api/src/services/GroupService.ts (1)
platforms/dreamSync/shared/schema.ts (1)
  • groups (120-134)
platforms/eCurrency-api/src/controllers/WebhookController.ts (3)
platforms/eCurrency-api/src/services/UserService.ts (1)
  • UserService (5-66)
platforms/eCurrency-api/src/services/GroupService.ts (1)
  • GroupService (6-178)
platforms/eCurrency-api/src/services/MessageService.ts (1)
  • MessageService (6-134)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/services/PlatformEVaultService.ts (1)
  • PlatformEVaultService (41-320)
platforms/eCurrency-api/src/services/PlatformEVaultService.ts (1)
platforms/eCurrency-api/src/database/data-source.ts (1)
  • AppDataSource (27-27)
⏰ 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: build
🔇 Additional comments (9)
platforms/eCurrency-api/src/services/GroupService.ts (1)

102-110: Potential race condition in DM duplication guard.

Two concurrent requests could both pass findGroupByMembers before either creates the group, resulting in duplicate DMs. Consider adding a database-level unique constraint or using a transaction with a lock.

The current approach relies on application-level checking which is susceptible to TOCTOU races under concurrent load. Verify if this is acceptable for your use case or if stronger guarantees are needed.

platforms/eCurrency-api/src/database/entities/Group.ts (1)

73-75: LGTM!

The @OneToMany relation is correctly configured with the inverse side referencing message.group, properly establishing the bidirectional relationship with the Message entity.

platforms/eCurrency-api/src/database/data-source.ts (1)

9-10: LGTM!

New entities Message and UserEVaultMapping are correctly imported and registered with the TypeORM data source.

Also applies to: 21-21

platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts (1)

381-382: LGTM!

Message relations correctly defined for entity loading.

platforms/eCurrency-api/src/index.ts (1)

28-44: Verify silent failure is acceptable for production.

The eVault initialization catches errors and logs them without stopping the server. While this prevents startup failures from blocking the application, it means:

  1. Transaction notifications will silently fail if eVault initialization failed
  2. There's no health check or retry mechanism for failed initialization

Consider adding:

  • A health check status for eVault initialization state (line 82-83 already has service checks)
  • A background retry mechanism for failed initialization
platforms/eCurrency-api/src/services/TransactionNotificationService.ts (1)

17-21: LGTM on service instantiation.

Constructor properly initializes required service dependencies. The pattern is consistent with other services in the codebase.

platforms/eCurrency-api/src/controllers/WebhookController.ts (1)

4-4: LGTM! Message service integration follows established patterns.

The imports and service initialization are consistent with existing UserService and GroupService patterns in this controller.

Also applies to: 7-8, 14-14, 20-20

platforms/eCurrency-api/src/database/migrations/1765208128946-migration.ts (1)

9-10: Consider CASCADE behavior for message deletions.

The foreign keys use ON DELETE NO ACTION, which means deleting a user or group will fail if they have associated messages. Consider if ON DELETE CASCADE or ON DELETE SET NULL would be more appropriate for your use case.

Verify the desired behavior:

  • Should messages be deleted when a user/group is deleted? Use ON DELETE CASCADE
  • Should messages be orphaned (sender/group set to NULL)? Use ON DELETE SET NULL
  • Should deletions be prevented? Keep ON DELETE NO ACTION
platforms/eCurrency-api/src/services/PlatformEVaultService.ts (1)

105-105: Hardcoded placeholder public key may cause issues.

Line 105 uses a hardcoded zero address "0x00000000000000000000000000000000000000" as the public key. This appears to be a placeholder and may not be appropriate for production use.

Verify whether this is:

  1. Intentional for the eCurrency platform's design
  2. A TODO that needs implementation
  3. Should be derived from actual cryptographic keys

If this is a placeholder, consider adding a TODO comment or throwing an error if a proper key configuration is missing.

Comment on lines +227 to +235
// For system messages, ensure the prefix is preserved
if (isSystemMessage && !(local.data.text as string).startsWith('$$system-message$$')) {
message.text = `$$system-message$$ ${local.data.text as string}`;
} else {
message.text = local.data.text as string;
}
message.sender = sender || undefined;
message.group = group;
message.isSystemMessage = isSystemMessage as boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential data loss: sender field may be unintentionally cleared.

On line 233, setting message.sender = sender || undefined will set sender to undefined if the sender lookup fails, even if the message previously had a valid sender. This could unintentionally clear the sender field during updates.

Consider preserving the existing sender if lookup fails:

                     if (isSystemMessage && !(local.data.text as string).startsWith('$$system-message$$')) {
                         message.text = `$$system-message$$ ${local.data.text as string}`;
                     } else {
                         message.text = local.data.text as string;
                     }
-                    message.sender = sender || undefined;
+                    // Only update sender if explicitly provided (preserve existing sender if lookup fails)
+                    if (local.data.sender) {
+                        message.sender = sender || undefined;
+                    }
                     message.group = group;
                     message.isSystemMessage = isSystemMessage as boolean;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// For system messages, ensure the prefix is preserved
if (isSystemMessage && !(local.data.text as string).startsWith('$$system-message$$')) {
message.text = `$$system-message$$ ${local.data.text as string}`;
} else {
message.text = local.data.text as string;
}
message.sender = sender || undefined;
message.group = group;
message.isSystemMessage = isSystemMessage as boolean;
// For system messages, ensure the prefix is preserved
if (isSystemMessage && !(local.data.text as string).startsWith('$$system-message$$')) {
message.text = `$$system-message$$ ${local.data.text as string}`;
} else {
message.text = local.data.text as string;
}
// Only update sender if explicitly provided (preserve existing sender if lookup fails)
if (local.data.sender) {
message.sender = sender || undefined;
}
message.group = group;
message.isSystemMessage = isSystemMessage as boolean;
🤖 Prompt for AI Agents
In platforms/eCurrency-api/src/controllers/WebhookController.ts around lines 227
to 235, the code currently assigns message.sender = sender || undefined which
will clear an existing sender when the lookup returns a falsy value; change the
logic to only overwrite message.sender when sender is a valid value (e.g.,
non-null/defined) so that if the lookup fails the existing message.sender is
preserved, and ensure types are respected (cast or guard as needed) when
conditionally assigning.

Comment on lines +155 to +167
private async resolveEndpoint(w3id: string): Promise<string> {
try {
const registryUrl =
process.env.PUBLIC_REGISTRY_URL || "http://localhost:3000";
const response = await axios.get(
new URL(`resolve?w3id=${w3id}`, registryUrl).toString(),
);
return new URL("/graphql", response.data.uri).toString();
} catch (error) {
console.error("Error resolving eVault endpoint:", error);
throw new Error("Failed to resolve eVault endpoint");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add timeout configuration for external API calls.

The code makes HTTP requests to external services (registry, eVault) without timeout configuration. This can cause the application to hang if services are unresponsive.

Apply this diff to add timeouts:

     private async resolveEndpoint(w3id: string): Promise<string> {
         try {
             const registryUrl =
                 process.env.PUBLIC_REGISTRY_URL || "http://localhost:3000";
             const response = await axios.get(
                 new URL(`resolve?w3id=${w3id}`, registryUrl).toString(),
+                { timeout: 5000 } // 5 second timeout
             );
             return new URL("/graphql", response.data.uri).toString();
         } catch (error) {

Also consider adding a timeout to the GraphQL client:

             this.client = new GraphQLClient(this.endpoint, {
                 headers: {
                     "X-ENAME": w3id,
                 },
+                timeout: 10000, // 10 second timeout
             });

Also applies to: 172-184

Comment on lines +289 to +319
async updatePlatformProfile(
updates: Partial<PlatformProfile>,
): Promise<void> {
const mapping = await this.getPlatformEVaultMapping();
if (!mapping) {
throw new Error("Platform eVault mapping not found");
}

const client = await this.ensureClient(mapping.evaultW3id);

// Get current profile data
const currentData = mapping.userProfileData as PlatformProfile;
const updatedData = {
...currentData,
...updates,
updatedAt: new Date().toISOString(),
};

// Update in eVault
await client.request<MetaEnvelopeResponse>(STORE_META_ENVELOPE, {
input: {
ontology: "550e8400-e29b-41d4-a716-446655440000",
payload: updatedData,
acl: ["*"],
},
});

// Update local mapping
mapping.userProfileData = updatedData;
await AppDataSource.getRepository(UserEVaultMapping).save(mapping);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential data consistency issue between eVault and local database.

The method updates the eVault (line 308) and then the local database (line 318) without transaction semantics. If the local save fails after the eVault update succeeds, the systems will be inconsistent.

Consider adding error handling to detect and log this scenario:

         // Update in eVault
-        await client.request<MetaEnvelopeResponse>(STORE_META_ENVELOPE, {
+        try {
+            await client.request<MetaEnvelopeResponse>(STORE_META_ENVELOPE, {
-            input: {
-                ontology: "550e8400-e29b-41d4-a716-446655440000",
-                payload: updatedData,
-                acl: ["*"],
-            },
-        });
+                input: {
+                    ontology: "550e8400-e29b-41d4-a716-446655440000",
+                    payload: updatedData,
+                    acl: ["*"],
+                },
+            });
+        } catch (error) {
+            console.error("Failed to update platform profile in eVault:", error);
+            throw error;
+        }
 
         // Update local mapping
         mapping.userProfileData = updatedData;
-        await AppDataSource.getRepository(UserEVaultMapping).save(mapping);
+        try {
+            await AppDataSource.getRepository(UserEVaultMapping).save(mapping);
+        } catch (error) {
+            console.error("CRITICAL: eVault updated but local DB save failed. Manual reconciliation needed:", error);
+            throw error;
+        }
     }

Comment on lines +1 to +15
{
"tableName": "messages",
"schemaId": "550e8400-e29b-41d4-a716-446655440004",
"ownerEnamePath": "groups(group.ename)||users(group.members[].ename)",
"ownedJunctionTables": [],
"localToUniversalMap": {
"text": "content",
"sender": "users(sender.id),senderId",
"group": "groups(group.id),chatId",
"isSystemMessage": "isSystemMessage",
"createdAt": "createdAt",
"updatedAt": "updatedAt",
"isArchived": "isArchived"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find platforms/eCurrency-api/src/web3adapter/mappings/ -name "*.json" -type f

Repository: MetaState-Prototype-Project/prototype

Length of output: 425


🏁 Script executed:

for file in platforms/eCurrency-api/src/web3adapter/mappings/{user,group,currency}.mapping.json; do
  echo "=== $file ==="
  cat "$file"
  echo ""
done

Repository: MetaState-Prototype-Project/prototype

Length of output: 2245


🏁 Script executed:

cat platforms/eCurrency-api/src/web3adapter/mappings/ledger.mapping.json

Repository: MetaState-Prototype-Project/prototype

Length of output: 558


Duplicate schemaId detected—must be corrected before merging.

The message mapping uses schemaId 550e8400-e29b-41d4-a716-446655440004, which is already assigned to the currency mapping. Each schema must have a unique identifier. Update this to an unused UUID, such as 550e8400-e29b-41d4-a716-446655440002 to maintain the sequence consistency across mappings.

The localToUniversalMap and field mappings are otherwise correct and consistent with the existing mapping structure.

🤖 Prompt for AI Agents
In platforms/eCurrency-api/src/web3adapter/mappings/message.mapping.json around
lines 1 to 15, the schemaId currently duplicates another mapping
(550e8400-e29b-41d4-a716-446655440004); replace it with a unique UUID (for
example 550e8400-e29b-41d4-a716-446655440002) so each mapping has a distinct
schemaId, ensuring no collisions across mappings; leave the rest of the file and
localToUniversalMap unchanged.

@coodos coodos merged commit 849c36f into main Dec 8, 2025
4 checks passed
@coodos coodos deleted the feat/ecurrency-transaction-messages branch December 8, 2025 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Send transaction messages when money is sent or received

3 participants