diff --git a/client.go b/client.go
index 9746f739f27e0e94f39222fb27a5f60c725caa1f..97adec9afb87248d622c0b055eb0dbdf5e0cdc4b 100644
--- a/client.go
+++ b/client.go
@@ -487,7 +487,7 @@ func (c *Client) write(ctx context.Context, msg any, retry bool) error {
 			return err
 		}
 	}
-	err := c.writeConn.writeJSON(ctx, msg)
+	err := c.writeConn.WriteJSON(ctx, msg)
 	if err != nil {
 		c.writeConn = nil
 		if !retry {
@@ -562,7 +562,7 @@ func (c *Client) dispatch(codec ServerCodec) {
 
 		// Reconnect:
 		case newcodec := <-c.reconnected:
-			log.Debug().Bool("reading", reading).Str("conn", newcodec.remoteAddr()).Msg("RPC client reconnected")
+			log.Debug().Bool("reading", reading).Str("conn", newcodec.RemoteAddr()).Msg("RPC client reconnected")
 			if reading {
 				// Wait for the previous read loop to exit. This is a rare case which
 				// happens if this loop isn't notified in time after the connection breaks.
@@ -618,7 +618,7 @@ func (c *Client) read(codec ServerCodec) {
 	for {
 		msgs, batch, err := codec.ReadBatch()
 		if _, ok := err.(*json.SyntaxError); ok {
-			codec.writeJSON(context.Background(), errorMessage(&parseError{err.Error()}))
+			codec.WriteJSON(context.Background(), errorMessage(&parseError{err.Error()}))
 		}
 		if err != nil {
 			c.readErr <- err
diff --git a/handler.go b/handler.go
index b1afde1a620de9495b9d4388ceae8bad00e43c45..7154be94ab9b5adc4d46f65a36e9dd62c40fd9be 100644
--- a/handler.go
+++ b/handler.go
@@ -84,7 +84,7 @@ func newHandler(connCtx context.Context, conn jsonWriter, reg Router) *handler {
 		log:        zlog.Ctx(connCtx),
 	}
 	if h.peer.RemoteAddr != "" {
-		cl := h.log.With().Str("conn", conn.remoteAddr()).Logger()
+		cl := h.log.With().Str("conn", conn.RemoteAddr()).Logger()
 		h.log = &cl
 	}
 	h.unsubscribeCb = newCallback(reflect.Value{}, reflect.ValueOf(h.unsubscribe))
@@ -96,7 +96,7 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
 	// Emit error response for empty batches:
 	if len(msgs) == 0 {
 		h.startCallProc(func(cp *callProc) {
-			h.conn.writeJSON(cp.ctx, errorMessage(&invalidRequestError{"empty batch"}))
+			h.conn.WriteJSON(cp.ctx, errorMessage(&invalidRequestError{"empty batch"}))
 		})
 		return
 	}
@@ -120,7 +120,7 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
 		}
 		h.addSubscriptions(cp.notifiers)
 		if len(answers) > 0 {
-			h.conn.writeJSON(cp.ctx, answers)
+			h.conn.WriteJSON(cp.ctx, answers)
 		}
 		for _, n := range cp.notifiers {
 			n.activate()
@@ -137,7 +137,7 @@ func (h *handler) handleMsg(msg *jsonrpcMessage) {
 		answer := h.handleCallMsg(cp, msg)
 		h.addSubscriptions(cp.notifiers)
 		if answer != nil {
-			h.conn.writeJSON(cp.ctx, answer)
+			h.conn.WriteJSON(cp.ctx, answer)
 		}
 		for _, n := range cp.notifiers {
 			n.activate()
diff --git a/http.go b/http.go
index 463686378d1e5b3d30078e317b1d6d1bc584349f..3045457139b1fc721684314bfa7821c2f4f3840e 100644
--- a/http.go
+++ b/http.go
@@ -60,15 +60,15 @@ type httpConn struct {
 // and some methods don't work. The panic() stubs here exist to ensure
 // this special treatment is correct.
 
-func (hc *httpConn) writeJSON(context.Context, any) error {
-	panic("writeJSON called on httpConn")
+func (hc *httpConn) WriteJSON(context.Context, any) error {
+	panic("WriteJSON called on httpConn")
 }
 
 func (hc *httpConn) PeerInfo() PeerInfo {
 	panic("PeerInfo called on httpConn")
 }
 
-func (hc *httpConn) remoteAddr() string {
+func (hc *httpConn) RemoteAddr() string {
 	return hc.url
 }
 
@@ -271,8 +271,8 @@ func (c *httpServerConn) ReadBatch() (messages []*jsonrpcMessage, batch bool, er
 	return c.jc.ReadBatch()
 }
 
-func (c *httpServerConn) writeJSON(ctx context.Context, v any) error {
-	return c.jc.writeJSON(ctx, v)
+func (c *httpServerConn) WriteJSON(ctx context.Context, v any) error {
+	return c.jc.WriteJSON(ctx, v)
 }
 
 func (c *httpServerConn) close() {
@@ -287,10 +287,6 @@ func (c *httpServerConn) closed() <-chan any {
 // Close does nothing and always returns nil.
 func (t *httpServerConn) Close() error { return nil }
 
-func (c *httpServerConn) remoteAddr() string {
-	return c.RemoteAddr()
-}
-
 // RemoteAddr returns the peer address of the underlying connection.
 func (t *httpServerConn) RemoteAddr() string {
 	return t.PeerInfo().RemoteAddr
diff --git a/json.go b/json.go
index 2cc9d99e09438f86fa08c8d83ae3c9d30ceb0c8d..20e0b10a04244ed6363a33a82b0371af16bdf200 100644
--- a/json.go
+++ b/json.go
@@ -221,7 +221,7 @@ func (c *jsonCodec) PeerInfo() PeerInfo {
 	return PeerInfo{Transport: "ipc", RemoteAddr: c.remote}
 }
 
-func (c *jsonCodec) remoteAddr() string {
+func (c *jsonCodec) RemoteAddr() string {
 	return c.remote
 }
 
@@ -243,7 +243,7 @@ func (c *jsonCodec) ReadBatch() (messages []*jsonrpcMessage, batch bool, err err
 	return messages, batch, nil
 }
 
-func (c *jsonCodec) writeJSON(ctx context.Context, v any) error {
+func (c *jsonCodec) WriteJSON(ctx context.Context, v any) error {
 	c.encMu.Lock()
 	defer c.encMu.Unlock()
 
diff --git a/server.go b/server.go
index 4163d552f222d2407750edf3ef738fd894f97ee5..b80012275dd0b1e0a43c9236c9662dbc25b4312a 100644
--- a/server.go
+++ b/server.go
@@ -82,7 +82,7 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) {
 	reqs, batch, err := codec.ReadBatch()
 	if err != nil {
 		if err != io.EOF {
-			codec.writeJSON(ctx, errorMessage(&invalidMessageError{"parse error"}))
+			codec.WriteJSON(ctx, errorMessage(&invalidMessageError{"parse error"}))
 		}
 		return
 	}
diff --git a/subscription.go b/subscription.go
index 83420c9fe3bd8f4f67c4d694e9dbd4b910b94cb4..686d408e851ae0f03572c1fc122e8302b646a080 100644
--- a/subscription.go
+++ b/subscription.go
@@ -159,7 +159,7 @@ func (n *Notifier) activate() error {
 func (n *Notifier) send(sub *Subscription, data json.RawMessage) error {
 	params, _ := jzon.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data})
 	ctx := context.Background()
-	return n.h.conn.writeJSON(ctx, &jsonrpcMessage{
+	return n.h.conn.WriteJSON(ctx, &jsonrpcMessage{
 		Method: n.namespace + notificationMethodSuffix,
 		Params: params,
 	})
diff --git a/types.go b/types.go
index 5a0e309054e265e3e7f5c8e52f3756f596b0dd21..6f8bee586728fcf8d7a1dbf7b9e2a19926bbb9f5 100644
--- a/types.go
+++ b/types.go
@@ -38,11 +38,11 @@ type ServerCodec interface {
 // jsonWriter can write JSON messages to its underlying connection.
 // Implementations must be safe for concurrent use.
 type jsonWriter interface {
-	writeJSON(context.Context, any) error
+	WriteJSON(context.Context, any) error
 	// Closed returns a channel which is closed when the connection is closed.
 	closed() <-chan any
 	// RemoteAddr returns the peer address of the connection.
-	remoteAddr() string
+	RemoteAddr() string
 }
 
 // DecimalOrHex unmarshals a non-negative decimal or hex parameter into a uint64.
diff --git a/websocket.go b/websocket.go
index 94538b862c51b3d6bca01f68ef4051e331b8870d..f064764ed136192cdb3df2dc0c6b6db7f1adcd87 100644
--- a/websocket.go
+++ b/websocket.go
@@ -198,8 +198,8 @@ func (wc *websocketCodec) PeerInfo() PeerInfo {
 	return wc.info
 }
 
-func (wc *websocketCodec) writeJSON(ctx context.Context, v any) error {
-	err := wc.jsonCodec.writeJSON(ctx, v)
+func (wc *websocketCodec) WriteJSON(ctx context.Context, v any) error {
+	err := wc.jsonCodec.WriteJSON(ctx, v)
 	if err == nil {
 		// Notify pingLoop to delay the next idle ping.
 		select {