Skip to content

feat: add relayer e2e ci and relayer service optimizations#113

Merged
JoE11-y merged 12 commits intomainfrom
stellar-integrations
Apr 13, 2026
Merged

feat: add relayer e2e ci and relayer service optimizations#113
JoE11-y merged 12 commits intomainfrom
stellar-integrations

Conversation

@JoE11-y
Copy link
Copy Markdown
Contributor

@JoE11-y JoE11-y commented Apr 12, 2026

Summary

Update full Stellar (Soroban) support to ProofBridge alongside the existing EVM implementation:

- `scripts/relayer-e2e/` exercises the backend-relayer HTTP API through the ad + trade lifecycles; ABIs are loaded at runtime from Foundry build output so the scripts stay

decoupled from backend source.

  • CI — four new GitHub Actions workflows: evm-contracts, stellar-contracts, cross-chain-e2e, backend-relayer-tests (unit + jest e2e in parallel), backend-relayer-e2e
    (the HTTP lifecycle suite). Shared chain + circuit bring-up moved into scripts/start_chains.sh / scripts/build_circuits.sh.

Test plan

  • pnpm --filter backend-relayer test (unit)
  • pnpm --filter backend-relayer test:e2e (jest e2e against testcontainers Postgres)
  • bash scripts/relayer-e2e/e2e.sh against local anvil + stellar-rpc
  • scripts/cross-chain-e2e full flow locally
  • forge test in contracts/evm
  • cargo test in contracts/stellar
  • All five new GH Actions pass on the PR

Summary by CodeRabbit

  • New Features

    • End-to-end relayer CLI, local chain orchestration, runtime seeding flows, new container image and CI workflows to run E2E flows.
  • Bug Fixes

    • Unified cross-chain address normalization and stricter address validation to reduce malformed-address errors.
  • Documentation

    • Updated env examples and README to reflect new secret/config keys and defaults.
  • Tests

    • Reworked test scaffolding: added standalone E2E flows and helpers; removed legacy integration suites.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a relayer end-to-end framework: CI workflows, Docker/runtime assets, chain lifecycle scripts, cross-chain deploy/seed/flow tooling, a relayer-e2e CLI and flows, chain-aware address/typed-data changes, and removal of legacy integration test scaffolding.

Changes

Cohort / File(s) Summary
CI Workflows
/.github/workflows/backend-relayer-e2e.yml, /.github/workflows/backend-relayer-tests.yml, /.github/workflows/cross-chain-e2e.yml
New GitHub Actions for relayer unit/e2e and cross-chain e2e: toolchain setup, start chains, deploy contracts, run services, seed DB, execute flows, teardown and upload logs on failure.
Container & Compose
apps/backend-relayer/Dockerfile, apps/backend-relayer/docker-compose.e2e.yaml, apps/backend-relayer/.env.example, apps/backend-relayer/README.md, apps/backend-relayer/package.json
Add multi-stage Dockerfile and e2e compose; rename env var ADMIN_SECRETEVM_ADMIN_PRIVATE_KEY, add STELLAR_ADMIN_SECRET; move prisma to runtime deps; adjust scripts.
Core address & typed-data
apps/backend-relayer/src/providers/viem/ethers/typedData.ts, apps/backend-relayer/src/providers/stellar/utils/eip712.ts
Add exported normalizeChainAddress() supporting EVM/Stellar canonical forms; change salt conversion to uuidToBigInt(...) for EIP‑712 struct hashing.
Business logic (ads & trades)
apps/backend-relayer/src/modules/ads/ad.service.ts, apps/backend-relayer/src/modules/trades/trade.service.ts, apps/backend-relayer/src/modules/ads/dto/ad.dto.ts
Replace EVM-only getAddress usage with chain-aware normalization/validation; persist normalized addresses; add DTO definite-assignment and optional signerPublicKey.
Stellar provider & auth tests
apps/backend-relayer/src/providers/stellar/stellar.service.ts, apps/backend-relayer/src/modules/auth/stellar/stellar-auth.service.spec.ts
Minor formatting tweaks; adjust test helper to use `
Remove legacy integration tests & setups
apps/backend-relayer/test/integrations/*, apps/backend-relayer/test/setups/*, apps/backend-relayer/test/integrations/jest-e2e.json
Delete old Supertest helpers, E2E suites, EVM/Stellar deploy/setup helpers, jest integration setup and related fixtures.
Test setup refactor
apps/backend-relayer/test/setups/create-app.ts, apps/backend-relayer/test/setups/jest-e2e.setup.ts, apps/backend-relayer/test/setups/mock-chain-adapter.ts, apps/backend-relayer/test/setups/seed.ts
Make testing app options to toggle real chain adapters, add JWT env defaults, simplify seeding to seedDBe2e, small lint tweak.
New relayer-e2e framework (scripts)
scripts/relayer-e2e/cli.ts, scripts/relayer-e2e/e2e.sh, scripts/relayer-e2e/** (lib/, flows/, package.json, tsconfig)
Add CLI, bash runner, HTTP client, auth helpers, assertions, ad/trade lifecycle flows, deploy/seed libs, package manifest and tsconfig.
Cross-chain deployment refactor
scripts/cross-chain-e2e/lib/deploy.ts, scripts/cross-chain-e2e/run.ts
Introduce reusable deploy/link APIs for Stellar/EVM and refactor run.ts to use high-level deploy/link functions.
Chain lifecycle scripts & workspace
scripts/start_chains.sh, scripts/stop_chains.sh, pnpm-workspace.yaml
start_chains now emits .chains.env, records anvil PID/log, exports additional secrets; add stop_chains to teardown; include scripts/relayer-e2e in pnpm workspace.
EVM tooling & seed
scripts/relayer-e2e/lib/evm-actions.ts, scripts/relayer-e2e/lib/eth.ts, scripts/relayer-e2e/lib/seed.ts
Switch ABI loading to Foundry artifacts, add eth client/funder helpers, add DB seeding logic for deployed snapshot and helper types.
Relayer-e2e helper libs
scripts/relayer-e2e/lib/api.ts, scripts/relayer-e2e/lib/auth.ts, scripts/relayer-e2e/lib/assert.ts, scripts/relayer-e2e/lib/amount.ts
New HTTP client and typed API wrappers, login helpers for EVM/Stellar, assertion utilities, and chain-aware amount conversion utilities.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as relayer-e2e CLI
    participant Deploy as Deploy Lib
    participant Stellar as Stellar Node
    participant EVM as EVM Node
    participant DB as Postgres
    participant API as Backend Relayer API

    CLI->>Deploy: deployAll()
    Deploy->>Stellar: deployStellarChain()
    Stellar-->>Deploy: stellar addresses/wasm/vk
    Deploy->>EVM: deployEvmChain()
    EVM-->>Deploy: evm contract addresses
    Deploy->>Deploy: linkChains(stellar, evm)
    Deploy-->>CLI: snapshot.json

    CLI->>DB: seedDb(snapshot)
    DB-->>CLI: seeded

    CLI->>API: login (Stellar/EVM)
    API-->>CLI: access token

    CLI->>API: apiCreateAd()
    API->>DB: insert ad
    DB-->>API: adId
    CLI->>Stellar: createAdSoroban()
    Stellar-->>CLI: txHash
    CLI->>API: apiConfirm(adId, txHash)
    API->>DB: update ad → ACTIVE
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • Ugo-X

"🐰
I hopped through scripts and CI lights,
Linked chains by starlit nights,
Seeded DBs and ran the flights,
Built containers, fixed address rites—
Carrot-powered tests take flight!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Stellar integrations' is overly broad and vague. While the PR does add Stellar support, the title does not convey the comprehensive scope: it omits critical changes to backend-relayer (address normalization, config updates, Docker setup), e2e test infrastructure, CI workflows, and chain lifecycle management. For a reviewer scanning history, this generic phrasing obscures the main change and fails to distinguish this PR from other Stellar-related work. Consider a more specific title like 'Add Stellar support with e2e test infrastructure and CI workflows' or 'Add Stellar Soroban integration with end-to-end lifecycle testing' to better convey the main initiatives and scope.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stellar-integrations

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.

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: 14

Caution

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

⚠️ Outside diff range comments (3)
scripts/relayer-e2e/lib/evm-actions.ts (1)

175-179: ⚠️ Potential issue | 🟡 Minor

Use ORDER_PORTAL_ABI for the OrderPortal role check.

This hasRole read targets orderChain.orderPortalAddress, but Line 177 uses AD_MANAGER_ABI. It works only while both contracts expose the same AccessControl surface. Using the contract’s own ABI keeps this call resilient to ABI drift.

Suggested fix
   const isAdmin = await publicClient.readContract({
     address: orderChain.orderPortalAddress,
-    abi: AD_MANAGER_ABI,
+    abi: ORDER_PORTAL_ABI,
     functionName: 'hasRole',
     args: [DEFAULT_ADMIN_ROLE, mgrAddr],
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/relayer-e2e/lib/evm-actions.ts` around lines 175 - 179, The
readContract call that determines isAdmin is using AD_MANAGER_ABI against
orderChain.orderPortalAddress; change it to use ORDER_PORTAL_ABI so the hasRole
check targets the OrderPortal contract's ABI (update the
publicClient.readContract invocation that passes AD_MANAGER_ABI to pass
ORDER_PORTAL_ABI instead, keeping the same functionName 'hasRole' and args
[DEFAULT_ADMIN_ROLE, mgrAddr]).
apps/backend-relayer/README.md (1)

290-290: ⚠️ Potential issue | 🟡 Minor

Fix env var typo to prevent copy/paste misconfiguration.

Line 290 uses JWT_REFRESH_EXPIRTY; it should be JWT_REFRESH_EXPIRY.

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

In `@apps/backend-relayer/README.md` at line 290, Rename the mistyped environment
variable JWT_REFRESH_EXPIRTY to JWT_REFRESH_EXPIRY wherever it's defined or
documented (e.g., in the README and any env templates), and update any code or
config that reads process.env.JWT_REFRESH_EXPIRTY to use
process.env.JWT_REFRESH_EXPIRY so copies/pastes won't introduce a
misconfiguration.
apps/backend-relayer/test/setups/seed.ts (1)

11-17: ⚠️ Potential issue | 🟠 Major

Don’t swallow seed failures; let setup fail fast.

Line 14-Line 16 logs errors but continues, so tests can run against a partially seeded DB. Also, disconnecting in both try and finally is redundant.

Proposed fix
 export const seedDBe2e = async () => {
   const prisma = new PrismaClient();
   try {
     await prisma.$connect();

     await seedAdmin(prisma, 'admin@x.com', 'ChangeMe123!');

-    await prisma.$disconnect();
-
     console.log('Seeding completed.');
   } catch (error) {
     console.error('Error seeding db:', error);
+    throw error;
   } finally {
     await prisma.$disconnect();
   }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend-relayer/test/setups/seed.ts` around lines 11 - 17, Remove the
redundant prisma.$disconnect() in the try block and make the script fail fast by
rethrowing the caught error (or calling process.exit(1)) instead of only logging
it; specifically, in the seeding function where prisma.$disconnect is used, keep
a single await prisma.$disconnect() in the finally block and replace the catch
body to log the error and then throw error (or call process.exit(1)) so tests
won't proceed against a partially seeded DB.
🧹 Nitpick comments (11)
scripts/relayer-e2e/lib/amount.ts (1)

12-18: Add a defensive decimals guard at the API boundary.

Line 15 currently accepts any number; rejecting negative or non-integer values here makes failures clearer and earlier.

Proposed hardening
 export function toBaseUnits(
   amount: string,
   chainKind: ChainKind,
   decimals: number = DEFAULT_DECIMALS[chainKind]
 ): string {
+  if (!Number.isInteger(decimals) || decimals < 0) {
+    throw new Error(`Invalid decimals: ${decimals}. Expected a non-negative integer.`);
+  }
   return parseUnits(amount, decimals).toString();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/relayer-e2e/lib/amount.ts` around lines 12 - 18, The toBaseUnits
function currently accepts any number for the decimals parameter; add a
defensive guard at the API boundary to validate decimals is an integer >= 0 (and
optionally finite), and throw a clear TypeError if not; locate toBaseUnits and
DEFAULT_DECIMALS (and the parseUnits call) and before calling parseUnits check
Number.isInteger(decimals) && decimals >= 0 (or fallback to
DEFAULT_DECIMALS[chainKind] if undefined), and throw a descriptive error like
"Invalid decimals: must be a non-negative integer" when the check fails so
callers get immediate, clear feedback.
scripts/relayer-e2e/lib/stellar-actions.ts (1)

17-20: Cross-package import from E2E scripts to backend source.

This import reaches directly into the backend-relayer source tree. While functional, this creates a coupling where changes to address.ts could break E2E scripts. Consider whether these utilities should live in a shared package or be duplicated in the E2E scripts for isolation.

The .js extension is correct for ESM module resolution.

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

In `@scripts/relayer-e2e/lib/stellar-actions.ts` around lines 17 - 20, The E2E
script imports hex32ToBuffer and hex32ToContractId directly from backend source,
creating tight coupling; either extract these utilities into a shared package
(publish or workspace package) and update the import in stellar-actions.ts to
import those symbols from the shared module, or copy/duplicate the
implementations of hex32ToBuffer and hex32ToContractId into a local e2e utility
file and change the import to that local file (keeping the .js ESM extension).
Ensure exports/signatures remain identical so callers in stellar-actions.ts need
no further changes.
apps/backend-relayer/src/libs/configs.ts (1)

21-21: Validate the admin private key eagerly.

Now that this field holds the EVM signing key, || '' pushes misconfiguration to the first chain operation. Consider validating EVM_ADMIN_PRIVATE_KEY during config load so startup fails with a clear error.

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

In `@apps/backend-relayer/src/libs/configs.ts` at line 21, The config currently
sets admin: process.env.EVM_ADMIN_PRIVATE_KEY || '' which defers
misconfiguration; instead validate EVM_ADMIN_PRIVATE_KEY eagerly during config
load: read process.env.EVM_ADMIN_PRIVATE_KEY, ensure it exists and matches
expected EVM private key format (e.g., hex 64 chars or 0x-prefixed 66 chars),
normalize (add 0x if needed) and assign the normalized value to the admin
property, and throw a clear Error during startup if missing/invalid so the
application fails fast; update the config initialization code that sets admin to
perform this validation/normalization rather than defaulting to an empty string.
apps/backend-relayer/test/setups/jest-e2e.setup.ts (1)

42-43: Prefer fallback assignment for JWT expiry vars (consistency + configurability).

Line 42-Line 43 currently force overwrite any externally provided values. Consider matching the || pattern used for other env defaults in this file.

Proposed tweak
-  process.env.JWT_EXPIRY = '7d';
-  process.env.JWT_REFRESH_EXPIRY = '30d';
+  process.env.JWT_EXPIRY = process.env.JWT_EXPIRY || '7d';
+  process.env.JWT_REFRESH_EXPIRY = process.env.JWT_REFRESH_EXPIRY || '30d';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend-relayer/test/setups/jest-e2e.setup.ts` around lines 42 - 43,
Replace the unconditional assignments for JWT_EXPIRY and JWT_REFRESH_EXPIRY with
fallback assignments so external env values aren't overwritten; update the lines
that set process.env.JWT_EXPIRY and process.env.JWT_REFRESH_EXPIRY to use the
same "use existing value or default" pattern used elsewhere in this file (i.e.,
assign a default only when the env var is falsy) so tests keep configurability
and consistency with other env defaults.
scripts/relayer-e2e/lib/api.ts (1)

18-21: Content-Type header set for all requests including GET.

Setting content-type: application/json for GET requests (which typically have no body) is unconventional but harmless. Most servers ignore it for bodyless requests.

💡 Only set Content-Type when body is present
 async function request<T = any>(
   method: "GET" | "POST" | "PATCH" | "DELETE",
   path: string,
   opts: { body?: unknown; token?: string } = {}
 ): Promise<ApiResponse<T>> {
-  const headers: Record<string, string> = {
-    "content-type": "application/json",
-  };
+  const headers: Record<string, string> = {};
+  if (opts.body !== undefined) {
+    headers["content-type"] = "application/json";
+  }
   if (opts.token) headers.authorization = `Bearer ${opts.token}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/relayer-e2e/lib/api.ts` around lines 18 - 21, The headers object
currently always sets "content-type": "application/json"; change it so
Content-Type is only added when a request body is present (e.g., check for
opts.body or a non-GET method) instead of unconditionally; keep the existing
authorization logic that sets headers.authorization = `Bearer ${opts.token}`
when opts.token is present and ensure the Content-Type is added conditionally
before sending the request in the same scope where headers is constructed.
scripts/relayer-e2e/flows/trade-lifecycle.ts (2)

39-43: Cross-package import from source may cause maintenance issues.

Importing directly from ../../../apps/backend-relayer/src/providers/viem/ethers/typedData.js creates tight coupling between the E2E test suite and backend internals. If the backend path or exports change, this test will break.

Consider extracting shared types/utilities to a common package or duplicating the necessary definitions in the E2E test lib.

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

In `@scripts/relayer-e2e/flows/trade-lifecycle.ts` around lines 39 - 43, The test
imports backend internals (domain, orderTypes, signTypedOrder) directly which
creates tight coupling; stop importing from the backend source and either (a)
extract the needed symbols (domain, orderTypes, signTypedOrder) into a shared
package/utility library that both backend and E2E tests depend on and update the
import in trade-lifecycle.ts to use that shared module, or (b) copy the minimal
required definitions into the E2E test lib and import them locally from the test
helpers; ensure all references to domain, orderTypes, and signTypedOrder are
updated to the new module path and exported shape matches what the test expects.

48-61: Same concern: non-null assertions on environment variables.

Similar to ad-lifecycle.ts, this file uses non-null assertions on multiple environment variables without early validation. The same early validation pattern would improve error messages.

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

In `@scripts/relayer-e2e/flows/trade-lifecycle.ts` around lines 48 - 61, The code
uses non-null assertions for several env vars (bridgerKey, bridgerStellarSecret,
adCreatorSecret, adCreatorEvmKey, stellarChainId, evmChainId) which can crash
without helpful errors; add an early validation block at the top of this module
that checks each required process.env key, logs a clear descriptive error
(naming the missing variable(s)) and exits/throws before any calls to
privateKeyToAccount or Keypair.fromSecret; update the variable assignments to
assume validated presence (remove non-null assertions) so runtime failures are
avoided and diagnostics point to the missing env var.
scripts/relayer-e2e/flows/ad-lifecycle.ts (2)

33-40: Non-null assertions on environment variables could cause cryptic errors.

The code uses process.env.STELLAR_AD_CREATOR_SECRET! with non-null assertions. If any of these environment variables are not set, the error will occur deep in the flow (e.g., when trying to create a Keypair from undefined) rather than at startup.

Consider adding early validation:

💡 Proposed validation
 export async function runAdLifecycle(): Promise<void> {
   phase("A", "Ad lifecycle");

+  const adCreatorSecret = process.env.STELLAR_AD_CREATOR_SECRET;
+  const adCreatorEvmKey = process.env.EVM_AD_CREATOR_PRIVATE_KEY as `0x${string}` | undefined;
+  const stellarChainId = process.env.STELLAR_CHAIN_ID;
+  const evmChainId = process.env.EVM_CHAIN_ID;
+  
+  if (!adCreatorSecret || !adCreatorEvmKey || !stellarChainId || !evmChainId) {
+    throw new Error("Missing required env vars: STELLAR_AD_CREATOR_SECRET, EVM_AD_CREATOR_PRIVATE_KEY, STELLAR_CHAIN_ID, EVM_CHAIN_ID");
+  }
+
-  const adCreatorSecret = process.env.STELLAR_AD_CREATOR_SECRET!;
   const adCreator = Keypair.fromSecret(adCreatorSecret);
-
-  const adCreatorEvmKey = process.env.EVM_AD_CREATOR_PRIVATE_KEY as `0x${string}`;
   const adCreatorEvm = privateKeyToAccount(adCreatorEvmKey);
-
-  const stellarChainId = process.env.STELLAR_CHAIN_ID!;
-  const evmChainId = process.env.EVM_CHAIN_ID!;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/relayer-e2e/flows/ad-lifecycle.ts` around lines 33 - 40, The code is
using non-null assertions for environment vars (adCreatorSecret,
adCreatorEvmKey, stellarChainId, evmChainId) which can produce cryptic runtime
errors; replace the inline non-null assertions in the ad-lifecycle
initialization block by adding an explicit validation step that checks
process.env.STELLAR_AD_CREATOR_SECRET, process.env.EVM_AD_CREATOR_PRIVATE_KEY,
process.env.STELLAR_CHAIN_ID, and process.env.EVM_CHAIN_ID, and if any are
missing throw a clear Error (or exit) with a descriptive message before calling
Keypair.fromSecret or privateKeyToAccount so adCreator, adCreatorEvm,
stellarChainId and evmChainId are only constructed after successful validation.

130-133: The StrKey validation fallback may mask API response issues.

Using adCreator.publicKey() as a fallback when withdraw.body.to or close.body.to is not a valid Ed25519 public key could silently mask issues where the API returns unexpected data. Consider logging when the fallback is used.

💡 Proposed logging for fallback
+  const withdrawTo = StrKey.isValidEd25519PublicKey(withdraw.body.to)
+    ? withdraw.body.to
+    : (console.warn(`[ad-lifecycle] withdraw.body.to invalid, using adCreator.publicKey()`), adCreator.publicKey());
   const txW = await withdrawFromAdSoroban(
     adCreator,
     withdraw.body.signature,
     withdraw.body.signerPublicKey,
     withdraw.body.authToken,
     withdraw.body.timeToExpire,
     withdraw.body.adId,
     withdraw.body.amount,
-    StrKey.isValidEd25519PublicKey(withdraw.body.to)
-      ? withdraw.body.to
-      : adCreator.publicKey(),
+    withdrawTo,
     withdraw.body.contractAddress,
   );

Also applies to: 154-157

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

In `@scripts/relayer-e2e/flows/ad-lifecycle.ts` around lines 130 - 133, The
current fallback to adCreator.publicKey() when
StrKey.isValidEd25519PublicKey(...) rejects withdraw.body.to (and similarly for
close.body.to) can hide bad API responses; update the code around the
StrKey.isValidEd25519PublicKey checks so that when the fallback is used you emit
a clear log (including the invalid value returned by withdraw.body.to or
close.body.to and context like "using adCreator.publicKey() as fallback") before
returning adCreator.publicKey(); modify both places referencing
StrKey.isValidEd25519PublicKey, withdraw.body.to, close.body.to and
adCreator.publicKey() to add that logging.
scripts/relayer-e2e/e2e.sh (1)

65-67: Consider adding a timeout for docker compose up.

The --wait flag waits for services to be healthy, but if a service never becomes healthy, this will hang indefinitely. Consider adding --wait-timeout for CI resilience.

💡 Proposed timeout addition
 export STELLAR_ADMIN_SECRET="${STELLAR_ADMIN_SECRET:-}"
 export STELLAR_NETWORK_PASSPHRASE="${STELLAR_NETWORK_PASSPHRASE:-Standalone Network ; February 2017}"
-"${COMPOSE[@]}" up -d --build --wait
+"${COMPOSE[@]}" up -d --build --wait --wait-timeout 120
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/relayer-e2e/e2e.sh` around lines 65 - 67, The docker-compose
invocation using "${COMPOSE[@]} up -d --build --wait" can hang indefinitely if a
service never becomes healthy; modify that command to include a bounded wait
timeout (e.g., add --wait-timeout <duration>) and preferably make the timeout
configurable via an env var (e.g., DOCKER_COMPOSE_WAIT_TIMEOUT) with a sensible
default; update the line with the COMPOSE invocation and ensure the new env var
is exported or referenced alongside STELLAR_ADMIN_SECRET and
STELLAR_NETWORK_PASSPHRASE so CI jobs won't hang forever.
scripts/relayer-e2e/lib/auth.ts (1)

61-61: Minor: Type assertion on passphrase.

The passphrase as Networks cast may be incorrect since passphrase could be any string (from env or challenge), not necessarily a member of the Networks enum. However, TransactionBuilder.fromXDR accepts any string for the network passphrase, so this works at runtime despite the type assertion being imprecise.

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

In `@scripts/relayer-e2e/lib/auth.ts` at line 61, The code incorrectly narrows
passphrase with a type assertion to Networks when TransactionBuilder.fromXDR
actually accepts any string; remove the "as Networks" cast in the call to
TransactionBuilder.fromXDR(xdrString, passphrase) and ensure the
variable/parameter named passphrase is typed as string (or any) in its
declaration/signature so the call uses the raw string type rather than an
incorrect Networks enum assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/backend-relayer-e2e.yml:
- Around line 6-24: The push and pull_request trigger path filters in the
workflow (the "paths" arrays under push and pull_request) are missing the root
package.json; update both lists to include "package.json" at the repository root
so changes to the root manifest will trigger this job when pnpm install or
workspace config changes affect the E2E run. Ensure you add "package.json"
alongside the existing entries in both the push and pull_request paths arrays.
- Around line 49-55: The "Install Stellar CLI" step currently downloads and
extracts the binary via the curl/tar commands without integrity checks; update
that step to verify the downloaded asset before extraction by either switching
to an official Stellar CLI GitHub Action that performs verification, or
implement manual verification: fetch the release asset metadata or checksum file
via the GitHub Releases API, compute the SHA256 of the downloaded file (the
asset downloaded by the curl command), compare it to the published checksum and
fail the job on mismatch, and only then run tar -xzf and chmod; alternatively,
if using repository GPG-signed tags, perform GPG tag verification prior to
accepting the binary.

In @.github/workflows/backend-relayer-tests.yml:
- Around line 6-18: The workflow paths are missing the root package.json, so
changes to it won't trigger the CI; update both jobs' paths arrays in
.github/workflows/backend-relayer-tests.yml to include the repository root
package.json (add "package.json" or "/package.json" alongside "pnpm-lock.yaml"
and "pnpm-workspace.yaml") so that edits to the root manifest will trigger the
workflow that runs pnpm install from the repo root.

In `@apps/backend-relayer/.env.example`:
- Line 1: The .env example uses quoted empty placeholders like
EVM_ADMIN_PRIVATE_KEY="", which triggers dotenv-linter; remove the quotes so the
placeholder is bare (EVM_ADMIN_PRIVATE_KEY=) and do the same for the other
affected empty placeholders mentioned (lines around the other entries), ensuring
all empty example values are unquoted to satisfy dotenv-linter.

In `@apps/backend-relayer/Dockerfile`:
- Around line 33-56: The Dockerfile runs "npx prisma migrate deploy" at
container start but the build step "pnpm --filter backend-relayer deploy --prod
--legacy /out" strips devDependencies, so the Prisma CLI (package "prisma")
isn't present; either move "prisma" from devDependencies to dependencies in
apps/backend-relayer/package.json so the CLI is packaged into /out/node_modules,
or remove the runtime CLI call and run migrations earlier (e.g., during the
builder stage) or via a separate init container; update the Dockerfile
CMD/ENTRYPOINT accordingly if you choose the build-time or init-container
approach and ensure references to "npx prisma migrate deploy" and the deploy
build step are consistent with the chosen strategy.
- Around line 38-56: The runtime Dockerfile runs migrations and the relayer as
root; switch to the non-root node user by ensuring the application files under
WORKDIR /app are owned by that user and adding USER node before ENTRYPOINT/CMD.
After the COPY steps (COPY --from=builder ...), chown the /app directory to the
node UID/GID (or use chown -R node:node /app) and then add USER node so the
subsequent ENTRYPOINT ("/sbin/tini", "--") and CMD ("sh", "-c", "npx prisma
migrate deploy && node dist/main.js") run with restricted privileges.

In `@apps/backend-relayer/src/modules/ads/ad.service.ts`:
- Around line 1059-1063: The returned object currently includes an extra
property "dto" that is not declared on ConfirmChainActionADResponseDto; either
remove the "dto" property from the returned object in ad.service.ts (the return
block that currently returns { adId, success, dto }) or update
ConfirmChainActionADResponseDto in ad.dto.ts to include a properly typed "dto"
field (and import/type it appropriately) so the method's response matches the
OpenAPI DTO; ensure the change is applied where the function returns the
response (search for the return with adId, success, dto) and update tests/docs
if present.

In `@apps/backend-relayer/src/modules/trades/trade.service.ts`:
- Around line 144-147: normalizeChainAddress is being called directly on
user-controlled query params (q.adCreatorAddress, q.bridgerAddress) which can
throw and currently surface as 500; wrap each normalization in try/catch and
when normalization fails throw a BadRequestException (or return an HTTP 400)
with a clear message indicating the invalid filter value for
adCreatorAddress/bridgerAddress, then only assign to where.adCreatorAddress /
where.bridgerAddress on success so invalid user input yields 400 instead of 500.
- Around line 1144-1150: The lookup for authorizationLog uses only tradeId and
normalized user.walletAddress and can pick the wrong row if the user authorized
multiple times; update the query in the unlock() flow (the
this.prisma.authorizationLog.findFirst call) to also filter by the submitted
dto.signature (and/or reqHash if available) so it only selects the record
matching the incoming signature, then proceed to validate and delete that
matched authorizationLog; keep normalizeChainAddress for the address match and
preserve include: { trade: true } as before.

In `@scripts/cross-chain-e2e/lib/deploy.ts`:
- Around line 261-278: The snapshot currently uses zero-address sentinels (e.g.,
adManagerAddress and orderPortalAddress inside the snapshot object in deploy.ts)
which downstream code treats as real addresses; update
writeDeployedSnapshot()/the snapshot creation so that undeployed contracts are
represented explicitly (null/undefined or by omitting the property) instead of
"0x00..."; change the eth.adManagerAddress, eth.orderPortalAddress,
stellar.orderPortalAddress (and any other zeroed fields) to be nullable/optional
and ensure any consumers validate for null/undefined rather than assuming a
valid address so a missing deployment surfaces as a setup error.
- Around line 273-284: The deployment currently serializes the environment
secret into deployed.json by setting the StellarLocal.adminSecret from
process.env.STELLAR_ADMIN_SECRET; remove the adminSecret property from the
object written to deployed.json (the StellarLocal entry) so secrets are not
persisted, and instead keep STELLAR_ADMIN_SECRET only in environment/config and
inject it at runtime into the signing code path (e.g., pass
process.env.STELLAR_ADMIN_SECRET into the functions that perform signing in the
seeding/signing modules such as the logic in scripts/relayer-e2e/lib/seed.ts) so
that deployed.json no longer contains the secret.

In `@scripts/relayer-e2e/lib/eth.ts`:
- Around line 50-82: In fundEthAddress, the function currently always tops up 10
ETH regardless of minBalanceEther; compute the shortfall as needed - current
(using parseEther for minBalanceEther and current from client.getBalance) and
use that amount for both funding paths instead of the hardcoded 10 ETH (for the
wallet.sendTransaction value and for the tryTopUpViaRpc hex value); after
sending/writing the top-up, wait for the transaction receipt (wallet path) and
re-query client.getBalance to verify the balance meets needed, throwing the
existing error if the balance is still insufficient.

In `@scripts/start_chains.sh`:
- Around line 109-113: Replace the nonexistent Stellar CLI subcommand used to
populate STELLAR_ADMIN_SECRET, STELLAR_AD_CREATOR_SECRET, and
STELLAR_ORDER_CREATOR_SECRET: change each invocation of "stellar keys show
<NAME>" to the correct "stellar keys secret <NAME>" so the script uses the valid
command (same as used in scripts/cross-chain-e2e/lib/stellar.ts) and can
successfully retrieve secret keys.

In `@scripts/stop_chains.sh`:
- Around line 19-23: The stop script ignores the persisted container name and
always falls back to the default; load the saved environment file before
resolving STELLAR_CONTAINER_NAME so the script picks up the real name written by
start_chains.sh. Specifically, source or parse the ".chains.env" file (if
present) before computing CONTAINER_NAME (the variable referenced in the script)
so CONTAINER_NAME uses the persisted STELLAR_CONTAINER_NAME when available,
while keeping the existing default ("stellar-e2e") as a fallback and preserving
the subsequent stop and cleanup logic.

---

Outside diff comments:
In `@apps/backend-relayer/README.md`:
- Line 290: Rename the mistyped environment variable JWT_REFRESH_EXPIRTY to
JWT_REFRESH_EXPIRY wherever it's defined or documented (e.g., in the README and
any env templates), and update any code or config that reads
process.env.JWT_REFRESH_EXPIRTY to use process.env.JWT_REFRESH_EXPIRY so
copies/pastes won't introduce a misconfiguration.

In `@apps/backend-relayer/test/setups/seed.ts`:
- Around line 11-17: Remove the redundant prisma.$disconnect() in the try block
and make the script fail fast by rethrowing the caught error (or calling
process.exit(1)) instead of only logging it; specifically, in the seeding
function where prisma.$disconnect is used, keep a single await
prisma.$disconnect() in the finally block and replace the catch body to log the
error and then throw error (or call process.exit(1)) so tests won't proceed
against a partially seeded DB.

In `@scripts/relayer-e2e/lib/evm-actions.ts`:
- Around line 175-179: The readContract call that determines isAdmin is using
AD_MANAGER_ABI against orderChain.orderPortalAddress; change it to use
ORDER_PORTAL_ABI so the hasRole check targets the OrderPortal contract's ABI
(update the publicClient.readContract invocation that passes AD_MANAGER_ABI to
pass ORDER_PORTAL_ABI instead, keeping the same functionName 'hasRole' and args
[DEFAULT_ADMIN_ROLE, mgrAddr]).

---

Nitpick comments:
In `@apps/backend-relayer/src/libs/configs.ts`:
- Line 21: The config currently sets admin: process.env.EVM_ADMIN_PRIVATE_KEY ||
'' which defers misconfiguration; instead validate EVM_ADMIN_PRIVATE_KEY eagerly
during config load: read process.env.EVM_ADMIN_PRIVATE_KEY, ensure it exists and
matches expected EVM private key format (e.g., hex 64 chars or 0x-prefixed 66
chars), normalize (add 0x if needed) and assign the normalized value to the
admin property, and throw a clear Error during startup if missing/invalid so the
application fails fast; update the config initialization code that sets admin to
perform this validation/normalization rather than defaulting to an empty string.

In `@apps/backend-relayer/test/setups/jest-e2e.setup.ts`:
- Around line 42-43: Replace the unconditional assignments for JWT_EXPIRY and
JWT_REFRESH_EXPIRY with fallback assignments so external env values aren't
overwritten; update the lines that set process.env.JWT_EXPIRY and
process.env.JWT_REFRESH_EXPIRY to use the same "use existing value or default"
pattern used elsewhere in this file (i.e., assign a default only when the env
var is falsy) so tests keep configurability and consistency with other env
defaults.

In `@scripts/relayer-e2e/e2e.sh`:
- Around line 65-67: The docker-compose invocation using "${COMPOSE[@]} up -d
--build --wait" can hang indefinitely if a service never becomes healthy; modify
that command to include a bounded wait timeout (e.g., add --wait-timeout
<duration>) and preferably make the timeout configurable via an env var (e.g.,
DOCKER_COMPOSE_WAIT_TIMEOUT) with a sensible default; update the line with the
COMPOSE invocation and ensure the new env var is exported or referenced
alongside STELLAR_ADMIN_SECRET and STELLAR_NETWORK_PASSPHRASE so CI jobs won't
hang forever.

In `@scripts/relayer-e2e/flows/ad-lifecycle.ts`:
- Around line 33-40: The code is using non-null assertions for environment vars
(adCreatorSecret, adCreatorEvmKey, stellarChainId, evmChainId) which can produce
cryptic runtime errors; replace the inline non-null assertions in the
ad-lifecycle initialization block by adding an explicit validation step that
checks process.env.STELLAR_AD_CREATOR_SECRET,
process.env.EVM_AD_CREATOR_PRIVATE_KEY, process.env.STELLAR_CHAIN_ID, and
process.env.EVM_CHAIN_ID, and if any are missing throw a clear Error (or exit)
with a descriptive message before calling Keypair.fromSecret or
privateKeyToAccount so adCreator, adCreatorEvm, stellarChainId and evmChainId
are only constructed after successful validation.
- Around line 130-133: The current fallback to adCreator.publicKey() when
StrKey.isValidEd25519PublicKey(...) rejects withdraw.body.to (and similarly for
close.body.to) can hide bad API responses; update the code around the
StrKey.isValidEd25519PublicKey checks so that when the fallback is used you emit
a clear log (including the invalid value returned by withdraw.body.to or
close.body.to and context like "using adCreator.publicKey() as fallback") before
returning adCreator.publicKey(); modify both places referencing
StrKey.isValidEd25519PublicKey, withdraw.body.to, close.body.to and
adCreator.publicKey() to add that logging.

In `@scripts/relayer-e2e/flows/trade-lifecycle.ts`:
- Around line 39-43: The test imports backend internals (domain, orderTypes,
signTypedOrder) directly which creates tight coupling; stop importing from the
backend source and either (a) extract the needed symbols (domain, orderTypes,
signTypedOrder) into a shared package/utility library that both backend and E2E
tests depend on and update the import in trade-lifecycle.ts to use that shared
module, or (b) copy the minimal required definitions into the E2E test lib and
import them locally from the test helpers; ensure all references to domain,
orderTypes, and signTypedOrder are updated to the new module path and exported
shape matches what the test expects.
- Around line 48-61: The code uses non-null assertions for several env vars
(bridgerKey, bridgerStellarSecret, adCreatorSecret, adCreatorEvmKey,
stellarChainId, evmChainId) which can crash without helpful errors; add an early
validation block at the top of this module that checks each required process.env
key, logs a clear descriptive error (naming the missing variable(s)) and
exits/throws before any calls to privateKeyToAccount or Keypair.fromSecret;
update the variable assignments to assume validated presence (remove non-null
assertions) so runtime failures are avoided and diagnostics point to the missing
env var.

In `@scripts/relayer-e2e/lib/amount.ts`:
- Around line 12-18: The toBaseUnits function currently accepts any number for
the decimals parameter; add a defensive guard at the API boundary to validate
decimals is an integer >= 0 (and optionally finite), and throw a clear TypeError
if not; locate toBaseUnits and DEFAULT_DECIMALS (and the parseUnits call) and
before calling parseUnits check Number.isInteger(decimals) && decimals >= 0 (or
fallback to DEFAULT_DECIMALS[chainKind] if undefined), and throw a descriptive
error like "Invalid decimals: must be a non-negative integer" when the check
fails so callers get immediate, clear feedback.

In `@scripts/relayer-e2e/lib/api.ts`:
- Around line 18-21: The headers object currently always sets "content-type":
"application/json"; change it so Content-Type is only added when a request body
is present (e.g., check for opts.body or a non-GET method) instead of
unconditionally; keep the existing authorization logic that sets
headers.authorization = `Bearer ${opts.token}` when opts.token is present and
ensure the Content-Type is added conditionally before sending the request in the
same scope where headers is constructed.

In `@scripts/relayer-e2e/lib/auth.ts`:
- Line 61: The code incorrectly narrows passphrase with a type assertion to
Networks when TransactionBuilder.fromXDR actually accepts any string; remove the
"as Networks" cast in the call to TransactionBuilder.fromXDR(xdrString,
passphrase) and ensure the variable/parameter named passphrase is typed as
string (or any) in its declaration/signature so the call uses the raw string
type rather than an incorrect Networks enum assertion.

In `@scripts/relayer-e2e/lib/stellar-actions.ts`:
- Around line 17-20: The E2E script imports hex32ToBuffer and hex32ToContractId
directly from backend source, creating tight coupling; either extract these
utilities into a shared package (publish or workspace package) and update the
import in stellar-actions.ts to import those symbols from the shared module, or
copy/duplicate the implementations of hex32ToBuffer and hex32ToContractId into a
local e2e utility file and change the import to that local file (keeping the .js
ESM extension). Ensure exports/signatures remain identical so callers in
stellar-actions.ts need no further changes.
🪄 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: ee607773-feeb-442d-9198-67dd3ec9b22e

📥 Commits

Reviewing files that changed from the base of the PR and between 01500ab and 849a41e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (48)
  • .github/workflows/backend-relayer-e2e.yml
  • .github/workflows/backend-relayer-tests.yml
  • .github/workflows/cross-chain-e2e.yml
  • apps/backend-relayer/.env.example
  • apps/backend-relayer/Dockerfile
  • apps/backend-relayer/README.md
  • apps/backend-relayer/docker-compose.e2e.yaml
  • apps/backend-relayer/package.json
  • apps/backend-relayer/src/libs/configs.ts
  • apps/backend-relayer/src/modules/ads/ad.service.ts
  • apps/backend-relayer/src/modules/ads/dto/ad.dto.ts
  • apps/backend-relayer/src/modules/auth/stellar/stellar-auth.service.spec.ts
  • apps/backend-relayer/src/modules/trades/trade.service.ts
  • apps/backend-relayer/src/providers/stellar/stellar.service.ts
  • apps/backend-relayer/src/providers/stellar/utils/eip712.ts
  • apps/backend-relayer/src/providers/viem/ethers/typedData.ts
  • apps/backend-relayer/test/integrations/api.ts
  • apps/backend-relayer/test/integrations/eth-stellar.e2e-integration.ts
  • apps/backend-relayer/test/integrations/jest-e2e.json
  • apps/backend-relayer/test/setups/create-app.ts
  • apps/backend-relayer/test/setups/evm-deployed-contracts.json
  • apps/backend-relayer/test/setups/evm-setup.ts
  • apps/backend-relayer/test/setups/jest-e2e.setup.ts
  • apps/backend-relayer/test/setups/jest-integrations.setup.ts
  • apps/backend-relayer/test/setups/mock-chain-adapter.ts
  • apps/backend-relayer/test/setups/seed.ts
  • apps/backend-relayer/test/setups/stellar-setup.ts
  • apps/backend-relayer/test/setups/utils.ts
  • pnpm-workspace.yaml
  • scripts/cross-chain-e2e/lib/deploy.ts
  • scripts/cross-chain-e2e/run.ts
  • scripts/relayer-e2e/cli.ts
  • scripts/relayer-e2e/e2e.sh
  • scripts/relayer-e2e/flows/ad-lifecycle.ts
  • scripts/relayer-e2e/flows/trade-lifecycle.ts
  • scripts/relayer-e2e/lib/amount.ts
  • scripts/relayer-e2e/lib/api.ts
  • scripts/relayer-e2e/lib/assert.ts
  • scripts/relayer-e2e/lib/auth.ts
  • scripts/relayer-e2e/lib/deploy.ts
  • scripts/relayer-e2e/lib/eth.ts
  • scripts/relayer-e2e/lib/evm-actions.ts
  • scripts/relayer-e2e/lib/seed.ts
  • scripts/relayer-e2e/lib/stellar-actions.ts
  • scripts/relayer-e2e/package.json
  • scripts/relayer-e2e/tsconfig.json
  • scripts/start_chains.sh
  • scripts/stop_chains.sh
💤 Files with no reviewable changes (8)
  • apps/backend-relayer/test/setups/evm-deployed-contracts.json
  • apps/backend-relayer/test/integrations/jest-e2e.json
  • apps/backend-relayer/test/setups/evm-setup.ts
  • apps/backend-relayer/test/setups/jest-integrations.setup.ts
  • apps/backend-relayer/test/integrations/eth-stellar.e2e-integration.ts
  • apps/backend-relayer/test/setups/stellar-setup.ts
  • apps/backend-relayer/package.json
  • apps/backend-relayer/test/integrations/api.ts

Comment thread .github/workflows/backend-relayer-e2e.yml
Comment thread .github/workflows/backend-relayer-e2e.yml
Comment thread .github/workflows/backend-relayer-tests.yml
Comment thread apps/backend-relayer/.env.example Outdated
Comment thread apps/backend-relayer/Dockerfile Outdated
Comment thread scripts/cross-chain-e2e/lib/deploy.ts
Comment thread scripts/cross-chain-e2e/lib/deploy.ts
Comment thread scripts/relayer-e2e/lib/eth.ts
Comment thread scripts/start_chains.sh
Comment thread scripts/stop_chains.sh
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

♻️ Duplicate comments (4)
.github/workflows/backend-relayer-e2e.yml (2)

6-24: ⚠️ Potential issue | 🟠 Major

Include root package.json in workflow trigger paths.

Changes to root workspace manifest can affect pnpm install but won’t trigger this workflow currently.

Suggested fix
   push:
     branches: [main]
     paths:
+      - "package.json"
       - "apps/backend-relayer/**"
@@
   pull_request:
     paths:
+      - "package.json"
       - "apps/backend-relayer/**"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/backend-relayer-e2e.yml around lines 6 - 24, The workflow
path filters in backend-relayer-e2e.yml are missing the root package.json so
changes to the workspace manifest won't trigger the workflow; update both the
push and pull_request "paths" arrays (the lists currently containing
"pnpm-workspace.yaml" and "pnpm-lock.yaml") to also include "package.json" (the
root package manifest) so edits to the root package.json will trigger the
workflow.

56-63: ⚠️ Potential issue | 🟠 Major

Verify Stellar CLI artifact integrity before extraction.

Line 58 downloads and Line 60 extracts an executable without checksum validation.

Suggested fix
       - name: Install Stellar CLI
         run: |
-          curl -Ls https://github.com/stellar/stellar-cli/releases/download/v23.3.0/stellar-cli-23.3.0-x86_64-unknown-linux-gnu.tar.gz -o /tmp/stellar-cli.tar.gz
+          curl -fLsS https://github.com/stellar/stellar-cli/releases/download/v23.3.0/stellar-cli-23.3.0-x86_64-unknown-linux-gnu.tar.gz -o /tmp/stellar-cli.tar.gz
+          echo "${{ vars.STELLAR_CLI_SHA256 }}  /tmp/stellar-cli.tar.gz" | sha256sum -c -
           mkdir -p "$HOME/.local/bin"
           tar -xzf /tmp/stellar-cli.tar.gz -C "$HOME/.local/bin" stellar
           chmod +x "$HOME/.local/bin/stellar"
           echo "$HOME/.local/bin" >> "$GITHUB_PATH"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/backend-relayer-e2e.yml around lines 56 - 63, The workflow
currently downloads /tmp/stellar-cli.tar.gz and extracts it without verifying
integrity; update the "Install Stellar CLI" run step to fetch the published
checksum or signature for the matching release (e.g., a .sha256 or .asc asset
from the Stellar CLI release), verify the downloaded artifact (use sha256sum -c
or gpg --verify as appropriate) and fail the job on mismatch before running tar;
reference the downloaded path (/tmp/stellar-cli.tar.gz), the curl command, and
the extraction step that uses tar so the verification sits between the download
and tar extraction and exits the workflow on verification failure.
apps/backend-relayer/Dockerfile (2)

42-60: ⚠️ Potential issue | 🟠 Major

Run the runtime container as a non-root user.

Line 42 onward never drops root, so both migrations and the relayer run as UID 0. Please switch to the node user before ENTRYPOINT/CMD.

Suggested fix
 FROM node:20-alpine AS runtime
@@
 WORKDIR /app
@@
 COPY --from=builder /out/prisma ./prisma
+
+RUN chown -R node:node /app
+USER node
@@
 ENTRYPOINT ["/sbin/tini", "--"]
 CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main.js"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend-relayer/Dockerfile` around lines 42 - 60, The Dockerfile
currently runs migrations and the relayer as root (ENTRYPOINT ["/sbin/tini",
"--"] and CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main.js"]);
switch to the unprivileged node user before those commands by adding a USER node
directive after copying files and setting proper ownership/permissions of /app
(chown/chmod the copied package.json, node_modules, dist, prisma) so the node
user can run npx prisma migrate deploy and node dist/main.js; keep the
ENTRYPOINT and CMD unchanged but ensure all runtime writable dirs (if any) are
owned by UID/GID of the node user.

35-37: ⚠️ Potential issue | 🟠 Major

prisma migrate deploy at startup requires Prisma CLI in the runtime image.

Line 60 calls npx prisma migrate deploy, while Line 35 builds a --prod deploy artifact. If prisma is not a production dependency in apps/backend-relayer/package.json, container boot will fail.

#!/bin/bash
# Verify where prisma packages are declared.
python - <<'PY'
import json
from pathlib import Path
pkg = json.loads(Path("apps/backend-relayer/package.json").read_text())
for section in ("dependencies", "devDependencies", "optionalDependencies"):
    deps = pkg.get(section, {})
    prisma = deps.get("prisma")
    client = deps.get("@prisma/client")
    if prisma or client:
        print(f"{section}: prisma={prisma!r}, `@prisma/client`={client!r}")
PY

Also applies to: 60-60

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

In `@apps/backend-relayer/Dockerfile` around lines 35 - 37, The container runs
"npx prisma migrate deploy" at startup but the build step uses "RUN pnpm
--filter backend-relayer deploy --prod --legacy /out" which will omit prisma
from the runtime image if prisma is only a devDependency; either ensure the
Prisma CLI is present in the runtime by moving "prisma" to dependencies (not
devDependencies) in apps/backend-relayer/package.json, or perform migrations
during image build instead of at startup (run prisma migrate deploy in a build
stage that includes dev deps), or copy the prisma binary into the final
image—update the package.json or Dockerfile accordingly so the "npx prisma
migrate deploy" invocation can find the Prisma CLI at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/backend-relayer-e2e.yml:
- Around line 92-96: The workflow redundantly remaps secrets/environment vars
causing possible empty values; remove the explicit remapping of
STELLAR_ADMIN_SECRET and the DATABASE_URL: ${{ env.HOST_DATABASE_URL }}
assignment so the job inherits the already-exported values (STELLAR_ADMIN_SECRET
written by start_chains.sh and HOST_DATABASE_URL defined at workflow level)
instead of referencing them via `${{ env.VAR }}` which can resolve to empty;
update the backend-relayer job environment block to rely on inherited env vars
(leave STELLAR_ADMIN_SECRET and DATABASE_URL unset in that mapping) and keep the
sed export step that writes to GITHUB_ENV unchanged.

---

Duplicate comments:
In @.github/workflows/backend-relayer-e2e.yml:
- Around line 6-24: The workflow path filters in backend-relayer-e2e.yml are
missing the root package.json so changes to the workspace manifest won't trigger
the workflow; update both the push and pull_request "paths" arrays (the lists
currently containing "pnpm-workspace.yaml" and "pnpm-lock.yaml") to also include
"package.json" (the root package manifest) so edits to the root package.json
will trigger the workflow.
- Around line 56-63: The workflow currently downloads /tmp/stellar-cli.tar.gz
and extracts it without verifying integrity; update the "Install Stellar CLI"
run step to fetch the published checksum or signature for the matching release
(e.g., a .sha256 or .asc asset from the Stellar CLI release), verify the
downloaded artifact (use sha256sum -c or gpg --verify as appropriate) and fail
the job on mismatch before running tar; reference the downloaded path
(/tmp/stellar-cli.tar.gz), the curl command, and the extraction step that uses
tar so the verification sits between the download and tar extraction and exits
the workflow on verification failure.

In `@apps/backend-relayer/Dockerfile`:
- Around line 42-60: The Dockerfile currently runs migrations and the relayer as
root (ENTRYPOINT ["/sbin/tini", "--"] and CMD ["sh", "-c", "npx prisma migrate
deploy && node dist/main.js"]); switch to the unprivileged node user before
those commands by adding a USER node directive after copying files and setting
proper ownership/permissions of /app (chown/chmod the copied package.json,
node_modules, dist, prisma) so the node user can run npx prisma migrate deploy
and node dist/main.js; keep the ENTRYPOINT and CMD unchanged but ensure all
runtime writable dirs (if any) are owned by UID/GID of the node user.
- Around line 35-37: The container runs "npx prisma migrate deploy" at startup
but the build step uses "RUN pnpm --filter backend-relayer deploy --prod
--legacy /out" which will omit prisma from the runtime image if prisma is only a
devDependency; either ensure the Prisma CLI is present in the runtime by moving
"prisma" to dependencies (not devDependencies) in
apps/backend-relayer/package.json, or perform migrations during image build
instead of at startup (run prisma migrate deploy in a build stage that includes
dev deps), or copy the prisma binary into the final image—update the
package.json or Dockerfile accordingly so the "npx prisma migrate deploy"
invocation can find the Prisma CLI at runtime.
🪄 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: 28b4bfd8-eecc-4b4e-80fb-6d3e2569b254

📥 Commits

Reviewing files that changed from the base of the PR and between 849a41e and 00632da.

📒 Files selected for processing (3)
  • .github/workflows/backend-relayer-e2e.yml
  • .github/workflows/cross-chain-e2e.yml
  • apps/backend-relayer/Dockerfile
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/cross-chain-e2e.yml

Comment thread .github/workflows/backend-relayer-e2e.yml
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: 3

♻️ Duplicate comments (1)
scripts/relayer-e2e/lib/eth.ts (1)

62-72: ⚠️ Potential issue | 🟠 Major

Wallet funding path skips final balance verification.

This branch returns right after receipt wait, so it can report success without confirming to actually reached needed (unlike the RPC branch). Please run the same post-funding balance check before returning.

Suggested fix
   if (funderKey) {
     const wallet = createWalletClient({
       chain: client.chain ?? ethLocalnet,
       transport: http(),
       account: privateKeyToAccount(funderKey),
     });

     const hash = await wallet.sendTransaction({ to, value: missing });
     await client.waitForTransactionReceipt({ hash });
-    return;
+    const funded = await client.getBalance({ address: to });
+    if (funded < needed) {
+      throw new Error(`Unable to fund ${to} to ${minBalanceEther} ETH.`);
+    }
+    return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/relayer-e2e/lib/eth.ts` around lines 62 - 72, The wallet-funder
branch in eth.ts (using createWalletClient, sendTransaction and
client.waitForTransactionReceipt) returns immediately after waiting for the
receipt and skips the post-funding balance verification; update this branch to
perform the same balance check as the RPC branch by fetching the recipient
balance after client.waitForTransactionReceipt (use the same client.getBalance
or equivalent logic that compares the balance against needed/missing) and only
return once the balance meets the required amount (or throw/report an error if
it does not).
🧹 Nitpick comments (3)
apps/backend-relayer/.env.example (1)

1-1: Consider adding a generation hint for consistency.

Similar to the helpful comment on line 12 for STELLAR_AUTH_SECRET, you could add a comment above EVM_ADMIN_PRIVATE_KEY explaining how to generate it (e.g., using cast wallet new or equivalent tooling).

📝 Example addition
+# EVM admin private key. Generate with: cast wallet new --json | jq -r '.private_key'
 EVM_ADMIN_PRIVATE_KEY=
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend-relayer/.env.example` at line 1, Add a short generation hint
comment above the EVM_ADMIN_PRIVATE_KEY entry in the .env.example to show how to
create a key (e.g., using tooling like `cast wallet new` or other wallet/keygen
methods); update the file by inserting a one-line comment referencing
EVM_ADMIN_PRIVATE_KEY and a suggested command or note on safe storage so
contributors know how to generate and populate that variable.
scripts/cross-chain-e2e/lib/deploy.ts (1)

265-288: Consider typing the snapshot schema explicitly to prevent producer/consumer drift.

A named exported snapshot type here would make nullability and field contracts compile-time enforced for downstream tooling.

♻️ Proposed refactor
+export interface DeployedSnapshot {
+  eth: {
+    name: string;
+    chainId: string;
+    adManagerAddress: string | null;
+    orderPortalAddress: string | null;
+    merkleManagerAddress: string;
+    verifierAddress: string;
+    tokenName: string;
+    tokenSymbol: string;
+    tokenAddress: string;
+  };
+  stellar: {
+    name: string;
+    chainId: string;
+    adManagerAddress: string | null;
+    orderPortalAddress: string | null;
+    merkleManagerAddress: string;
+    verifierAddress: string;
+    tokenName: string;
+    tokenSymbol: string;
+    tokenAddress: string;
+  };
+}
+
 export function writeDeployedSnapshot(
   outPath: string,
   { stellar, evm }: DeployAllResult,
 ): void {
-  const snapshot = {
+  const snapshot: DeployedSnapshot = {
     eth: {
       name: "AnvilLocal",
       chainId: evm.chainId.toString(),
       adManagerAddress: null as string | null,
       orderPortalAddress: evm.addresses.orderPortal,
@@
       tokenAddress: stellar.adTokenHex,
     },
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/cross-chain-e2e/lib/deploy.ts` around lines 265 - 288, Declare and
export an explicit Snapshot type (e.g., export type Snapshot = { eth: { name:
string; chainId: string; adManagerAddress: string | null; orderPortalAddress:
string; merkleManagerAddress: string; verifierAddress: string; tokenName:
string; tokenSymbol: string; tokenAddress: string; }; stellar: { name: string;
chainId: string; adManagerAddress: string | null; orderPortalAddress: string |
null; merkleManagerAddress: string; verifierAddress: string; tokenName: string;
tokenSymbol: string; tokenAddress: string; }; }) and annotate the snapshot
constant with it (const snapshot: Snapshot = { ... }), replacing inline casts
like "as string | null" and ensuring fields derived from evm, stellar and
utilities (evm.chainId, evm.addresses.*, stellar.chainId, stellar.adManagerHex,
strkeyToHex(stellar.*), stellar.adTokenHex) match the declared nullable/required
types so consumers get compile-time guarantees.
scripts/relayer-e2e/lib/seed.ts (1)

53-160: Use a single transaction for chain/token/route seeding.

Line 53-Line 160 performs dependent upserts across multiple tables without transaction boundaries; failures can leave partial state.

Refactor sketch
-    // EVM chain + token.
-    const ethAdManager =
-      deployed.eth.adManagerAddress ?? sentinelFor("adManager", "eth", 40);
-    ...
-    if (deployed.stellar) {
-      ...
-      await prisma.route.upsert({ ... });
-    }
+    await prisma.$transaction(async (tx) => {
+      const ethAdManager =
+        deployed.eth.adManagerAddress ?? sentinelFor("adManager", "eth", 40);
+      ...
+      const ethChain = await tx.chain.upsert({ ... });
+      const ethToken = await tx.token.upsert({ ... });
+
+      if (deployed.stellar) {
+        ...
+        const stellarChain = await tx.chain.upsert({ ... });
+        const stellarToken = await tx.token.upsert({ ... });
+        await tx.route.upsert({ ... });
+      }
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/relayer-e2e/lib/seed.ts` around lines 53 - 160, The seed logic
performs dependent upserts (prisma.chain.upsert, prisma.token.upsert, and
prisma.route.upsert) for eth and optional stellar without a transaction, risking
partial commits on failure; wrap the related operations that create
ethChain/ethToken and the stellarChain/stellarToken + route into a single atomic
prisma.$transaction (or prisma.$executeTransaction) so either all upserts
succeed or all rollback, ensuring ethChain, ethToken, stellarChain, stellarToken
and the prisma.route.upsert are executed inside the same transaction scope and
their results used from the transaction response.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend-relayer/src/modules/ads/ad.service.ts`:
- Around line 423-424: The creator wallet normalization currently calls
normalizeChainAddress(user.walletAddress) without specifying the ad chain, which
can allow cross-chain mismatches; in create() pass the ad chain kind from
route.adToken.chain.kind into normalizeChainAddress (e.g.,
normalizeChainAddress(user.walletAddress, route.adToken.chain.kind)). For the
other methods referenced (fund, withdraw, close, confirmChainAction) first load
the Ad by id (using the same repository/service method already used elsewhere in
this file) and then call normalizeChainAddress(user.walletAddress,
ad.route.adToken.chain.kind) so the creator address is validated/normalized
against the ad's route chain. Ensure you update all call sites noted (around the
original lines 423, 501, 623, 845, 991–992) to use the ad chain kind parameter.

In `@scripts/relayer-e2e/lib/seed.ts`:
- Around line 46-51: The seed currently sets a known password for admin@x.com by
always calling argon2hash and prisma.admin.upsert; change this to avoid
unconditionally overwriting real admin credentials — only perform the upsert
when running in a safe context (e.g., require NODE_ENV==='test' or a dedicated
FORCE_SEED env var), or change logic to only create the admin if none exists
(use prisma.admin.findUnique to check and skip update), and allow the password
to come from a secure env var (e.g., ADMIN_PASSWORD) rather than a hardcoded
string; update the code paths around argon2hash and prisma.admin.upsert to
implement this guard and emit a clear warning/log if seeding is skipped.

In `@scripts/stop_chains.sh`:
- Around line 17-21: The current stop logic reads PID_FILE into ANVIL_PID and
blindly kills it; to avoid killing a reused unrelated PID validate the process
identity before killing by checking the target process command line for the
expected Anvil signature (e.g., check /proc/${ANVIL_PID}/cmdline or use ps -p
${ANVIL_PID} -o comm= and ensure it contains "anvil"/"foundry" or whatever
binary name you expect) and only call kill on ANVIL_PID if the command matches;
update the block that references PID_FILE and ANVIL_PID to perform this identity
check and skip/clear the stale PID otherwise.

---

Duplicate comments:
In `@scripts/relayer-e2e/lib/eth.ts`:
- Around line 62-72: The wallet-funder branch in eth.ts (using
createWalletClient, sendTransaction and client.waitForTransactionReceipt)
returns immediately after waiting for the receipt and skips the post-funding
balance verification; update this branch to perform the same balance check as
the RPC branch by fetching the recipient balance after
client.waitForTransactionReceipt (use the same client.getBalance or equivalent
logic that compares the balance against needed/missing) and only return once the
balance meets the required amount (or throw/report an error if it does not).

---

Nitpick comments:
In `@apps/backend-relayer/.env.example`:
- Line 1: Add a short generation hint comment above the EVM_ADMIN_PRIVATE_KEY
entry in the .env.example to show how to create a key (e.g., using tooling like
`cast wallet new` or other wallet/keygen methods); update the file by inserting
a one-line comment referencing EVM_ADMIN_PRIVATE_KEY and a suggested command or
note on safe storage so contributors know how to generate and populate that
variable.

In `@scripts/cross-chain-e2e/lib/deploy.ts`:
- Around line 265-288: Declare and export an explicit Snapshot type (e.g.,
export type Snapshot = { eth: { name: string; chainId: string; adManagerAddress:
string | null; orderPortalAddress: string; merkleManagerAddress: string;
verifierAddress: string; tokenName: string; tokenSymbol: string; tokenAddress:
string; }; stellar: { name: string; chainId: string; adManagerAddress: string |
null; orderPortalAddress: string | null; merkleManagerAddress: string;
verifierAddress: string; tokenName: string; tokenSymbol: string; tokenAddress:
string; }; }) and annotate the snapshot constant with it (const snapshot:
Snapshot = { ... }), replacing inline casts like "as string | null" and ensuring
fields derived from evm, stellar and utilities (evm.chainId, evm.addresses.*,
stellar.chainId, stellar.adManagerHex, strkeyToHex(stellar.*),
stellar.adTokenHex) match the declared nullable/required types so consumers get
compile-time guarantees.

In `@scripts/relayer-e2e/lib/seed.ts`:
- Around line 53-160: The seed logic performs dependent upserts
(prisma.chain.upsert, prisma.token.upsert, and prisma.route.upsert) for eth and
optional stellar without a transaction, risking partial commits on failure; wrap
the related operations that create ethChain/ethToken and the
stellarChain/stellarToken + route into a single atomic prisma.$transaction (or
prisma.$executeTransaction) so either all upserts succeed or all rollback,
ensuring ethChain, ethToken, stellarChain, stellarToken and the
prisma.route.upsert are executed inside the same transaction scope and their
results used from the transaction response.
🪄 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: a01c55cd-5588-4622-8091-815924afcd1b

📥 Commits

Reviewing files that changed from the base of the PR and between 00632da and 5d2d9e3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .github/workflows/backend-relayer-e2e.yml
  • .github/workflows/backend-relayer-tests.yml
  • apps/backend-relayer/.env.example
  • apps/backend-relayer/Dockerfile
  • apps/backend-relayer/package.json
  • apps/backend-relayer/src/modules/ads/ad.service.ts
  • apps/backend-relayer/src/modules/trades/trade.service.ts
  • scripts/cross-chain-e2e/lib/deploy.ts
  • scripts/relayer-e2e/lib/eth.ts
  • scripts/relayer-e2e/lib/seed.ts
  • scripts/stop_chains.sh
✅ Files skipped from review due to trivial changes (2)
  • .github/workflows/backend-relayer-tests.yml
  • apps/backend-relayer/Dockerfile
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend-relayer/src/modules/trades/trade.service.ts

Comment thread apps/backend-relayer/src/modules/ads/ad.service.ts Outdated
Comment thread scripts/relayer-e2e/lib/seed.ts
Comment thread scripts/stop_chains.sh
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 the current code and only fix it if needed.

Inline comments:
In `@apps/backend-relayer/Dockerfile`:
- Line 63: The Dockerfile CMD currently runs "npx prisma migrate deploy && node
dist/src/main.js" which points to a non-existent built entry; update the CMD to
run the relayer's actual production entrypoint ("npx prisma migrate deploy &&
node dist/main") so the container starts the compiled Nest app correctly (change
the CMD string in the Dockerfile to use node dist/main to match
apps/backend-relayer/package.json).
🪄 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: de7b4daa-879e-4921-a381-002e6f76f924

📥 Commits

Reviewing files that changed from the base of the PR and between 5d2d9e3 and db52c84.

📒 Files selected for processing (1)
  • apps/backend-relayer/Dockerfile

Comment thread apps/backend-relayer/Dockerfile
@JoE11-y JoE11-y merged commit 57c15b8 into main Apr 13, 2026
5 checks passed
@JoE11-y JoE11-y changed the title Stellar integrations feat: add relayer e2e ci and relayer service optimizations Apr 13, 2026
@coderabbitai coderabbitai bot mentioned this pull request Apr 14, 2026
6 tasks
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