Integrate Tor/Orbot to wallet for indexer requests#88
Conversation
There was a problem hiding this comment.
Pull request overview
Integrates Tor/Orbot support so Silent Payments indexer requests can be routed via an Orbot-provided SOCKS5 proxy (with optional Tor-only mode), and adds UI + settings plumbing to control it from the app.
Changes:
- Added a Tor settings screen (enable/disable, Tor-only, test connection, advanced SOCKS5 port, Orbot install/status).
- Added Tor-aware routing to the Silent Payments indexer HTTP client with onion URL support and clearnet fallback behavior.
- Wired Tor settings into app navigation and settings context, plus Android package visibility for Orbot detection.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| screen/settings/TorSettings.tsx | New Tor settings UI (enable, Tor-only, status, test, port, Orbot install). |
| screen/settings/Settings.tsx | Re-enables entry point to Network settings. |
| screen/settings/NetworkSettings.tsx | Adds navigation entry to Tor settings. |
| navigation/DetailViewStackParamList.ts | Adds TorSettings route type. |
| navigation/DetailViewScreensStack.tsx | Registers TorSettings screen in stack navigator. |
| loc/en.json | Adds Tor/Orbot-related localized strings. |
| helpers/silent-payments/types.ts | Extends indexer config to accept optional onionUrl. |
| helpers/silent-payments/IndexerHttpClient.ts | Tries onion over SOCKS5 first, with retries and Tor-only enforcement. |
| components/Context/SettingsProvider.tsx | Loads/persists Tor settings and exposes them via Settings context. |
| blue_modules/torManager.ts | Stores Tor settings, checks proxy availability, exposes status/listeners, Orbot utilities. |
| blue_modules/socks5Fetch.ts | Implements basic HTTP-over-SOCKS5 request helper. |
| blue_modules/SilentPaymentIndexer.ts | Passes onionUrl into the HTTP client. |
| android/app/src/main/AndroidManifest.xml | Adds Orbot package visibility query for Android 11+. |
| App.tsx | Initializes indexer with an onion URL. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
test plan should also include, detecting a payment, without this we really can't confirm that the integration actually works. we can just use any past txn, and update the filterSpent to false and just check for the txn to show up. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| initializeIndexer({ | ||
| baseUrl: 'https://superparamount-kendal-halting.ngrok-free.dev/', | ||
| onionUrl: 'http://azzaasniov2hjxrkhpvjyse3a4jaww74n76umntatcerp4drzwotk5yd.onion', | ||
| timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) | ||
| }); | ||
|
|
There was a problem hiding this comment.
initializeIndexer() is configured with hard-coded endpoints (a specific ngrok baseUrl and a specific .onion URL). If this is not intended to ship, please move these to build-time config / env (or a constants file for dev only) to avoid accidentally releasing a build pointed at ephemeral or environment-specific infrastructure.
| initializeIndexer({ | |
| baseUrl: 'https://superparamount-kendal-halting.ngrok-free.dev/', | |
| onionUrl: 'http://azzaasniov2hjxrkhpvjyse3a4jaww74n76umntatcerp4drzwotk5yd.onion', | |
| timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) | |
| }); | |
| const indexerBaseUrl = typeof process?.env?.INDEXER_BASE_URL === 'string' ? process.env.INDEXER_BASE_URL : ''; | |
| const indexerOnionUrl = typeof process?.env?.INDEXER_ONION_URL === 'string' ? process.env.INDEXER_ONION_URL : ''; | |
| if (indexerBaseUrl && indexerOnionUrl) { | |
| initializeIndexer({ | |
| baseUrl: indexerBaseUrl, | |
| onionUrl: indexerOnionUrl, | |
| timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) | |
| }); | |
| } |
There was a problem hiding this comment.
pre-existing pattern. Moving to env-variable config makes sense but is out of this PR's scope.
cc @theanmolsharma Should this be taken care of, in this PR or in a follow up issue?
There was a problem hiding this comment.
Lets do it in a followup PR
|
Tor status isn't downgraded when
Range scans issue many requests back-to-back, so during a Tor outage the user can sit on "scanning" for a long time per range with no signal that Tor is the bottleneck. Suggested fixes (any one):
Option 1 is probably the smallest change and keeps the status indicator in the UI honest. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| finalize(pending[0] === 0x05 && pending[1] === 0x00); | ||
| } | ||
| }); | ||
|
|
There was a problem hiding this comment.
_testSocksProxy() only resolves on data (or the 5s timeout / error). If the TCP connection closes cleanly before any data is received (or after sending a partial reply), this promise will hang until the timeout. Add a close/end handler that finalizes with false to make status checks respond promptly and avoid unnecessary 5s delays.
| client.on('end', () => finalize(false)); | |
| client.on('close', () => finalize(false)); |
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } |
There was a problem hiding this comment.
In the Tor retry loop, any non-2xx HTTP response is thrown and treated as a Tor failure. That means a valid onion response like 404/500 will trigger retries, call torManager.markUnavailable(), and potentially fall back to clearnet (or show a misleading “Tor unavailable” error in Tor-only). Consider treating an HTTP response as “Tor reachable” and: (a) don’t mark Tor unavailable, and (b) avoid retrying/falling back on HTTP status errors unless you explicitly want that behavior (typically only network/proxy errors should drive Tor-unavailable state).
| this._settings = { | ||
| enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : DEFAULT_SETTINGS.enabled, | ||
| torOnly: typeof parsed.torOnly === 'boolean' ? parsed.torOnly : DEFAULT_SETTINGS.torOnly, |
There was a problem hiding this comment.
loadSettings() can hydrate torOnly: true even when enabled: false (e.g., from older/hand-edited storage). TorManager.isTorOnly correctly gates on enabled, but SettingsProvider/UI also read the raw torOnly setting, which can lead to confusing state (warning shown while Tor disabled, Tor-only preselected when re-enabling). Consider normalizing on load (force torOnly = false when enabled is false) or returning a normalized settings object.
| this._settings = { | |
| enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : DEFAULT_SETTINGS.enabled, | |
| torOnly: typeof parsed.torOnly === 'boolean' ? parsed.torOnly : DEFAULT_SETTINGS.torOnly, | |
| const enabled = typeof parsed.enabled === 'boolean' ? parsed.enabled : DEFAULT_SETTINGS.enabled; | |
| const torOnly = enabled && (typeof parsed.torOnly === 'boolean' ? parsed.torOnly : DEFAULT_SETTINGS.torOnly); | |
| this._settings = { | |
| enabled, | |
| torOnly, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
users should not be able to enable tor, unless, they have orbot installed. (now, im able to enable it even when orbot is uninstalled, although the status is notActive) |
|
got this error while testing the app, |
|
|
||
| <TouchableOpacity onPress={() => setShowAdvanced(s => !s)}> | ||
| <BlueText style={styles.advancedToggle}> | ||
| {showAdvanced ? '▼ ' : '▸ '} |
There was a problem hiding this comment.
sizes should be same imo.
this should be implemented in a better way, even i had a hard time finding it.
| useEffect(() => { | ||
| const check = () => { | ||
| TorManager.isOrbotInstalled() | ||
| .then(setOrbotInstalled) | ||
| .catch(() => setOrbotInstalled(null)); | ||
| }; | ||
| check(); | ||
| const sub = AppState.addEventListener('change', state => { | ||
| if (state === 'active') check(); | ||
| }); | ||
| return () => sub.remove(); | ||
| }, []); |
There was a problem hiding this comment.
TorManager.isOrbotInstalled() is only checked once on mount. If the user taps “Install Orbot”, installs it, and returns to this screen without remounting, the UI will continue to show the old install status. Consider re-checking on screen focus (e.g. useFocusEffect) or on app foregrounding (AppState) so the status row reflects changes.
exp3rimenter
left a comment
There was a problem hiding this comment.
Hi, new here to this project. Not sure about the changes but can you guys please don't add Claude as the co-author as this sets a bad impression for external users. New users might not use the vibe coded wallet. The only thing that needs to be done is just don't let Claude code to commit/push or even if you do then give instructions to not add Claude as the co-author.
Cheers,
experimenter
Hey, welcome to the project — but I'm going to push back here. The position you're describing isn't "don't use AI," it's "use AI but strip the attribution so external users don't notice." That's the worst option IMHO. If a tool meaningfully contributed to a commit, the AI-assisted development is the norm in 2026. Devs who aren't using it are slower, and devs who use it but pretend they don't are just the deceptive subset of the same group. I'd rather be in neither bucket. This PR has been through several rounds of substantive review — there are concrete code-level discussions in the thread. If you have feedback on the actual changes, very happy to engage with that. "Vibe coded" without pointing at any specific code isn't really actionable. Cheers |
| initializeIndexer({ | ||
| baseUrl: 'https://superparamount-kendal-halting.ngrok-free.dev/', | ||
| onionUrl: 'http://azzaasniov2hjxrkhpvjyse3a4jaww74n76umntatcerp4drzwotk5yd.onion', | ||
| timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) | ||
| }); |
| "tor_orbot_label": "Orbot: ", | ||
| "tor_orbot_installed": "Installed", | ||
| "tor_orbot_not_installed": "Not Installed", | ||
| "tor_status_label": "Status: ", |
| private _testSocksProxy(): Promise<boolean> { | ||
| return new Promise(resolve => { | ||
| let resolved = false; | ||
| let pending = Buffer.alloc(0); |
|
|
||
| const client = TcpSocket.createConnection({ host: DEFAULT_SOCKS_HOST, port: this._settings.socksPort }, () => { | ||
| // Send SOCKS5 greeting: version 5, 1 method, no-auth | ||
| const greeting = Buffer.from([0x05, 0x01, 0x00]); |
| async setSocksPort(port: number): Promise<void> { | ||
| await this.saveSettings({ socksPort: port }); | ||
| if (this._settings.enabled) { | ||
| await this.checkConnection(); | ||
| } | ||
| } |
| status, | ||
| statusText, | ||
| headers: responseHeaders, | ||
| json: async () => JSON.parse(responseBody), |
- IndexerHttpClient: wrap response.json() in try/catch so JSON parse failures retry with backoff instead of escaping the Tor loop - TorSettings: disable Tor toggle on Android when Orbot is not installed - TorSettings: replace useEffect with useFocusEffect for Orbot installed check so it re-runs when navigating back to the settings screen - socks5Fetch: add separate connectTimeout (10s default) for the SOCKS5 handshake phase; swap to full request timeout once tunnel is established
There was a problem hiding this comment.
we should use Uint8Array instead of Buffer
This PR integrates Tor/Orbot to wallet for routing indexer requests
Test plan
Tor-Only auto-clears on disable, requires deliberate re-opt-in
lint checkunit and integration test checkLogs
Made a txn and detected it in tor-only mode