Skip to content

Feat: Integrate Chainrails for multichain support - #108

Open
stephanniegb wants to merge 2 commits into
Provable-Games:mainfrom
stephanniegb:main
Open

Feat: Integrate Chainrails for multichain support#108
stephanniegb wants to merge 2 commits into
Provable-Games:mainfrom
stephanniegb:main

Conversation

@stephanniegb

@stephanniegb stephanniegb commented Feb 21, 2026

Copy link
Copy Markdown

Overview

This PR integrates Chainrails into Loot Survivor, to allows users pay for dungeon entry using multiple tokens on 11+ chains and credit cards(soon). The payment is bridged to Starknet as USDC, then automatically swapped to TICKET tokens and used to enter the dungeon.

Architecture

User (another chain) 
    │
    │ (PaymentModal from @chainrails/react)
    ▼
Chainrails Protocol
    │ (Bridges and swaps to USDC on Starknet)
    ▼
Controller Wallet (USDC deposited)
    │
    │ (onSuccess callback triggers buyDungeonTicket)
    │ (swap USDC → TICKET via Ekubo)
    ▼
Dungeon Contract
    │
    ▼
Game Token (minted to user)

Configuration

Environment Variables

  • CHAINRAILS_API_KEY - Chainrails API key (set in server/index.ts or .env)

Server Setup

  • Server runs on port 3000 (default)
  • Must be running for Chainrails payment sessions to work
  • Session URL in useChainrailsPayment.ts points to http://localhost:3000/create-session
Screenshot 2026-02-21 at 5 40 39 PM

Summary by CodeRabbit

  • New Features

    • Added multi-flow payment system enabling users to pay via Controller or from other chains.
    • Integrated Chain Rails payment gateway supporting cross-chain transactions with USDC.
    • Added back button for improved navigation within payment flows.
  • Chores

    • Added server infrastructure for payment session management.
    • Updated dependencies with Chain Rails SDK and related libraries.

@vercel

vercel Bot commented Feb 21, 2026

Copy link
Copy Markdown

@stephanniegb is attempting to deploy a commit to the Provable Games Team on Vercel.

A member of the Team first needs to authorize it.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @stephanniegb, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances Loot Survivor's payment capabilities by integrating Chainrails, enabling users to pay for dungeon entry using various tokens across multiple blockchain networks and even credit cards. The new system streamlines the process by bridging external payments to USDC on Starknet, which is then automatically swapped for TICKET tokens required for game entry. This broadens accessibility and offers greater flexibility in how players acquire game tokens.

Highlights

  • Multichain Payment Integration: Introduced Chainrails to enable dungeon entry payments using various tokens across multiple blockchain networks and credit cards, significantly expanding payment options.
  • New Payment Flow UI: Implemented a new user interface within the PaymentOptionsModal that allows users to select between paying from their controller wallet or via Chainrails for multichain support.
  • Dedicated Chainrails Server: Added a new Express server to handle Chainrails API interactions, specifically for creating payment sessions, ensuring secure and controlled access to the Chainrails protocol.
  • Automated Token Swapping: Ensured that all payments, whether from other chains via Chainrails (bridged as USDC) or directly from the controller, are automatically swapped to TICKET tokens for dungeon entry.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • client/package.json
    • Added @chainrails/react and @chainrails/sdk as new dependencies.
  • client/pnpm-lock.yaml
    • Updated the dependency lockfile to include new Chainrails-related packages and reflect various version updates across the client's dependencies.
  • client/src/components/PaymentOptionsModal.tsx
    • Implemented a new payment flow state and UI to differentiate between controller wallet payments and Chainrails payments.
    • Integrated the PaymentModal component from @chainrails/react and the custom useChainrailsPayment hook.
    • Modified the buyDungeonTicket function to conditionally handle payments originating from Chainrails (using USDC) or existing controller tokens.
    • Added a back button for navigation within the payment flow options.
  • client/src/hooks/useChainrailsPayment.ts
    • Created a new React hook useChainrailsPayment to manage the creation and lifecycle of Chainrails payment sessions.
    • Dynamically constructed the Chainrails session URL based on the recipient address, destination chain (Starknet), and token (USDC).
  • server/bun.lock
    • Generated a new Bun lockfile to manage server-side dependencies consistently.
  • server/index.ts
    • Created a new Express server application to serve as an intermediary for Chainrails API calls.
    • Implemented an endpoint /create-session to securely obtain session tokens from the Chainrails API.
    • Added environment variable configuration for CHAINRAILS_API_KEY.
  • server/package.json
    • Defined new server-side dependencies including @chainrails/sdk, cors, dotenv, and express.
  • server/tsconfig.json
    • Configured TypeScript settings for the new server application.
  • server/vercel.json
    • Added Vercel deployment configuration for the Bun-based server.
Activity
  • The pull request introduces a new feature for multichain payment support.
  • New client-side dependencies for Chainrails integration were added.
  • A new client-side hook was created to manage Chainrails payment sessions.
  • A new server-side application was introduced to handle Chainrails API interactions and session creation.
  • Server-side dependencies and configuration files were added.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Feb 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request adds Chainrails payment integration across the codebase. On the client side, a multi-flow payment model (INITIAL, CONTROLLER, CHAINRAILS) is introduced to PaymentOptionsModal, new dependencies are added, and a useChainrailsPayment hook is created. On the server side, a new Express application is established to handle Chainrails API session creation and validation.

Changes

Cohort / File(s) Summary
Client Dependencies
client/package.json
Added @chainrails/react (^0.3.4) and @chainrails/sdk (^0.3.3) for Chainrails integration.
Server Infrastructure
server/package.json, server/tsconfig.json, server/vercel.json
Established new Node.js server project with Express, Chainrails SDK, and TypeScript configuration; set Bun runtime version.
Payment Flow Refactor
client/src/components/PaymentOptionsModal.tsx
Refactored payment modal to support three distinct flows (INITIAL, CONTROLLER, CHAINRAILS) with conditional UI rendering. Expanded TokenSelectionContent props to expose flow state, USDC pricing, and navigation controls. Integrated useChainrailsPayment hook and PaymentModal component for cross-chain payments.
Payment Integration Hook
client/src/hooks/useChainrailsPayment.ts
Created new React hook that builds payment session URLs using controllerAddress and wraps usePaymentSession with optional success/cancel callbacks.
Server API Implementation
server/index.ts
Implemented Express server with health checks (GET /, GET /healthz) and session creation endpoint (GET /create-session) that validates Chainrails API key and returns session tokens for specified recipients and chains.

Sequence Diagram

sequenceDiagram
    actor User
    participant PaymentModal as PaymentOptionsModal
    participant Hook as useChainrailsPayment
    participant PaymentUI as PaymentModal Component
    participant Server as Server /create-session
    participant ChainrailsAPI as Chainrails API

    User->>PaymentModal: Select "Pay from another chain"
    PaymentModal->>PaymentModal: setPaymentFlow(CHAINRAILS)
    PaymentModal->>Hook: Call useChainrailsPayment({controllerAddress})
    Hook->>Hook: Build sessionUrl with controllerAddress
    Hook->>PaymentUI: Return paymentSession object
    
    User->>PaymentUI: Click "Enter Dungeon" / Pay button
    PaymentUI->>Server: GET /create-session?recipient=X&destinationChain=Y&token=Z
    Server->>Server: Validate params & API key
    Server->>ChainrailsAPI: crapi.auth.getSessionToken(params)
    ChainrailsAPI-->>Server: Return session token
    Server-->>PaymentUI: Return session response
    PaymentUI->>PaymentUI: Handle payment flow
    PaymentUI->>PaymentUI: onSuccess callback triggered
    PaymentUI->>PaymentModal: Trigger dungeon entry
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Next #62: Modifies PaymentOptionsModal.tsx's payment flow selection and view rendering logic with similar multi-option patterns.
  • Next #88: Adjusts PaymentOptionsModal.tsx payment flow handling and action button callbacks for different payment modes.

Poem

🐰 Hops with glee through chains so new,
Chainrails stitched in every view!
Flows that branch like forest trees,
Cross-chain payments flow with ease,
Sessions built with token care,
Payment magic in the air!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the pull request: integrating Chainrails for multichain payment support across the codebase.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

The pull request successfully integrates Chainrails for multichain support, allowing users to pay for dungeon entry using tokens from various chains. The architecture correctly bridges tokens to USDC on Starknet and then swaps them for TICKET tokens. However, there are several critical and high-severity issues that need to be addressed: hardcoded production URLs in the client-side hook, missing input validation and incorrect configuration placement in the server-side code, and potential precision issues when calculating payment amounts. Additionally, the UI logic for fetching quotes should be more reactive to state changes to ensure users are presented with up-to-date pricing.


const sessionUrl = useMemo(
() =>
`https://death-mountain.vercel.app/create-session?recipient=${controllerAddress}&destinationChain=STARKNET&token=USDC`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The session URL is hardcoded to a production Vercel domain. This should be an environment variable to allow for local development and different deployment environments (e.g., staging vs production).

Suggested change
`https://death-mountain.vercel.app/create-session?recipient=${controllerAddress}&destinationChain=STARKNET&token=USDC`,
`${import.meta.env.VITE_SERVER_URL || 'http://localhost:3000'}/create-session?recipient=${controllerAddress}&destinationChain=STARKNET&token=USDC`,

Comment thread server/index.ts
}

const session = await crapi.auth.getSessionToken({
amount: '0',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The amount is hardcoded to '0'. While the client might specify an amount in the PaymentModal, the session itself should ideally reflect the expected cost to ensure the session token is scoped correctly and to provide a better user experience in the modal.

const quote = await getSwapQuote(-1e18, dungeon.ticketAddress, USDC);
if (quote) {
const usdcDecimals = usdcToken.decimals || 6;
const usdcAmountValue = ((quote.total * -1) / Math.pow(10, usdcDecimals)).toFixed(2);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using toFixed(2) for the payment amount can lead to precision loss. If the actual cost is slightly higher than the rounded value (e.g., 1.239 rounded to 1.24 is fine, but 1.231 rounded to 1.23 might cause the transaction to fail due to insufficient funds), the swap will fail. It is safer to use a small buffer or round up.

Suggested change
const usdcAmountValue = ((quote.total * -1) / Math.pow(10, usdcDecimals)).toFixed(2);
const usdcAmountValue = (Math.ceil((quote.total * -1) / Math.pow(10, usdcDecimals - 2)) / 100).toFixed(2);

Comment thread server/index.ts

app.get("/create-session", async (req, res) => {
try {
Chainrails.config({ api_key: CHAINRAILS_API_KEY });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Configuring the SDK inside the request handler is inefficient as it runs on every request. It should be initialized once at the top level of the application.

Chainrails.config({ api_key: CHAINRAILS_API_KEY });

app.get("/create-session", async (req, res) => {

}, [dungeon.ticketAddress, USDC, usdcToken]);

useEffect(() => {
fetchUSDCAmount();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The USDC quote is only fetched once when the component mounts. Since crypto prices are volatile, the quote should be refreshed when the user switches to the Chainrails payment flow to ensure they see the most accurate price.

  useEffect(() => {
    if (paymentFlow === PAYMENT_FLOW.CHAINRAILS) {
      fetchUSDCAmount();
    }
  }, [paymentFlow, fetchUSDCAmount]);

symbol: usdcToken.name,
balance: tokenBalances[usdcToken.name] || 0,
address: usdcToken.address,
decimals: usdcToken.decimals || 18,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Defaulting to 18 decimals for USDC is incorrect as USDC typically uses 6 decimals on most chains, including Starknet. This inconsistency with line 584 (which defaults to 6) could lead to calculation errors if the token metadata is missing.

        decimals: usdcToken.decimals || 6,

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (4)
client/src/hooks/useChainrailsPayment.ts (1)

23-44: Dual export is redundant.

The hook is exported both as a named export and as a default export. Pick one for consistency — named exports are generally preferred for better refactoring support and tree-shaking.

Suggested simplification
 export const useChainrailsPayment = ({
   ...
 };
-
-export default useChainrailsPayment;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/hooks/useChainrailsPayment.ts` around lines 23 - 44, The file
currently exports useChainrailsPayment both as a named export and as the default
export; remove the redundant default export and keep the named export for
consistency and better tree-shaking. Specifically, delete the line exporting
useChainrailsPayment as the default (the "export default useChainrailsPayment;"
statement) and ensure any import sites that relied on the default import are
updated to import { useChainrailsPayment } instead; locate the hook by the
function name useChainrailsPayment to make the change.
server/index.ts (2)

37-37: Prefer res.json(session) over res.send(session) for explicit JSON response.

res.send() with an object works in Express 5, but res.json() makes the intent unambiguous and consistently sets Content-Type: application/json.

♻️ Proposed fix
-    res.send(session);
+    res.json(session);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.ts` at line 37, Replace the ambiguous res.send(session) call
with res.json(session) to explicitly return JSON and ensure Content-Type:
application/json is set; locate the response in the route handler that builds
the session object (the line calling res.send(session) in server/index.ts) and
change it to res.json(session) while keeping the same variable and flow.

22-22: Move Chainrails.config() out of the request handler.

Calling Chainrails.config() on every request needlessly re-initialises the SDK. Move it to module level, alongside the API key validation.

♻️ Proposed fix
 if (!CHAINRAILS_API_KEY) {
   throw new Error("CHAINRAILS_API_KEY is not set");
 }
+
+Chainrails.config({ api_key: CHAINRAILS_API_KEY });
 
 const app = express();
 ...
 
 app.get("/create-session", async (req, res) => {
   try {
-    Chainrails.config({ api_key: CHAINRAILS_API_KEY });
-
     const {recipient, destinationChain, token } = req.query;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.ts` at line 22, Chainrails.config({ api_key: CHAINRAILS_API_KEY
}) is being called inside the request handler causing re-initialisation per
request; move that call to module initialization alongside the existing API key
validation so it runs once at import time. Remove the Chainrails.config(...)
invocation from the handler and add a single module-level call directly after
the CHAINRAILS_API_KEY is validated (where the top-level API key check occurs)
so the SDK is configured once for the lifetime of the process.
server/package.json (1)

1-16: No scripts field — add build and start scripts.

Without a scripts section there is no canonical way to compile or launch the server. The PR notes it must run on port 3000 but leaves runtime invocation undocumented.

♻️ Proposed addition
  "name": "loot-survivor-2-server",
  "version": "1.0.0",
  "type": "module",
+ "scripts": {
+   "build": "tsc",
+   "start": "bun run dist/index.js",
+   "dev": "bun run --watch server/index.ts"
+ },
  "dependencies": {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/package.json` around lines 1 - 16, Add a "scripts" section to
package.json containing at least "build" and "start": set "build" to invoke the
TypeScript compiler (e.g., tsc -p .) to produce JS into your dist output, and
set "start" to run the compiled server entry (e.g., node --enable-source-maps
./dist/index.js); add keys "scripts", "build", and "start" so maintainers can
compile with npm run build and launch with npm start (adjust the compiled entry
path to match your server's main file).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/src/components/PaymentOptionsModal.tsx`:
- Line 424: paymentFlow (state managed by paymentFlow and setPaymentFlow) is not
reset when the modal closes or currentView changes, causing stale sub-views
(e.g., PAYMENT_FLOW.CHAINRAILS) to persist; add a useEffect that calls
setPaymentFlow(PAYMENT_FLOW.INITIAL) whenever currentView changes and also when
the modal open/close state toggles (or on modal open) so the paymentFlow always
resets to PAYMENT_FLOW.INITIAL before the token view is mounted again.
- Around line 332-343: The onClick currently calls paymentSession.open() without
ensuring it exists; update the Button so it is disabled unless
paymentSession.open is a function (and keep existing checks). Specifically, in
the PaymentOptionsModal component adjust the Button's disabled condition to also
check that paymentSession?.open is truthy (or typeof paymentSession.open ===
"function") and in the onClick handler guard the call by calling
paymentSession.open() only when present (e.g., if (paymentSession?.open)
paymentSession.open()). This uses the existing usePaymentSession return
(paymentSession) and the Button element to prevent runtime errors when open is
undefined.
- Around line 100-102: The computed value in hasEnoughBalance accesses
selectedTokenData.balance but selectedTokenData can be undefined (result of
userTokens.find when no match), causing a crash; update the useMemo to guard for
undefined by treating missing selectedTokenData as zero (or return false) before
comparing to tokenQuote.amount, e.g., check selectedTokenData &&
selectedTokenData.balance (or coerce to 0) and ensure tokenQuote exists; modify
the hasEnoughBalance computation (referencing hasEnoughBalance,
selectedTokenData, tokenQuote, and the userTokens.find that sets
selectedTokenData) so it safely handles undefined selectedTokenData and does not
dereference .balance.
- Around line 598-600: The effect using useEffect currently has an empty
dependency array and thus may never re-run when fetchUSDCAmount's inputs change;
update the dependency array to include fetchUSDCAmount (or the underlying
dependencies dungeon.ticketAddress, USDC, usdcToken) so the effect re-runs when
those values become available — e.g., change useEffect(() => {
fetchUSDCAmount(); }, []); to useEffect(() => { fetchUSDCAmount(); },
[fetchUSDCAmount]); ensuring fetchUSDCAmount remains memoized via useCallback
with its existing dependencies.
- Around line 512-527: In buyDungeonTicket, the USDC token fallback for decimals
is incorrectly set to 18; locate the isChainrails branch where usdcToken is
resolved and selectedTokenData is constructed (references: buyDungeonTicket,
usdcToken, selectedTokenData) and change the decimals fallback from 18 to 6 so
USDC uses 6 decimals (keep displayDecimals as-is); ensure tokenBalances lookup
and address remain unchanged.
- Around line 349-354: TokenSelectionContent currently renders PaymentModal
unconditionally which can trigger Chainrails SDK activity when usdcAmount is
null; update the JSX to only render the PaymentModal (component name:
PaymentModal) when paymentFlow === PAYMENT_FLOW.CHAINRAILS and pass a valid
amount (use usdcAmount only when non-null/defined instead of falling back to the
string "0"); locate the render where PaymentModal is used and wrap it in a
conditional check against paymentFlow, removing the unconditional defaulting to
"0" to avoid unintended side effects.

In `@client/src/hooks/useChainrailsPayment.ts`:
- Around line 29-33: The session URL in useChainrailsPayment.ts is hardcoded in
the useMemo that defines sessionUrl and also interpolates controllerAddress
without encoding; change it to build the URL from a configurable base (e.g.,
read NEXT_PUBLIC_BACKEND_URL or REACT_APP_BACKEND_URL as backendBase) instead of
the fixed https://death-mountain.vercel.app, use
encodeURIComponent(controllerAddress) when inserting the address, and include
the backend base variable in the useMemo dependency array so sessionUrl updates
in different environments.

In `@server/index.ts`:
- Around line 20-45: Change the /create-session handler from a GET to a POST:
replace app.get("/create-session", ...) with app.post("/create-session", ...)
and read recipient, destinationChain, and token from req.body instead of
req.query; ensure your Express app uses JSON body parsing middleware
(express.json()) so crapi.auth.getSessionToken receives the same fields
(amount:'0', recipient, destinationChain, token). Also update the client code
that calls /create-session (referenced in useChainrailsPayment.ts) to stop using
the hardcoded "http://localhost:3000/create-session" and instead use a
configurable base URL (e.g., from an environment variable) so production
deployments point to the correct server.
- Line 15: The CORS middleware is currently wide open via app.use(cors()), which
exposes the /create-session endpoint that mints payment tokens; restrict it to
known client origins by replacing the blanket call with a CORS configuration
that only allows your deployed origin(s) (e.g., from an env var like
CLIENT_ORIGIN or a whitelist array) and apply it to the Express app (the same
app instance using app.use). Implement either a static origin value or a dynamic
origin function that checks the request origin against the whitelist and rejects
others, ensuring the change affects the route that handles create-session so
only allowed frontends can call it.
- Around line 30-35: The call to crapi.auth.getSessionToken is using a hardcoded
amount ('0') and unsafe casts (destinationChain as any, token as any); replace
the hardcoded amount with a validated value (read from req.body/req.query or an
env var, validate/parse it to the expected numeric/string format and reject
invalid inputs) and remove the any casts by importing and using the proper
types/enums from `@chainrails/sdk` for destinationChain and token so the call uses
correctly typed values (update the function/handler that builds amount,
recipient, destinationChain, token before calling crapi.auth.getSessionToken).

In `@server/package.json`:
- Around line 11-15: The devDependencies list in package.json is missing the
TypeScript declarations for cors; add "@types/cors" to the "devDependencies"
object (e.g., alongside "@types/express" and "@types/bun") so imports from
"cors" are properly typed—pick a compatible version (or latest) and update
package.json's "devDependencies" to include "@types/cors": "<version>" and then
run the install step.

In `@server/tsconfig.json`:
- Around line 2-7: The tsconfig compilerOptions currently omit strict mode
causing unchecked casts and untyped params (observed in server/index.ts with "as
any" and untyped query handling); enable TypeScript strictness by adding
"strict": true under the existing compilerOptions in tsconfig.json and then
update affected files (e.g., remove/replace "as any" casts in server/index.ts
and add proper typings for query params and any untyped functions) to satisfy
strictNullChecks and noImplicitAny rules.

In `@server/vercel.json`:
- Around line 1-3: Add Vercel routing and a serverless entry so Vercel doesn't
404: update vercel.json to include a "rewrites" rule that routes all incoming
paths (e.g., "source": "/(.*)") to the serverless function path (e.g.,
"destination": "/api/index") and keep "bunVersion" intact, and create an api
entry file that re-exports your Express app (e.g., add server/api/index.ts that
does "export { default } from '../index';" or similar) so the exported symbol
from your main server module (export default app) is reachable by Vercel's
serverless runtime.

---

Nitpick comments:
In `@client/src/hooks/useChainrailsPayment.ts`:
- Around line 23-44: The file currently exports useChainrailsPayment both as a
named export and as the default export; remove the redundant default export and
keep the named export for consistency and better tree-shaking. Specifically,
delete the line exporting useChainrailsPayment as the default (the "export
default useChainrailsPayment;" statement) and ensure any import sites that
relied on the default import are updated to import { useChainrailsPayment }
instead; locate the hook by the function name useChainrailsPayment to make the
change.

In `@server/index.ts`:
- Line 37: Replace the ambiguous res.send(session) call with res.json(session)
to explicitly return JSON and ensure Content-Type: application/json is set;
locate the response in the route handler that builds the session object (the
line calling res.send(session) in server/index.ts) and change it to
res.json(session) while keeping the same variable and flow.
- Line 22: Chainrails.config({ api_key: CHAINRAILS_API_KEY }) is being called
inside the request handler causing re-initialisation per request; move that call
to module initialization alongside the existing API key validation so it runs
once at import time. Remove the Chainrails.config(...) invocation from the
handler and add a single module-level call directly after the CHAINRAILS_API_KEY
is validated (where the top-level API key check occurs) so the SDK is configured
once for the lifetime of the process.

In `@server/package.json`:
- Around line 1-16: Add a "scripts" section to package.json containing at least
"build" and "start": set "build" to invoke the TypeScript compiler (e.g., tsc -p
.) to produce JS into your dist output, and set "start" to run the compiled
server entry (e.g., node --enable-source-maps ./dist/index.js); add keys
"scripts", "build", and "start" so maintainers can compile with npm run build
and launch with npm start (adjust the compiled entry path to match your server's
main file).

Comment on lines 100 to 102
const hasEnoughBalance = useMemo(() => {
return Number(selectedTokenData.balance) >= Number(tokenQuote.amount);
}, [selectedTokenData, tokenQuote]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

selectedTokenData can be undefined, causing a runtime crash.

userTokens.find(...) on line 75 returns undefined when no token matches selectedToken. On line 101, selectedTokenData.balance is accessed unconditionally. This will throw a TypeError when the component first mounts (before a token is selected) or when in INITIAL/CHAINRAILS flows where the selected token might not be in the filtered userTokens list.

Proposed fix
 const hasEnoughBalance = useMemo(() => {
-  return Number(selectedTokenData.balance) >= Number(tokenQuote.amount);
+  if (!selectedTokenData) return false;
+  return Number(selectedTokenData.balance) >= Number(tokenQuote.amount);
 }, [selectedTokenData, tokenQuote]);
📝 Committable suggestion

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

Suggested change
const hasEnoughBalance = useMemo(() => {
return Number(selectedTokenData.balance) >= Number(tokenQuote.amount);
}, [selectedTokenData, tokenQuote]);
const hasEnoughBalance = useMemo(() => {
if (!selectedTokenData) return false;
return Number(selectedTokenData.balance) >= Number(tokenQuote.amount);
}, [selectedTokenData, tokenQuote]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/PaymentOptionsModal.tsx` around lines 100 - 102, The
computed value in hasEnoughBalance accesses selectedTokenData.balance but
selectedTokenData can be undefined (result of userTokens.find when no match),
causing a crash; update the useMemo to guard for undefined by treating missing
selectedTokenData as zero (or return false) before comparing to
tokenQuote.amount, e.g., check selectedTokenData && selectedTokenData.balance
(or coerce to 0) and ensure tokenQuote exists; modify the hasEnoughBalance
computation (referencing hasEnoughBalance, selectedTokenData, tokenQuote, and
the userTokens.find that sets selectedTokenData) so it safely handles undefined
selectedTokenData and does not dereference .balance.

Comment on lines +332 to +343
<Box sx={{ display: "flex", justifyContent: "center", px: 2, mb: 2 }}>
<Button
variant="contained"
sx={styles.activateButton}
onClick={() => paymentSession.open()}
fullWidth
disabled={usdcLoading || !!usdcError || !usdcAmount}
>
<Typography sx={styles.buttonText}>
Enter Dungeon
</Typography>
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

No guard against paymentSession.open being undefined.

On line 336, paymentSession.open() is called directly in the onClick handler. If usePaymentSession returns an object where open is undefined (e.g., session URL is invalid or the session hasn't been created yet), this will throw at runtime. Consider adding a null check or disabling the button when paymentSession.open is not available.

Proposed fix — add guard to disabled condition
  <Button
    variant="contained"
    sx={styles.activateButton}
    onClick={() => paymentSession.open()}
    fullWidth
-   disabled={usdcLoading || !!usdcError || !usdcAmount}
+   disabled={usdcLoading || !!usdcError || !usdcAmount || !paymentSession?.open}
  >
📝 Committable suggestion

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

Suggested change
<Box sx={{ display: "flex", justifyContent: "center", px: 2, mb: 2 }}>
<Button
variant="contained"
sx={styles.activateButton}
onClick={() => paymentSession.open()}
fullWidth
disabled={usdcLoading || !!usdcError || !usdcAmount}
>
<Typography sx={styles.buttonText}>
Enter Dungeon
</Typography>
</Button>
<Box sx={{ display: "flex", justifyContent: "center", px: 2, mb: 2 }}>
<Button
variant="contained"
sx={styles.activateButton}
onClick={() => paymentSession.open()}
fullWidth
disabled={usdcLoading || !!usdcError || !usdcAmount || !paymentSession?.open}
>
<Typography sx={styles.buttonText}>
Enter Dungeon
</Typography>
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/PaymentOptionsModal.tsx` around lines 332 - 343, The
onClick currently calls paymentSession.open() without ensuring it exists; update
the Button so it is disabled unless paymentSession.open is a function (and keep
existing checks). Specifically, in the PaymentOptionsModal component adjust the
Button's disabled condition to also check that paymentSession?.open is truthy
(or typeof paymentSession.open === "function") and in the onClick handler guard
the call by calling paymentSession.open() only when present (e.g., if
(paymentSession?.open) paymentSession.open()). This uses the existing
usePaymentSession return (paymentSession) and the Button element to prevent
runtime errors when open is undefined.

Comment on lines +349 to +354
<PaymentModal
{...paymentSession}
amount={usdcAmount || "0"}
styles={{ theme: "loot-survivor-e697da73" }}
className="chainrails-payment-modal"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

PaymentModal is rendered unconditionally, even outside the CHAINRAILS flow.

The PaymentModal component is rendered for all payment flows (INITIAL, CONTROLLER, CHAINRAILS) inside TokenSelectionContent. When the flow is not CHAINRAILS, usdcAmount may be null, which falls back to "0". This could trigger unintended side effects or unnecessary network requests from the Chainrails SDK. Consider gating it behind paymentFlow === PAYMENT_FLOW.CHAINRAILS.

Proposed fix
-      <PaymentModal
-        {...paymentSession}
-        amount={usdcAmount || "0"}
-        styles={{ theme: "loot-survivor-e697da73" }}
-        className="chainrails-payment-modal"
-      />
+      {paymentFlow === PAYMENT_FLOW.CHAINRAILS && (
+        <PaymentModal
+          {...paymentSession}
+          amount={usdcAmount || "0"}
+          styles={{ theme: "loot-survivor-e697da73" }}
+          className="chainrails-payment-modal"
+        />
+      )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/PaymentOptionsModal.tsx` around lines 349 - 354,
TokenSelectionContent currently renders PaymentModal unconditionally which can
trigger Chainrails SDK activity when usdcAmount is null; update the JSX to only
render the PaymentModal (component name: PaymentModal) when paymentFlow ===
PAYMENT_FLOW.CHAINRAILS and pass a valid amount (use usdcAmount only when
non-null/defined instead of falling back to the string "0"); locate the render
where PaymentModal is used and wrap it in a conditional check against
paymentFlow, removing the unconditional defaulting to "0" to avoid unintended
side effects.

amount: "",
loading: false,
});
const [paymentFlow, setPaymentFlow] = useState<PaymentFlow>(PAYMENT_FLOW.INITIAL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

paymentFlow state is not reset when the modal closes or when currentView changes.

When the user navigates away from the token view (e.g., via footer links) and comes back, paymentFlow retains its previous value (e.g., CHAINRAILS). This could show a stale sub-view. Consider resetting paymentFlow to INITIAL when currentView changes or when the modal re-opens.

Proposed fix — reset on view change

Add a useEffect in the parent component:

useEffect(() => {
  setPaymentFlow(PAYMENT_FLOW.INITIAL);
}, [currentView]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/PaymentOptionsModal.tsx` at line 424, paymentFlow
(state managed by paymentFlow and setPaymentFlow) is not reset when the modal
closes or currentView changes, causing stale sub-views (e.g.,
PAYMENT_FLOW.CHAINRAILS) to persist; add a useEffect that calls
setPaymentFlow(PAYMENT_FLOW.INITIAL) whenever currentView changes and also when
the modal open/close state toggles (or on modal open) so the paymentFlow always
resets to PAYMENT_FLOW.INITIAL before the token view is mounted again.

Comment on lines +512 to +527
const buyDungeonTicket = async (isChainrails = false) => {
let selectedTokenData;

if (isChainrails) {
const usdcToken = paymentTokens.find((token: any) => token.name === "USDC");
if (!usdcToken) {
console.error("USDC token not found");
return;
}
selectedTokenData = {
symbol: usdcToken.name,
balance: tokenBalances[usdcToken.name] || 0,
address: usdcToken.address,
decimals: usdcToken.decimals || 18,
displayDecimals: usdcToken.displayDecimals || 4,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

USDC decimals fallback of 18 is incorrect — should be 6.

On line 525, the fallback for decimals is 18, but USDC on Starknet uses 6 decimals. Compare with fetchUSDCAmount on line 584 which correctly defaults to 6. Using 18 here would produce a wildly incorrect swap amount, causing the transaction to fail or swap the wrong amount.

Proposed fix
       selectedTokenData = {
         symbol: usdcToken.name,
         balance: tokenBalances[usdcToken.name] || 0,
         address: usdcToken.address,
-        decimals: usdcToken.decimals || 18,
+        decimals: usdcToken.decimals || 6,
         displayDecimals: usdcToken.displayDecimals || 4,
       };
📝 Committable suggestion

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

Suggested change
const buyDungeonTicket = async (isChainrails = false) => {
let selectedTokenData;
if (isChainrails) {
const usdcToken = paymentTokens.find((token: any) => token.name === "USDC");
if (!usdcToken) {
console.error("USDC token not found");
return;
}
selectedTokenData = {
symbol: usdcToken.name,
balance: tokenBalances[usdcToken.name] || 0,
address: usdcToken.address,
decimals: usdcToken.decimals || 18,
displayDecimals: usdcToken.displayDecimals || 4,
};
const buyDungeonTicket = async (isChainrails = false) => {
let selectedTokenData;
if (isChainrails) {
const usdcToken = paymentTokens.find((token: any) => token.name === "USDC");
if (!usdcToken) {
console.error("USDC token not found");
return;
}
selectedTokenData = {
symbol: usdcToken.name,
balance: tokenBalances[usdcToken.name] || 0,
address: usdcToken.address,
decimals: usdcToken.decimals || 6,
displayDecimals: usdcToken.displayDecimals || 4,
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/PaymentOptionsModal.tsx` around lines 512 - 527, In
buyDungeonTicket, the USDC token fallback for decimals is incorrectly set to 18;
locate the isChainrails branch where usdcToken is resolved and selectedTokenData
is constructed (references: buyDungeonTicket, usdcToken, selectedTokenData) and
change the decimals fallback from 18 to 6 so USDC uses 6 decimals (keep
displayDecimals as-is); ensure tokenBalances lookup and address remain
unchanged.

Comment thread server/index.ts
Comment on lines +20 to +45
app.get("/create-session", async (req, res) => {
try {
Chainrails.config({ api_key: CHAINRAILS_API_KEY });

const {recipient, destinationChain, token } = req.query;

if (!recipient || !destinationChain || !token) {
return res.status(400).json({ error: "Missing required parameters" });
}

const session = await crapi.auth.getSessionToken({
amount: '0',
recipient: recipient as string,
destinationChain: destinationChain as any,
token: token as any,
});

res.send(session);
} catch (error) {
console.error("Error creating session:", error);
res.status(500).json({
error: "Internal server error",
message: error instanceof Error ? error.message : "Unknown error"
});
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use POST (not GET) for /create-session — sensitive data must not travel in query strings.

recipient, destinationChain, and token are wallet/chain identifiers that end up in URL query strings, meaning they are:

  • Captured by every reverse proxy and CDN access log (including Vercel's).
  • Visible in browser history and referrer headers.

Session creation is also a state-changing operation; GET is semantically incorrect. Migrate to POST with a JSON body.

🔒 Proposed fix
-app.get("/create-session", async (req, res) => {
+app.post("/create-session", async (req, res) => {
   try {
     Chainrails.config({ api_key: CHAINRAILS_API_KEY });
 
-    const {recipient, destinationChain, token } = req.query;
+    const { recipient, destinationChain, token } = req.body as {
+      recipient?: string;
+      destinationChain?: string;
+      token?: string;
+    };

Additionally, the client-side useChainrailsPayment.ts references http://localhost:3000/create-session — this hardcoded localhost URL must be replaced with the deployed server URL (or an env variable) for production.

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

In `@server/index.ts` around lines 20 - 45, Change the /create-session handler
from a GET to a POST: replace app.get("/create-session", ...) with
app.post("/create-session", ...) and read recipient, destinationChain, and token
from req.body instead of req.query; ensure your Express app uses JSON body
parsing middleware (express.json()) so crapi.auth.getSessionToken receives the
same fields (amount:'0', recipient, destinationChain, token). Also update the
client code that calls /create-session (referenced in useChainrailsPayment.ts)
to stop using the hardcoded "http://localhost:3000/create-session" and instead
use a configurable base URL (e.g., from an environment variable) so production
deployments point to the correct server.

Comment thread server/index.ts
Comment on lines +30 to +35
const session = await crapi.auth.getSessionToken({
amount: '0',
recipient: recipient as string,
destinationChain: destinationChain as any,
token: token as any,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

amount: '0' is hardcoded — this likely produces incorrect payment sessions, and as any casts hide SDK type errors.

Two issues in this block:

  1. Amount: Passing '0' unconditionally means every session is created for a zero-value payment. If the Chainrails SDK uses the amount to compute the on-chain transfer, all sessions will be underfunded. The actual ticket price should be read from an env variable or computed server-side, or at minimum forwarded as a validated query/body param.

  2. Type casts: destinationChain as any and token as any suppress the SDK's enum/union types. Import and use the proper types from @chainrails/sdk instead.

♻️ Proposed fix
+import type { ChainId, TokenSymbol } from "@chainrails/sdk"; // adjust to actual exports
 
-    const session = await crapi.auth.getSessionToken({
-      amount: '0',
-      recipient: recipient as string,
-      destinationChain: destinationChain as any,
-      token: token as any,
-    });
+    const session = await crapi.auth.getSessionToken({
+      amount: process.env.TICKET_PRICE_USDC ?? '0.74',
+      recipient: recipient as string,
+      destinationChain: destinationChain as ChainId,
+      token: token as TokenSymbol,
+    });
📝 Committable suggestion

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

Suggested change
const session = await crapi.auth.getSessionToken({
amount: '0',
recipient: recipient as string,
destinationChain: destinationChain as any,
token: token as any,
});
import type { ChainId, TokenSymbol } from "@chainrails/sdk";
Suggested change
const session = await crapi.auth.getSessionToken({
amount: '0',
recipient: recipient as string,
destinationChain: destinationChain as any,
token: token as any,
});
const session = await crapi.auth.getSessionToken({
amount: process.env.TICKET_PRICE_USDC ?? '0.74',
recipient: recipient as string,
destinationChain: destinationChain as ChainId,
token: token as TokenSymbol,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.ts` around lines 30 - 35, The call to crapi.auth.getSessionToken
is using a hardcoded amount ('0') and unsafe casts (destinationChain as any,
token as any); replace the hardcoded amount with a validated value (read from
req.body/req.query or an env var, validate/parse it to the expected
numeric/string format and reject invalid inputs) and remove the any casts by
importing and using the proper types/enums from `@chainrails/sdk` for
destinationChain and token so the call uses correctly typed values (update the
function/handler that builds amount, recipient, destinationChain, token before
calling crapi.auth.getSessionToken).

Comment thread server/package.json
Comment on lines +11 to +15
"devDependencies": {
"@types/express": "5.0.0",
"@types/bun": "1.3.1",
"typescript": "5.8.3"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add @types/cors to devDependencies.

The cors package doesn't ship its own TypeScript declarations. Without @types/cors, the import is implicitly typed as any, which defeats type checking for CORS options.

🛠 Proposed fix
  "devDependencies": {
+   "@types/cors": "^2.8.17",
    "@types/express": "5.0.0",
    "@types/bun": "1.3.1",
    "typescript": "5.8.3"
  }
📝 Committable suggestion

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

Suggested change
"devDependencies": {
"@types/express": "5.0.0",
"@types/bun": "1.3.1",
"typescript": "5.8.3"
}
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "5.0.0",
"@types/bun": "1.3.1",
"typescript": "5.8.3"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/package.json` around lines 11 - 15, The devDependencies list in
package.json is missing the TypeScript declarations for cors; add "@types/cors"
to the "devDependencies" object (e.g., alongside "@types/express" and
"@types/bun") so imports from "cors" are properly typed—pick a compatible
version (or latest) and update package.json's "devDependencies" to include
"@types/cors": "<version>" and then run the install step.

Comment thread server/tsconfig.json
Comment on lines +2 to +7
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"skipLibCheck": true,
"types": ["bun"]
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Enable TypeScript strict mode — required by project guidelines.

"strict": true is missing. This disables strictNullChecks, noImplicitAny, and other checks, which is directly observable in server/index.ts (unchecked as any casts, untyped query params). As per coding guidelines, TypeScript strict mode must be used.

♻️ Proposed fix
  "compilerOptions": {
    "target": "ESNext",
    "module": "NodeNext",
+   "strict": true,
    "skipLibCheck": true,
    "types": ["bun"]
  },
📝 Committable suggestion

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

Suggested change
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"skipLibCheck": true,
"types": ["bun"]
},
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"strict": true,
"skipLibCheck": true,
"types": ["bun"]
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/tsconfig.json` around lines 2 - 7, The tsconfig compilerOptions
currently omit strict mode causing unchecked casts and untyped params (observed
in server/index.ts with "as any" and untyped query handling); enable TypeScript
strictness by adding "strict": true under the existing compilerOptions in
tsconfig.json and then update affected files (e.g., remove/replace "as any"
casts in server/index.ts and add proper typings for query params and any untyped
functions) to satisfy strictNullChecks and noImplicitAny rules.

Comment thread server/vercel.json
Comment on lines +1 to +3
{
"bunVersion": "1.x"
} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -30

Repository: Provable-Games/death-mountain

Length of output: 819


🏁 Script executed:

find . -name "vercel.json" -o -name "*.json" | grep -E "(vercel|package)" | head -20

Repository: Provable-Games/death-mountain

Length of output: 158


🏁 Script executed:

cat -n server/vercel.json

Repository: Provable-Games/death-mountain

Length of output: 119


🏁 Script executed:

ls -la server/

Repository: Provable-Games/death-mountain

Length of output: 493


🏁 Script executed:

find server -type f -name "*.ts" -o -name "*.js" | head -20

Repository: Provable-Games/death-mountain

Length of output: 88


🏁 Script executed:

cat -n server/index.ts

Repository: Provable-Games/death-mountain

Length of output: 1931


🏁 Script executed:

rg "(create-session|healthz)" server/

Repository: Provable-Games/death-mountain

Length of output: 189


🏁 Script executed:

cat -n server/package.json

Repository: Provable-Games/death-mountain

Length of output: 505


🏁 Script executed:

cat -n client/vercel.json

Repository: Provable-Games/death-mountain

Length of output: 248


🏁 Script executed:

rg "build|vercel" server/package.json

Repository: Provable-Games/death-mountain

Length of output: 55


🏁 Script executed:

find . -maxdepth 2 -name "vercel.json" -exec cat -n {} \;

Repository: Provable-Games/death-mountain

Length of output: 294


Vercel deployment will 404 — missing routing rules and api/ entry structure.

vercel.json only sets bunVersion. For Vercel to route all inbound requests to the Express app you also need:

  1. The entry file placed (or re-exported) under an api/ directory that Vercel treats as a serverless function, and
  2. A rewrites rule mapping all paths to that function.

Without this, /create-session and /healthz will return 404 in production even though export default app is present.

♻️ Example vercel.json additions
 {
-  "bunVersion": "1.x"
+  "bunVersion": "1.x",
+  "rewrites": [{ "source": "/(.*)", "destination": "/api" }]
 }

And add server/api/index.ts:

// server/api/index.ts
export { default } from "../index";
📝 Committable suggestion

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

Suggested change
{
"bunVersion": "1.x"
}
{
"bunVersion": "1.x",
"rewrites": [{ "source": "/(.*)", "destination": "/api" }]
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/vercel.json` around lines 1 - 3, Add Vercel routing and a serverless
entry so Vercel doesn't 404: update vercel.json to include a "rewrites" rule
that routes all incoming paths (e.g., "source": "/(.*)") to the serverless
function path (e.g., "destination": "/api/index") and keep "bunVersion" intact,
and create an api entry file that re-exports your Express app (e.g., add
server/api/index.ts that does "export { default } from '../index';" or similar)
so the exported symbol from your main server module (export default app) is
reachable by Vercel's serverless runtime.

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