feat: add relayer e2e ci and relayer service optimizations#113
feat: add relayer e2e ci and relayer service optimizations#113
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorUse
ORDER_PORTAL_ABIfor the OrderPortal role check.This
hasRoleread targetsorderChain.orderPortalAddress, but Line 177 usesAD_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 | 🟡 MinorFix env var typo to prevent copy/paste misconfiguration.
Line 290 uses
JWT_REFRESH_EXPIRTY; it should beJWT_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 | 🟠 MajorDon’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 defensivedecimalsguard 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.tscould break E2E scripts. Consider whether these utilities should live in a shared package or be duplicated in the E2E scripts for isolation.The
.jsextension 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 validatingEVM_ADMIN_PRIVATE_KEYduring 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/jsonfor 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.jscreates 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 whenwithdraw.body.toorclose.body.tois 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
--waitflag waits for services to be healthy, but if a service never becomes healthy, this will hang indefinitely. Consider adding--wait-timeoutfor 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 Networkscast may be incorrect sincepassphrasecould be any string (from env or challenge), not necessarily a member of theNetworksenum. However,TransactionBuilder.fromXDRaccepts 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.ymlapps/backend-relayer/.env.exampleapps/backend-relayer/Dockerfileapps/backend-relayer/README.mdapps/backend-relayer/docker-compose.e2e.yamlapps/backend-relayer/package.jsonapps/backend-relayer/src/libs/configs.tsapps/backend-relayer/src/modules/ads/ad.service.tsapps/backend-relayer/src/modules/ads/dto/ad.dto.tsapps/backend-relayer/src/modules/auth/stellar/stellar-auth.service.spec.tsapps/backend-relayer/src/modules/trades/trade.service.tsapps/backend-relayer/src/providers/stellar/stellar.service.tsapps/backend-relayer/src/providers/stellar/utils/eip712.tsapps/backend-relayer/src/providers/viem/ethers/typedData.tsapps/backend-relayer/test/integrations/api.tsapps/backend-relayer/test/integrations/eth-stellar.e2e-integration.tsapps/backend-relayer/test/integrations/jest-e2e.jsonapps/backend-relayer/test/setups/create-app.tsapps/backend-relayer/test/setups/evm-deployed-contracts.jsonapps/backend-relayer/test/setups/evm-setup.tsapps/backend-relayer/test/setups/jest-e2e.setup.tsapps/backend-relayer/test/setups/jest-integrations.setup.tsapps/backend-relayer/test/setups/mock-chain-adapter.tsapps/backend-relayer/test/setups/seed.tsapps/backend-relayer/test/setups/stellar-setup.tsapps/backend-relayer/test/setups/utils.tspnpm-workspace.yamlscripts/cross-chain-e2e/lib/deploy.tsscripts/cross-chain-e2e/run.tsscripts/relayer-e2e/cli.tsscripts/relayer-e2e/e2e.shscripts/relayer-e2e/flows/ad-lifecycle.tsscripts/relayer-e2e/flows/trade-lifecycle.tsscripts/relayer-e2e/lib/amount.tsscripts/relayer-e2e/lib/api.tsscripts/relayer-e2e/lib/assert.tsscripts/relayer-e2e/lib/auth.tsscripts/relayer-e2e/lib/deploy.tsscripts/relayer-e2e/lib/eth.tsscripts/relayer-e2e/lib/evm-actions.tsscripts/relayer-e2e/lib/seed.tsscripts/relayer-e2e/lib/stellar-actions.tsscripts/relayer-e2e/package.jsonscripts/relayer-e2e/tsconfig.jsonscripts/start_chains.shscripts/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
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
.github/workflows/backend-relayer-e2e.yml (2)
6-24:⚠️ Potential issue | 🟠 MajorInclude root
package.jsonin workflow trigger paths.Changes to root workspace manifest can affect
pnpm installbut 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 | 🟠 MajorVerify 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 | 🟠 MajorRun 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
nodeuser beforeENTRYPOINT/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 deployat startup requires Prisma CLI in the runtime image.Line 60 calls
npx prisma migrate deploy, while Line 35 builds a--proddeploy artifact. Ifprismais not a production dependency inapps/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}") PYAlso 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
📒 Files selected for processing (3)
.github/workflows/backend-relayer-e2e.yml.github/workflows/cross-chain-e2e.ymlapps/backend-relayer/Dockerfile
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/cross-chain-e2e.yml
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
scripts/relayer-e2e/lib/eth.ts (1)
62-72:⚠️ Potential issue | 🟠 MajorWallet funding path skips final balance verification.
This branch returns right after receipt wait, so it can report success without confirming
toactually reachedneeded(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 aboveEVM_ADMIN_PRIVATE_KEYexplaining how to generate it (e.g., usingcast wallet newor 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (11)
.github/workflows/backend-relayer-e2e.yml.github/workflows/backend-relayer-tests.ymlapps/backend-relayer/.env.exampleapps/backend-relayer/Dockerfileapps/backend-relayer/package.jsonapps/backend-relayer/src/modules/ads/ad.service.tsapps/backend-relayer/src/modules/trades/trade.service.tsscripts/cross-chain-e2e/lib/deploy.tsscripts/relayer-e2e/lib/eth.tsscripts/relayer-e2e/lib/seed.tsscripts/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
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
apps/backend-relayer/Dockerfile
Summary
Update full Stellar (Soroban) support to ProofBridge alongside the existing EVM implementation:
decoupled from backend source.
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.shagainst local anvil + stellar-rpcscripts/cross-chain-e2efull flow locallyforge testincontracts/evmcargo testincontracts/stellarSummary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests