Skip to content

feat: wallet connection and holder session - #28

Merged
DanielCotoJ merged 5 commits into
ACTA-Team:developfrom
sotoJ24:issue/4
Jun 23, 2026
Merged

DanielCotoJ merged 5 commits into
ACTA-Team:developfrom
sotoJ24:issue/4

Conversation

@sotoJ24

@sotoJ24 sotoJ24 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the wallet connection and holder session layer for the
credit-history app (issue #4). This is the identity foundation that
feeds the share flow (#7) and all routes that require a holder session.

New files

src/providers/acta-provider.tsx

  • 'use client' wrapper around ActaConfig from @acta-products/acta
  • Reads NEXT_PUBLIC_STELLAR_NETWORK to select between the SDK's mainNet
    / testNet URL constants (baseURL is a string literal union, not a
    generic string)
  • Reads NEXT_PUBLIC_ACTA_API_KEY; falls back to a placeholder in Batch 1
    because no real API calls are made yet (mock data layer from Mock data layer + SDK integration seam #5 handles
    all reads)
  • Mounted inside NextIntlClientProvider in layout.tsx — as low as
    possible per the spec

src/session/wallet-connector.ts

  • WalletConnector interface: connect(): Promise<{ address: string }>,
    disconnect(): Promise<void>
  • MockWalletConnector: returns a fixed brand-book G... address after 800 ms
    simulated latency; no signing, no real wallet
  • getWalletConnector() — single swap-point factory with documented seam
    comment explaining what Freighter / Stellar Wallets Kit replaces and that
    the UI does not change

src/session/session-provider.tsx

  • SessionProvider + useSession() Client Component
  • Exposes: status ('disconnected' | 'connecting' | 'connected'),
    address, did, connect(), disconnect()
  • DID always derived with didPkhStellar(network, address) — never by hand
  • Session persisted in localStorage under key acta:session:address
    (see persistence decision below)

src/components/wallet-button.tsx

  • Three states:
    • disconnected → "Connect wallet" button with wallet icon
    • connecting → disabled button with spinner + i18n label
    • connected → truncated GABC...7KQ4 + truncated DID in font-mono
      • Disconnect button
  • All text via useTranslations('session') — zero hardcoded strings

Results:

Screenshot from 2026-06-22 15-59-43 Screenshot from 2026-06-22 15-59-26

Modified files

src/components/header.tsx

  • Added <WalletButton /> alongside <MobileMenu />
  • Header stays a Server Component — placing a Client Component inside is
    valid in Next.js App Router

src/app/layout.tsx

  • Wraps the shell with <ActaProvider><SessionProvider> inside
    NextIntlClientProvider

src/i18n/messages/en.json + es.json

  • New session namespace: connect, connecting, disconnect,
    connectedAs, notConnected, connectCta, connectButton
  • Spanish translations included

.env.example

  • Documents NEXT_PUBLIC_STELLAR_NETWORK and NEXT_PUBLIC_ACTA_API_KEY

Persistence decision

localStorage (key acta:session:address) was chosen over
sessionStorage because sessionStorage does not survive a page refresh,
which breaks the acceptance criterion "session persists during navigation
(and ideally survives refresh)"
. In a later batch, when Freighter is
wired, the connector itself handles persistence (Freighter remembers the
permission grant) and the localStorage entry becomes redundant and can
be removed.

How to wire the real wallet (future batch)

  1. Implement a FreighterWalletConnector implements WalletConnector using
    Freighter / Stellar Wallets Kit — provides address (G...) and
    signTransaction
  2. Replace return new MockWalletConnector() in getWalletConnector()
  3. The UI (SessionProvider, useSession, WalletButton) does not change

Verification checklist

  • Connect → truncated G... + DID in exact format
    did:pkh:stellar:{network}:{G...} appears in header
  • Disconnect → returns to initial state, header reflects change
  • Refresh with active session → session restored from localStorage
  • NEXT_PUBLIC_STELLAR_NETWORK=mainnet → DID network segment changes
    to mainnet (proof it is not hardcoded)
  • Zero hardcoded strings — all text via session i18n namespace
  • ActaConfig mounted as 'use client', as low as possible in tree
  • Seam documented in single comment in getWalletConnector()
  • pnpm lint && pnpm typecheck && pnpm build pass

Environment variables

Variable Default Description
NEXT_PUBLIC_STELLAR_NETWORK testnet Stellar network for DID derivation and ACTA API base URL
NEXT_PUBLIC_ACTA_API_KEY (placeholder) ACTA API key — not used in Batch 1; replace with real key when API calls are needed

Real keys go in .env.local (gitignored). Never commit secrets.

Closes #4

Summary by CodeRabbit

  • New Features

    • Added wallet connection functionality allowing users to connect and disconnect their Stellar wallets.
    • Header now displays wallet connection status and truncated wallet address when connected.
    • App now retrieves real credentials instead of using mock data.
  • Documentation

    • Added comprehensive Spanish translations for wallet features and other interface elements.
    • Added English translation strings for wallet session and connection states.

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

@sotoJ24 is attempting to deploy a commit to the ACTA Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a Stellar wallet session layer (SessionProvider, WalletConnector, useSession), a new ActaProvider for ACTA SDK configuration, and a WalletButton header component. The mock credential source in packages/acta is refactored to support configurable error/empty/normal modes via a new getCredentialSource() factory. Both providers are wired into the app layout, pages are updated to call getCredentialSource(), and i18n strings for session UI are added in English and Spanish.

Changes

Wallet Session, Credential Source, and UI

Layer / File(s) Summary
Configurable mock credential source
packages/acta/src/mock.ts
Replaces the old singleton mock with a MockCredentialSource class supporting error/empty/normal modes, a computeSummary helper derived from FIXTURES, and a new getCredentialSource(options?) factory reading NEXT_PUBLIC_DATA_SOURCE and NEXT_PUBLIC_MOCK_MODE. Removes getMockCreditCredentialSource, mockCreditCredentials, and mockProfileSummary.
WalletConnector interface and mock
apps/credit-history/src/session/wallet-connector.ts
Defines the WalletConnector interface (connect/disconnect), implements MockWalletConnector with simulated latency and a fixed address, and exports getWalletConnector() returning the mock unconditionally.
SessionProvider context and useSession hook
apps/credit-history/src/session/session-provider.tsx
Adds SessionStatus and SessionState types, a React context, and SessionProvider with localStorage-based session restore on mount; connect/disconnect callbacks delegate to getWalletConnector() and persist the address; derives DID via didPkhStellar; exports useSession().
ActaProvider and layout wiring
apps/credit-history/src/providers/acta-provider.tsx, apps/credit-history/src/app/layout.tsx
Adds ActaProvider resolving NEXT_PUBLIC_STELLAR_NETWORK and NEXT_PUBLIC_ACTA_API_KEY to configure ActaConfig; updates RootLayout to mount both ActaProvider and SessionProvider wrapping the existing shell.
WalletButton component, header integration, and i18n
apps/credit-history/src/components/wallet-button.tsx, apps/credit-history/src/components/header.tsx, apps/credit-history/src/i18n/messages/en.json, apps/credit-history/src/i18n/messages/es.json
Adds WalletButton with disconnected/connecting/connected render states driven by useSession(); address/DID truncation helpers; mounts it in the header; adds session i18n namespace in English and Spanish; completes Spanish localization for nav, footer, common, home, credentials, and share sections.
Pages updated to getCredentialSource and .gitignore
apps/credit-history/src/app/share/page.tsx, apps/credit-history/src/app/verify/[token]/page.tsx, .gitignore
Switches share/page.tsx and verify/[token]/page.tsx from getMockCreditCredentialSource() to getCredentialSource(); adds .env.local to .gitignore.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WalletButton
    participant SessionProvider
    participant WalletConnector
    participant localStorage

    User->>WalletButton: clicks "Connect wallet"
    WalletButton->>SessionProvider: connect()
    SessionProvider->>SessionProvider: status = "connecting"
    SessionProvider->>WalletConnector: getWalletConnector().connect()
    WalletConnector-->>SessionProvider: { address: "G...xyz" }
    SessionProvider->>localStorage: setItem("acta:session:address", address)
    SessionProvider->>SessionProvider: status = "connected", derive DID
    SessionProvider-->>WalletButton: re-render with address + DID
    User->>WalletButton: clicks "Disconnect"
    WalletButton->>SessionProvider: disconnect()
    SessionProvider->>WalletConnector: getWalletConnector().disconnect()
    SessionProvider->>localStorage: removeItem("acta:session:address")
    SessionProvider->>SessionProvider: status = "disconnected", address = null
    SessionProvider-->>WalletButton: re-render with Connect button
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • Mock data layer + SDK integration seam #5 — The packages/acta/src/mock.ts refactor directly implements the getCredentialSource() factory and configurable MockCredentialSource described in this issue, and the page updates demonstrate consuming it.
  • UX states: empty, loading, and error #18 — The new error/empty/normal modes in MockSourceOptions and the session/wallet infrastructure added in this PR are the foundational pieces this issue depends on.

Possibly related PRs

  • ACTA-Team/products-acta#23: Both PRs touch the /share and /verify/[token] pages and the credential source usage; this PR further replaces getMockCreditCredentialSource calls with getCredentialSource().
  • ACTA-Team/products-acta#24: Both PRs modify src/app/layout.tsx and src/components/header.tsx in the credit-history app; the retrieved PR established the i18n shell that this PR extends with provider wrapping and the wallet button.

Poem

🐇 Hoppity-hop, the wallet connects,
A session restored, the DID reflects.
Mock modes of error, empty, and norm—
The bunny refactored right through the storm!
Providers nest like burrows so deep,
New Spanish strings tucked snugly to sleep. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title 'feat: wallet connection and holder session' clearly and concisely summarizes the main changes: adding wallet connection functionality and session management for holders.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@sotoJ24 sotoJ24 changed the title Issue/4 feat: wallet connection and holder session Jun 22, 2026

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

🧹 Nitpick comments (1)
apps/credit-history/src/session/wallet-connector.ts (1)

53-55: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use a stable connector instance for session lifecycle calls.

Creating a new connector on every call can break real wallet adapters that track connection state/listeners per instance. Return one shared connector instance from the factory.

♻️ Proposed refactor
 class MockWalletConnector implements WalletConnector {
@@
 }
 
+const walletConnector: WalletConnector = new MockWalletConnector();
+
 // ─── Seam factory ─────────────────────────────────────────────────────────────
@@
 export function getWalletConnector(): WalletConnector {
-  return new MockWalletConnector();
+  return walletConnector;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/credit-history/src/session/wallet-connector.ts` around lines 53 - 55,
The getWalletConnector() function creates a new MockWalletConnector instance on
every call, which breaks wallet adapters that track connection state and
listeners per instance. Instead, create a single shared connector instance at
the module level and cache it, then return that same cached instance from
getWalletConnector() on all subsequent calls rather than instantiating a new
MockWalletConnector each time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/credit-history/src/providers/acta-provider.tsx`:
- Around line 7-20: Remove the declare const statements for
NEXT_PUBLIC_STELLAR_NETWORK and NEXT_PUBLIC_ACTA_API_KEY at the top of the file.
Update the resolveNetwork function to reference
process.env.NEXT_PUBLIC_STELLAR_NETWORK directly instead of using the declared
constant, which will enable Next.js to properly inline these environment
variables at build time through its static analysis instead of leaving them
undefined at runtime.

In `@apps/credit-history/src/session/session-provider.tsx`:
- Around line 98-105: The disconnect function has the try-catch block in the
wrong position. The connector.disconnect() call is not wrapped in the try-catch,
so if it fails, the subsequent setAddress, setStatus, and localStorage cleanup
are never executed. Restructure the disconnect useCallback function to wrap the
connector.disconnect() call in a try-catch block, ensuring that
setAddress(null), setStatus('disconnected'), and the
localStorage.removeItem(SESSION_STORAGE_KEY) are executed in a finally block or
are guaranteed to run regardless of whether the connector.disconnect() call
succeeds or throws an error.
- Around line 25-34: The declare const pattern for NEXT_PUBLIC_STELLAR_NETWORK
prevents Next.js from inlining the environment variable at build time, causing
it to always be undefined at runtime and fall back to 'testnet'. Remove the
declare const statement and refactor the resolveNetwork function to directly
access process.env.NEXT_PUBLIC_STELLAR_NETWORK with a nullish coalescing
operator and 'testnet' fallback instead of using the typeof check and type
casting. Apply the same fix to the acta-provider.tsx file which uses the same
broken pattern.

In `@packages/acta/src/mock.ts`:
- Around line 196-212: The listCredentials() and getCredential() methods expose
shared references to objects in the FIXTURES array, allowing downstream
mutations to corrupt the mock state. While listCredentials() spreads the array,
the objects within are still references to the originals. The getCredential()
method returns a direct reference to the found object. Fix both methods by
creating deep copies of the returned objects before returning them so that
mutations by consumers do not affect the shared FIXTURES state.
- Around line 224-237: Remove the ambient declarations for NEXT_PUBLIC_MOCK_MODE
and NEXT_PUBLIC_DATA_SOURCE at the top of the file. In the resolveMockMode
function, replace the reference to NEXT_PUBLIC_MOCK_MODE with
process.env.NEXT_PUBLIC_MOCK_MODE. In the getCredentialSource function, replace
the reference to NEXT_PUBLIC_DATA_SOURCE with
process.env.NEXT_PUBLIC_DATA_SOURCE. This ensures Next.js's static replacement
mechanism works correctly by accessing environment variables through the
standard process.env object.

---

Nitpick comments:
In `@apps/credit-history/src/session/wallet-connector.ts`:
- Around line 53-55: The getWalletConnector() function creates a new
MockWalletConnector instance on every call, which breaks wallet adapters that
track connection state and listeners per instance. Instead, create a single
shared connector instance at the module level and cache it, then return that
same cached instance from getWalletConnector() on all subsequent calls rather
than instantiating a new MockWalletConnector each time.
🪄 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: c419e209-7a96-4306-8a22-7709ebf5567d

📥 Commits

Reviewing files that changed from the base of the PR and between 5b3a0ac and 0963c42.

📒 Files selected for processing (12)
  • .gitignore
  • apps/credit-history/src/app/layout.tsx
  • apps/credit-history/src/app/share/page.tsx
  • apps/credit-history/src/app/verify/[token]/page.tsx
  • apps/credit-history/src/components/header.tsx
  • apps/credit-history/src/components/wallet-button.tsx
  • apps/credit-history/src/i18n/messages/en.json
  • apps/credit-history/src/i18n/messages/es.json
  • apps/credit-history/src/providers/acta-provider.tsx
  • apps/credit-history/src/session/session-provider.tsx
  • apps/credit-history/src/session/wallet-connector.ts
  • packages/acta/src/mock.ts

Comment on lines +7 to +20
// Next.js inlines NEXT_PUBLIC_* at build time — declare so TypeScript resolves.
declare const NEXT_PUBLIC_STELLAR_NETWORK: string | undefined;
declare const NEXT_PUBLIC_ACTA_API_KEY: string | undefined;

function resolveBaseURL(network: StellarNetwork): baseURL {
return network === 'mainnet' ? mainNet : testNet;
}

function resolveNetwork(): StellarNetwork {
const raw =
typeof NEXT_PUBLIC_STELLAR_NETWORK !== 'undefined'
? (NEXT_PUBLIC_STELLAR_NETWORK as string)
: 'testnet';
return raw === 'mainnet' ? 'mainnet' : 'testnet';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find NEXT_PUBLIC usage patterns =="
rg -n -C2 'NEXT_PUBLIC_(STELLAR_NETWORK|ACTA_API_KEY)|process\.env\.NEXT_PUBLIC_(STELLAR_NETWORK|ACTA_API_KEY)'

echo
echo "== Check for explicit global injections of bare NEXT_PUBLIC_* =="
rg -n -C2 'globalThis\.NEXT_PUBLIC_|window\.NEXT_PUBLIC_|declare const NEXT_PUBLIC_'

echo
echo "== Inspect Next config for custom env/define wiring =="
fd -i 'next.config.*' -x sh -c 'echo "--- {} ---"; sed -n "1,240p" "{}"'
rg -n -C3 'DefinePlugin|webpack\(|env\s*:' --iglob '*next.config.*'

Repository: ACTA-Team/products-acta

Length of output: 5470


🌐 Web query:

Next.js NEXT_PUBLIC environment variables process.env vs declare const globals

💡 Result:

In Next.js, the NEXT_PUBLIC_ prefix and manual constant declarations (or configuration-based variables) serve different purposes regarding accessibility, security, and the build process. NEXT_PUBLIC_ Environment Variables By prefixing an environment variable with NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_API_URL), you instruct Next.js to inline the value directly into the JavaScript bundle at build time [1][2][3]. This makes the variable accessible to both server-side code and client-side browser code [1][4][5]. Because these values are baked into the static JavaScript files, they are publicly visible to anyone who inspects the browser source code [6][7]. Consequently, you must never store sensitive secrets (such as API private keys or database passwords) using the NEXT_PUBLIC_ prefix [3][6][5]. Furthermore, because they are inlined at build time, any changes to these variables require a full application rebuild; they cannot be updated at runtime without redeploying the app [1][3][6]. Process.env vs. Global Constants Next.js relies on static analysis (via tools like Webpack's DefinePlugin or Turbopack) to perform the replacement of process.env.NEXT_PUBLIC_ variables [8][3][9]. For this to work correctly, you must use the full, explicit syntax: process.env.NEXT_PUBLIC_VAR_NAME [8][3]. Do not attempt to destructure process.env (e.g., const { NEXT_PUBLIC_VAR } = process.env) or use dynamic access (e.g., process.env[key]), as these patterns bypass the build-time replacement mechanism and will return undefined in the browser bundle [3][9]. Similarly, accessing global.process.env instead of process.env is considered dynamic and will also fail to inline correctly [8]. Server-Only Variables Any environment variable defined in your .env files that does not start with NEXT_PUBLIC_ is strictly server-only [1][10][11]. These variables are accessible in Node.js environments—such as API routes, Server Components, and middleware—but are completely stripped from the client-side JavaScript bundle [3][5][12]. This is the secure, default method for handling sensitive credentials [3][11]. Summary of Best Practices 1. Sensitive Secrets: Use standard environment variables (no prefix) and access them only on the server [5][7]. 2. Public Configuration: Use the NEXT_PUBLIC_ prefix only for non-sensitive values like public API base URLs or feature flags [3][7]. 3. Avoid Destructuring: Always reference variables using the full process.env.VARIABLE_NAME syntax [3][9]. 4. Startup Validation: Use a library like Zod to validate your environment variables at application startup, which helps catch missing or misconfigured variables immediately [6][5][12]. 5. No Runtime Updates: Remember that NEXT_PUBLIC_ values are frozen at build time; for runtime-configurable values, you must expose them through an API endpoint or another dynamic delivery mechanism [1][2].

Citations:


🌐 Web query:

Next.js how are NEXT_PUBLIC_ variables exposed to browser client code

💡 Result:

In Next.js, environment variables prefixed with NEXT_PUBLIC_ are exposed to the browser by being statically "inlined" into your JavaScript bundles during the build process [1][2]. When you run the build command (e.g., next build), the Next.js bundler (such as webpack or Turbopack) scans your code for any occurrences of process.env.NEXT_PUBLIC_VARIABLE_NAME and replaces these references with the actual string value defined in your environment at that time [1][3][4]. Key implications of this mechanism include: 1. Build-Time Freezing: Because these variables are hard-coded into your JavaScript bundles at build time, they are "frozen" [1][5]. If you change the value of a NEXT_PUBLIC_ variable in your environment (such as in your.env file or a hosting provider's dashboard) after the build is complete, your application will not reflect that change until you rebuild the project [1][3][5]. 2. Client-Side Exposure: Any variable prefixed with NEXT_PUBLIC_ is intentionally exposed to the browser [1][4]. Consequently, these values are visible to anyone who inspects the client-side JavaScript or the source code of your page [6][5]. You must never use this prefix for sensitive secrets, such as API private keys, database credentials, or internal configuration values [6][3][5]. 3. Default Behavior for Non-Prefixed Variables: Environment variables that do not use the NEXT_PUBLIC_ prefix are kept server-only [1][3]. If you attempt to access a non-prefixed environment variable (like process.env.DATABASE_URL) in client-side code, it will resolve to undefined [7][3][5]. 4. Strict Pattern Matching: The replacement process performs literal text matching [4]. It requires that you use the full expression, such as process.env.NEXT_PUBLIC_VAR, directly in your code [8][4]. Destructuring process.env or using dynamic keys will prevent the bundler from correctly identifying and replacing the variable, likely resulting in undefined at runtime [8][4]. For values that need to be dynamic or secret, the recommended approach is to keep those variables on the server and access them only through server-side code or by creating a dedicated API route that the client can call to fetch necessary configuration at runtime [1][6].

Citations:


Replace bare NEXT_PUBLIC_* declarations with process.env.NEXT_PUBLIC_* to enable proper environment variable inlining.

Next.js uses static analysis to replace process.env.NEXT_PUBLIC_* references at build time. The current approach using declare const bypasses this mechanism entirely, leaving these variables undefined at runtime and forcing fallback to 'testnet' and the placeholder API key. Use the standard Next.js pattern: process.env.NEXT_PUBLIC_STELLAR_NETWORK and process.env.NEXT_PUBLIC_ACTA_API_KEY.

Suggested fix
-'use client';
+'use client';

 import { ActaConfig, mainNet, testNet } from '`@acta-products/acta`';
 import type { baseURL } from '`@acta-products/acta`';
 import type { StellarNetwork } from '`@acta-products/acta/did`';
-
-// Next.js inlines NEXT_PUBLIC_* at build time — declare so TypeScript resolves.
-declare const NEXT_PUBLIC_STELLAR_NETWORK: string | undefined;
-declare const NEXT_PUBLIC_ACTA_API_KEY: string | undefined;
 
 function resolveBaseURL(network: StellarNetwork): baseURL {
   return network === 'mainnet' ? mainNet : testNet;
 }
 
 function resolveNetwork(): StellarNetwork {
-  const raw =
-    typeof NEXT_PUBLIC_STELLAR_NETWORK !== 'undefined'
-      ? (NEXT_PUBLIC_STELLAR_NETWORK as string)
-      : 'testnet';
+  const raw = process.env.NEXT_PUBLIC_STELLAR_NETWORK ?? 'testnet';
   return raw === 'mainnet' ? 'mainnet' : 'testnet';
 }
@@
 export function ActaProvider({ children }: ActaProviderProps) {
   const network = resolveNetwork();
   const baseURL = resolveBaseURL(network);
-  const apiKey =
-    (typeof NEXT_PUBLIC_ACTA_API_KEY !== 'undefined' &&
-    (NEXT_PUBLIC_ACTA_API_KEY as string).length > 0)
-      ? (NEXT_PUBLIC_ACTA_API_KEY as string)
-      : 'placeholder-batch1-no-api-calls';
+  const apiKey =
+    process.env.NEXT_PUBLIC_ACTA_API_KEY?.trim().length
+      ? process.env.NEXT_PUBLIC_ACTA_API_KEY
+      : 'placeholder-batch1-no-api-calls';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/credit-history/src/providers/acta-provider.tsx` around lines 7 - 20,
Remove the declare const statements for NEXT_PUBLIC_STELLAR_NETWORK and
NEXT_PUBLIC_ACTA_API_KEY at the top of the file. Update the resolveNetwork
function to reference process.env.NEXT_PUBLIC_STELLAR_NETWORK directly instead
of using the declared constant, which will enable Next.js to properly inline
these environment variables at build time through its static analysis instead of
leaving them undefined at runtime.

Comment on lines +25 to +34
// Next.js inlines NEXT_PUBLIC_* at build time.
declare const NEXT_PUBLIC_STELLAR_NETWORK: string | undefined;

function resolveNetwork(): StellarNetwork {
const raw =
typeof NEXT_PUBLIC_STELLAR_NETWORK !== 'undefined'
? (NEXT_PUBLIC_STELLAR_NETWORK as string)
: 'testnet';
return raw === 'mainnet' ? 'mainnet' : 'testnet';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify how NEXT_PUBLIC_STELLAR_NETWORK is consumed across session/config layers.
rg -n "NEXT_PUBLIC_STELLAR_NETWORK|process\\.env" \
  apps/credit-history/src/session/session-provider.tsx \
  apps/credit-history/src/providers/acta-provider.tsx

Repository: ACTA-Team/products-acta

Length of output: 862


🏁 Script executed:

sed -n '100,120p' apps/credit-history/src/session/session-provider.tsx

Repository: ACTA-Team/products-acta

Length of output: 575


🏁 Script executed:

sed -n '1,50p' apps/credit-history/src/providers/acta-provider.tsx

Repository: ACTA-Team/products-acta

Length of output: 1344


🌐 Web query:

Next.js NEXT_PUBLIC_ environment variables declare const vs process.env

💡 Result:

In Next.js, the NEXT_PUBLIC_ prefix and process.env are fundamental to how the framework handles environment variables. Understanding their behavior is critical for both security and functionality [1][2][3]. NEXT_PUBLIC_ Prefix and Browser Exposure The NEXT_PUBLIC_ prefix is the mechanism used to explicitly expose an environment variable to the browser [4][1]. When you prefix a variable with NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_API_URL), Next.js automatically inlines the value of that variable into the JavaScript bundle during the build process (next build) [4][2][5]. Because these variables are inlined as hard-coded strings, they become part of your client-side source code [4][6]. Consequently: - Security: You must never use the NEXT_PUBLIC_ prefix for secrets, private keys, or credentials [1][2][5]. Anything prefixed this way can be easily viewed by any user inspecting your website's source code [1][5]. - Build Time: The values are frozen at build time [4][2][5]. If you change the value in your environment after the app has been built, the browser will continue to use the old, inlined value until you rebuild your application [4][5]. process.env Usage process.env is the standard Node.js object used to access all loaded environment variables [7]. - Server-Side Access: Any environment variable (whether it has the NEXT_PUBLIC_ prefix or not) is available to your server-side code (e.g., in Server Components, API Routes, or Server Actions) via process.env [1][8][3]. - Client-Side Access: Only variables prefixed with NEXT_PUBLIC_ can be accessed via process.env in client-side code (e.g., Client Components) [4][1][7]. Critical Implementation Rules To ensure environment variables work correctly in Next.js, you must follow these specific patterns: - Do Not Destructure: Never use object destructuring (e.g., const { API_KEY } = process.env) [2][9]. Because Next.js performs a static search-and-replace to inline these values at build time, it requires the full, explicit reference (e.g., process.env.API_KEY) to function [2][6][9]. - Do Not Use Dynamic Keys: Avoid using dynamic keys (e.g., process.env[variableName]), as this bypasses the static replacement mechanism and will result in the variable being undefined in the client bundle [2]. - Validate Variables: Because process.env types are inherently broad (string | undefined), it is recommended to validate environment variables at application startup (e.g., using a library like Zod) to catch configuration errors early [1][5]. In summary, use the NEXT_PUBLIC_ prefix only for values that are safe to expose to the public, always reference variables using the full process.env.VARIABLE_NAME syntax, and keep all sensitive secrets server-only by omitting the prefix [1][2][3].

Citations:


Use process.env to allow Next.js to inline NEXT_PUBLIC_STELLAR_NETWORK at build time.

The declare const pattern prevents Next.js from performing static replacement on the NEXT_PUBLIC_STELLAR_NETWORK variable. This causes it to be undefined at runtime, forcing fallback to 'testnet'. If the actual environment variable is set to 'mainnet', the DID generated at line 110 will be inconsistent with the network configuration used elsewhere. Replace with process.env.NEXT_PUBLIC_STELLAR_NETWORK ?? 'testnet' to allow Next.js to inline the value correctly at build time.

This also applies to acta-provider.tsx (line 8), which uses the same broken pattern.

✅ Suggested fix
-// Next.js inlines NEXT_PUBLIC_* at build time.
-declare const NEXT_PUBLIC_STELLAR_NETWORK: string | undefined;
-
 function resolveNetwork(): StellarNetwork {
-  const raw =
-    typeof NEXT_PUBLIC_STELLAR_NETWORK !== 'undefined'
-      ? (NEXT_PUBLIC_STELLAR_NETWORK as string)
-      : 'testnet';
+  const raw = process.env.NEXT_PUBLIC_STELLAR_NETWORK ?? 'testnet';
   return raw === 'mainnet' ? 'mainnet' : 'testnet';
 }
📝 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
// Next.js inlines NEXT_PUBLIC_* at build time.
declare const NEXT_PUBLIC_STELLAR_NETWORK: string | undefined;
function resolveNetwork(): StellarNetwork {
const raw =
typeof NEXT_PUBLIC_STELLAR_NETWORK !== 'undefined'
? (NEXT_PUBLIC_STELLAR_NETWORK as string)
: 'testnet';
return raw === 'mainnet' ? 'mainnet' : 'testnet';
}
function resolveNetwork(): StellarNetwork {
const raw = process.env.NEXT_PUBLIC_STELLAR_NETWORK ?? 'testnet';
return raw === 'mainnet' ? 'mainnet' : 'testnet';
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/credit-history/src/session/session-provider.tsx` around lines 25 - 34,
The declare const pattern for NEXT_PUBLIC_STELLAR_NETWORK prevents Next.js from
inlining the environment variable at build time, causing it to always be
undefined at runtime and fall back to 'testnet'. Remove the declare const
statement and refactor the resolveNetwork function to directly access
process.env.NEXT_PUBLIC_STELLAR_NETWORK with a nullish coalescing operator and
'testnet' fallback instead of using the typeof check and type casting. Apply the
same fix to the acta-provider.tsx file which uses the same broken pattern.

Comment on lines +98 to +105
const disconnect = useCallback(async () => {
const connector = getWalletConnector();
await connector.disconnect();
setAddress(null);
setStatus('disconnected');
try {
localStorage.removeItem(SESSION_STORAGE_KEY);
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Always clear local session state even when wallet disconnect fails.

If connector.disconnect() throws, address/status/localStorage cleanup is skipped, leaving a stale connected session in UI.

🛠️ Suggested fix
 const disconnect = useCallback(async () => {
-  const connector = getWalletConnector();
-  await connector.disconnect();
-  setAddress(null);
-  setStatus('disconnected');
-  try {
-    localStorage.removeItem(SESSION_STORAGE_KEY);
-  } catch {
-    // Non-fatal.
-  }
+  try {
+    const connector = getWalletConnector();
+    await connector.disconnect();
+  } finally {
+    setAddress(null);
+    setStatus('disconnected');
+    try {
+      localStorage.removeItem(SESSION_STORAGE_KEY);
+    } catch {
+      // Non-fatal.
+    }
+  }
 }, []);
📝 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 disconnect = useCallback(async () => {
const connector = getWalletConnector();
await connector.disconnect();
setAddress(null);
setStatus('disconnected');
try {
localStorage.removeItem(SESSION_STORAGE_KEY);
} catch {
const disconnect = useCallback(async () => {
try {
const connector = getWalletConnector();
await connector.disconnect();
} finally {
setAddress(null);
setStatus('disconnected');
try {
localStorage.removeItem(SESSION_STORAGE_KEY);
} catch {
// Non-fatal.
}
}
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/credit-history/src/session/session-provider.tsx` around lines 98 - 105,
The disconnect function has the try-catch block in the wrong position. The
connector.disconnect() call is not wrapped in the try-catch, so if it fails, the
subsequent setAddress, setStatus, and localStorage cleanup are never executed.
Restructure the disconnect useCallback function to wrap the
connector.disconnect() call in a try-catch block, ensuring that
setAddress(null), setStatus('disconnected'), and the
localStorage.removeItem(SESSION_STORAGE_KEY) are executed in a finally block or
are guaranteed to run regardless of whether the connector.disconnect() call
succeeds or throws an error.

Comment thread packages/acta/src/mock.ts
Comment on lines 196 to 212
async listCredentials(): Promise<CreditCredential[]> {
// Simulate slight API delay for fidelity
return new Promise((resolve) => {
setTimeout(() => resolve(mockCreditCredentials), 100);
});
await this.delay();
if (this.mode === 'error') {
throw new Error('[MockCredentialSource] Simulated network error in listCredentials()');
}
if (this.mode === 'empty') return [];
return [...FIXTURES];
}

async getCredential(id: string): Promise<CreditCredential | null> {
return new Promise((resolve) => {
setTimeout(() => {
const cred = mockCreditCredentials.find((c) => c.id === id);
resolve(cred || null);
}, 50);
});
await this.delay();
if (this.mode === 'error') {
throw new Error(`[MockCredentialSource] Simulated network error in getCredential(${id})`);
}
if (this.mode === 'empty') return null;
return FIXTURES.find((c) => c.id === id) ?? null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return defensive copies from listCredentials() and getCredential().

These methods currently expose shared FIXTURES object references. Any downstream mutation can bleed into future reads and corrupt mock state across flows.

Proposed fix
+function cloneCredential(c: CreditCredential): CreditCredential {
+  return {
+    ...c,
+    claims: { ...c.claims },
+  };
+}
+
 class MockCredentialSource implements CreditCredentialSource {
@@
   async listCredentials(): Promise<CreditCredential[]> {
@@
-    return [...FIXTURES];
+    return FIXTURES.map(cloneCredential);
   }
@@
   async getCredential(id: string): Promise<CreditCredential | null> {
@@
-    return FIXTURES.find((c) => c.id === id) ?? null;
+    const credential = FIXTURES.find((c) => c.id === id);
+    return credential ? cloneCredential(credential) : null;
   }
📝 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
async listCredentials(): Promise<CreditCredential[]> {
// Simulate slight API delay for fidelity
return new Promise((resolve) => {
setTimeout(() => resolve(mockCreditCredentials), 100);
});
await this.delay();
if (this.mode === 'error') {
throw new Error('[MockCredentialSource] Simulated network error in listCredentials()');
}
if (this.mode === 'empty') return [];
return [...FIXTURES];
}
async getCredential(id: string): Promise<CreditCredential | null> {
return new Promise((resolve) => {
setTimeout(() => {
const cred = mockCreditCredentials.find((c) => c.id === id);
resolve(cred || null);
}, 50);
});
await this.delay();
if (this.mode === 'error') {
throw new Error(`[MockCredentialSource] Simulated network error in getCredential(${id})`);
}
if (this.mode === 'empty') return null;
return FIXTURES.find((c) => c.id === id) ?? null;
}
function cloneCredential(c: CreditCredential): CreditCredential {
return {
...c,
claims: { ...c.claims },
};
}
class MockCredentialSource implements CreditCredentialSource {
async listCredentials(): Promise<CreditCredential[]> {
await this.delay();
if (this.mode === 'error') {
throw new Error('[MockCredentialSource] Simulated network error in listCredentials()');
}
if (this.mode === 'empty') return [];
return FIXTURES.map(cloneCredential);
}
async getCredential(id: string): Promise<CreditCredential | null> {
await this.delay();
if (this.mode === 'error') {
throw new Error(`[MockCredentialSource] Simulated network error in getCredential(${id})`);
}
if (this.mode === 'empty') return null;
const credential = FIXTURES.find((c) => c.id === id);
return credential ? cloneCredential(credential) : null;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/acta/src/mock.ts` around lines 196 - 212, The listCredentials() and
getCredential() methods expose shared references to objects in the FIXTURES
array, allowing downstream mutations to corrupt the mock state. While
listCredentials() spreads the array, the objects within are still references to
the originals. The getCredential() method returns a direct reference to the
found object. Fix both methods by creating deep copies of the returned objects
before returning them so that mutations by consumers do not affect the shared
FIXTURES state.

Comment thread packages/acta/src/mock.ts
Comment on lines +224 to +237
// Inlined by Next.js build — declared here so TypeScript resolves the names.
declare const NEXT_PUBLIC_MOCK_MODE: string | undefined;
declare const NEXT_PUBLIC_DATA_SOURCE: string | undefined;

function resolveMockMode(): MockMode {
const envMode =
typeof NEXT_PUBLIC_MOCK_MODE !== 'undefined' ? (NEXT_PUBLIC_MOCK_MODE as string) : '';
if (envMode === 'empty' || envMode === 'error') return envMode as MockMode;
return 'normal';
}

export function getCredentialSource(options?: MockSourceOptions): CreditCredentialSource {
const dataSource =
typeof NEXT_PUBLIC_DATA_SOURCE !== 'undefined' ? (NEXT_PUBLIC_DATA_SOURCE as string) : 'mock';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify env access patterns around credential source configuration.
rg -n -C2 'declare const NEXT_PUBLIC_(MOCK_MODE|DATA_SOURCE)' packages/acta/src/mock.ts
rg -n -C2 'NEXT_PUBLIC_(MOCK_MODE|DATA_SOURCE)' packages/acta/src/mock.ts
rg -n -C2 'process\.env\.NEXT_PUBLIC_(MOCK_MODE|DATA_SOURCE)' packages/acta/src/mock.ts apps packages

Repository: ACTA-Team/products-acta

Length of output: 1363


🌐 Web query:

Next.js NEXT_PUBLIC environment variables access pattern best practices

💡 Result:

In Next.js, the NEXT_PUBLIC_ prefix is the designated mechanism for exposing environment variables to the browser [1][2]. When a variable is prefixed with NEXT_PUBLIC_, Next.js automatically "inlines" its value into the JavaScript bundle at build time, replacing all references to process.env.NEXT_PUBLIC_... with the actual value [1][3]. Best Practices and Critical Considerations: 1. Never Expose Secrets: Variables prefixed with NEXT_PUBLIC_ are visible to anyone who inspects the client-side JavaScript bundle or page source [4][5]. Never store sensitive information such as database credentials, API secret keys, or private tokens using this prefix [4][6]. 2. Build-Time Freezing: Because these variables are inlined during next build, they are "frozen" at that moment [1][2]. If you change the value of a NEXT_PUBLIC_ variable, the application must be rebuilt and redeployed for the change to take effect [2][5]. They cannot be dynamically updated at runtime [3]. 3. Use Proper Variable Categorization: - Server-Only: Use variables without the NEXT_PUBLIC_ prefix for any sensitive data. These remain accessible only in Node.js environments (e.g., API routes, Server Components, Server Actions) and are never included in the client-side bundle [4][2]. - Client-Accessible: Only use NEXT_PUBLIC_ for non-sensitive, public configuration, such as analytics IDs, public API base URLs, or feature flags [4][5]. 4. Implementation Patterns: - Always access variables using the full process.env.NEXT_PUBLIC_VAR_NAME syntax [2]. Avoid destructuring process.env (e.g., const { VAR } = process.env), as this prevents Next.js from performing the static replacement required for inlining [2][7]. - Avoid dynamic key access (e.g., process.env[key]), as this will not be inlined and will return undefined in the browser [2][6]. 5. Validation: Use schema validation libraries (such as Zod) to validate environment variables at application startup [2][5]. This ensures the application fails early with a clear error if required variables are missing, rather than experiencing unexpected runtime behavior [5]. 6. File Management: Maintain an .env.local file for local development (which must be included in .gitignore to prevent committing secrets) and use your hosting platform's environment variable management (e.g., Vercel's dashboard) for production settings [4][2][5]. Document required variables in a committed .env.example file [6].

Citations:


🏁 Script executed:

# Check if there are other patterns in the codebase for accessing public env vars
rg -n 'process\.env\.NEXT_PUBLIC' packages/acta/src --type ts
rg -n 'NEXT_PUBLIC' packages/acta/src --type ts | head -30

Repository: ACTA-Team/products-acta

Length of output: 610


🏁 Script executed:

# Check if this pattern is used elsewhere or if there's configuration guidance
fd -e md -e ts -e js . packages/acta | xargs rg -l 'NEXT_PUBLIC' | head -10

Repository: ACTA-Team/products-acta

Length of output: 93


Use process.env.NEXT_PUBLIC_* for proper Next.js environment variable inlining.

The current code declares ambient NEXT_PUBLIC_MOCK_MODE and NEXT_PUBLIC_DATA_SOURCE constants and accesses them as bare globals. This bypasses Next.js's static replacement mechanism, which requires using process.env.NEXT_PUBLIC_* directly to inline values at build time. The bare globals approach is non-standard and may result in undefined values at runtime.

Replace the ambient declarations and bare global reads with process.env.NEXT_PUBLIC_MOCK_MODE and process.env.NEXT_PUBLIC_DATA_SOURCE to align with Next.js best practices:

Proposed fix
-// Inlined by Next.js build — declared here so TypeScript resolves the names.
-declare const NEXT_PUBLIC_MOCK_MODE: string | undefined;
-declare const NEXT_PUBLIC_DATA_SOURCE: string | undefined;
-
 function resolveMockMode(): MockMode {
-  const envMode =
-    typeof NEXT_PUBLIC_MOCK_MODE !== 'undefined' ? (NEXT_PUBLIC_MOCK_MODE as string) : '';
+  const envMode =
+    typeof process !== 'undefined' ? (process.env.NEXT_PUBLIC_MOCK_MODE ?? '') : '';
   if (envMode === 'empty' || envMode === 'error') return envMode as MockMode;
   return 'normal';
 }
 
 export function getCredentialSource(options?: MockSourceOptions): CreditCredentialSource {
   const dataSource =
-    typeof NEXT_PUBLIC_DATA_SOURCE !== 'undefined' ? (NEXT_PUBLIC_DATA_SOURCE as string) : 'mock';
+    typeof process !== 'undefined' ? (process.env.NEXT_PUBLIC_DATA_SOURCE ?? 'mock') : 'mock';
📝 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
// Inlined by Next.js build — declared here so TypeScript resolves the names.
declare const NEXT_PUBLIC_MOCK_MODE: string | undefined;
declare const NEXT_PUBLIC_DATA_SOURCE: string | undefined;
function resolveMockMode(): MockMode {
const envMode =
typeof NEXT_PUBLIC_MOCK_MODE !== 'undefined' ? (NEXT_PUBLIC_MOCK_MODE as string) : '';
if (envMode === 'empty' || envMode === 'error') return envMode as MockMode;
return 'normal';
}
export function getCredentialSource(options?: MockSourceOptions): CreditCredentialSource {
const dataSource =
typeof NEXT_PUBLIC_DATA_SOURCE !== 'undefined' ? (NEXT_PUBLIC_DATA_SOURCE as string) : 'mock';
function resolveMockMode(): MockMode {
const envMode =
typeof process !== 'undefined' ? (process.env.NEXT_PUBLIC_MOCK_MODE ?? '') : '';
if (envMode === 'empty' || envMode === 'error') return envMode as MockMode;
return 'normal';
}
export function getCredentialSource(options?: MockSourceOptions): CreditCredentialSource {
const dataSource =
typeof process !== 'undefined' ? (process.env.NEXT_PUBLIC_DATA_SOURCE ?? 'mock') : 'mock';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/acta/src/mock.ts` around lines 224 - 237, Remove the ambient
declarations for NEXT_PUBLIC_MOCK_MODE and NEXT_PUBLIC_DATA_SOURCE at the top of
the file. In the resolveMockMode function, replace the reference to
NEXT_PUBLIC_MOCK_MODE with process.env.NEXT_PUBLIC_MOCK_MODE. In the
getCredentialSource function, replace the reference to NEXT_PUBLIC_DATA_SOURCE
with process.env.NEXT_PUBLIC_DATA_SOURCE. This ensures Next.js's static
replacement mechanism works correctly by accessing environment variables through
the standard process.env object.

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.

Wallet connection and holder session

2 participants