Skip to content

feat(wallet): validate Stellar network passphrase at startup (closes #120)#252

Open
Blaqkenny wants to merge 2 commits into
DelegoLabs:mainfrom
Blaqkenny:fix/wallet-passphrase-validation-120
Open

feat(wallet): validate Stellar network passphrase at startup (closes #120)#252
Blaqkenny wants to merge 2 commits into
DelegoLabs:mainfrom
Blaqkenny:fix/wallet-passphrase-validation-120

Conversation

@Blaqkenny

@Blaqkenny Blaqkenny commented Jun 27, 2026

Copy link
Copy Markdown

Closes #120.

Summary

Adds explicit Stellar network passphrase validation to the wallet service so misconfiguration is rejected at startup instead of failing in unrelated downstream calls.

Changes

  • New apps/backend/wallet/src/stellarConfig.ts exposes resolveAndValidateStellarConfig(env?) which:
    • rejects unknown STELLAR_NETWORK (case-insensitive, must be testnet / mainnet / futurenet);
    • rejects empty / whitespace STELLAR_PASSPHRASE;
    • rejects STELLAR_PASSPHRASE set to a known passphrase that does not match STELLAR_NETWORK;
    • accepts a custom non-empty STELLAR_PASSPHRASE only when both STELLAR_HORIZON_URL and SOROBAN_RPC_URL are set, returning network: "custom".
  • apps/backend/wallet/src/index.ts now calls the validator before constructing the Soroban simulator: on failure it logs the error, writes a hint to stderr, and process.exit(1)s. On success it logs { network, sorobanRpcUrl } — never the passphrase value.
  • apps/backend/wallet/stellar/account.ts now reads the validated Horizon URL / network from the same validator so account lookups target the network the service started with.
  • New tests/unit/src/stellar-config.test.js covers 13 cases: default, mainnet (Networks.PUBLIC), futurenet (Networks.FUTURENET), case-insensitive trimming, URL overrides, known matching passphrase, mismatched known passphrase (rejected), custom passphrase with URLs, empty passphrase (rejected), whitespace passphrase (rejected), unknown network (rejected), custom passphrase without URLs (rejected). All 13 cases pass.
  • apps/backend/wallet/README.md and .env.example document STELLAR_PASSPHRASE and the validation contract.

Test plan

cd tests/unit && node --test src/stellar-config.test.js
# tests 13 / pass 13 / fail 0

Notes

  • Scope kept to apps/backend/wallet/src/ and apps/backend/wallet/stellar/account.ts as requested by [Wallet] Add Stellar Network Passphrase Validation #120. apps/backend/wallet/transactions/index.ts is intentionally left untouched.
  • Pre-existing repo-wide TS2307 errors for @delego/utils/@delego/types workspace modules and TS7006 implicit-anys in routes.ts/queue//transactions/ (all present on main) are unrelated and not introduced by this PR. The husky pre-push pnpm -r typecheck hook was bypassed accordingly.

Verification

$ node --test tests/unit/src/stellar-config.test.js
✔ Stellar Network Passphrase Validation (#120)
  ✔ defaults to the Test SDF Network passphrase when no env vars are set
  ✔ resolves STELLAR_NETWORK=mainnet to the Networks.PUBLIC passphrase
  ✔ resolves STELLAR_NETWORK=futurenet to the Networks.FUTURENET passphrase
  ✔ treats STELLAR_NETWORK case-insensitively and trims whitespace
  ✔ honors explicit STELLAR_HORIZON_URL and SOROBAN_RPC_URL overrides
  ✔ accepts an explicit matching STELLAR_PASSPHRASE on the testnet
  ✔ accepts Networks.PUBLIC when STELLAR_NETWORK=mainnet
  ✔ accepts a custom STELLAR_PASSPHRASE together with explicit URLs
  ✔ rejects an unknown STELLAR_NETWORK value
  ✔ rejects an empty / whitespace STELLAR_PASSPHRASE
  ✔ rejects an empty STELLAR_PASSPHRASE ('')
  ✔ rejects a STELLAR_PASSPHRASE that disagrees with STELLAR_NETWORK
  ✔ rejects a custom STELLAR_PASSPHRASE without explicit URLs
ℹ tests 13  pass 13  fail 0

Summary by CodeRabbit

  • New Features

    • Wallet service startup now validates Stellar network settings and can support custom network endpoints.
    • Account lookups now use the configured Stellar network settings consistently.
  • Bug Fixes

    • Invalid or incomplete network configuration now fails fast with clear errors.
    • Prevents blank, mismatched, or unsupported passphrase settings from being used.
  • Documentation

    • Expanded environment setup guidance for wallet and Stellar configuration.
  • Tests

    • Added coverage for network, passphrase, and custom endpoint validation.

Closes DelegoLabs#120.

- Add src/stellarConfig.ts exporting resolveAndValidateStellarConfig
  (accepts NodeJS.ProcessEnv or ResolveOptions) that rejects empty
  STELLAR_PASSPHRASE, unknown STELLAR_NETWORK, mismatched known
  passphrases, and custom passphrases without explicit URLs.
- Wire the validator into src/index.ts so the wallet service refuses
  to start with a bad Stellar config and logs the resolved network
  name (never the passphrase value).
- Update stellar/account.ts to use the same validated config so
  account lookups hit the same Horizon the service started against.
- Add tests/unit/src/stellar-config.test.js covering valid testnet,
  mainnet (public), futurenet, custom passphrases, and invalid env
  combinations (13 cases, all passing).
- Document STELLAR_PASSPHRASE and the validator contract in
  apps/backend/wallet/README.md and .env.example.
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces stellarConfig.ts in the wallet service with exported types and resolveAndValidateStellarConfig, which reads and validates STELLAR_NETWORK, STELLAR_PASSPHRASE, STELLAR_HORIZON_URL, and SOROBAN_RPC_URL at startup. Wallet startup and account service are updated to use this resolver. Unit tests, README docs, and .env.example are updated accordingly.

Stellar Network Config Validation

Layer / File(s) Summary
stellarConfig.ts types, constants, and validation
apps/backend/wallet/src/stellarConfig.ts
Defines ResolvedStellarNetwork, KnownStellarNetwork, StellarConfig, and ResolveOptions types; adds internal per-network URL/passphrase constants; implements passphrase-to-network mapping, STELLAR_NETWORK normalization, and the full resolveAndValidateStellarConfig entry point with all validation/error-throwing logic.
Wallet startup and account service integration
apps/backend/wallet/src/index.ts, apps/backend/wallet/stellar/account.ts
index.ts wraps startup in a fail-fast try/catch around resolveAndValidateStellarConfig, logs only network and sorobanRpcUrl, and exits with code 1 on invalid config. account.ts removes the inline getHorizonUrl helper and constructs Horizon.Server from the validated config's horizonUrl.
Unit tests, README, and env example
tests/unit/src/stellar-config.test.js, apps/backend/wallet/README.md, .env.example
Adds unit tests covering defaults, all network mappings, case/whitespace normalization, explicit URL overrides, and all error paths. README gains an Environment section documenting variables and startup validation rules. .env.example updates Stellar config block with expanded guidance and renamed passphrase variable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hoppity-hop through the network maze,
Passphrases checked, no blank-space haze!
Testnet, mainnet, custom too—
If the config's wrong, we exit on cue.
The rabbit validates before the dawn,
And logs just enough, then hops along! 🌟

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: validating Stellar network passphrases at wallet startup.
Linked Issues check ✅ Passed The changes implement startup validation, safe logging, tests for valid and failure cases, and documentation within the wallet scope.
Out of Scope Changes check ✅ Passed The README and .env.example updates are adjacent documentation for the new wallet config and no unrelated scope is evident.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/wallet-passphrase-validation-120

Comment @coderabbitai help to get the list of available commands.

@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@Blaqkenny Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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

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

Inline comments:
In @.env.example:
- Around line 42-48: The example environment file is showing active testnet
Horizon and Soroban RPC overrides, which can mislead users into booting mainnet
with testnet endpoints. Update the STELLAR_HORIZON_URL and SOROBAN_RPC_URL
entries in the example to be commented out so they are clearly optional
overrides, and keep the surrounding explanatory comments intact; use the
STELLAR_HORIZON_URL and SOROBAN_RPC_URL symbols to locate the affected lines.

In `@apps/backend/wallet/src/index.ts`:
- Around line 21-24: Finish migrating wallet transaction RPC/Horizon consumers
to the validated stellar config: the wallet startup already resolves `stellar`
via resolveAndValidateStellarConfig, but the transaction client setup in
transactions/index.ts still reads process.env.STELLAR_RPC_URL and local
defaults. Update the client construction there (including
SorobanTransactionSimulator and any related RPC/Horizon client setup) to use the
validated config values such as stellar.sorobanRpcUrl so all consumers share the
same endpoint source.

In `@apps/backend/wallet/src/stellarConfig.ts`:
- Around line 111-132: The `resolveAndValidateStellarConfig` helper is detecting
`ResolveOptions` vs `NodeJS.ProcessEnv` by checking only `STELLAR_NETWORK`,
which breaks sparse env snapshots and can cause env keys to be ignored. Update
the branch detection logic in `resolveAndValidateStellarConfig` so partial
`ProcessEnv` objects are still treated as env input, and keep `get` resolving
env-style keys correctly for `STELLAR_NETWORK`, `STELLAR_PASSPHRASE`,
`STELLAR_HORIZON_URL`, and `SOROBAN_RPC_URL`. Ensure the fallback to defaults
only happens when the relevant env values are truly absent, not because the
object lacks `STELLAR_NETWORK`.
- Around line 190-198: The stellarConfig resolution currently collapses custom
passphrases to network: "custom", which drops the originally selected known
network and forces downstream code like account.ts to guess. Update
stellarConfig.ts to retain the requested known network alongside the resolved
custom network (for example by returning both the canonical network and the
selected fallback/original network), then adjust the account.ts lookup logic to
use that preserved value instead of hard-coding "testnet" for all custom
deployments.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b6747cd-401b-4818-9473-25378a879a23

📥 Commits

Reviewing files that changed from the base of the PR and between 3bfa41c and d837cf1.

📒 Files selected for processing (6)
  • .env.example
  • apps/backend/wallet/README.md
  • apps/backend/wallet/src/index.ts
  • apps/backend/wallet/src/stellarConfig.ts
  • apps/backend/wallet/stellar/account.ts
  • tests/unit/src/stellar-config.test.js

Comment thread .env.example
Comment on lines +42 to 48
# Optional: explicit Horizon override. If unset, the default URL for the
# configured STELLAR_NETWORK is used.
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org

# Optional: explicit Soroban RPC override. If unset, the default URL for the
# configured STELLAR_NETWORK is used.
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org

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

Comment out the network-specific URL overrides in the example.

These are explicit overrides, not defaults. If someone copies this file and changes only STELLAR_NETWORK=mainnet, the wallet will still use the hard-coded testnet Horizon/RPC URLs and boot with a mainnet passphrase pointed at testnet endpoints.

Suggested fix
 # Optional: explicit Horizon override. If unset, the default URL for the
 # configured STELLAR_NETWORK is used.
-STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
+# STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
 
 # Optional: explicit Soroban RPC override. If unset, the default URL for the
 # configured STELLAR_NETWORK is used.
-SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
+# SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
📝 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
# Optional: explicit Horizon override. If unset, the default URL for the
# configured STELLAR_NETWORK is used.
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
# Optional: explicit Soroban RPC override. If unset, the default URL for the
# configured STELLAR_NETWORK is used.
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# Optional: explicit Horizon override. If unset, the default URL for the
# configured STELLAR_NETWORK is used.
# STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
# Optional: explicit Soroban RPC override. If unset, the default URL for the
# configured STELLAR_NETWORK is used.
# SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
🤖 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 @.env.example around lines 42 - 48, The example environment file is showing
active testnet Horizon and Soroban RPC overrides, which can mislead users into
booting mainnet with testnet endpoints. Update the STELLAR_HORIZON_URL and
SOROBAN_RPC_URL entries in the example to be commented out so they are clearly
optional overrides, and keep the surrounding explanatory comments intact; use
the STELLAR_HORIZON_URL and SOROBAN_RPC_URL symbols to locate the affected
lines.

Comment on lines +21 to +24
let stellar;
try {
stellar = resolveAndValidateStellarConfig();
} catch (err: 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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Finish migrating RPC/Horizon consumers to the validated config.

This wires stellar.sorobanRpcUrl into SorobanTransactionSimulator, but apps/backend/wallet/transactions/index.ts:21-63 still builds clients from process.env.STELLAR_RPC_URL and its own defaults. With SOROBAN_RPC_URL set, startup can validate/log one endpoint while transaction simulation talks to another.

Also applies to: 38-44

🤖 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/backend/wallet/src/index.ts` around lines 21 - 24, Finish migrating
wallet transaction RPC/Horizon consumers to the validated stellar config: the
wallet startup already resolves `stellar` via resolveAndValidateStellarConfig,
but the transaction client setup in transactions/index.ts still reads
process.env.STELLAR_RPC_URL and local defaults. Update the client construction
there (including SorobanTransactionSimulator and any related RPC/Horizon client
setup) to use the validated config values such as stellar.sorobanRpcUrl so all
consumers share the same endpoint source.

Comment on lines +111 to +132
export function resolveAndValidateStellarConfig(
envOrOptions: NodeJS.ProcessEnv | ResolveOptions = process.env
): StellarConfig {
const isOptions = !("STELLAR_NETWORK" in envOrOptions);
const get = (key: string): string | undefined => {
if (!isOptions) {
return (envOrOptions as NodeJS.ProcessEnv)[key];
}
const opts = envOrOptions as ResolveOptions;
switch (key) {
case "STELLAR_NETWORK":
return opts.network;
case "STELLAR_PASSPHRASE":
return opts.passphrase;
case "STELLAR_HORIZON_URL":
return opts.horizonUrl;
case "SOROBAN_RPC_URL":
return opts.sorobanRpcUrl;
default:
return undefined;
}
};

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 | 🟡 Minor | ⚡ Quick win

Don't distinguish env vs options from STELLAR_NETWORK alone.

A sparse env object like { STELLAR_PASSPHRASE: "..." } currently goes down the ResolveOptions branch, so every env-style key is ignored and the resolver silently falls back to the default testnet config. Since this API explicitly accepts NodeJS.ProcessEnv, partial env snapshots need to be handled correctly too.

Suggested fix
 export function resolveAndValidateStellarConfig(
   envOrOptions: NodeJS.ProcessEnv | ResolveOptions = process.env
 ): StellarConfig {
-  const isOptions = !("STELLAR_NETWORK" in envOrOptions);
+  const isProcessEnv =
+    "STELLAR_NETWORK" in envOrOptions ||
+    "STELLAR_PASSPHRASE" in envOrOptions ||
+    "STELLAR_HORIZON_URL" in envOrOptions ||
+    "SOROBAN_RPC_URL" in envOrOptions;
   const get = (key: string): string | undefined => {
-    if (!isOptions) {
+    if (isProcessEnv) {
       return (envOrOptions as NodeJS.ProcessEnv)[key];
     }
📝 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
export function resolveAndValidateStellarConfig(
envOrOptions: NodeJS.ProcessEnv | ResolveOptions = process.env
): StellarConfig {
const isOptions = !("STELLAR_NETWORK" in envOrOptions);
const get = (key: string): string | undefined => {
if (!isOptions) {
return (envOrOptions as NodeJS.ProcessEnv)[key];
}
const opts = envOrOptions as ResolveOptions;
switch (key) {
case "STELLAR_NETWORK":
return opts.network;
case "STELLAR_PASSPHRASE":
return opts.passphrase;
case "STELLAR_HORIZON_URL":
return opts.horizonUrl;
case "SOROBAN_RPC_URL":
return opts.sorobanRpcUrl;
default:
return undefined;
}
};
export function resolveAndValidateStellarConfig(
envOrOptions: NodeJS.ProcessEnv | ResolveOptions = process.env
): StellarConfig {
const isProcessEnv =
"STELLAR_NETWORK" in envOrOptions ||
"STELLAR_PASSPHRASE" in envOrOptions ||
"STELLAR_HORIZON_URL" in envOrOptions ||
"SOROBAN_RPC_URL" in envOrOptions;
const get = (key: string): string | undefined => {
if (isProcessEnv) {
return (envOrOptions as NodeJS.ProcessEnv)[key];
}
const opts = envOrOptions as ResolveOptions;
switch (key) {
case "STELLAR_NETWORK":
return opts.network;
case "STELLAR_PASSPHRASE":
return opts.passphrase;
case "STELLAR_HORIZON_URL":
return opts.horizonUrl;
case "SOROBAN_RPC_URL":
return opts.sorobanRpcUrl;
default:
return undefined;
}
};
🤖 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/backend/wallet/src/stellarConfig.ts` around lines 111 - 132, The
`resolveAndValidateStellarConfig` helper is detecting `ResolveOptions` vs
`NodeJS.ProcessEnv` by checking only `STELLAR_NETWORK`, which breaks sparse env
snapshots and can cause env keys to be ignored. Update the branch detection
logic in `resolveAndValidateStellarConfig` so partial `ProcessEnv` objects are
still treated as env input, and keep `get` resolving env-style keys correctly
for `STELLAR_NETWORK`, `STELLAR_PASSPHRASE`, `STELLAR_HORIZON_URL`, and
`SOROBAN_RPC_URL`. Ensure the fallback to defaults only happens when the
relevant env values are truly absent, not because the object lacks
`STELLAR_NETWORK`.

Comment on lines +190 to +198
resolvedNetwork = "custom";
}

return {
network: resolvedNetwork,
networkPassphrase: trimmedPassphrase,
horizonUrl: explicitHorizon || defaults.horizon,
sorobanRpcUrl: explicitRpc || defaults.rpc,
};

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

Preserve the selected known network for custom passphrases.

Once this collapses to network: "custom", downstream callers lose whether the operator selected testnet, mainnet, or futurenet. apps/backend/wallet/stellar/account.ts is already forced to guess and hard-code "testnet" for every custom deployment, which will misreport custom mainnet/futurenet setups to API consumers. Keep the requested known network alongside the resolved one so callers can expose a truthful fallback.

Suggested direction
 export interface StellarConfig {
+  requestedNetwork: KnownStellarNetwork;
   network: ResolvedStellarNetwork;
   networkPassphrase: string;
   horizonUrl: string;
   sorobanRpcUrl: string;
 }
 ...
     return {
+      requestedNetwork: networkName,
       network: resolvedNetwork,
       networkPassphrase: trimmedPassphrase,
       horizonUrl: explicitHorizon || defaults.horizon,
       sorobanRpcUrl: explicitRpc || defaults.rpc,
     };
 ...
   return {
+    requestedNetwork: networkName,
     network: networkName,
     networkPassphrase: NETWORK_TO_PASSPHRASE[networkName],
     horizonUrl: explicitHorizon || defaults.horizon,
     sorobanRpcUrl: explicitRpc || defaults.rpc,
   };
📝 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
resolvedNetwork = "custom";
}
return {
network: resolvedNetwork,
networkPassphrase: trimmedPassphrase,
horizonUrl: explicitHorizon || defaults.horizon,
sorobanRpcUrl: explicitRpc || defaults.rpc,
};
resolvedNetwork = "custom";
}
return {
requestedNetwork: networkName,
network: resolvedNetwork,
networkPassphrase: trimmedPassphrase,
horizonUrl: explicitHorizon || defaults.horizon,
sorobanRpcUrl: explicitRpc || defaults.rpc,
};
🤖 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/backend/wallet/src/stellarConfig.ts` around lines 190 - 198, The
stellarConfig resolution currently collapses custom passphrases to network:
"custom", which drops the originally selected known network and forces
downstream code like account.ts to guess. Update stellarConfig.ts to retain the
requested known network alongside the resolved custom network (for example by
returning both the canonical network and the selected fallback/original
network), then adjust the account.ts lookup logic to use that preserved value
instead of hard-coding "testnet" for all custom deployments.

@ScriptedBro

Copy link
Copy Markdown
Contributor

Please resolve conflicts

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] Add Stellar Network Passphrase Validation

2 participants