Skip to content

refactor: codebase cleanup, standardization & performance improvements#89

Merged
LennyMalcolm0 merged 12 commits into
mainfrom
dev
May 5, 2026
Merged

refactor: codebase cleanup, standardization & performance improvements#89
LennyMalcolm0 merged 12 commits into
mainfrom
dev

Conversation

@LennyMalcolm0
Copy link
Copy Markdown
Member

@LennyMalcolm0 LennyMalcolm0 commented May 5, 2026

This PR covers a broad set of refactoring, chore, and performance improvements focused on cleaning up dependencies, standardizing status codes, improving environment variable handling, and optimizing runtime behavior.

Summary by CodeRabbit

  • Bug Fixes

    • Standardized HTTP status codes and tightened error mappings across the API and middleware.
  • Chores

    • Centralized environment handling and validation via a new Env utility; updated default PORT and CORS origins.
    • Reworked Stellar integration and asset handling for improved reliability.
    • Documentation: consolidated related project links in README.
    • Tests: aligned suites to new status codes and config paths.

@devasign-app
Copy link
Copy Markdown

devasign-app Bot commented May 5, 2026

❌ PR Review Failed


Error Details

The PR review system encountered an error while analyzing this pull request:

Review failed: Pull request analysis timed out after 300000ms. Please review manually.

What to do next

  1. Manual Review: Please proceed with manual code review
  2. Retry: A review can be triggered by the repo maintainer commenting 'review'
  3. Support: If this error persists, please contact support

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

Introduces a centralized Env utility, normalizes status codes in api/utils/data.ts, migrates Stellar integration from the wallet SDK to @stellar/stellar-sdk, updates many modules to use Env and new STATUS_CODES, adjusts routes/tests, and removes the @stellar/typescript-wallet-sdk dependency.

Changes

Environment Management and HTTP Status Normalization Refactor

Layer / File(s) Summary
Data Shape / Constants
api/utils/data.ts
Replaced project-specific status constants with standard HTTP status mappings (OK, CREATED, ACCEPTED, BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, TOO_MANY_REQUESTS, INTERNAL_SERVER_ERROR, etc.); removed ALLOWED_ORIGINS.
Env Abstraction
api/utils/env.ts
Added Env class with many static accessors (port, corsOrigins, GitHub/Firebase/Stellar/GCP/Sumsub/etc.) and optional throwing behavior for required variables; validates PORT as positive integer.
Config Wiring
api/config/*.ts, api/index.ts
Rewired config modules and server bootstrap to use Env (port, CORS origins, logger/env values); tightened JSON body parsing limit.
Services / Initialization
api/services/*.ts, api/config/firebase.config.ts
Replaced ad-hoc process.env reads with Env accessors across services (Cloud Tasks, KMS, Octokit, Statsig, ErrorHandler, ContractService, etc.).
Middlewares / Error Handling
api/middlewares/*.ts, api/models/error.model.ts
Switched to Env for env checks and to new STATUS_CODES; mapped Authorization/Validation/Timeout and unknown errors to appropriate HTTP codes; updated error sanitization to use Env.nodeEnv().
Controllers / Routes / Utils
api/controllers/**, api/routes/**, api/schemas/**, api/utils/helper.js
Updated imports from utilities/utils/; replaced response status values to new STATUS_CODES (e.g., SUCCESS → OK, PARTIAL_SUCCESS → OK, SERVER_ERROR → INTERNAL_SERVER_ERROR, UNAUTHENTICATED → UNAUTHORIZED or FORBIDDEN as applicable); updated schema import sources.
Tests & Helpers
tests/**, tests/helpers/database-test-helper.ts
Updated test imports and 100+ assertions to new STATUS_CODES values; changed seed logic to idempotent upsert for subscription packages and permissions; fixed small test imports.
Docs & Examples
.env.example, README.md
Changed default PORT to 5000, added CORS_ORIGINS, adjusted CONTRIBUTOR_APP_URL formatting; consolidated related-project links in README.
Dependency change
package.json
Removed @stellar/typescript-wallet-sdk dependency.

Stellar SDK Integration Migration

Layer / File(s) Summary
Config / Assets
api/config/stellar.config.ts
Now derives network (isMainnet), horizon URL, network passphrase, and anchor domain via Env; exports stellarServer and Asset instances (xlmAsset, usdcAsset) instead of wallet-sdk artifacts; removed wallet/account/anchor exports.
Core Implementation
api/services/stellar.service.ts
Rewrote service to use @stellar/stellar-sdk primitives: Keypair, Asset, TransactionBuilder, Operation, Horizon calls; updated wallet creation (standard & sponsored), Friendbot funding via HTTP, trustlines, transfers (payment vs path payment), fee-bump flows, swaps (effects inspection), account reads, and top-up discovery; method signatures now accept Asset objects.
Integration / Controllers / Tests
api/controllers/wallet/index.controller.ts, api/routes/_test_/stellar.test.route.ts, tests/**
Controllers and test routes updated to pass Asset objects (xlmAsset, usdcAsset) and to use Env-sourced master secret; tests adapted to new method signatures and response constants.
Build / Dependencies
package.json
Removed @stellar/typescript-wallet-sdk dependency as it’s no longer used.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Route as Controller/Route
    participant Env as Env Utility
    participant StellarSvc as StellarService
    participant Horizon as Horizon Server

    Client->>Route: HTTP request (withdraw/transfer/swap)
    activate Route
    Route->>Env: Env.stellarMasterSecretKey(true)
    Env-->>Route: master secret
    Route->>StellarSvc: transferAsset(sourceSecret, sendAsset, destAsset, amount, memo)
    activate StellarSvc
    StellarSvc->>Env: Env.* (stellar server config/assets)
    Env-->>StellarSvc: horizonUrl, networkPassphrase, Asset instances
    StellarSvc->>Horizon: loadAccount / fetchBaseFee
    Horizon-->>StellarSvc: account + fee
    StellarSvc->>StellarSvc: build Transaction (Operation.payment/pathPayment)
    StellarSvc->>StellarSvc: sign tx (Keypair)
    StellarSvc->>Horizon: submitTransaction(tx)
    Horizon-->>StellarSvc: txResult (hash)
    StellarSvc-->>Route: txHash / result
    Route-->>Client: HTTP 200 (STATUS_CODES.OK) + payload
    deactivate Route
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • bishopBethel

Poem

🐰
Soft paws in code, I hop and stare,
Env gathers secrets from the air.
Stellar now builds transactions true,
Status codes tidy — clear and new.
Hooray — a tidy launch, from rabbit to you!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Created PR with unit tests: #90

Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
api/services/github-webhook/issue_comment-webhook.service.ts (1)

511-533: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Misleading user-facing error message in the outer async catch.

The message "Bounty was created but there was an issue while posting the bounty comment or adding the bounty label on issue" is only accurate when the outer catch fires after task creation and escrow commitment. However, several earlier unguarded call sites can also propagate to this outer handler — for example, stellarService.getAccountInfo (line 340), KMSService.decryptWallet (line 358), and prisma.task.create (line 379) are not wrapped in their own try/catch blocks. If any of those throws, the outer catch fires while no bounty was ever created, and the message tells the user the opposite.

The generic fallback that was used before this PR — "Bounty creation failed due to an internal error. Please try again on the DevAsign dashboard." — is accurate for any failure stage and should be kept (or passed explicitly) in the outer catch. Move the specific "Bounty was created but..." message to only the inner catch where the GitHub comment/label mutation fails (lines 458–469), where it is already correct.

🛠️ Proposed fix
-                    await this.bountyFailureComment(
-                        installationId,
-                        repository.full_name,
-                        issue.number,
-                        "Bounty was created but there was an issue while posting the bounty comment or adding the bounty label on issue"
-                    );
+                    // No explicit message: defaults to the generic internal-error text,
+                    // which is accurate regardless of which step failed.
+                    await this.bountyFailureComment(
+                        installationId,
+                        repository.full_name,
+                        issue.number
+                    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/services/github-webhook/issue_comment-webhook.service.ts` around lines
511 - 533, The outer catch currently posts a user-facing message ("Bounty was
created but...") that is only correct when the inner GitHub comment/label
mutation fails; change the outer catch to use the generic fallback message
("Bounty creation failed due to an internal error. Please try again on the
DevAsign dashboard.") and ensure statsigService.logEvent and dataLogger.error
remain unchanged; move the specific "Bounty was created but there was an issue
while posting the bounty comment or adding the bounty label on issue" message so
it is only used in the inner catch that wraps the GitHub comment/label
operations (the block that calls bountyFailureComment), and verify that earlier
calls like stellarService.getAccountInfo, KMSService.decryptWallet, and
prisma.task.create are allowed to bubble to the outer handler which now uses the
generic message.
api/controllers/installation/github.controller.ts (1)

122-129: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hardcoded status: 200 missed in the migration.

All other responseWrapper calls in this file use STATUS_CODES.OK, but the early-return branch for the existing bounty label still uses a literal 200, leaving one raw magic number the PR explicitly set out to remove.

🛠️ Proposed fix
-        if (bountyLabel) {
-            return responseWrapper({
-                res,
-                status: 200,
-                data: { bountyLabel },
-                message: "Bounty label already exists"
-            });
-        }
+        if (bountyLabel) {
+            return responseWrapper({
+                res,
+                status: STATUS_CODES.OK,
+                data: { bountyLabel },
+                message: "Bounty label already exists"
+            });
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/controllers/installation/github.controller.ts` around lines 122 - 129,
The early-return branch that returns when bountyLabel exists is using a
hardcoded literal 200; update that responseWrapper call to use STATUS_CODES.OK
(consistent with other calls in this controller) and ensure STATUS_CODES is
imported/available in the module so the call becomes status: STATUS_CODES.OK
instead of status: 200.
tests/integration/api/task/{taskId}.api.test.ts (1)

972-1003: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use AuthorizationError for authorization failures instead of ValidationError.

Lines 665–668 in requestTimelineExtension and lines 731–734 in replyTimelineExtensionRequest throw ValidationError when authorization fails (user is not the contributor/creator), causing the controllers to return HTTP 400. Other task endpoints correctly use AuthorizationError for authorization checks, which returns HTTP 403 (see addBountyCommentId line 59 and updateTaskBounty line 144).

The authorization check should be separated from the status validation check: throw AuthorizationError for "user is not contributor/creator" and ValidationError for "task is not in progress". This aligns with RFC 9110 semantics where 403 Forbidden indicates the request is understood but denied due to authorization, while 400 Bad Request should indicate malformed syntax or invalid data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/api/task/`{taskId}.api.test.ts around lines 972 - 1003, In
requestTimelineExtension and replyTimelineExtensionRequest, replace the
ValidationError thrown for authorization failures (the checks that verify "user
is contributor" or "user is creator") with AuthorizationError while keeping
ValidationError for status/state validation (e.g., "task is not in progress");
locate the checks in the requestTimelineExtension and
replyTimelineExtensionRequest functions and swap the thrown error type to
AuthorizationError (import/ensure AuthorizationError is available) so
authorization failures return 403 while other validation issues still throw
ValidationError and return 400.
tests/integration/api/webhook/github.api.test.ts (1)

537-570: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Webhook validation errors should return 4xx, not 500.

The webhook validation errors in lines 551, 566, 1615, 1640, and 1662 return STATUS_CODES.INTERNAL_SERVER_ERROR (500) for:

  • Missing webhook signature
  • Invalid webhook signature
  • Webhook secret not configured
  • Invalid request body format

These are all client/configuration errors that should return 4xx status codes (e.g., 400 BAD_REQUEST for malformed body/signature, or 401 UNAUTHORIZED for missing credentials). Using 500 misrepresents the issue as a server fault when the actual problem is with the request or client configuration.

The root cause is in the GitHubWebhookError class (error.model.ts, lines 243-252) which hardcodes STATUS_CODES.INTERNAL_SERVER_ERROR as the default status. Update it to accept a configurable status parameter in the constructor, then pass the appropriate 4xx code when throwing these validation errors from the webhook middleware.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/api/webhook/github.api.test.ts` around lines 537 - 570,
Update the GitHubWebhookError class (error.model.ts / GitHubWebhookError) to
accept an HTTP status code parameter in its constructor (instead of hardcoding
STATUS_CODES.INTERNAL_SERVER_ERROR) and store it as the error's status; then
update the webhook middleware/validation sites that throw this error (the places
handling missing signature, invalid signature, missing webhook secret, and
invalid body format) to pass appropriate 4xx codes (e.g.,
STATUS_CODES.BAD_REQUEST for malformed body/signature, STATUS_CODES.UNAUTHORIZED
for missing/invalid signature or secret). Also update the tests expecting
STATUS_CODES.INTERNAL_SERVER_ERROR to assert the new 4xx codes so behavior and
test expectations align.
api/services/socket.service.ts (1)

38-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Extract CORS origins outside the connection callback to avoid uncaught exceptions

Env.corsOrigins(true) throws when CORS_ORIGINS is unset. Since it's called inside the origin callback — which the cors package invokes synchronously without a try/catch wrapper — any exception propagates unhandled. Additionally, calling it per-connection defeats the caching intent; the origins list should be captured once at initialization.

🛡️ Proposed fix — pre-compute origins at initialization
  static initialize(httpServer: HttpServer) {
+     const allowedOrigins = Env.corsOrigins(true)!;
      this.io = new SocketIOServer(httpServer, {
          cors: {
              origin(origin, callback) {
-                 if (!origin || Env.corsOrigins(true)!.includes(origin)) {
+                 if (!origin || allowedOrigins.includes(origin)) {
                      callback(null, true);
                  } else {
                      callback(new Error("Not allowed by CORS"));
                  }
              },
              methods: ["GET", "POST"]
          }
      });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/services/socket.service.ts` around lines 38 - 50, Pre-compute and cache
the allowed origins before constructing the SocketIOServer to avoid uncaught
exceptions and per-connection calls: call Env.corsOrigins(true) once inside
SocketService.initialize (store result in a const like allowedOrigins), guard
that call with try/catch to handle the unset CORS_ORIGINS case (log or fallback
to an empty array), then update the cors.origin callback to reference
allowedOrigins (e.g., if (!origin || allowedOrigins.includes(origin))
callback(null,true) else callback(new Error("Not allowed by CORS"))), removing
any further calls to Env.corsOrigins from inside the callback.
api/services/cloud-tasks.service.ts (1)

76-100: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Field initializer throws before the constructor's soft-fail aggregation can run.

Public instance fields are added to the instance at construction time in the base class — before the constructor body runs. Because Env.gcpProjectId(true), Env.gcpLocationId(true), Env.cloudRunServiceUrl(true), Env.cloudRunPrivateServiceUrl(true), and Env.cloudTasksServiceAccountEmail(true) all call getOrThrowError (which throws an ErrorClass when the variable is absent), any single missing top-level env var causes the field initializer to throw and the constructor body at lines 107–125 to never execute. The carefully-built missing[] array and the consolidated log message are completely bypassed.

Two secondary problems follow from this:

  1. Dead || "" on line 81. getOrThrowError either throws (var absent) or returns a non-empty string (var present); the || "" fallback is unreachable in both cases.
  2. Inconsistent error surface. Queue vars (lines 83–89) use throwError=false and flow through the constructor's aggregation, while the top-level vars throw raw ErrorClass exceptions before aggregation can run.

Fix — pick one:

Option A: Remove throwError=true from field initializers so all validation flows through the constructor's aggregation block.
-    project: Env.gcpProjectId(true)!,
-    location: Env.gcpLocationId(true)!,
-    cloudRunServiceUrl: Env.cloudRunServiceUrl(true)!,
-    cloudRunPrivateServiceUrl: Env.cloudRunPrivateServiceUrl(true)!,
-    cloudTasksServiceAccountEmail: (Env.cloudTasksServiceAccountEmail(true) || ""),
+    project: Env.gcpProjectId() || "",
+    location: Env.gcpLocationId() || "",
+    cloudRunServiceUrl: Env.cloudRunServiceUrl() || "",
+    cloudRunPrivateServiceUrl: Env.cloudRunPrivateServiceUrl() || "",
+    cloudTasksServiceAccountEmail: Env.cloudTasksServiceAccountEmail() || "",
Option B: Remove the constructor's soft-fail block and throw consistently (rely on field initializer throws).
     constructor() {
-        const missing: string[] = [];
-        if (!Env.gcpProjectId()) missing.push("GCP_PROJECT_ID");
-        if (!Env.gcpLocationId()) missing.push("GCP_LOCATION_ID");
-        if (!Env.cloudRunServiceUrl()) missing.push("CLOUD_RUN_SERVICE_URL");
-        if (!Env.cloudRunPrivateServiceUrl()) missing.push("CLOUD_RUN_PRIVATE_SERVICE_URL");
-        if (!((Env.cloudTasksServiceAccountEmail() || "") || "")) missing.push("CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL");
-        if (!Env.cloudTasksPrAnalysisQueue()) missing.push("CLOUD_TASKS_PR_ANALYSIS_QUEUE");
-        if (!Env.cloudTasksManualPrAnalysisQueue()) missing.push("CLOUD_TASKS_MANUAL_PR_ANALYSIS_QUEUE");
-        if (!Env.cloudTasksRepoIndexingQueue()) missing.push("CLOUD_TASKS_REPO_INDEXING_QUEUE");
-        if (!Env.cloudTasksIncrementalIndexingQueue()) missing.push("CLOUD_TASKS_INCREMENTAL_INDEXING_QUEUE");
-        if (!Env.cloudTasksBountyPayoutQueue()) missing.push("CLOUD_TASKS_BOUNTY_PAYOUT_QUEUE");
-        if (!Env.cloudTasksClearInstallationQueue()) missing.push("CLOUD_TASKS_CLEAR_INSTALLATION_QUEUE");
-        if (!Env.cloudTasksClearRepoQueue()) missing.push("CLOUD_TASKS_CLEAR_REPO_QUEUE");
-        if (missing.length > 0) {
-            dataLogger.error(`Missing required environment variables for CloudTasksService: ${missing.join(", ")}`);
-        }
         this.client = new CloudTasksClient();
     }

And change all queue entries to also use throwError=true:

-    "pr-analysis": Env.cloudTasksPrAnalysisQueue() || "",
+    "pr-analysis": Env.cloudTasksPrAnalysisQueue(true)!,
     // ...

Also applies to: 107-125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/services/cloud-tasks.service.ts` around lines 76 - 100, The config field
initializer calls Env.gcpProjectId(true), Env.gcpLocationId(true),
Env.cloudRunServiceUrl(true), Env.cloudRunPrivateServiceUrl(true) and
Env.cloudTasksServiceAccountEmail(true) which throw before the constructor's
missing[] aggregation runs; move the entire config construction out of the
public instance field and into the class constructor after the existing
validation/aggregation block (i.e., build this.config inside the constructor
using the validated Env values or Env.*(..., throwError=false) so all missing
envs are collected consistently), and make the queue entries consistent with the
chosen approach (either use throwError=false in those Env calls or ensure they
also throw inside the constructor) so no field initializer can throw before
aggregation.
api/controllers/user/index.controller.ts (1)

155-163: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

meta: { error } passes a raw Error object — use error.message for consistency.

The rest of the catch block (lines 150–152) already extracts error instanceof Error ? error.message : "Unknown Error" for the Statsig event. The meta field here diverges from that pattern. A raw Error object serializes to {} in JSON (all its properties are non-enumerable), so the field is both useless to the client and inconsistent.

✏️ Suggested fix
         responseWrapper({
             res,
             status: STATUS_CODES.OK,
             data: user,
             message: "User created successfully",
             warning: "Failed to add USDC trustline to wallet",
-            meta: { error }
+            meta: { error: error instanceof Error ? error.message : "Unknown error" }
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/controllers/user/index.controller.ts` around lines 155 - 163, The meta
field is passing a raw Error object (meta: { error }) which JSON-serializes to
{} and is inconsistent with the Statsig extraction; update the catch block to
pass the error message string instead (e.g., meta: { error: error instanceof
Error ? error.message : String(error) }) when calling responseWrapper so clients
receive a useful message; locate the responseWrapper call in the same catch
handler around the user creation flow and replace meta: { error } with the
message extraction expression (or an existing errorMessage variable) to keep
behavior consistent.
tests/integration/api/installation/team.api.test.ts (1)

234-246: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test descriptions claim "404" but assertions expect 403 FORBIDDEN.

All three "should return 404 when installation not found" blocks assert STATUS_CODES.FORBIDDEN (403). The description will actively mislead anyone debugging a future failure.

✏️ Suggested fix (repeat for lines 330 and 441)
-        it("should return 404 when installation not found", async () => {
+        it("should return 403 when installation not found", async () => {

Also applies to: 330-342, 441-448

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/api/installation/team.api.test.ts` around lines 234 - 246,
The test descriptions say "404" but the assertions use STATUS_CODES.FORBIDDEN
(403); update the three failing tests' description strings (the it(...) blocks
that call getEndpointWithPrefix([...]) and assert
.expect(STATUS_CODES.FORBIDDEN)) to say "should return 403 when installation not
found" (repeat the same change for the other two occurrences noted), so the
human-readable message matches the actual expectation.
api/index.ts (1)

47-65: ⚠️ Potential issue | 🟠 Major

Add CORS_ORIGINS to startup environment validation to prevent silent crash on missing configuration.

corsOrigins(true) is called at module initialization (api/index.ts:47), and when CORS_ORIGINS is not configured, getOrThrowError() throws an exception that crashes the process at startup with no prior validation warning.

CORS_ORIGINS is not included in requiredVars or warningVars in error-handler.service.ts (lines 132–165), so missing configuration is not caught during the startup validation routine. This creates a silent failure mode: the app crashes with a server misconfiguration error instead of a clear, logged configuration error.

Either add CORS_ORIGINS to the validation checks or make it optional with a sensible default (e.g., [] to deny all cross-origin requests by default).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/index.ts` around lines 47 - 65, The startup crash happens because
Env.corsOrigins(true) (corsOrigins) is invoked at module init but CORS_ORIGINS
isn't validated; update the startup validation to include "CORS_ORIGINS" (add it
to the requiredVars or warningVars list in the error-handler service where
startup envs are validated) or make corsOrigins optional by changing the call to
Env.corsOrigins(false) / defaulting to [] so missing CORS_ORIGINS doesn't throw;
adjust the validation arrays (requiredVars/warningVars) or the corsOrigins
invocation accordingly and keep the CORS behavior consistent with the intended
default (deny all or explicit allow-list).
api/middlewares/auth.middleware.ts (1)

94-156: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move env reads inside the try block to prevent unhandled promise rejection if variables are missing.

In Express 4, synchronous errors thrown inside async middleware/route handlers that occur outside a try/catch become unhandled promise rejections. If CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL or CLOUD_RUN_SERVICE_URL are missing in production, the env calls at lines 102–104 throw synchronously, creating a rejected promise that Express 4 does not automatically catch. This causes the client to hang indefinitely waiting for a response.

Move both env reads into the try block so the existing catch handler properly calls next(new AuthorizationError(...)) on failure.

Suggested fix
     try {
+        const cloudTasksServiceAccountEmail = Env.cloudTasksServiceAccountEmail(true)!;
+        const cloudRunServiceUrl = Env.cloudRunServiceUrl(true)!;
+
         // Extract the Bearer token from the Authorization header
         const authHeader = req.headers.authorization;
         if (!authHeader || !authHeader.startsWith("Bearer ")) {

Remove the duplicate declarations at lines 102–104.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/middlewares/auth.middleware.ts` around lines 94 - 156, The env reads for
cloudTasksServiceAccountEmail and cloudRunServiceUrl are done outside the try
block in validateCloudTasksRequest which can throw synchronously and produce
unhandled promise rejections; move the calls to
Env.cloudTasksServiceAccountEmail(true) and Env.cloudRunServiceUrl(true) into
the existing try block (remove the earlier top-level declarations) so any thrown
errors are caught by the catch and routed to next(new AuthorizationError(...));
keep the rest of the verification logic unchanged (authClient.verifyIdToken,
payload checks, dataLogger usage).
api/controllers/installation/index.controller.ts (1)

170-201: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't downgrade missing Stellar credentials into a recoverable warning.

Line 173 can now throw a server-misconfiguration error, but this catch block turns it into a successful installation response with only a warning. That hides a deployment problem and leaves the new wallet without its required trustline. Resolve the secret before this partial-success path, or rethrow config errors here instead of treating them like transient trustline failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/controllers/installation/index.controller.ts` around lines 170 - 201, The
catch currently turns any error from resolving secrets or adding the USDC
trustline into a recoverable warning; instead, ensure missing/misconfigured
Stellar credentials are treated as fatal: resolve and validate
Env.stellarMasterSecretKey(true) before the try (or immediately throw if it
returns falsy), or in the catch rethrow errors that indicate configuration
issues (e.g., missing secret or a ConfigError) rather than returning a 200 with
a warning. Update the block around Env.stellarMasterSecretKey(true) and
stellarService.addTrustLineViaSponsor so that only transient trustline failures
are converted to a partial-success response while credential/config errors
propagate as server errors.
🧹 Nitpick comments (4)
api/controllers/wallet/index.controller.ts (1)

104-119: ⚡ Quick win

usdcAsset local variable shadows the module-level import

const usdcAsset declared here (and again at line 267 in swapAsset) shadows the usdcAsset imported from stellar.config.js. Both are correctly block-scoped so there's no runtime mixup at the current call sites, but the naming collision makes the code fragile — a future refactor that moves or hoists the variable could silently pass a balance object where a Stellar Asset is expected.

♻️ Suggested rename
-    const usdcAsset = accountInfo.balances.find(
+    const usdcBalance = accountInfo.balances.find(
         (asset): asset is USDCBalance => "asset_code" in asset && asset.asset_code === "USDC"
     );
 
-    if (!usdcAsset) {
+    if (!usdcBalance) {
         throw new ValidationError("No USDC asset found in wallet");
     }
 
-    if (parseFloat(usdcAsset.balance) < Number(amount)) {
+    if (parseFloat(usdcBalance.balance) < Number(amount)) {
         throw new ValidationError(
             "Insufficient USDC balance",
-            { available: usdcAsset.balance }
+            { available: usdcBalance.balance }
         );
     }

Apply the same rename to the else block in swapAsset (around line 267).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/controllers/wallet/index.controller.ts` around lines 104 - 119, The local
constant usdcAsset in the balance-check block should be renamed to avoid
shadowing the module-level usdcAsset import from stellar.config.js; change the
local variable name (e.g., to usdcBalance or usdcWalletBalance) wherever it’s
declared and referenced in the balance-checking logic inside the controller, and
apply the same rename to the corresponding local declaration in the swapAsset
function’s else block so both places stop using the imported symbol name while
keeping all type guards and usages intact.
api/services/kms.service.ts (1)

8-16: ⚡ Quick win

Redundant fallbacks undermine the module-level guard's intent

Line 13 applies two successive || "" fallbacks to Env.gcpProjectId(true), even though the guard at line 8 already throws if gcpProjectId is absent. At runtime PROJECT_ID will always be a non-empty string here; the double fallback suggests distrust of the guard and is inconsistent with lines 14–16, which use the cleaner ! non-null assertion pattern. Similarly, the || "" inside the guard condition on line 8 is equivalent to just !Env.gcpProjectId() (empty string is already falsy).

♻️ Proposed cleanup
-if (!(Env.gcpProjectId() || "") || !Env.gcpLocationId() || !Env.gcpKeyRingId() || !Env.gcpKeyId()) {
+if (!Env.gcpProjectId() || !Env.gcpLocationId() || !Env.gcpKeyRingId() || !Env.gcpKeyId()) {
     throw new KmsServiceError("Missing GCP KMS credentials (GCP_PROJECT_ID, GCP_LOCATION_ID, GCP_KEY_RING_ID, GCP_KEY_ID) in environment variables");
 }

-const PROJECT_ID = ((Env.gcpProjectId(true) || "") || "");
+const PROJECT_ID = Env.gcpProjectId(true)!;
 const LOCATION_ID = Env.gcpLocationId(true)!;
 const KEY_RING_ID = Env.gcpKeyRingId(true)!;
 const KEY_ID = Env.gcpKeyId(true)!;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/services/kms.service.ts` around lines 8 - 16, The guard in the KmsService
module redundantly uses `|| ""` fallbacks and an extra `|| ""` inside the
initial presence check; simplify the guard and use consistent non-null
assertions: update the guard that throws KmsServiceError to check presence with
straightforward falsy checks (e.g., `if (!Env.gcpProjectId() ||
!Env.gcpLocationId() || !Env.gcpKeyRingId() || !Env.gcpKeyId()) { ... }`) and
set PROJECT_ID using the same pattern as the others (e.g., `const PROJECT_ID =
Env.gcpProjectId(true)!;`) removing the double `|| ""` fallbacks so PROJECT_ID,
LOCATION_ID, KEY_RING_ID, and KEY_ID are handled consistently.
api/services/contract.service.ts (1)

19-43: 💤 Low value

Redundant || "" wrappers in both the static validation block and CONFIG initialization.

Lines 22–23 use !(Env.stellarNetwork() || "") which is logically identical to !Env.stellarNetwork() — the || "" converts undefined"" and """", but !undefined and !"" are both true, so the result is the same in every case. Lines 24–26 correctly omit it, creating an unnecessary inconsistency.

Lines 34–35 double the redundancy with ((Env.stellarNetwork(true) || "") || ""). With throwError=true, getOrThrowError either throws (so the fallback never runs) or returns the string value (so || "" never applies). The expression simplifies to Env.stellarNetwork(true)!.

♻️ Suggested simplification
-        if (!(Env.stellarNetwork() || "")) missing.push("STELLAR_NETWORK");
-        if (!(Env.stellarRpcUrl() || "")) missing.push("STELLAR_RPC_URL");
+        if (!Env.stellarNetwork()) missing.push("STELLAR_NETWORK");
+        if (!Env.stellarRpcUrl()) missing.push("STELLAR_RPC_URL");
-        network: ((Env.stellarNetwork(true) || "") || ""),
-        rpcUrl: ((Env.stellarRpcUrl(true) || "") || ""),
+        network: Env.stellarNetwork(true)!,
+        rpcUrl: Env.stellarRpcUrl(true)!,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/services/contract.service.ts` around lines 19 - 43, The static
initialization uses redundant "|| ''" fallbacks and inconsistent checks: remove
the unnecessary "|| ''" wrappers in the static validation block (replace
conditions like !(Env.stellarNetwork() || "") with !Env.stellarNetwork()) and
simplify CONFIG initializers to directly use the thrown-or-present values
(replace expressions like ((Env.stellarNetwork(true) || "") || "") with
Env.stellarNetwork(true)!, Env.stellarRpcUrl(true)!,
Env.taskEscrowContractId(true)!, Env.usdcContractId(true)!, and
Env.stellarMasterPublicKey(true)!), keeping the existing EscrowContractError
throw logic and the networkPassphrase/maxFee computation unchanged.
api/models/error.model.ts (1)

45-53: 💤 Low value

AuthorizationError code "UNAUTHORIZED" conflicts with its new HTTP status 403 FORBIDDEN.

This PR changed the status to STATUS_CODES.FORBIDDEN (403) while the internal code field remains "UNAUTHORIZED". 401 Unauthorized and 403 Forbidden have distinct HTTP semantics — mixing the names makes error logging and client-side error-code checks misleading.

♻️ Suggested fix
 export class AuthorizationError extends ErrorClass {
     constructor(message: string, details: unknown = null) {
         super(
-            "UNAUTHORIZED",
+            "FORBIDDEN",
             details,
             message,
             STATUS_CODES.FORBIDDEN
         );
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/models/error.model.ts` around lines 45 - 53, The AuthorizationError class
currently sets its internal code to "UNAUTHORIZED" while using
STATUS_CODES.FORBIDDEN (403); update the AuthorizationError constructor to use a
matching code (e.g., "FORBIDDEN") instead of "UNAUTHORIZED" so the code string
aligns with the HTTP 403 status; ensure you only change the literal passed as
the first argument in the AuthorizationError constructor and leave
STATUS_CODES.FORBIDDEN and other parameters intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.example:
- Line 63: The CORS_ORIGINS environment variable is currently wrapped in quotes
which can cause some env loaders to treat the whole string literally; update the
CORS_ORIGINS entry so the value is an unquoted comma-separated list (e.g.,
CORS_ORIGINS=http://localhost:3000,http://localhost:4000,http://localhost:3001),
ensure there are no surrounding quotes or extra spaces, and keep the variable
name CORS_ORIGINS unchanged.

In `@api/routes/agent.route.ts`:
- Around line 14-17: The x402Config fallback uses "0" which is truthy and
defeats the guard; change the fallback for Env.x402PayeeAddress() to an empty
string so x402Config.payeeAddress is falsy when the env var is missing, ensuring
the conditional if (facilitatorUrl && x402Config.payeeAddress &&
Env.x402ApiKey()) prevents registering the X402 payment middleware (the code
path that sets payTo when registering the middleware around the x402 payment
setup).

In `@api/services/stellar.service.ts`:
- Around line 35-44: The current startup validation requires
Env.stellarMasterPublicKey() even though the service never uses it; update the
check so it only validates the master secret key (Env.stellarMasterSecretKey())
and removes the dependency on Env.stellarMasterPublicKey() so startup won't
throw StellarServiceError when only the secret is present; ensure the code path
that creates this.masterAccount via Keypair.fromSecret(...) remains unchanged
and that any references to the public key are derived from this.masterAccount
instead of Env.stellarMasterPublicKey().
- Around line 399-405: Current pathPaymentStrictSend calls use a hard-coded
destMin ("0.0000001"), which allows almost-zero fills; change the code to accept
a caller-supplied destMin (derived from the quote or explicit slippage
tolerance) and use that value in the Operation.pathPaymentStrictSend call
instead of the dust constant. Update the method signatures that build these
transactions (the function that builds txBuilder and any helpers that reference
sendAsset, destAsset, amount, destinationAddress) to accept a required destMin
parameter for cross-asset path payments, validate that destMin is present and >
0 (and reject the request if missing or below an acceptable threshold), and
apply the same change to the other two occurrences noted (the other
pathPaymentStrictSend calls).
- Around line 224-229: The Friendbot fetch call in StellarService (around the
try block that requests funding with
fetch(`https://friendbot.stellar.org?addr=...`)) needs an AbortController-based
timeout so the server thread can't hang; create an AbortController, start a
timer (e.g., setTimeout) that calls controller.abort() after the desired
timeout, pass controller.signal to fetch, and in the catch/abort branch convert
the AbortError/timeout into a StellarServiceError with a clear message before
rethrowing; ensure the timer is cleared on success to avoid leaks and reference
the fetch call and the surrounding try/catch in StellarService to make the
change.

In `@api/utils/env.ts`:
- Around line 28-32: The CORS origin list returned by static corsOrigins may
contain entries with leading/trailing spaces or empty strings; modify
corsOrigins to normalize the result from both branches (the throwError branch
using this.getOrThrowError("CORS_ORIGINS").split(",") and the fallback using
process.env.CORS_ORIGINS?.split(",")) by trimming each entry and filtering out
empty strings (e.g., .map(s => s.trim()).filter(Boolean)) before returning the
array so only valid, canonical origins are returned.
- Around line 329-334: Env.port currently coerces the PORT value with
Number(...) which can yield NaN; change Env.port (and use the string from
getOrThrowError("PORT") when throwError is true) to parse and validate the port:
parse the string (e.g., parseInt/Number), ensure it's a finite integer within
the valid TCP port range (>=1 && <=65535), and throw a clear error if validation
fails; keep the existing non-throwing branch behavior but consider returning NaN
only when throwError is false to preserve backwards compatibility.

In `@tests/integration/api/webhook/sumsub.api.test.ts`:
- Line 204: The assertion uses expect(...).toContain with a duplicate array
[STATUS_CODES.INTERNAL_SERVER_ERROR, STATUS_CODES.INTERNAL_SERVER_ERROR], which
hides the intent; update the test in sumsub.api.test.ts to either assert the
single expected status with
expect(response.status).toBe(STATUS_CODES.INTERNAL_SERVER_ERROR) or, if two
different outcomes are allowed, replace the duplicate with the correct second
constant (e.g., STATUS_CODES.UNKNOWN or STATUS_CODES.UNAUTHORIZED) so the array
passed to toContain contains two distinct meaningful values.

---

Outside diff comments:
In `@api/controllers/installation/github.controller.ts`:
- Around line 122-129: The early-return branch that returns when bountyLabel
exists is using a hardcoded literal 200; update that responseWrapper call to use
STATUS_CODES.OK (consistent with other calls in this controller) and ensure
STATUS_CODES is imported/available in the module so the call becomes status:
STATUS_CODES.OK instead of status: 200.

In `@api/controllers/installation/index.controller.ts`:
- Around line 170-201: The catch currently turns any error from resolving
secrets or adding the USDC trustline into a recoverable warning; instead, ensure
missing/misconfigured Stellar credentials are treated as fatal: resolve and
validate Env.stellarMasterSecretKey(true) before the try (or immediately throw
if it returns falsy), or in the catch rethrow errors that indicate configuration
issues (e.g., missing secret or a ConfigError) rather than returning a 200 with
a warning. Update the block around Env.stellarMasterSecretKey(true) and
stellarService.addTrustLineViaSponsor so that only transient trustline failures
are converted to a partial-success response while credential/config errors
propagate as server errors.

In `@api/controllers/user/index.controller.ts`:
- Around line 155-163: The meta field is passing a raw Error object (meta: {
error }) which JSON-serializes to {} and is inconsistent with the Statsig
extraction; update the catch block to pass the error message string instead
(e.g., meta: { error: error instanceof Error ? error.message : String(error) })
when calling responseWrapper so clients receive a useful message; locate the
responseWrapper call in the same catch handler around the user creation flow and
replace meta: { error } with the message extraction expression (or an existing
errorMessage variable) to keep behavior consistent.

In `@api/index.ts`:
- Around line 47-65: The startup crash happens because Env.corsOrigins(true)
(corsOrigins) is invoked at module init but CORS_ORIGINS isn't validated; update
the startup validation to include "CORS_ORIGINS" (add it to the requiredVars or
warningVars list in the error-handler service where startup envs are validated)
or make corsOrigins optional by changing the call to Env.corsOrigins(false) /
defaulting to [] so missing CORS_ORIGINS doesn't throw; adjust the validation
arrays (requiredVars/warningVars) or the corsOrigins invocation accordingly and
keep the CORS behavior consistent with the intended default (deny all or
explicit allow-list).

In `@api/middlewares/auth.middleware.ts`:
- Around line 94-156: The env reads for cloudTasksServiceAccountEmail and
cloudRunServiceUrl are done outside the try block in validateCloudTasksRequest
which can throw synchronously and produce unhandled promise rejections; move the
calls to Env.cloudTasksServiceAccountEmail(true) and
Env.cloudRunServiceUrl(true) into the existing try block (remove the earlier
top-level declarations) so any thrown errors are caught by the catch and routed
to next(new AuthorizationError(...)); keep the rest of the verification logic
unchanged (authClient.verifyIdToken, payload checks, dataLogger usage).

In `@api/services/cloud-tasks.service.ts`:
- Around line 76-100: The config field initializer calls Env.gcpProjectId(true),
Env.gcpLocationId(true), Env.cloudRunServiceUrl(true),
Env.cloudRunPrivateServiceUrl(true) and Env.cloudTasksServiceAccountEmail(true)
which throw before the constructor's missing[] aggregation runs; move the entire
config construction out of the public instance field and into the class
constructor after the existing validation/aggregation block (i.e., build
this.config inside the constructor using the validated Env values or Env.*(...,
throwError=false) so all missing envs are collected consistently), and make the
queue entries consistent with the chosen approach (either use throwError=false
in those Env calls or ensure they also throw inside the constructor) so no field
initializer can throw before aggregation.

In `@api/services/github-webhook/issue_comment-webhook.service.ts`:
- Around line 511-533: The outer catch currently posts a user-facing message
("Bounty was created but...") that is only correct when the inner GitHub
comment/label mutation fails; change the outer catch to use the generic fallback
message ("Bounty creation failed due to an internal error. Please try again on
the DevAsign dashboard.") and ensure statsigService.logEvent and
dataLogger.error remain unchanged; move the specific "Bounty was created but
there was an issue while posting the bounty comment or adding the bounty label
on issue" message so it is only used in the inner catch that wraps the GitHub
comment/label operations (the block that calls bountyFailureComment), and verify
that earlier calls like stellarService.getAccountInfo, KMSService.decryptWallet,
and prisma.task.create are allowed to bubble to the outer handler which now uses
the generic message.

In `@api/services/socket.service.ts`:
- Around line 38-50: Pre-compute and cache the allowed origins before
constructing the SocketIOServer to avoid uncaught exceptions and per-connection
calls: call Env.corsOrigins(true) once inside SocketService.initialize (store
result in a const like allowedOrigins), guard that call with try/catch to handle
the unset CORS_ORIGINS case (log or fallback to an empty array), then update the
cors.origin callback to reference allowedOrigins (e.g., if (!origin ||
allowedOrigins.includes(origin)) callback(null,true) else callback(new
Error("Not allowed by CORS"))), removing any further calls to Env.corsOrigins
from inside the callback.

In `@tests/integration/api/installation/team.api.test.ts`:
- Around line 234-246: The test descriptions say "404" but the assertions use
STATUS_CODES.FORBIDDEN (403); update the three failing tests' description
strings (the it(...) blocks that call getEndpointWithPrefix([...]) and assert
.expect(STATUS_CODES.FORBIDDEN)) to say "should return 403 when installation not
found" (repeat the same change for the other two occurrences noted), so the
human-readable message matches the actual expectation.

In `@tests/integration/api/task/`{taskId}.api.test.ts:
- Around line 972-1003: In requestTimelineExtension and
replyTimelineExtensionRequest, replace the ValidationError thrown for
authorization failures (the checks that verify "user is contributor" or "user is
creator") with AuthorizationError while keeping ValidationError for status/state
validation (e.g., "task is not in progress"); locate the checks in the
requestTimelineExtension and replyTimelineExtensionRequest functions and swap
the thrown error type to AuthorizationError (import/ensure AuthorizationError is
available) so authorization failures return 403 while other validation issues
still throw ValidationError and return 400.

In `@tests/integration/api/webhook/github.api.test.ts`:
- Around line 537-570: Update the GitHubWebhookError class (error.model.ts /
GitHubWebhookError) to accept an HTTP status code parameter in its constructor
(instead of hardcoding STATUS_CODES.INTERNAL_SERVER_ERROR) and store it as the
error's status; then update the webhook middleware/validation sites that throw
this error (the places handling missing signature, invalid signature, missing
webhook secret, and invalid body format) to pass appropriate 4xx codes (e.g.,
STATUS_CODES.BAD_REQUEST for malformed body/signature, STATUS_CODES.UNAUTHORIZED
for missing/invalid signature or secret). Also update the tests expecting
STATUS_CODES.INTERNAL_SERVER_ERROR to assert the new 4xx codes so behavior and
test expectations align.

---

Nitpick comments:
In `@api/controllers/wallet/index.controller.ts`:
- Around line 104-119: The local constant usdcAsset in the balance-check block
should be renamed to avoid shadowing the module-level usdcAsset import from
stellar.config.js; change the local variable name (e.g., to usdcBalance or
usdcWalletBalance) wherever it’s declared and referenced in the balance-checking
logic inside the controller, and apply the same rename to the corresponding
local declaration in the swapAsset function’s else block so both places stop
using the imported symbol name while keeping all type guards and usages intact.

In `@api/models/error.model.ts`:
- Around line 45-53: The AuthorizationError class currently sets its internal
code to "UNAUTHORIZED" while using STATUS_CODES.FORBIDDEN (403); update the
AuthorizationError constructor to use a matching code (e.g., "FORBIDDEN")
instead of "UNAUTHORIZED" so the code string aligns with the HTTP 403 status;
ensure you only change the literal passed as the first argument in the
AuthorizationError constructor and leave STATUS_CODES.FORBIDDEN and other
parameters intact.

In `@api/services/contract.service.ts`:
- Around line 19-43: The static initialization uses redundant "|| ''" fallbacks
and inconsistent checks: remove the unnecessary "|| ''" wrappers in the static
validation block (replace conditions like !(Env.stellarNetwork() || "") with
!Env.stellarNetwork()) and simplify CONFIG initializers to directly use the
thrown-or-present values (replace expressions like ((Env.stellarNetwork(true) ||
"") || "") with Env.stellarNetwork(true)!, Env.stellarRpcUrl(true)!,
Env.taskEscrowContractId(true)!, Env.usdcContractId(true)!, and
Env.stellarMasterPublicKey(true)!), keeping the existing EscrowContractError
throw logic and the networkPassphrase/maxFee computation unchanged.

In `@api/services/kms.service.ts`:
- Around line 8-16: The guard in the KmsService module redundantly uses `|| ""`
fallbacks and an extra `|| ""` inside the initial presence check; simplify the
guard and use consistent non-null assertions: update the guard that throws
KmsServiceError to check presence with straightforward falsy checks (e.g., `if
(!Env.gcpProjectId() || !Env.gcpLocationId() || !Env.gcpKeyRingId() ||
!Env.gcpKeyId()) { ... }`) and set PROJECT_ID using the same pattern as the
others (e.g., `const PROJECT_ID = Env.gcpProjectId(true)!;`) removing the double
`|| ""` fallbacks so PROJECT_ID, LOCATION_ID, KEY_RING_ID, and KEY_ID are
handled consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bde8927d-2a37-4e5d-8c93-77bb71aecdc3

📥 Commits

Reviewing files that changed from the base of the PR and between f86b3bf and c8574f1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (83)
  • .env.example
  • README.md
  • api/config/firebase.config.ts
  • api/config/logger.config.ts
  • api/config/stellar.config.ts
  • api/controllers/installation/github.controller.ts
  • api/controllers/installation/index.controller.ts
  • api/controllers/installation/team.controller.ts
  • api/controllers/internal/index.controller.ts
  • api/controllers/task/activities.controller.ts
  • api/controllers/task/contributor.controller.ts
  • api/controllers/task/index.controller.ts
  • api/controllers/task/installation.controller.ts
  • api/controllers/task/{taskId}.controller.ts
  • api/controllers/user/index.controller.ts
  • api/controllers/user/sumsub.controller.ts
  • api/controllers/wallet/index.controller.ts
  • api/controllers/wallet/transactions.controller.ts
  • api/controllers/webhook/github.controller.ts
  • api/controllers/webhook/sumsub.controller.ts
  • api/index.ts
  • api/middlewares/auth.middleware.ts
  • api/middlewares/error.middleware.ts
  • api/middlewares/rate-limit.middleware.ts
  • api/middlewares/request.middleware.ts
  • api/middlewares/webhook.middleware.ts
  • api/models/error.model.ts
  • api/routes/_test_/contract.test.route.ts
  • api/routes/_test_/general.test.route.ts
  • api/routes/_test_/octokit.test.route.ts
  • api/routes/_test_/stellar.test.route.ts
  • api/routes/agent.route.ts
  • api/routes/installation.route.ts
  • api/routes/internal.route.ts
  • api/routes/task.route.ts
  • api/routes/user.route.ts
  • api/routes/wallet.route.ts
  • api/routes/webhook.route.ts
  • api/schemas/global.schema.ts
  • api/schemas/installation.schema.ts
  • api/schemas/task.schema.ts
  • api/schemas/wallet.schema.ts
  • api/services/cloud-tasks.service.ts
  • api/services/contract.service.ts
  • api/services/error-handler.service.ts
  • api/services/github-webhook/installation-webhook.service.ts
  • api/services/github-webhook/installation_repositories-webhook.service.ts
  • api/services/github-webhook/issue_comment-webhook.service.ts
  • api/services/github-webhook/pull_request-webhook.service.ts
  • api/services/github-webhook/push-webhook.service.ts
  • api/services/kms.service.ts
  • api/services/octokit.service.ts
  • api/services/socket.service.ts
  • api/services/statsig.service.ts
  • api/services/stellar.service.ts
  • api/utils/data.ts
  • api/utils/env.ts
  • api/utils/helper.ts
  • package.json
  • tests/helpers/__tests__/database-test-utilities.test.ts
  • tests/helpers/database-test-helper.ts
  • tests/helpers/test-utils.ts
  • tests/integration/api/installation/github.api.test.ts
  • tests/integration/api/installation/index.api.test.ts
  • tests/integration/api/installation/team.api.test.ts
  • tests/integration/api/internal/index.api.test.ts
  • tests/integration/api/task/activities.api.test.ts
  • tests/integration/api/task/contributor.api.test.ts
  • tests/integration/api/task/index.api.test.ts
  • tests/integration/api/task/installation.api.test.ts
  • tests/integration/api/task/{taskId}.api.test.ts
  • tests/integration/api/user/index.api.test.ts
  • tests/integration/api/user/sumsub.api.test.ts
  • tests/integration/api/wallet/index.api.test.ts
  • tests/integration/api/wallet/transactions.api.test.ts
  • tests/integration/api/webhook/github.api.test.ts
  • tests/integration/api/webhook/sumsub.api.test.ts
  • tests/integration/middlewares/auth.middleware.test.ts
  • tests/integration/middlewares/error.middleware.test.ts
  • tests/integration/middlewares/rate-limit.middleware.test.ts
  • tests/integration/middlewares/request.middleware.test.ts
  • tests/integration/middlewares/webhook.middleware.test.ts
  • tests/unit/helper.test.ts
💤 Files with no reviewable changes (1)
  • package.json

Comment thread .env.example Outdated
Comment thread api/routes/agent.route.ts
Comment thread api/services/stellar.service.ts Outdated
Comment thread api/services/stellar.service.ts
Comment on lines +399 to +405
txBuilder = txBuilder.addOperation(Operation.pathPaymentStrictSend({
destination: destinationAddress,
sendAsset,
destAsset,
sendAmount: amount,
destMin: "0.0000001" // For simplicity, accepting any amount of destAsset.
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

These path payments currently accept effectively any fill price.

Using destMin: "0.0000001" means a thin or manipulated order book can complete the path payment for an almost-zero return. For transfer and swap flows, that is a financial correctness bug. destMin needs to come from a quote or explicit slippage tolerance, not a hard-coded dust value.

Suggested direction
-                    destMin: "0.0000001"
+                    destMin

Pass destMin into these methods from a quoted amount or a caller-supplied slippage calculation, and reject requests where it is missing for cross-asset path payments.

Also applies to: 486-492, 565-570

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/services/stellar.service.ts` around lines 399 - 405, Current
pathPaymentStrictSend calls use a hard-coded destMin ("0.0000001"), which allows
almost-zero fills; change the code to accept a caller-supplied destMin (derived
from the quote or explicit slippage tolerance) and use that value in the
Operation.pathPaymentStrictSend call instead of the dust constant. Update the
method signatures that build these transactions (the function that builds
txBuilder and any helpers that reference sendAsset, destAsset, amount,
destinationAddress) to accept a required destMin parameter for cross-asset path
payments, validate that destMin is present and > 0 (and reject the request if
missing or below an acceptable threshold), and apply the same change to the
other two occurrences noted (the other pathPaymentStrictSend calls).

Comment thread api/utils/env.ts Outdated
Comment thread api/utils/env.ts
Comment thread tests/integration/api/webhook/sumsub.api.test.ts Outdated
@devasign-app
Copy link
Copy Markdown

devasign-app Bot commented May 5, 2026

Merge Score: 95/100

🟢 ███████████████████░ 95%

This PR introduces a comprehensive refactoring of the codebase, focusing on standardizing HTTP status codes, centralizing environment variable management via a new Env class, and updating Stellar SDK usage. The changes significantly improve code maintainability and consistency. A minor bug was identified in the Stellar service where a Keypair object was passed instead of a public key string for fee bump transactions, which has been flagged for correction.

Code Suggestions (5)

High Priority (1)

  1. api/services/stellar.service.ts (Line 480)
    Fix TransactionBuilder.buildFeeBumpTransaction parameter type.

Reasoning: TransactionBuilder.buildFeeBumpTransaction expects the feeAccount parameter to be a string (public key) or an Account object. Passing a Keypair object directly will cause an error or unexpected behavior. You should pass sponsorKeyPair.publicKey() instead.

Suggested Code:

            const feeBump = TransactionBuilder.buildFeeBumpTransaction(
                sponsorKeyPair.publicKey(),
                baseFee,
                transaction,
                networkPassphrase
            );

Low Priority (4)

  1. api/services/cloud-tasks.service.ts (Line 112)
    Simplify redundant environment variable checks.

Reasoning: The expression !((Env.cloudTasksServiceAccountEmail() || "") || "") is overly complex and redundant. It can be simplified to !Env.cloudTasksServiceAccountEmail().

Suggested Code:

        if (!Env.cloudTasksServiceAccountEmail()) missing.push("CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL");
  1. api/services/contract.service.ts (Line 35)
    Simplify redundant environment variable checks.

Reasoning: The expressions ((Env.stellarNetwork(true) || "") || "") and ((Env.stellarRpcUrl(true) || "") || "") are overly complex and redundant. They can be simplified to Env.stellarNetwork(true)! and Env.stellarRpcUrl(true)!.

Suggested Code:

    private static CONFIG = {
        network: Env.stellarNetwork(true)!,
        rpcUrl: Env.stellarRpcUrl(true)!,
  1. api/services/octokit.service.ts (Line 19)
    Simplify redundant environment variable checks.

Reasoning: The expression !((Env.githubAppId() || "") || "") || !((Env.githubAppPrivateKey() || "") || "") is overly complex and redundant. It can be simplified to !Env.githubAppId() || !Env.githubAppPrivateKey().

Suggested Code:

if (!Env.githubAppId() || !Env.githubAppPrivateKey()) {
    throw new GitHubAPIError("Missing GitHub App credentials (GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY) in environment variables");
}
  1. api/services/kms.service.ts (Line 6)
    Simplify redundant environment variable checks.

Reasoning: The expression !((Env.gcpProjectId() || "") || "") is overly complex and redundant. It can be simplified to !Env.gcpProjectId().

Suggested Code:

if (!Env.gcpProjectId() || !Env.gcpLocationId() || !Env.gcpKeyRingId() || !Env.gcpKeyId()) {
    throw new KmsServiceError("Missing GCP KMS credentials (GCP_PROJECT_ID, GCP_LOCATION_ID, GCP_KEY_RING_ID, GCP_KEY_ID) in environment variables");
}

// Set Google Cloud KMS environment variables
const PROJECT_ID = Env.gcpProjectId(true)!;
📋 Copy Review for AI Agent

Copy the prompt below and paste it into your AI coding assistant to apply all suggestions.

Apply the following code review suggestions to the codebase. For each item, make the described change at the specified file and line. Use the suggested code when provided, otherwise implement the fix based on the description and reasoning.

1. [HIGH] api/services/stellar.service.ts:480
   Type: fix
   Issue: Fix `TransactionBuilder.buildFeeBumpTransaction` parameter type.
   Reasoning: `TransactionBuilder.buildFeeBumpTransaction` expects the `feeAccount` parameter to be a `string` (public key) or an `Account` object. Passing a `Keypair` object directly will cause an error or unexpected behavior. You should pass `sponsorKeyPair.publicKey()` instead.
   Suggested fix:
```typescript
            const feeBump = TransactionBuilder.buildFeeBumpTransaction(
                sponsorKeyPair.publicKey(),
                baseFee,
                transaction,
                networkPassphrase
            );
```

2. [LOW] api/services/cloud-tasks.service.ts:112
   Type: style
   Issue: Simplify redundant environment variable checks.
   Reasoning: The expression `!((Env.cloudTasksServiceAccountEmail() || "") || "")` is overly complex and redundant. It can be simplified to `!Env.cloudTasksServiceAccountEmail()`.
   Suggested fix:
```typescript
        if (!Env.cloudTasksServiceAccountEmail()) missing.push("CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL");
```

3. [LOW] api/services/contract.service.ts:35
   Type: style
   Issue: Simplify redundant environment variable checks.
   Reasoning: The expressions `((Env.stellarNetwork(true) || "") || "")` and `((Env.stellarRpcUrl(true) || "") || "")` are overly complex and redundant. They can be simplified to `Env.stellarNetwork(true)!` and `Env.stellarRpcUrl(true)!`.
   Suggested fix:
```typescript
    private static CONFIG = {
        network: Env.stellarNetwork(true)!,
        rpcUrl: Env.stellarRpcUrl(true)!,
```

4. [LOW] api/services/octokit.service.ts:19
   Type: style
   Issue: Simplify redundant environment variable checks.
   Reasoning: The expression `!((Env.githubAppId() || "") || "") || !((Env.githubAppPrivateKey() || "") || "")` is overly complex and redundant. It can be simplified to `!Env.githubAppId() || !Env.githubAppPrivateKey()`.
   Suggested fix:
```typescript
if (!Env.githubAppId() || !Env.githubAppPrivateKey()) {
    throw new GitHubAPIError("Missing GitHub App credentials (GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY) in environment variables");
}
```

5. [LOW] api/services/kms.service.ts:6
   Type: style
   Issue: Simplify redundant environment variable checks.
   Reasoning: The expression `!((Env.gcpProjectId() || "") || "")` is overly complex and redundant. It can be simplified to `!Env.gcpProjectId()`.
   Suggested fix:
```typescript
if (!Env.gcpProjectId() || !Env.gcpLocationId() || !Env.gcpKeyRingId() || !Env.gcpKeyId()) {
    throw new KmsServiceError("Missing GCP KMS credentials (GCP_PROJECT_ID, GCP_LOCATION_ID, GCP_KEY_RING_ID, GCP_KEY_ID) in environment variables");
}

// Set Google Cloud KMS environment variables
const PROJECT_ID = Env.gcpProjectId(true)!;
```
📊 Review Metadata
  • Processing Time: 277s
  • Analysis Date: 5/5/2026, 4:18:08 PM

🤖 This review was generated by AI. While we strive for accuracy, please use your judgment when applying suggestions.

💬 Questions about this review? Open an issue or contact support.

@devasign-app
Copy link
Copy Markdown

devasign-app Bot commented May 5, 2026

❌ PR Review Failed


Error Details

The PR review system encountered an error while analyzing this pull request:

Follow-up review failed: Pull request follow-up analysis timed out after 300000ms. Please review manually.

What to do next

  1. Manual Review: Please proceed with manual code review
  2. Retry: A review can be triggered by the repo maintainer commenting 'review'
  3. Support: If this error persists, please contact support

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/services/kms.service.ts`:
- Around line 8-16: The module currently validates GCP KMS env vars at import
time (the top-level check that throws KmsServiceError and the constants
PROJECT_ID, LOCATION_ID, KEY_RING_ID, KEY_ID), which can crash the app even when
KMS is unused; move this validation lazily into the KMS service methods (or a
single helper e.g., ensureKmsEnv()) so the checks run at runtime before any KMS
call. Replace top-level throws and direct constant initialization with functions
that call Env.gcpProjectId(true) etc. inside service methods (or the new
ensureKmsEnv) and throw KmsServiceError there if missing, keeping fail-fast
behavior only when KMS is actually invoked.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e0b3e39-497d-4c27-9cb7-f71f6dc90cc9

📥 Commits

Reviewing files that changed from the base of the PR and between c8574f1 and 84a99da.

📒 Files selected for processing (10)
  • .env.example
  • api/routes/_test_/general.test.route.ts
  • api/routes/agent.route.ts
  • api/services/cloud-tasks.service.ts
  • api/services/contract.service.ts
  • api/services/kms.service.ts
  • api/services/octokit.service.ts
  • api/services/stellar.service.ts
  • api/utils/env.ts
  • tests/integration/api/webhook/sumsub.api.test.ts
✅ Files skipped from review due to trivial changes (3)
  • tests/integration/api/webhook/sumsub.api.test.ts
  • api/services/contract.service.ts
  • api/services/cloud-tasks.service.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • api/routes/test/general.test.route.ts
  • api/services/octokit.service.ts
  • api/utils/env.ts
  • api/routes/agent.route.ts
  • api/services/stellar.service.ts

Comment on lines +8 to +16
if (!Env.gcpProjectId() || !Env.gcpLocationId() || !Env.gcpKeyRingId() || !Env.gcpKeyId()) {
throw new KmsServiceError("Missing GCP KMS credentials (GCP_PROJECT_ID, GCP_LOCATION_ID, GCP_KEY_RING_ID, GCP_KEY_ID) in environment variables");
}

// Set Google Cloud KMS environment variables
const PROJECT_ID = process.env.GCP_PROJECT_ID;
const LOCATION_ID = process.env.GCP_LOCATION_ID;
const KEY_RING_ID = process.env.GCP_KEY_RING_ID;
const KEY_ID = process.env.GCP_KEY_ID;
const PROJECT_ID = Env.gcpProjectId(true)!;
const LOCATION_ID = Env.gcpLocationId(true)!;
const KEY_RING_ID = Env.gcpKeyRingId(true)!;
const KEY_ID = Env.gcpKeyId(true)!;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Defer KMS env validation to runtime usage instead of module import.

Validating and throwing at module scope can crash app startup as soon as this file is imported, even when KMS is not used on that code path. Keep fail-fast behavior, but do it lazily inside the service methods (or a shared resolver) so unrelated flows can still boot.

Suggested refactor
-// Verify required environment variables are present
-if (!Env.gcpProjectId() || !Env.gcpLocationId() || !Env.gcpKeyRingId() || !Env.gcpKeyId()) {
-    throw new KmsServiceError("Missing GCP KMS credentials (GCP_PROJECT_ID, GCP_LOCATION_ID, GCP_KEY_RING_ID, GCP_KEY_ID) in environment variables");
-}
-
-// Set Google Cloud KMS environment variables
-const PROJECT_ID = Env.gcpProjectId(true)!;
-const LOCATION_ID = Env.gcpLocationId(true)!;
-const KEY_RING_ID = Env.gcpKeyRingId(true)!;
-const KEY_ID = Env.gcpKeyId(true)!;
-
 const client = new KeyManagementServiceClient();
-const keyName = client.cryptoKeyPath(PROJECT_ID, LOCATION_ID, KEY_RING_ID, KEY_ID);
+const getKeyName = () => {
+    const projectId = Env.gcpProjectId(true);
+    const locationId = Env.gcpLocationId(true);
+    const keyRingId = Env.gcpKeyRingId(true);
+    const keyId = Env.gcpKeyId(true);
+
+    if (!projectId || !locationId || !keyRingId || !keyId) {
+        throw new KmsServiceError(
+            "Missing GCP KMS credentials (GCP_PROJECT_ID, GCP_LOCATION_ID, GCP_KEY_RING_ID, GCP_KEY_ID) in environment variables"
+        );
+    }
+
+    return client.cryptoKeyPath(projectId, locationId, keyRingId, keyId);
+};
@@
-                name: keyName,
+                name: getKeyName(),
@@
-                name: keyName,
+                name: getKeyName(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/services/kms.service.ts` around lines 8 - 16, The module currently
validates GCP KMS env vars at import time (the top-level check that throws
KmsServiceError and the constants PROJECT_ID, LOCATION_ID, KEY_RING_ID, KEY_ID),
which can crash the app even when KMS is unused; move this validation lazily
into the KMS service methods (or a single helper e.g., ensureKmsEnv()) so the
checks run at runtime before any KMS call. Replace top-level throws and direct
constant initialization with functions that call Env.gcpProjectId(true) etc.
inside service methods (or the new ensureKmsEnv) and throw KmsServiceError there
if missing, keeping fail-fast behavior only when KMS is actually invoked.

@LennyMalcolm0 LennyMalcolm0 merged commit 83dbb39 into main May 5, 2026
3 checks passed
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.

1 participant