SOCKS version 5 sockets for Dart and Flutter, eg. ElectrumX and/or Fulcrum over Tor via socket(s).
- Dart & Flutter 3.
- Support for SOCKS version 5 protocol.
- Supports ElectrumX and Fulcrum servers via socket(s).
- Async support for non-blocking network communication.
- Lightweight and minimal dependencies.
- Configurable connection timeouts (handshake and operation).
- Reconnection support without creating a new instance.
- Newline option for line-delimited protocols (ElectrumX).
- Connection state tracking (disconnected, connecting, connected, error).
- Typed exception hierarchy with SOCKS5 reply codes.
- Tor circuit isolation via
isolationToken. - Cancellation of in-flight connections via
cancel().
See socks_socket.dart itself for properties and methods and the example for reference.
import 'dart:io';
import 'package:socks_socket/socks.dart';
// The SOCKS5 port of your Tor instance, for example Tor.instance.port when
// Tor runs in-app through package:tor_ffi_plugin.
const proxyPort = 9050;
// Instantiate a socks socket at localhost and on the port selected by the tor service.
var socksSocket = await SOCKSSocket.create(
proxyHost: InternetAddress.loopbackIPv4.address,
proxyPort: proxyPort,
sslEnabled: true, // For SSL connections.
);
// Connect to the socks instantiated above.
await socksSocket.connect();
// Connect to bitcoin.stackwallet.com on port 50002 via socks socket.
//
// Note that this is an SSL example.
await socksSocket.connectTo('bitcoin.stackwallet.com', 50002);
// Send a server features command to the socket, see method for more specific usage example.
await socksSocket.sendServerFeaturesCommand();socks.dart omits the ConnectionState alias for use alongside Flutter.
// Configure custom timeouts for slow networks like Tor.
var socksSocket = await SOCKSSocket.create(
proxyHost: InternetAddress.loopbackIPv4.address,
proxyPort: proxyPort,
sslEnabled: true,
handshakeTimeout: Duration(seconds: 60),
operationTimeout: Duration(seconds: 45),
);// Use isolationToken to request a separate Tor circuit.
var socksSocket = await SOCKSSocket.create(
proxyHost: InternetAddress.loopbackIPv4.address,
proxyPort: proxyPort,
sslEnabled: true,
isolationToken: 'wallet-btc-001',
);
await socksSocket.connect();
await socksSocket.connectTo('bitcoin.stackwallet.com', 50002);
// Reconnect with a different token; requires an earlier connectTo().
await socksSocket.reconnect(isolationToken: 'wallet-btc-002');Tokens are sent as SOCKS5 credentials. A proxy selecting no-auth is rejected unless requireIsolation: false.
// After a connection drops, reconnect to the same target.
await socksSocket.reconnect();
// Continue sending data on the restored connection.
await socksSocket.write('{"jsonrpc":"2.0","method":"server.ping","id":1}',
newline: true);If connect() fails, the instance is spent: state is error, another connect() throws, and reconnect() has no target. Create a new instance. After a failed connectTo(), reconnect() retries that target. After cancel() or close() during connect, reconnect() opens a new connection once a target is known. reconnect() closes the current connection first, which ends inputStream; a close() or destroy() while it is in progress cancels it, including one made from that stream's onDone. Peer close is noticed, and state updated, only while inputStream has a listener.
By default the connection closes once the peer closes its side. Pass closeOnPeerEof: false to create() to keep writing after a peer half-close until you call close().
destroy() aborts a connection without draining output; pending writes fail, and a connect, TLS handshake or reconnect() in flight ends with SocksCancelledException. Use close() to drain accepted output before closing; it ends a connect or reconnect() in flight the same way. When a protocol requires request EOF before the response, use closeOutput(), consume the response from inputStream, then call close().
SOCKSSocket.socket, responseController and subscription have been removed. All transport operations now go through the wrapper so writes and teardown share the same lifecycle tracking.
| Previous operation | Replacement |
|---|---|
socks.socket.destroy() |
socks.destroy() |
socks.socket.close() |
await socks.closeOutput() to preserve input (the native half-close behavior), or await socks.close() to close both directions |
socks.socket.add(bytes) |
socks.outputStream.add(bytes) |
socks.socket.addStream(source) |
await socks.outputStream.addStream(source) |
| Reading the underlying socket | socks.inputStream or socks.listen(...) |
socks.responseController, socks.subscription |
socks.inputStream; pause or cancel your own subscription to it |
SecureSocket.secure(socks.socket, ...) |
Set sslEnabled: true and, if needed, securityContext on SOCKSSocket.create(...) |
For an awaited binary write, use await socks.outputStream.addStream(Stream.value(bytes)). The output sink accepts one stream at a time. For text, use await socks.write(text). To signal request EOF while continuing to receive, use await socks.closeOutput(). It immediately rejects new writes while draining accepted output; a drain timeout or transport failure aborts the connection and fails pending output. To drain and finish the entire connection, use await socks.close().
Built-in TLS starts during connectTo(), after the SOCKS handshake. Upgrading an established plaintext application session to TLS is not exposed by SOCKSSocket.
SocksConnection.start still returns a ConnectionTask<Socket> for HttpClient.connectionFactory; that separate API is unchanged.
SocksConnection.start returns a cancellable ConnectionTask<Socket> for HttpClient.connectionFactory; see example/http/http_connection.dart. Pass tlsHost for HTTPS. Requires Dart 3.5.