feat(web/env): add NEXT_PUBLIC_SOROBAN_RPC_HEADER environment variable support (#391) - #499
Conversation
…e support (#391) - Support optional NEXT_PUBLIC_SOROBAN_RPC_HEADER environment variable in Zod validation schema and environment exports. - Update getStellarServerOptions helper to parse JSON strings, Key: Value format, and raw authorization token strings into HTTP headers. - Pass config.network.rpcHeader to RpcServer and Horizon.Server instances in StellarService. - Document NEXT_PUBLIC_SOROBAN_RPC_HEADER in .env.example. - Remove duplicate @fundable/sdk key in �pps/web/package.json. - Add unit test coverage for env validation and RPC connection options header parsing.
|
@OluwapelumiElisha 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! 🚀 |
📝 WalkthroughWalkthroughAdds optional Soroban RPC header configuration, validates and exposes it through the web environment, parses supported header formats, and passes the resulting options to Soroban RPC and Horizon clients. ChangesRPC header support
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant process.env
participant validateEnv
participant StellarService
participant getStellarServerOptions
participant RpcServer
process.env->>validateEnv: NEXT_PUBLIC_SOROBAN_RPC_HEADER
validateEnv->>StellarService: validated rpcHeader
StellarService->>getStellarServerOptions: URL and rpcHeader
getStellarServerOptions-->>StellarService: allowHttp and headers
StellarService->>RpcServer: client options
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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 `@apps/web/.env.example`:
- Around line 22-23: Remove the private bearer-token example from
NEXT_PUBLIC_SOROBAN_RPC_HEADER in the environment template. Document only
browser-safe RPC header usage there, and if private RPC authentication is
supported, introduce a server-only environment variable and configure the
server-side RPC proxy to add the authorization header before documenting that
pattern.
In `@apps/web/src/lib/constants.ts`:
- Line 6: Update every StellarService construction path to pass the configured
SOROBAN_RPC_HEADER through the network configuration as rpcHeader, ensuring
getStellarServerOptions receives it. Reuse the existing SOROBAN_RPC_HEADER
constant and preserve all other Stellar client options.
In `@apps/web/src/services/stellar.service.ts`:
- Around line 88-90: The StellarService Horizon.Server initialization must stop
forwarding the RPC header by default. Update the Horizon options around
getStellarServerOptions so RPC credentials are omitted unless horizonUrl is
explicitly the same trusted provider as rpcUrl, or use a dedicated Horizon
header configuration; keep the shared header available for RpcServer.
In `@apps/web/src/utils/rpc-connection-options.ts`:
- Around line 26-31: Update the JSON parsing logic in the rpc connection options
construction to assign options.headers only when the parsed object’s values are
all strings. Explicitly handle invalid JSON or non-string header values through
the existing configuration-error path, rather than passing invalid headers to
the SDK.
🪄 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 Plus
Run ID: 98c326c8-c4f6-41a7-aefd-654282711d65
📒 Files selected for processing (9)
apps/web/.env.exampleapps/web/package.jsonapps/web/src/lib/__tests__/env.test.tsapps/web/src/lib/constants.tsapps/web/src/lib/env.tsapps/web/src/services/stellar.service.tsapps/web/src/services/types.tsapps/web/src/utils/__tests__/rpc-connection-options.test.tsapps/web/src/utils/rpc-connection-options.ts
💤 Files with no reviewable changes (1)
- apps/web/package.json
| # Optional: Authentication header for private RPC endpoint (e.g., "Authorization: Bearer xxx" or JSON) | ||
| NEXT_PUBLIC_SOROBAN_RPC_HEADER= |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate env.example =="
fd -a '.env.example$' . | sed 's#^\./##'
echo "== apps/web/.env.example lines 1-40 =="
if [ -f apps/web/.env.example ]; then
sed -n '1,60p' apps/web/.env.example | nl -ba
fi
echo "== references to NEXT_PUBLIC_SOROBAN_RPC_HEADER =="
rg -n "NEXT_PUBLIC_SOROBAN_RPC_HEADER|SOROBAN_RPC_HEADER|Authorization: Bearer" . || true
echo "== package/version hints =="
fd 'package.json$|pnpm-lock.yaml$|package-lock.json$|yarn.lock$' . -x sh -c 'echo "--- $1"; sed -n "1,120p" "$1"' sh {} | sed -n '1,220p'Repository: Fundable-Protocol/stellar_client_os
Length of output: 277
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate env.example =="
find . -maxdepth 6 -type f -name '.env.example' -print 2>/dev/null
echo "== apps/web/.env.example lines 1-60 =="
if [ -f apps/web/.env.example ]; then
awk '{printf "%6d\t%s\n", NR, $0}' apps/web/.env.example | sed -n '1,60p'
fi
echo "== references to NEXT_PUBLIC_SOROBAN_RPC_HEADER =="
grep -RIn "NEXT_PUBLIC_SOROBAN_RPC_HEADER|SOROBAN_RPC_HEADER|Authorization: Bearer" . || true
echo "== package/version hints =="
find . -maxdepth 4 \( -name 'package.json' -o -name 'pnpm-lock.yaml' -o -name 'package-lock.json' -o -name 'yarn.lock' \) -print 2>/dev/null | while read -r f; do
echo "--- $f"
sed -n '1,120p' "$f"
doneRepository: Fundable-Protocol/stellar_client_os
Length of output: 18140
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== app files mentioning Soroban RPC variables =="
find apps packages -maxdepth 6 \( -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' \) \) -print 2>/dev/null | while read -r f; do
if grep -RIn "NEXT_PUBLIC_SOROBAN_RPC_HEADER\|process\.env\..*SOROBAN_RPC_HEADER\|SOROBAN_RPC_HEADER\|Authorization|RpcProvider|soroban_rpc" "$f" >/dev/null 2>&1; then
echo "--- $f"
grep -n "NEXT_PUBLIC_SOROBAN_RPC_HEADER\|process\.env\..*SOROBAN_RPC_HEADER\|SOROBAN_RPC_HEADER\|Authorization\|RpcProvider\|soroban_rpc" "$f" || true
fi
done
echo "== Next environment variable docs from CDN/package if present =="
python3 - <<'PY'
print("Next environment variables are documented at nextjs.org/docs/guides/environment-variables/#node-environment-variables")
print("Variables starting with NEXT_PUBLIC_ at build time are exposed to the browser bundle.")
PYRepository: Fundable-Protocol/stellar_client_os
Length of output: 1346
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== env.ts relevant sections =="
awk '{printf "%6d\t%s\n", NR, $0}' apps/web/src/lib/env.ts | sed -n '1,180p'
echo "== constants/env usage references =="
rg -n "SOROBAN_RPC_HEADER|NEXT_PUBLIC_SOROBAN_RPC_HEADER|process\.env\." apps/web/src packages apps/web -g '*.ts' -g '*.tsx' || true
echo "== test file context =="
awk '{printf "%6d\t%s\n", NR, $0}' apps/web/src/lib/__tests__/env.test.ts | sed -n '1,90p'Repository: Fundable-Protocol/stellar_client_os
Length of output: 24228
Do not put RPC bearer tokens into NEXT_PUBLIC_SOROBAN_RPC_HEADER.
NEXT_PUBLIC_* values are exposed to the browser, so a value like Authorization: Bearer xxx can be recovered and replayed by any user. For private RPC auth, place the service token in a server-only env variable and add the header through a server-side RPC proxy before documenting that pattern here.
🤖 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/web/.env.example` around lines 22 - 23, Remove the private bearer-token
example from NEXT_PUBLIC_SOROBAN_RPC_HEADER in the environment template.
Document only browser-safe RPC header usage there, and if private RPC
authentication is supported, introduce a server-only environment variable and
configure the server-side RPC proxy to add the authorization header before
documenting that pattern.
| export const PAYMENT_STREAM_CONTRACT_ID = env.NEXT_PUBLIC_PAYMENT_STREAM_CONTRACT_ID; | ||
| export const DISTRIBUTOR_CONTRACT_ID = env.NEXT_PUBLIC_DISTRIBUTOR_CONTRACT_ID; | ||
| export const SOROBAN_RPC_URL = env.NEXT_PUBLIC_SOROBAN_RPC_URL; | ||
| export const SOROBAN_RPC_HEADER = env.NEXT_PUBLIC_SOROBAN_RPC_HEADER; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -nP '\bSOROBAN_RPC_HEADER\b|\brpcHeader\s*:' apps/web/srcRepository: Fundable-Protocol/stellar_client_os
Length of output: 275
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Tracked React files (limited):\n'
git ls-files 'apps/web/src/*' 'apps/web/src/lib/*' | sed -n '1,200p'
printf '\nSearch Stellar/Server config/network terms:\n'
rg -n "Stellar|stellar|network|client|rpcHeader|SOROBAN_RPC|NEXT_PUBLIC" apps/web/src || true
printf '\nFile list including constants.ts around libs:\n'
fd -a 'constants\.ts|.*config' apps/web/src | sed -n '1,200p'Repository: Fundable-Protocol/stellar_client_os
Length of output: 50391
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'StellarService relevant ranges:\n'
sed -n '70,95p;120,130p' apps/web/src/services/stellar.service.ts
printf '\nStellarService factories relevant ranges:\n'
sed -n '940,985p' apps/web/src/services/stellar.service.ts
printf '\nlib/api.ts StellarService construction:\n'
sed -n '1,35p' apps/web/src/lib/api.ts
printf '\nutils rpc-connection-options.ts:\n'
cat -n apps/web/src/utils/rpc-connection-options.ts
printf '\nAll imports/usages of SOROBAN_RPC_HEADER:\n'
rg -n "SOROBAN_RPC_HEADER|NEXT_PUBLIC_SOROBAN_RPC_HEADER" apps/web/srcRepository: Fundable-Protocol/stellar_client_os
Length of output: 7094
Wire SOROBAN_RPC_HEADER into Stellar client config.
SOROBAN_RPC_HEADER is only validated/exported; every StellarService construction path omits network.rpcHeader, so the header is accepted but never passed into getStellarServerOptions.
🤖 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/web/src/lib/constants.ts` at line 6, Update every StellarService
construction path to pass the configured SOROBAN_RPC_HEADER through the network
configuration as rpcHeader, ensuring getStellarServerOptions receives it. Reuse
the existing SOROBAN_RPC_HEADER constant and preserve all other Stellar client
options.
| this.horizonServer = new Horizon.Server( | ||
| config.network.horizonUrl, | ||
| getStellarServerOptions(config.network.horizonUrl) | ||
| getStellarServerOptions(config.network.horizonUrl, config.network.rpcHeader) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate stellar.service.ts and config references =="
fd -a 'stellar\.service\.ts$' . || true
rg -n "getStellarServerOptions|Horizon\.Server|horizonUrl|rpcUrl|rpcHeader|stellar" . -S --glob '!node_modules' --glob '!dist' --glob '!build' | head -200
echo
echo "== file outline =="
file="$(fd 'stellar\.service\.ts$' . | head -1)"
if [ -n "$file" ]; then
ast-grep outline "$file" || true
echo "== file content lines 1-180 =="
sed -n '1,180p' "$file" | nl -ba
fi
echo
echo "== configs mentioning horizon/rpc =="
rg -n "horizonUrl|rpcUrl|rpcHeader|stellar" . --glob '!node_modules' --glob '!dist' --glob '!build' -S | head -300Repository: Fundable-Protocol/stellar_client_os
Length of output: 21219
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== stellar.service constructor =="
sed -n '70,100p' apps/web/src/services/stellar.service.ts
echo
echo "== rpc-connection-options file =="
file="$(fd 'rpc-connection-options\.(ts|tsx|js)$' apps/web/src | head -1)"
echo "FILE=$file"
sed -n '1,220p' "$file" | sed -n '1,220p'
echo
echo "== relevant tests around shared headers =="
rg -n "rpcHeader|horizonServer|RpcServer|getStellarServerOptions|horizonUrl|rpcUrl" apps/web/src/services -S
sed -n '70,240p' apps/web/src/services/stellar.service.edge.test.ts
echo
echo "== package SDK versions =="
rg -n '"`@stellar/stellar-sdk`"|`@stellar/stellar-sdk`@14' package.json apps/web/package.json packages/sdk/package.json pnpm-lock.yaml scratch/package.json scratch/package-lock.json -SRepository: Fundable-Protocol/stellar_client_os
Length of output: 14771
Do not forward rpcHeader to Horizon.
getStellarServerOptions returns the parsed/normalized header whenever it is present, and StellarService passes it into both RpcServer and Horizon.Server. Since rpcUrl and horizonUrl are independent, a bearer token or custom RPC header can be sent to Horizon on every Horizon request. Use a separate Horizon header, or only attach the shared header when the Horizon endpoint is explicitly the same trusted provider as the configured 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/web/src/services/stellar.service.ts` around lines 88 - 90, The
StellarService Horizon.Server initialization must stop forwarding the RPC header
by default. Update the Horizon options around getStellarServerOptions so RPC
credentials are omitted unless horizonUrl is explicitly the same trusted
provider as rpcUrl, or use a dedicated Horizon header configuration; keep the
shared header available for RpcServer.
| if (rpcHeader && rpcHeader.trim() !== '') { | ||
| try { | ||
| const parsed = JSON.parse(rpcHeader); | ||
| if (typeof parsed === 'object' && parsed !== null) { | ||
| options.headers = parsed; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file context =="
cat -n apps/web/src/utils/rpc-connection-options.ts | sed -n '1,80p'
echo
echo "== declarations/usages =="
rg -n "rpcConnectionOptions|RPC_CONNECTION_OPTIONS|headers:" apps/web/src -S || true
echo
echo "== TypeScript config relevant =="
cat -n tsconfig*.json 2>/dev/null | sed -n '1,120p' || true
test -f apps/web/tsconfig.app.json && cat -n apps/web/tsconfig.app.json | sed -n '1,120p' || true
echo
echo "== package versions =="
if [ -f package.json ]; then cat package.json | jq '.dependencies, .devDependencies' 2>/dev/null | sed -n '1,160p'; fi
if [ -f apps/web/package.json ]; then cat apps/web/package.json | jq '.dependencies, .devDependencies' 2>/dev/null | sed -n '1,160p'; fiRepository: Fundable-Protocol/stellar_client_os
Length of output: 4565
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== exact usages of getStellarServerOptions =="
rg -n "getStellarServerOptions|rpcHeader" . -S || true
echo
echo "== local TypeScript availability =="
command -v tsc || true
if command -v tsc >/dev/null; then
tsc --version
fi
echo
echo "== standalone TypeScript-like behavior probe using Node JSON parsing =="
node - <<'JS'
const inputs = [
'{"Accept":["application/json"]}',
'{"X-Num":42}',
'{}',
'[]',
'null',
'""',
'{"Key":"Bearer x"}'
];
for (const rpcHeader of inputs) {
let parsed;
try {
parsed = JSON.parse(rpcHeader);
} catch (e) {
parsed = `<parse-error:${e.code}>`;
}
const current = typeof parsed === 'object' && parsed !== null;
const proposed = parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Object.values(parsed).every(v => typeof v === 'string');
console.log(JSON.stringify({ rpcHeader, parsed: parsed, currentAssigns: current, proposedAssigns: proposed }));
}
JS
echo
echo "== fetch header validation behavior probe =="
node - <<'JS'
const inputs = {
acceptArray: { Accept: ['application/json'] },
xNum: { 'X-Num': 42 },
object: {},
array: [],
null: null,
string: 'Authorization: token'
};
for (const [name, parsed] of Object.entries(inputs)) {
try {
const req = new Request('http://localhost', { headers: parsed });
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
console.log(name + ' object: ' + JSON.stringify([...req.headers.entries()]));
} else {
console.log(name + ': ' + String(req.headers));
}
} catch (e) {
console.log(name + ': ' + e.name + ': ' + e.message);
}
}
JS
echo
echo "== `@stellar/stellar-sdk` local header type if available =="
if command -v tsc >/dev/null && node -e "require.resolve('`@stellar/stellar-sdk`')" >/tmp/stellar-sdk-res.txt 2>&1; then
cat /tmp/stellar-sdk-res.txt
rg -n "headers\\?:\\s*Record<string, string>|headers.*Record<string, string>|ServerOptions" node_modules/@stellar/stellar-sdk -S | head -n 30 || true
fiRepository: Fundable-Protocol/stellar_client_os
Length of output: 4599
🌐 Web query:
@stellar/stellar-sdk RpcServer options headers Record<string, string>
💡 Result:
In the @stellar/stellar-sdk library, the RpcServer constructor accepts an optional configuration object which includes a headers property [1][2]. This property is defined as a Record<string, string>, allowing you to specify additional HTTP headers to be included with all requests made to the Soroban RPC server [1][3][4]. The interface for these options is as follows: interface Options { allowHttp?: boolean; timeout?: number; headers?: Record<string, string>; } You can provide these headers when initializing the RpcServer [3][5]: const server = new RpcServer('https://your-rpc-url', { headers: { 'Authorization': 'Bearer your-token', 'X-Custom-Header': 'value' } }); Additionally, because the RpcServer exposes an httpClient property (which uses axios under the hood), you can also modify headers dynamically or add interceptors after the server instance has been created [1][2][4]: // Adding a default header server.httpClient.defaults.headers['Authorization'] = 'Bearer token'; // Using an interceptor server.httpClient.interceptors.request.use((config) => { console.log('Request:', config.url); return config; });
Citations:
- 1: https://github.com/stellar/js-stellar-sdk/blob/main/src/rpc/server.ts
- 2: https://cdn.jsdelivr.net/npm/@stellar/stellar-sdk@16.1.0/lib/esm/rpc/server.d.ts
- 3: https://github.com/stellar/js-stellar-sdk/blob/2f52d0e7/src/rpc/server.ts
- 4: https://deepwiki.com/stellar/js-stellar-sdk/4.1-rpc-server-connection
- 5: Respect custom headers when using
rpc.Serverstellar/js-stellar-sdk#988
Validate JSON header values before assigning them.
Valid JSON such as {"X-Num": 42} or {"Accept": ["application/json"]} currently satisfies this guard, so the SDK receives a non-string header value. Only assign objects whose values are strings, and handle invalid configs explicitly instead of passing them on.
🤖 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/web/src/utils/rpc-connection-options.ts` around lines 26 - 31, Update
the JSON parsing logic in the rpc connection options construction to assign
options.headers only when the parsed object’s values are all strings. Explicitly
handle invalid JSON or non-string header values through the existing
configuration-error path, rather than passing invalid headers to the SDK.
|
dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates |
2 similar comments
|
dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates |
|
dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates |
Summary
Fixes #391.
This PR adds support for passing authentication headers to private/protected Soroban RPC and Horizon endpoints via the optional
NEXT_PUBLIC_SOROBAN_RPC_HEADERenvironment variable.Changes Made
⚙️ Environment & Configuration
NEXT_PUBLIC_SOROBAN_RPC_HEADER: z.string().optional().or(z.literal(""))toenvSchema, mapped it invalidateEnv(), and added fallback handling for test mode.SOROBAN_RPC_HEADER = env.NEXT_PUBLIC_SOROBAN_RPC_HEADER.🌐 RPC & Service Layer
getStellarServerOptionsto accept an optionalrpcHeader?: stringand parse:{"Authorization": "Bearer token", "X-Custom-Header": "value"}Header-Name: valuestrings:"Authorization: Bearer token""Bearer token"(defaults key toAuthorization)rpcHeader?: stringproperty toNetworkConfig.config.network.rpcHeadertogetStellarServerOptionswhen initializingRpcServerandHorizon.Server.🧹 Cleanup
@fundable/sdkkey underdependencies.🧪 Tests
NEXT_PUBLIC_SOROBAN_RPC_HEADER.How to Test
NEXT_PUBLIC_SOROBAN_RPC_HEADER="Authorization: Bearer your_token_here"to.env.local.pnpm --filter @fundable/web test.Summary by CodeRabbit
New Features
Header: Value, or token formats.Tests