diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,29 +1,33 @@
 # Revision history for socks5
 
-## 0.1.0.0 -- 2025-06-30
+## 0.8.0.0 -- 2025-07-09
 
-- First version. Released on an unsuspecting world.
+- Adjusted the argument of client API. Low-level API for created connections added.
 
-## 0.2.0.0 -- 2025-07-06
+## 0.6.0.1 -- 2025-07-08
 
-- TLS version API and server executable's CLI args added.
+- Changed base requirement for example and tests
 
-## 0.3.0.0 -- 2025-07-07
+## 0.6.0.0 -- 2025-07-08
 
-- Adjusted the order of argument for client side API.
+- Removed a record from client config. Rename test directory to tests. Added tests for TLS and UserPass authentication.
 
+## 0.5.0.0 -- 2025-07-07
+
+- Moved some types to separate file. Haddock documentation added. Default server port changed to 1080.
+
 ## 0.4.0.0 -- 2025-07-07
 
 - UserPass authentication added, adjusted server API and CLI.
 
-## 0.5.0.0 -- 2025-07-07
+## 0.3.0.0 -- 2025-07-07
 
-- Moved some types to separate file. Haddock documentation added. Default server port changed to 1080.
+- Adjusted the order of argument for client side API.
 
-## 0.6.0.0 -- 2025-07-08
+## 0.2.0.0 -- 2025-07-06
 
-- Removed a record from client config. Rename test directory to tests. Added tests for TLS and UserPass authentication.
+- TLS version API and server executable's CLI args added.
 
-## 0.6.0.1 -- 2025-07-08
+## 0.1.0.0 -- 2025-06-30
 
-- Changed base requirement for example and tests
+- First version. Released on an unsuspecting world.
diff --git a/Network/SOCKS5/Client.hs b/Network/SOCKS5/Client.hs
--- a/Network/SOCKS5/Client.hs
+++ b/Network/SOCKS5/Client.hs
@@ -1,14 +1,24 @@
 {-# LANGUAGE OverloadedStrings #-}
 
--- | Functions to start connection using SOCKS5 proxy.
+-- |
+-- This module provides a client for the SOCKS5 proxy protocol
+-- It offers two tiers of functions:
 --
--- Here is an minimal example of how to use the library to connect to a remote server through a SOCKS5 proxy:
+-- * High-level 'run...' functions: These are "all-in-one" helpers that
+--     manage the entire connection, from connecting to the proxy to running
+--     your client code.
 --
+-- * Low-level 'exec...' functions: These functions execute a single SOCKS5
+--     command on an already established connection. They provide more control
+--     for users who need to manage the connection to the proxy manually.
+--
+-- = Example Usage
+-- Here is a minimal example of how to use the library to connect to a remote server through a SOCKS5 proxy:
+--
 -- > {-# LANGUAGE OverloadedStrings #-}
 -- >
 -- > import qualified Data.ByteString.Char8 as C8
 -- > import Network.SOCKS5.Client
--- > import Network.SOCKS5.Types
 -- > import Network.Socket.ByteString (recv, sendAll)
 -- >
 -- > main :: IO ()
@@ -19,10 +29,8 @@
 -- >             proxyPort = "11451",
 -- >             userPass = Nothing
 -- >           }
--- >   let destAddr = AddressDomain "example.com"
--- >   let destPort = "80"
 -- >
--- >   runTCPConnect destAddr destPort proxyConfig $ \sock -> do
+-- >   runTCPConnect "example.com" 80 proxyConfig $ \sock -> do
 -- >     putStrLn "Connected to example.com through SOCKS5 proxy!"
 -- >     sendAll sock "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
 -- >     response <- recv sock 4096
@@ -30,15 +38,20 @@
 module Network.SOCKS5.Client
   ( ClientConfig (..),
 
-    -- * SOCKS5
+    -- * High-level functions
     runTCPConnect,
     runTCPBind,
     runUDPAssociate,
 
-    -- * SOCKS5 with TLS
+    -- * High-level functions with TLS
     runTCPConnectTLS,
     runTCPBindTLS,
     runUDPAssociateTLS,
+
+    -- * Low-level functions
+    execConnect,
+    execBind,
+    execUDPAssociate,
   )
 where
 
@@ -46,199 +59,222 @@
 import Control.Monad.State
 import Data.Binary (Binary, decode)
 import Data.ByteString qualified as B
+import Data.IP
 import Data.Text.Lazy qualified as LT
 import Network.Run.TCP
 import Network.Run.UDP
 import Network.SOCKS5.Internal
-import Network.SOCKS5.Types
 import Network.Socket
 import Network.Socket.ByteString qualified as SB
 import Network.TLS
 import Text.Read (readMaybe)
 
+-- | Configuration for connecting to the SOCKS5 proxy server.
 data ClientConfig = ClientConfig
-  { -- | Hostname or IP address of the SOCKS5 proxy server
+  { -- | Hostname or IP address of the SOCKS5 proxy server.
     proxyHost :: HostName,
-    -- | Port number of the SOCKS5 proxy server
+    -- | Port number of the SOCKS5 proxy server.
     proxyPort :: ServiceName,
-    -- | Username and password for UserPass authentication, `Nothing` will then use `NoAuth`
+    -- | Credentials for authentication. 'Nothing' defaults to the 'NoAuth' method.
+    -- Otherwise 'UserPass' will be used with the provided value.
     userPass :: Maybe (LT.Text, LT.Text)
   }
 
 type StateIO a = StateT B.ByteString IO a
 
--- | Run a TCP connection to target address and port through proxy.
+-- | Establishes a TCP connection to a target through a SOCKS5 proxy using the CONNECT command.
 runTCPConnect ::
-  -- | Destination address
-  Address ->
-  -- | Destination port
-  ServiceName ->
-  -- | Proxy configuration
+  -- | Target hostname or IP address.
+  HostName ->
+  -- | Target port.
+  PortNumber ->
+  -- | SOCKS5 proxy configuration.
   ClientConfig ->
-  -- | Client function to run with the connected socket
+  -- | The action to run with the connected 'Socket'.
   (Socket -> IO a) ->
   IO a
-runTCPConnect destAddr destPort config client = do
+runTCPConnect destHost destPort config client = do
   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do
-    startConnect destAddr destPort config sock client
+    execConnect destHost destPort config sock
+    client sock
 
--- | Run a TCP connection to target address and port through proxy with TLS.
+-- | Establishes a TLS-secured TCP connection to a target through a SOCKS5 proxy using the CONNECT command.
 runTCPConnectTLS ::
-  -- | Destination address
-  Address ->
-  -- | Destination port
-  ServiceName ->
-  -- | Proxy configuration
+  HostName ->
+  PortNumber ->
   ClientConfig ->
-  -- | TLS parameters for the connection
+  -- | TLS parameters for the connection.
   ClientParams ->
-  -- | Client function to run with the connected TLS context
+  -- | The action to run with the TLS 'Context'.
   (Context -> IO a) ->
   IO a
-runTCPConnectTLS destAddr destPort config params client = do
+runTCPConnectTLS destHost destPort config params client = do
   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do
     ctx <- contextNew sock params
     handshake ctx
-    res <- startConnect destAddr destPort config ctx client
+    execConnect destHost destPort config ctx
+    res <- client ctx
     bye ctx
     return res
 
-startConnect ::
+-- | Executes the CONNECT command on an established connection to the proxy.
+-- This function only performs the SOCKS5 negotiation.
+execConnect ::
   (Connection c) =>
-  Address ->
-  ServiceName ->
+  -- | Target hostname or IP address.
+  HostName ->
+  -- | Target port.
+  PortNumber ->
+  -- | SOCKS5 proxy configuration.
   ClientConfig ->
+  -- | An existing connection to the proxy (e.g., a 'Socket' or 'Context').
   c ->
-  (c -> IO a) ->
-  IO a
-startConnect destAddr destPort config conn client =
+  IO ()
+execConnect destHost destPort config conn =
   flip evalStateT B.empty $ do
     performAuth config conn
-    portNum <- resolvePort destPort
-    encodeAndSend conn $ Request Connect destAddr portNum
+    let destAddr = hostToAddress destHost
+    encodeAndSend conn $ Request Connect destAddr destPort
     reply <- recv' conn
     case reply of
-      Reply Succeeded _ _ -> liftIO $ client conn
+      Reply Succeeded _ _ -> return ()
       _ -> liftIO $ throwIO $ HandshakeFail reply
 
--- | run a TCP connection to target by making proxy listen on a port and bind to it.
+-- | Asks the SOCKS5 proxy to BIND to a port and waits for an incoming connection.
+-- This is for protocols that require the client to accept a connection, like FTP.
 runTCPBind ::
-  -- | Destination address
-  Address ->
-  -- | Destination port
-  ServiceName ->
-  -- | Proxy configuration
+  -- | The hostname or IP address the proxy should connect to (often ignored).
+  HostName ->
+  -- | The port the proxy should connect to (often ignored).
+  PortNumber ->
   ClientConfig ->
-  -- | Function to call when the bind is successful, receives the proxy's listening address and port
-  (Address -> PortNumber -> IO ()) ->
-  -- | Client function to run with the bound socket, receives the connected peer's address and port
-  (Address -> PortNumber -> Socket -> IO a) ->
+  -- | An action called after the proxy successfully binds, providing the listening address and port.
+  (HostName -> PortNumber -> IO ()) ->
+  -- | The action to run with the 'Socket' once a peer connects, providing the peer's address and port.
+  (HostName -> PortNumber -> Socket -> IO a) ->
   IO a
-runTCPBind destAddr destPort config notify client = do
+runTCPBind destHost destPort config notify client = do
   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do
-    startBind destAddr destPort config sock notify client
+    (hostAddr, hostPort) <- execBind destHost destPort config sock notify
+    client hostAddr hostPort sock
 
--- | run a TCP connection to target by making proxy listen on a port and bind to it with TLS.
+-- | Asks the SOCKS5 proxy to BIND over a TLS-secured connection.
 runTCPBindTLS ::
-  -- | Destination address
-  Address ->
-  -- | Destination port
-  ServiceName ->
-  -- | Proxy configuration
+  HostName ->
+  PortNumber ->
   ClientConfig ->
-  -- | TLS parameters for the connection
   ClientParams ->
-  -- | Function to call when the bind is successful, receives the proxy's listening address and port
-  (Address -> PortNumber -> IO ()) ->
-  -- | Client function to run with the bound TLS context, receives the connected peer's address and port
-  (Address -> PortNumber -> Context -> IO a) ->
+  -- | An action called after the proxy successfully binds, providing the listening address and port.
+  (HostName -> PortNumber -> IO ()) ->
+  -- | The action to run with the 'Context' once a peer connects, providing the peer's address and port.
+  (HostName -> PortNumber -> Context -> IO a) ->
   IO a
-runTCPBindTLS destAddr destPort config params notify client = do
+runTCPBindTLS destHost destPort config params notify client = do
   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do
     ctx <- contextNew sock params
     handshake ctx
-    res <- startBind destAddr destPort config ctx notify client
+    (hostAddr, hostPort) <- execBind destHost destPort config ctx notify
+    res <- client hostAddr hostPort ctx
     bye ctx
     return res
 
-startBind ::
+-- | Executes the BIND command on an established connection.
+-- This function handles the two-stage reply from the proxy.
+execBind ::
   (Connection c) =>
-  Address ->
-  ServiceName ->
+  -- | The hostname or IP address the proxy should connect to (often ignored).
+  HostName ->
+  -- | The port the proxy should connect to (often ignored).
+  PortNumber ->
+  -- | SOCKS5 proxy configuration.
   ClientConfig ->
+  -- | An existing connection to the proxy (e.g., a 'Socket' or 'Context').
   c ->
-  (Address -> PortNumber -> IO ()) ->
-  (Address -> PortNumber -> c -> IO a) ->
-  IO a
-startBind destAddr destPort config conn notify client =
+  -- | An action called after the proxy successfully binds.
+  (HostName -> PortNumber -> IO ()) ->
+  -- | Returns the hostname/IP and port of the connecting peer.
+  IO (HostName, PortNumber)
+execBind destHost destPort config conn notify =
   flip evalStateT B.empty $ do
     performAuth config conn
-    portNum <- resolvePort destPort
-    encodeAndSend conn $ Request Bind destAddr portNum
+    let destAddr = hostToAddress destHost
+    encodeAndSend conn $ Request Bind destAddr destPort
     reply1 <- recv' conn
     case reply1 of
       Reply Succeeded listenAddr listenPort -> do
-        liftIO $ notify listenAddr listenPort
+        liftIO $ notify (show listenAddr) listenPort
         reply2 <- recv' conn
         case reply2 of
           Reply Succeeded hostAddr hostPort ->
-            liftIO $ client hostAddr hostPort conn
+            return (show hostAddr, hostPort)
           _ -> liftIO $ throwIO $ HandshakeFail reply2
       _ -> liftIO $ throwIO $ HandshakeFail reply1
 
--- | Start a UDP relay on proxy server and run a client function with the connected UDP socket.
+-- | Establishes a UDP ASSOCIATION with the SOCKS5 proxy, enabling UDP traffic to be relayed.
 runUDPAssociate ::
-  -- | Proxy configuration
   ClientConfig ->
-  -- | Client function to run with the connected UDP socket, receives a function to send data and a function to receive data
+  -- | Action to run with the connected UDP socket, providing function to send and receive datagrams.
   ((B.ByteString -> SockAddr -> IO ()) -> IO (B.ByteString, SockAddr) -> IO a) ->
   IO a
 runUDPAssociate config client = do
   runTCPClient (proxyHost config) (proxyPort config) $ \sockTCP -> do
-    startUDPAssociate config sockTCP client
+    (relayHost, relayPort) <- execUDPAssociate config sockTCP
+    runUDPClient relayHost (show relayPort) $ \sockUDP proxySockAddr -> do
+      let sendDataTo :: B.ByteString -> SockAddr -> IO ()
+          sendDataTo userData destination = do
+            let (dstAddr, dstPort) = fromSockAddr_ destination
+            sendUDPRequestTo sockUDP 0 dstAddr dstPort (B.fromStrict userData) proxySockAddr
+          recvDataFrom :: IO (B.ByteString, SockAddr)
+          recvDataFrom = do
+            (datagram, _) <- SB.recvFrom sockUDP 8192
+            let udpReply = decode (B.fromStrict datagram) :: UDPRequest
+                originalSender = toSockAddr_ (address udpReply) (port udpReply)
+            return (B.toStrict (payload udpReply), originalSender)
+      client sendDataTo recvDataFrom
 
--- | Start a UDP relay on proxy server with TLS and run a client function with the connected UDP socket. Note that the UDP socket is not encrypted, only the initial connection to the proxy is secured.
+-- | Establishes a UDP association over a TLS-secured control connection.
+-- Note: The control connection to the proxy is secured with TLS, but the resulting
+-- UDP traffic is not encrypted.
 runUDPAssociateTLS ::
-  -- | Proxy configuration
   ClientConfig ->
-  -- | TLS parameters for the connection
   ClientParams ->
-  -- | Client function to run with the connected UDP socket, receives a function to send data and a function to receive data
+  -- | Action to run with the connected UDP socket, providing function to send and receive datagrams.
   ((B.ByteString -> SockAddr -> IO ()) -> IO (B.ByteString, SockAddr) -> IO a) ->
   IO a
 runUDPAssociateTLS config params client = do
   runTCPClient (proxyHost config) (proxyPort config) $ \sockTCP -> do
     ctx <- contextNew sockTCP params
     handshake ctx
-    res <- startUDPAssociate config ctx client
+    (relayHost, relayPort) <- execUDPAssociate config ctx
+    res <- runUDPClient relayHost (show relayPort) $ \sockUDP proxySockAddr -> do
+      let sendDataTo :: B.ByteString -> SockAddr -> IO ()
+          sendDataTo userData destination = do
+            let (dstAddr, dstPort) = fromSockAddr_ destination
+            sendUDPRequestTo sockUDP 0 dstAddr dstPort (B.fromStrict userData) proxySockAddr
+          recvDataFrom :: IO (B.ByteString, SockAddr)
+          recvDataFrom = do
+            (datagram, _) <- SB.recvFrom sockUDP 8192
+            let udpReply = decode (B.fromStrict datagram) :: UDPRequest
+                originalSender = toSockAddr_ (address udpReply) (port udpReply)
+            return (B.toStrict (payload udpReply), originalSender)
+      client sendDataTo recvDataFrom
     bye ctx
     return res
 
-startUDPAssociate ::
+-- | Executes the UDP ASSOCIATE command on an established connection.
+execUDPAssociate ::
   (Connection c) =>
   ClientConfig ->
   c ->
-  ((B.ByteString -> SockAddr -> IO ()) -> IO (B.ByteString, SockAddr) -> IO a) ->
-  IO a
-startUDPAssociate config conn client =
+  -- | Returns the address and port of the UDP relay on the proxy server.
+  IO (HostName, PortNumber)
+execUDPAssociate config conn =
   flip evalStateT B.empty $ do
     performAuth config conn
     encodeAndSend conn $ Request UDPAssociate (AddressIPv4 "0.0.0.0") 0
     reply <- recv' conn
     case reply of
-      Reply Succeeded relayAddr relayPort -> liftIO $ do
-        runUDPClient (show relayAddr) (show relayPort) $ \sockUDP proxySockAddr -> do
-          let sendDataTo :: B.ByteString -> SockAddr -> IO ()
-              sendDataTo userData destination = do
-                let (dstAddr, dstPort) = fromSockAddr_ destination
-                sendUDPRequestTo sockUDP 0 dstAddr dstPort (B.fromStrict userData) proxySockAddr
-              recvDataFrom :: IO (B.ByteString, SockAddr)
-              recvDataFrom = do
-                (datagram, _) <- SB.recvFrom sockUDP 8192
-                let udpReply = decode (B.fromStrict datagram) :: UDPRequest
-                    originalSender = toSockAddr_ (address udpReply) (port udpReply)
-                return (B.toStrict (payload udpReply), originalSender)
-          client sendDataTo recvDataFrom
+      Reply Succeeded relayAddr relayPort -> return (show relayAddr, relayPort)
       _ -> liftIO $ throwIO $ HandshakeFail reply
 
 performAuth :: (Connection c) => ClientConfig -> c -> StateIO ()
@@ -260,11 +296,11 @@
         _ -> liftIO $ throwIO AuthMissingCredentials
     _ -> liftIO $ throwIO $ AuthUnsupported (method selectedMethod)
 
-resolvePort :: ServiceName -> StateIO PortNumber
-resolvePort port = liftIO $
-  case readMaybe port of
-    Just p -> return p
-    Nothing -> throwIO $ PortStringInvalid port
+hostToAddress :: HostName -> Address
+hostToAddress host = case readMaybe host :: Maybe IP of
+  Just (IPv4 ipv4) -> AddressIPv4 ipv4
+  Just (IPv6 ipv6) -> AddressIPv6 ipv6
+  Nothing -> AddressDomain (LT.pack host)
 
 recv' :: (Binary b, Connection c) => c -> StateIO b
 recv' conn = do
diff --git a/Network/SOCKS5/Internal.hs b/Network/SOCKS5/Internal.hs
--- a/Network/SOCKS5/Internal.hs
+++ b/Network/SOCKS5/Internal.hs
@@ -1,17 +1,21 @@
 module Network.SOCKS5.Internal
-  ( Hello (..),
+  ( Method (..),
+    Hello (..),
     MethodSelection (..),
     Command (..),
+    Address (..),
     Request (..),
     Rep (..),
     Reply (..),
     UDPRequest (..),
     sendUDPRequestTo,
     UserPassRequest (..),
-    UserPassResponse (..),
     Status (..),
+    UserPassResponse (..),
     SOCKSException (..),
     Connection (..),
+    fromSockAddr_,
+    toSockAddr_,
     recvAndDecode,
     encodeAndSend,
   )
@@ -25,9 +29,9 @@
 import Data.Binary.Put
 import Data.ByteString qualified as B
 import Data.ByteString.Lazy qualified as LB
+import Data.IP
 import Data.Text.Lazy qualified as LT
 import Data.Text.Lazy.Encoding qualified as LTE
-import Network.SOCKS5.Types
 import Network.Socket
 import Network.Socket.ByteString qualified as SB
 import Network.Socket.ByteString.Lazy qualified as LSB
@@ -42,6 +46,46 @@
 --    |  1  |    1     | 1 to 255 |
 --    +-----+----------+----------+
 
+--  The values currently defined for METHOD are:
+--    X'00': NO AUTHENTICATION REQUIRED
+--    X'01': GSSAPI
+--    X'02': USERNAME/PASSWORD
+--    X'03': to X'7F' IANA ASSIGNED
+--    X'80': to X'FE' RESERVED FOR PRIVATE METHODS
+--    X'FF': NO ACCEPTABLE METHODS
+
+data Method
+  = NoAuth
+  | GSSAPI
+  | UserPass
+  | IANAAssigned Word8
+  | PrivateMethod Word8
+  | NoAcceptableMethods
+  | UnknownMethod Word8
+  deriving (Eq, Show)
+
+instance Binary Method where
+  put :: Method -> Put
+  put NoAuth = putWord8 0x00
+  put GSSAPI = putWord8 0x01
+  put UserPass = putWord8 0x02
+  put (IANAAssigned m) = putWord8 m
+  put (PrivateMethod m) = putWord8 m
+  put NoAcceptableMethods = putWord8 0xFF
+  put (UnknownMethod m) = putWord8 m
+
+  get :: Get Method
+  get = do
+    m <- getWord8
+    case m of
+      0x00 -> return NoAuth
+      0x01 -> return GSSAPI
+      0x02 -> return UserPass
+      _ | m >= 0x03 && m <= 0x7F -> return $ IANAAssigned m
+      _ | m >= 0x80 && m <= 0xFE -> return $ PrivateMethod m
+      0xFF -> return NoAcceptableMethods
+      _ -> return $ UnknownMethod m
+
 newtype Hello = Hello
   { methods :: [Method]
   }
@@ -126,6 +170,62 @@
       0x03 -> return UDPAssociate
       _ -> fail $ "Unknown command: " ++ show cmd
 
+-- | Address type used for destination addresses in SOCKS5 protocol.
+data Address
+  = AddressIPv4 IPv4
+  | AddressIPv6 IPv6
+  | AddressDomain LT.Text
+  deriving (Eq)
+
+instance Show Address where
+  show :: Address -> String
+  show (AddressIPv4 ip) = show ip
+  show (AddressIPv6 ip) = show ip
+  show (AddressDomain name) = LT.unpack name
+
+instance Binary Address where
+  put :: Address -> Put
+  put (AddressIPv4 ip) = do
+    putWord8 0x01
+    putWord32be $ fromIPv4w ip
+  put (AddressIPv6 ip) = do
+    putWord8 0x04
+    let (a1, a2, a3, a4) = fromIPv6w ip
+     in mapM_ putWord32be [a1, a2, a3, a4]
+  put (AddressDomain name) = do
+    putWord8 0x03
+    let encodedName = LTE.encodeUtf8 name
+        byteLength = LB.length encodedName
+    if byteLength > fromIntegral (maxBound :: Word8)
+      then
+        error $
+          "DomainName (UTF-8 encoded) has length of "
+            ++ show byteLength
+            ++ " bytes. Max allowed is "
+            ++ show (maxBound :: Word8)
+      else putWord8 $ fromIntegral byteLength
+    putLazyByteString encodedName
+
+  get :: Get Address
+  get = do
+    addrType <- getWord8
+    case addrType of
+      0x01 -> do
+        AddressIPv4 . toIPv4w <$> getWord32be
+      0x04 -> do
+        a1 <- getWord32be
+        a2 <- getWord32be
+        a3 <- getWord32be
+        a4 <- getWord32be
+        return $ AddressIPv6 $ toIPv6w (a1, a2, a3, a4)
+      0x03 -> do
+        len <- getWord8
+        encodedName <- getLazyByteString (fromIntegral len)
+        case LTE.decodeUtf8' encodedName of
+          Left err -> fail $ "Failed to decode UTF-8 domain name: " ++ show err
+          Right name -> return $ AddressDomain name
+      _ -> fail $ "Unknown address type: " ++ show addrType
+
 data Request = Request
   { command :: Command,
     destinationAddress :: Address,
@@ -405,6 +505,16 @@
 
   connSend :: Context -> LB.ByteString -> IO ()
   connSend = sendData
+
+fromSockAddr_ :: SockAddr -> (Address, PortNumber)
+fromSockAddr_ (SockAddrInet port host) = (AddressIPv4 (fromHostAddress host), fromIntegral port)
+fromSockAddr_ (SockAddrInet6 port _ host _) = (AddressIPv6 (toIPv6w host), fromIntegral port)
+fromSockAddr_ path = error $ "Unexpected Unix socket address: " ++ show path
+
+toSockAddr_ :: Address -> PortNumber -> SockAddr
+toSockAddr_ (AddressIPv4 host) port = SockAddrInet (fromIntegral port) $ toHostAddress host
+toSockAddr_ (AddressIPv6 host) port = SockAddrInet6 (fromIntegral port) 0 (toHostAddress6 host) 0
+toSockAddr_ (AddressDomain host) _ = error $ "Address conversion for DomainName not implemented: " ++ show host
 
 recvAndDecode :: (Binary a, Connection c, MonadIO m) => c -> B.ByteString -> m (a, B.ByteString)
 recvAndDecode conn buffer = liftIO $ go $ pushChunk (runGetIncremental get) buffer
diff --git a/Network/SOCKS5/Server.hs b/Network/SOCKS5/Server.hs
--- a/Network/SOCKS5/Server.hs
+++ b/Network/SOCKS5/Server.hs
@@ -1,14 +1,11 @@
 {-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE OverloadedStrings #-}
 
--- | Functions to run a SOCKS5 server.
+-- |
+-- This module provides a server implementation for the SOCKS5 proxy protocol.
 module Network.SOCKS5.Server
   ( ServerConfig (..),
-
-    -- * SOCKS5 Server
     runSOCKS5Server,
-
-    -- * SOCKS5 Server with TLS
     runSOCKS5ServerTLS,
   )
 where
@@ -25,18 +22,20 @@
 import Data.Text.Lazy qualified as LT
 import Network.Run.TCP
 import Network.SOCKS5.Internal
-import Network.SOCKS5.Types
 import Network.Socket
 import Network.Socket.ByteString qualified as SB
 import Network.TLS
 import System.IO.Error
 
+-- | Configuration for the SOCKS5 server.
 data ServerConfig = ServerConfig
-  { -- | Hostname or IP address to bind the server to
+  { -- | Hostname or IP address to bind the server to.
     serverHost :: HostName,
-    -- | Port number to listen on
+    -- | Port number to listen on.
     serverPort :: ServiceName,
     -- | List of User/Password pairs for UserPass authentication
+    --  If empty, only 'NoAuth' method will be available.
+    -- Otherwise, 'UserPass' authentication is required.
     users :: [(LT.Text, LT.Text)]
   }
 
@@ -54,8 +53,12 @@
     handle (\(e :: SomeException) -> putStrLn $ show clientAddr ++ ": " ++ show e) $
       newClient config sockTCP sockTCP
 
--- | Run a SOCKS5 server with TLS support.
-runSOCKS5ServerTLS :: ServerConfig -> ServerParams -> IO ()
+-- | Run a SOCKS5 server with the control channel secured by TLS.
+runSOCKS5ServerTLS ::
+  ServerConfig ->
+  -- | TLS parameters for the server (e.g., certificate and key).
+  ServerParams ->
+  IO ()
 runSOCKS5ServerTLS config params = do
   let host = serverHost config
       port = serverPort config
diff --git a/Network/SOCKS5/Types.hs b/Network/SOCKS5/Types.hs
deleted file mode 100644
--- a/Network/SOCKS5/Types.hs
+++ /dev/null
@@ -1,123 +0,0 @@
--- Types you may want to import.
-module Network.SOCKS5.Types
-  ( Method (..),
-    Address (..),
-    fromSockAddr_,
-    toSockAddr_,
-  )
-where
-
-import Data.Binary
-import Data.Binary.Get
-import Data.Binary.Put
-import Data.ByteString.Lazy qualified as LB
-import Data.IP
-import Data.Text.Lazy qualified as LT
-import Data.Text.Lazy.Encoding qualified as LTE
-import Network.Socket
-
---  The values currently defined for METHOD are:
---    X'00': NO AUTHENTICATION REQUIRED
---    X'01': GSSAPI
---    X'02': USERNAME/PASSWORD
---    X'03': to X'7F' IANA ASSIGNED
---    X'80': to X'FE' RESERVED FOR PRIVATE METHODS
---    X'FF': NO ACCEPTABLE METHODS
-
-data Method
-  = NoAuth
-  | GSSAPI
-  | UserPass
-  | IANAAssigned Word8
-  | PrivateMethod Word8
-  | NoAcceptableMethods
-  | UnknownMethod Word8
-  deriving (Eq, Show)
-
-instance Binary Method where
-  put :: Method -> Put
-  put NoAuth = putWord8 0x00
-  put GSSAPI = putWord8 0x01
-  put UserPass = putWord8 0x02
-  put (IANAAssigned m) = putWord8 m
-  put (PrivateMethod m) = putWord8 m
-  put NoAcceptableMethods = putWord8 0xFF
-  put (UnknownMethod m) = putWord8 m
-
-  get :: Get Method
-  get = do
-    m <- getWord8
-    case m of
-      0x00 -> return NoAuth
-      0x01 -> return GSSAPI
-      0x02 -> return UserPass
-      _ | m >= 0x03 && m <= 0x7F -> return $ IANAAssigned m
-      _ | m >= 0x80 && m <= 0xFE -> return $ PrivateMethod m
-      0xFF -> return NoAcceptableMethods
-      _ -> return $ UnknownMethod m
-
--- | Address type used for destination addresses in SOCKS5 protocol.
-data Address
-  = AddressIPv4 IPv4
-  | AddressIPv6 IPv6
-  | AddressDomain LT.Text
-  deriving (Eq)
-
-instance Show Address where
-  show :: Address -> String
-  show (AddressIPv4 ip) = show ip
-  show (AddressIPv6 ip) = show ip
-  show (AddressDomain name) = LT.unpack name
-
-instance Binary Address where
-  put :: Address -> Put
-  put (AddressIPv4 ip) = do
-    putWord8 0x01
-    putWord32be $ fromIPv4w ip
-  put (AddressIPv6 ip) = do
-    putWord8 0x04
-    let (a1, a2, a3, a4) = fromIPv6w ip
-     in mapM_ putWord32be [a1, a2, a3, a4]
-  put (AddressDomain name) = do
-    putWord8 0x03
-    let encodedName = LTE.encodeUtf8 name
-        byteLength = LB.length encodedName
-    if byteLength > fromIntegral (maxBound :: Word8)
-      then
-        error $
-          "DomainName (UTF-8 encoded) has length of "
-            ++ show byteLength
-            ++ " bytes. Max allowed is "
-            ++ show (maxBound :: Word8)
-      else putWord8 $ fromIntegral byteLength
-    putLazyByteString encodedName
-
-  get :: Get Address
-  get = do
-    addrType <- getWord8
-    case addrType of
-      0x01 -> do
-        AddressIPv4 . toIPv4w <$> getWord32be
-      0x04 -> do
-        a1 <- getWord32be
-        a2 <- getWord32be
-        a3 <- getWord32be
-        a4 <- getWord32be
-        return $ AddressIPv6 $ toIPv6w (a1, a2, a3, a4)
-      0x03 -> do
-        len <- getWord8
-        encodedName <- getLazyByteString (fromIntegral len)
-        case LTE.decodeUtf8' encodedName of
-          Left err -> fail $ "Failed to decode UTF-8 domain name: " ++ show err
-          Right name -> return $ AddressDomain name
-      _ -> fail $ "Unknown address type: " ++ show addrType
-
-fromSockAddr_ :: SockAddr -> (Address, PortNumber)
-fromSockAddr_ (SockAddrInet port host) = (AddressIPv4 (fromHostAddress host), fromIntegral port)
-fromSockAddr_ (SockAddrInet6 port _ host _) = (AddressIPv6 (toIPv6w host), fromIntegral port)
-fromSockAddr_ path = error $ "Unexpected Unix socket address: " ++ show path
-
-toSockAddr_ :: Address -> PortNumber -> SockAddr
-toSockAddr_ (AddressIPv4 host) port = SockAddrInet (fromIntegral port) $ toHostAddress host
-toSockAddr_ (AddressIPv6 host) port = SockAddrInet6 (fromIntegral port) 0 (toHostAddress6 host) 0
-toSockAddr_ (AddressDomain host) _ = error $ "Address conversion for DomainName not implemented: " ++ show host
diff --git a/example/Main.hs b/example/Main.hs
--- a/example/Main.hs
+++ b/example/Main.hs
@@ -2,7 +2,6 @@
 
 import qualified Data.ByteString.Char8 as C8
 import Network.SOCKS5.Client
-import Network.SOCKS5.Types
 import Network.Socket.ByteString (recv, sendAll)
 
 main :: IO ()
@@ -13,10 +12,8 @@
             proxyPort = "11451",
             userPass = Nothing
           }
-  let destAddr = AddressDomain "example.com"
-  let destPort = "80"
 
-  runTCPConnect destAddr destPort proxyConfig $ \sock -> do
+  runTCPConnect "example.com" 80 proxyConfig $ \sock -> do
     putStrLn "Connected to example.com through SOCKS5 proxy!"
     sendAll sock "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
     response <- recv sock 4096
diff --git a/socks5.cabal b/socks5.cabal
--- a/socks5.cabal
+++ b/socks5.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: socks5
-version: 0.6.0.1
+version: 0.8.0.0
 synopsis: A SOCKS5 (RFC 1928) implementation
 description:
   This package provides a library with simple functions to create SOCKS5 clients and servers, along with a standalone server executable.
@@ -30,7 +30,6 @@
     Network.SOCKS5.Client
     Network.SOCKS5.Internal
     Network.SOCKS5.Server
-    Network.SOCKS5.Types
 
   build-depends:
     async ^>=2.2,
diff --git a/tests/Spec.hs b/tests/Spec.hs
--- a/tests/Spec.hs
+++ b/tests/Spec.hs
@@ -10,7 +10,6 @@
 import Network.Run.TCP
 import Network.SOCKS5.Client
 import Network.SOCKS5.Server
-import Network.SOCKS5.Types
 import Network.Socket
 import Network.Socket.ByteString
 import Network.TLS
@@ -22,8 +21,8 @@
   aroundAll withSOCKS5Server $ do
     describe "Network.SOCKS5.Client" $ do
       it "should run TCP connect successfully to ipinfo.io" $ \proxyConfig -> do
-        let destAddr = AddressDomain "ipinfo.io"
-            destPort = "80"
+        let destAddr = "ipinfo.io"
+            destPort = 80
         runTCPConnect destAddr destPort proxyConfig $ \sock -> do
           let request = C8.pack "GET / HTTP/1.1\r\nHost: ipinfo.io\r\nConnection: close\r\n\r\n"
           putStrLn "Sending HTTP GET request..."
@@ -37,8 +36,8 @@
           response `shouldSatisfy` ("HTTP/1.1 200 OK" `B.isPrefixOf`)
 
       it "should run BIND command successfully" $ \proxyConfig -> do
-        let destAddr = AddressIPv4 "8.8.8.8"
-            destPort = "80"
+        let destAddr = "8.8.8.8"
+            destPort = 80
         runTCPBind
           destAddr
           destPort
@@ -47,7 +46,7 @@
               void $ async $ do
                 threadDelay 200000
                 putStrLn $ "Third-party connector attempting to connect to " ++ show boundAddr ++ ":" ++ show boundPort
-                runTCPClient (show boundAddr) (show boundPort) $ \remoteSock -> do
+                runTCPClient boundAddr (show boundPort) $ \remoteSock -> do
                   putStrLn "Third-party connector: connection successful."
                   forever $ sendAll remoteSock "PING"
           )
@@ -79,8 +78,8 @@
   aroundAll withSOCKS5ServerTLS $ do
     describe "Network.SOCKS5.Client" $ do
       it "should run TCP connect successfully to ipinfo.io with TLS" $ \(proxyConfig, clientParams) -> do
-        let destAddr = AddressDomain "ipinfo.io"
-            destPort = "80"
+        let destAddr = "ipinfo.io"
+            destPort = 80
         runTCPConnectTLS destAddr destPort proxyConfig clientParams $ \ctx -> do
           let request = C8.pack "GET / HTTP/1.1\r\nHost: ipinfo.io\r\nConnection: close\r\n\r\n"
           putStrLn "Sending HTTP GET request..."
@@ -94,8 +93,8 @@
           response `shouldSatisfy` ("HTTP/1.1 200 OK" `B.isPrefixOf`)
 
       it "should run BIND command successfully with TLS" $ \(proxyConfig, clientParams) -> do
-        let destAddr = AddressIPv4 "8.8.8.8"
-            destPort = "80"
+        let destAddr = "8.8.8.8"
+            destPort = 80
         runTCPBindTLS
           destAddr
           destPort
@@ -105,7 +104,7 @@
               void $ async $ do
                 threadDelay 200000
                 putStrLn $ "Third-party connector attempting to connect to " ++ show boundAddr ++ ":" ++ show boundPort
-                runTCPClient (show boundAddr) (show boundPort) $ \remoteSock -> do
+                runTCPClient boundAddr (show boundPort) $ \remoteSock -> do
                   putStrLn "Third-party connector: connection successful."
                   sendAll remoteSock "PING"
           )
