Skip to content

Integrate Tor/Orbot to wallet for indexer requests#88

Open
chaitika wants to merge 14 commits into
CypherCommons:masterfrom
chaitika:orbot
Open

Integrate Tor/Orbot to wallet for indexer requests#88
chaitika wants to merge 14 commits into
CypherCommons:masterfrom
chaitika:orbot

Conversation

@chaitika
Copy link
Copy Markdown
Contributor

@chaitika chaitika commented Apr 23, 2026

This PR integrates Tor/Orbot to wallet for routing indexer requests

Test plan

  • Restore a wallet with seed + birth height while Tor is disabled
  • Toggle Tor on → off → on
    Tor-Only auto-clears on disable, requires deliberate re-opt-in
  • With Orbot missing, Install Orbot opens the direct APK download
  • With Orbot installed, status row reads "Orbot: Installed"; Test Connection probes and updates status
  • Advanced disclosure hides SOCKS5 Port details
  • lint check
  • unit and integration test check
  • Detect a payment

Logs

WhatsApp Image 2026-04-23 at 7 27 27 PM WhatsApp Image 2026-04-23 at 7 27 26 PM WhatsApp Image 2026-04-23 at 7 27 25 PM (1) WhatsApp Image 2026-04-23 at 7 27 25 PM WhatsApp Image 2026-04-23 at 7 27 24 PM WhatsApp Image 2026-04-23 at 7 27 23 PM WhatsApp Image 2026-04-23 at 7 27 22 PM WhatsApp Image 2026-04-23 at 7 27 20 PM

Made a txn and detected it in tor-only mode

log-45 log-45

Copilot AI review requested due to automatic review settings April 23, 2026 14:56
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread blue_modules/socks5Fetch.ts Outdated
Comment thread App.tsx Outdated
Comment thread blue_modules/socks5Fetch.ts Outdated
Comment thread screen/settings/NetworkSettings.tsx Outdated
Comment thread screen/settings/TorSettings.tsx Outdated
@notTanveer
Copy link
Copy Markdown
Contributor

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.
also add this ss in the PR description

Copilot AI review requested due to automatic review settings April 24, 2026 09:14
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread loc/en.json
Comment thread App.tsx
Comment on lines 16 to 21
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)
});

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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)
});
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Lets do it in a followup PR

Comment thread helpers/silent-payments/IndexerHttpClient.ts
Comment thread blue_modules/torManager.ts
Comment thread blue_modules/torManager.ts
@theanmolsharma
Copy link
Copy Markdown
Collaborator

Tor status isn't downgraded when socks5Fetch fails — every subsequent request re-runs the full retry/backoff budget.

TorManager._status only transitions via loadSettings / setEnabled / setSocksPort / checkConnection. A failed request never feeds back into the manager, so if Orbot dies mid-session while _status === 'connected', the gate at IndexerHttpClient.ts:23 keeps entering the retry loop on every call:

  • RETRY_ATTEMPTS = 3 × timeout = 100_000 ms (from App.tsx:19) + ~3 s backoff = up to ~5 minutes of wait per request before clearnet fallback.

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):

  1. After N consecutive socks5Fetch failures in IndexerHttpClient, flip status via TorManager (e.g. call checkConnection() or expose a markUnavailable() helper) so subsequent calls skip the Tor retry loop.
  2. Add a short cooldown window after a Tor failure during which executeGet goes straight to clearnet.
  3. Re-probe the proxy before the retry loop if the last probe is older than X seconds.

Option 1 is probably the smallest change and keeps the status indicator in the UI honest.

Copilot AI review requested due to automatic review settings April 25, 2026 18:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread blue_modules/torManager.ts Outdated
Comment thread screen/settings/TorSettings.tsx Outdated
Comment thread screen/settings/TorSettings.tsx Outdated
Comment thread helpers/silent-payments/IndexerHttpClient.ts Outdated
notTanveer and others added 8 commits April 26, 2026 10:52
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>
Copilot AI review requested due to automatic review settings April 26, 2026 05:28
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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);
}
});

Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

_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.

Suggested change
client.on('end', () => finalize(false));
client.on('close', () => finalize(false));

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +34
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment thread blue_modules/torManager.ts Outdated
Comment on lines +65 to +67
this._settings = {
enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : DEFAULT_SETTINGS.enabled,
torOnly: typeof parsed.torOnly === 'boolean' ? parsed.torOnly : DEFAULT_SETTINGS.torOnly,
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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,

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 26, 2026 05:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread helpers/silent-payments/IndexerHttpClient.ts Outdated
Comment thread blue_modules/torManager.ts
Comment thread loc/en.json
Comment thread screen/settings/TorSettings.tsx
@notTanveer
Copy link
Copy Markdown
Contributor

notTanveer commented Apr 26, 2026

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)

@notTanveer
Copy link
Copy Markdown
Contributor

got this error while testing the app,

StorageProvider.tsx:428 [refreshAllWalletTransactions] Error: Error: Timeout reached
    at anonymous (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:164535:29)
    at apply (native)
    at anonymous (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:22321:26)
    at _callTimer (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:22260:17)
    at callTimers (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:22416:19)
    at apply (native)
    at __callFunction (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:4000:38)
    at anonymous (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:3756:31)
    at __guard (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:3946:15)
    at callFunctionReturnFlushedQueue (http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.bitshala.shroud&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:3755:21)
    ```


<TouchableOpacity onPress={() => setShowAdvanced(s => !s)}>
<BlueText style={styles.advancedToggle}>
{showAdvanced ? '▼ ' : '▸ '}
Copy link
Copy Markdown
Contributor

@notTanveer notTanveer Apr 26, 2026

Choose a reason for hiding this comment

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

sizes should be same imo.

this should be implemented in a better way, even i had a hard time finding it.

Comment thread screen/settings/TorSettings.tsx Outdated
Comment on lines +42 to +53
useEffect(() => {
const check = () => {
TorManager.isOrbotInstalled()
.then(setOrbotInstalled)
.catch(() => setOrbotInstalled(null));
};
check();
const sub = AppState.addEventListener('change', state => {
if (state === 'active') check();
});
return () => sub.remove();
}, []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown

@exp3rimenter exp3rimenter left a comment

Choose a reason for hiding this comment

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

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

@theanmolsharma
Copy link
Copy Markdown
Collaborator

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 Co-Authored-By trailer is the honest thing to do; quietly removing it to manage optics is closer to misrepresentation than transparency. Open source projects in particular gain trust by being upfront about how the sausage is made, not by hiding it.

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

Copilot AI review requested due to automatic review settings May 19, 2026 09:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Comment thread App.tsx
Comment on lines 16 to 20
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)
});
Comment thread loc/en.json
"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]);
Comment on lines +106 to +111
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should use Uint8Array instead of Buffer

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.

5 participants