Skip to content

CW-519 Enable built-in Tor #1950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
23078a6
tor wip
MrCyjaneK Jan 9, 2025
812c5e6
Enable tor on iOS
MrCyjaneK Jan 13, 2025
179f039
Prevent app lag when node is exceptionally slow (usually over tor)
MrCyjaneK Jan 14, 2025
937c45f
fix: logic in daemonBlockchainHeight refresh
MrCyjaneK Jan 15, 2025
18c743a
Pin ledger_flutter_plus dependency to fix builds
MrCyjaneK Jan 15, 2025
7211252
bump arti version
MrCyjaneK Jan 15, 2025
20249e5
wip
MrCyjaneK Jan 16, 2025
f10f284
add single httpclient
MrCyjaneK Jan 16, 2025
3bc3b03
route everything I was able to catch trough the built-in tor node
MrCyjaneK Jan 16, 2025
1fec0df
Enable proxy for http.Client [run tests]
MrCyjaneK Jan 17, 2025
595e9fb
add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests]
MrCyjaneK Feb 3, 2025
99431ba
remove log pollution, cleanup [skip slack]
MrCyjaneK Feb 3, 2025
896cec8
fix tests not working in latest main [skip slack] [run tests]
MrCyjaneK Feb 3, 2025
6a858cb
remove cw_wownero import
MrCyjaneK Apr 1, 2025
4d723a1
fix build issues
MrCyjaneK Apr 1, 2025
cc32f23
migrate all remaining calls to use ProxyWrapper
MrCyjaneK Apr 3, 2025
ed92ce1
fix tor background sync (will work on test builds after #2142 is merg…
MrCyjaneK Apr 3, 2025
195c03f
wip [skip ci]
MrCyjaneK Apr 3, 2025
1915082
relicense to GPLv3 add socks5 license, build fixes
MrCyjaneK Apr 23, 2025
de1eccc
use ProxyWrapper instead of http in robinhood
MrCyjaneK Apr 25, 2025
28a7cc9
Revert "relicense to GPLv3"
MrCyjaneK Apr 25, 2025
38d943a
feat(cw_bitcoin): support socks proxy and CakeTor
MrCyjaneK Apr 25, 2025
9ce9c03
fix(tor): migrate OCP and EVM over to ProxyWrapper()
MrCyjaneK Apr 25, 2025
98cab3e
chore: cleanup
MrCyjaneK Apr 30, 2025
07fd2dd
fix: tor switch properly dismisses fullscreen loading dialog
MrCyjaneK Apr 30, 2025
0acab89
fix(tor): status check for xmr/wow/zano
MrCyjaneK May 1, 2025
a7db610
fix(tor): onramper request fix
MrCyjaneK May 18, 2025
c3a52d6
fix(api): ServicesResponse is now being cached and doesn't fetch data…
MrCyjaneK May 22, 2025
bc162fb
[skip ci] remove print
MrCyjaneK May 22, 2025
bb25264
address comments from review
MrCyjaneK May 23, 2025
7bfa3ad
fix: derusting tor implementation
MrCyjaneK May 27, 2025
923aa1a
fix conflicts with main
MrCyjaneK Jun 2, 2025
460adb5
fix(cw_wownero): tor connection
MrCyjaneK Jun 12, 2025
4a2aa28
fix(cw_evm): add missing chainId
MrCyjaneK Jun 13, 2025
1b258ee
feat: mark tor as experimental
MrCyjaneK Jun 17, 2025
29fb4af
fix re-formatting [skip ci]
OmarHatem28 Jun 17, 2025
e1f9e35
changes from review
MrCyjaneK Jun 18, 2025
ca57d0d
Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.s…
OmarHatem28 Jun 18, 2025
67506b4
fix missing imports
OmarHatem28 Jun 18, 2025
152b458
Merge branch 'main' into CW-519-add-tor-connection-feature-to-cake
OmarHatem28 Jun 20, 2025
ba07b71
Update pubspec_base.yaml
OmarHatem28 Jun 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/no_http_imports.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: No http imports

on: [pull_request]

jobs:
PR_test_build:
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4
- name: Check for http package usage
if: github.event_name == 'pull_request'
run: |
GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) || true)"
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use http imports"
echo "Using http package breaks proxy integration"
echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart"
exit 1

2 changes: 1 addition & 1 deletion .github/workflows/no_print_in_dart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ jobs:
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use print() statements"
echo "Please use printV from package: cw_core/utils/print_verbose.dart"
echo "Please use printV from package:cw_core/utils/print_verbose.dart"
exit 1
76 changes: 76 additions & 0 deletions assets/images/tor_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 6 additions & 12 deletions cw_bitcoin/lib/electrum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';

Expand Down Expand Up @@ -42,7 +44,7 @@ class ElectrumClient {
static const aliveTimerDuration = Duration(seconds: 4);

bool get isConnected => _isConnected;
Socket? socket;
ProxySocket? socket;
void Function(ConnectionStatus)? onConnectionStatusChange;
int _id;
final Map<String, SocketTask> _tasks;
Expand Down Expand Up @@ -72,18 +74,11 @@ class ElectrumClient {
} catch (_) {}
socket = null;

final ssl = !(useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum")));
try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
socket = await Socket.connect(host, port, timeout: connectionTimeout);
} else {
socket = await SecureSocket.connect(
host,
port,
timeout: connectionTimeout,
onBadCertificate: (_) => true,
);
}
socket = await ProxyWrapper().getSocksSocket(ssl, host, port, connectionTimeout: connectionTimeout);
} catch (e) {
printV("connect: $e");
if (e is HandshakeException) {
useSSL = !(useSSL ?? false);
}
Expand All @@ -105,7 +100,6 @@ class ElectrumClient {

// use ping to determine actual connection status since we could've just not timed out yet:
// _setConnectionStatus(ConnectionStatus.connected);

socket!.listen(
(Uint8List event) {
try {
Expand Down
52 changes: 23 additions & 29 deletions cw_bitcoin/lib/electrum_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:io';
import 'dart:isolate';

import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart';
Expand Down Expand Up @@ -49,7 +50,6 @@ import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart';
import 'package:hex/hex.dart';
import 'package:http/http.dart' as http;

part 'electrum_wallet.g.dart';

Expand Down Expand Up @@ -493,10 +493,9 @@ abstract class ElectrumWalletBase
Future<void> updateFeeRates() async {
if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) {
try {
final response = await http
.get(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended"))
.timeout(Duration(seconds: 5));

final response = await ProxyWrapper()
.get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended"))
.timeout(Duration(seconds: 15));
final result = json.decode(response.body) as Map<String, dynamic>;
final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
Expand Down Expand Up @@ -1176,20 +1175,18 @@ abstract class ElectrumWalletBase
}
});

return PendingBitcoinTransaction(
transaction,
type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
feeRate: feeRateInt.toString(),
network: network,
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
utxos: estimatedTx.utxos,
publicKeys: estimatedTx.publicKeys
)..addListener((transaction) async {
return PendingBitcoinTransaction(transaction, type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
feeRate: feeRateInt.toString(),
network: network,
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
utxos: estimatedTx.utxos,
publicKeys: estimatedTx.publicKeys)
..addListener((transaction) async {
transactionHistory.addOne(transaction);
if (estimatedTx.spendsSilentPayment) {
transactionHistory.transactions.values.forEach((tx) {
Expand Down Expand Up @@ -1880,20 +1877,17 @@ abstract class ElectrumWalletBase

if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) {
try {
final blockHash = await http.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/block-height/$height",
),
);
final blockHash = await ProxyWrapper()
.get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block-height/$height"))
.timeout(Duration(seconds: 15));

if (blockHash.statusCode == 200 &&
blockHash.body.isNotEmpty &&
jsonDecode(blockHash.body) != null) {
final blockResponse = await http.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/block/${blockHash.body}",
),
);
final blockResponse = await ProxyWrapper()
.get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block/${blockHash}"))
.timeout(Duration(seconds: 15));

if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) {
Expand Down
28 changes: 14 additions & 14 deletions cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import 'package:cw_bitcoin/payjoin/manager.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
import 'package:cw_bitcoin/psbt/signer.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:http/http.dart' as http;
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:payjoin_flutter/bitcoin_ffi.dart';
import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/receive.dart';
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
import 'package:http/http.dart' as very_insecure_http_do_not_use; // for errors

enum PayjoinReceiverRequestTypes {
processOriginalTx,
Expand All @@ -28,7 +29,7 @@ class PayjoinReceiverWorker {
final pendingRequests = <String, Completer<dynamic>>{};

PayjoinReceiverWorker._(this.sendPort);

static final client = ProxyWrapper().getHttpIOClient();
static Future<void> run(List<Object> args) async {
await pj.core.init();

Expand All @@ -42,11 +43,10 @@ class PayjoinReceiverWorker {
receivePort.listen(worker.handleMessage);

try {
final httpClient = http.Client();
final receiver = Receiver.fromJson(json: receiverJson);

final uncheckedProposal =
await worker.receiveUncheckedProposal(httpClient, receiver);
await worker.receiveUncheckedProposal(receiver);

final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast();
sendPort.send({
Expand All @@ -57,14 +57,14 @@ class PayjoinReceiverWorker {
final payjoinProposal = await worker.processPayjoinProposal(
uncheckedProposal,
);
final psbt = await worker.sendFinalProposal(httpClient, payjoinProposal);
final psbt = await worker.sendFinalProposal(payjoinProposal);
sendPort.send({
'type': PayjoinReceiverRequestTypes.proposalSent,
'psbt': psbt,
});
} catch (e) {
if (e is HttpException ||
(e is http.ClientException &&
(e is very_insecure_http_do_not_use.ClientException &&
e.message.contains("Software caused connection abort"))) {
sendPort.send(PayjoinSessionError.recoverable(e.toString()));
} else {
Expand Down Expand Up @@ -98,16 +98,16 @@ class PayjoinReceiverWorker {
return completer.future;
}

Future<UncheckedProposal> receiveUncheckedProposal(
http.Client httpClient, Receiver session) async {
Future<UncheckedProposal> receiveUncheckedProposal(Receiver session) async {
while (true) {
printV("Polling for Proposal (${session.id()})");
final extractReq = await session.extractReq(
ohttpRelay: PayjoinManager.randomOhttpRelayUrl());
ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
);
final request = extractReq.$1;

final url = Uri.parse(request.url.asString());
final httpRequest = await httpClient.post(url,
final httpRequest = await client.post(url,
headers: {'Content-Type': request.contentType}, body: request.body);

final proposal = await session.processRes(
Expand All @@ -116,14 +116,14 @@ class PayjoinReceiverWorker {
}
}

Future<String> sendFinalProposal(
http.Client httpClient, PayjoinProposal finalProposal) async {
Future<String> sendFinalProposal(PayjoinProposal finalProposal) async {
final req = await finalProposal.extractReq(
ohttpRelay: PayjoinManager.randomOhttpRelayUrl());
ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
);
final proposalReq = req.$1;
final proposalCtx = req.$2;

final request = await httpClient.post(
final request = await client.post(
Uri.parse(proposalReq.url.asString()),
headers: {"Content-Type": proposalReq.contentType},
body: proposalReq.body,
Expand Down
Loading
Loading