From 703c1869582d7cdffce2608fa765108eb0b5c3d8 Mon Sep 17 00:00:00 2001
From: fiatjaf <fiatjaf@gmail.com>
Date: Thu, 3 Apr 2025 14:50:25 -0300
Subject: [PATCH] much more colors everywhere and everything is prettier.

---
 bunker.go            |   2 +-
 count.go             |   2 +-
 dvm.go               |  12 ++--
 event.go             | 129 ++++++++++++++++++++++++++++++++++---------
 go.mod               |   2 +
 go.sum               |   6 --
 helpers.go           |  94 ++++++++++++++++++++++++++-----
 nostrfs/entitydir.go |   6 +-
 nostrfs/viewdir.go   |   6 +-
 req.go               |  36 ++----------
 wallet.go            |  30 +++-------
 11 files changed, 207 insertions(+), 118 deletions(-)

diff --git a/bunker.go b/bunker.go
index 0787e18..12e8e32 100644
--- a/bunker.go
+++ b/bunker.go
@@ -49,7 +49,7 @@ var bunker = &cli.Command{
 		qs := url.Values{}
 		relayURLs := make([]string, 0, c.Args().Len())
 		if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
-			relays := connectToAllRelays(ctx, relayUrls, nil)
+			relays := connectToAllRelays(ctx, c, relayUrls, nil)
 			if len(relays) == 0 {
 				log("failed to connect to any of the given relays.\n")
 				os.Exit(3)
diff --git a/count.go b/count.go
index 26157b0..c01b65e 100644
--- a/count.go
+++ b/count.go
@@ -70,7 +70,7 @@ var count = &cli.Command{
 		biggerUrlSize := 0
 		relayUrls := c.Args().Slice()
 		if len(relayUrls) > 0 {
-			relays := connectToAllRelays(ctx, relayUrls, nil)
+			relays := connectToAllRelays(ctx, c, relayUrls, nil)
 			if len(relays) == 0 {
 				log("failed to connect to any of the given relays.\n")
 				os.Exit(3)
diff --git a/dvm.go b/dvm.go
index 88ed9cc..5e05e39 100644
--- a/dvm.go
+++ b/dvm.go
@@ -7,7 +7,6 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/fatih/color"
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/nip90"
 	"github.com/urfave/cli/v3"
@@ -60,7 +59,7 @@ var dvm = &cli.Command{
 				Flags:                     flags,
 				Action: func(ctx context.Context, c *cli.Command) error {
 					relayUrls := c.StringSlice("relay")
-					relays := connectToAllRelays(ctx, relayUrls, nil)
+					relays := connectToAllRelays(ctx, c, relayUrls, nil)
 					if len(relays) == 0 {
 						log("failed to connect to any of the given relays.\n")
 						os.Exit(3)
@@ -103,10 +102,7 @@ var dvm = &cli.Command{
 					log("- publishing job request... ")
 					first := true
 					for res := range sys.Pool.PublishMany(ctx, relayUrls, evt) {
-						cleanUrl, ok := strings.CutPrefix(res.RelayURL, "wss://")
-						if !ok {
-							cleanUrl = res.RelayURL
-						}
+						cleanUrl, _ := strings.CutPrefix(res.RelayURL, "wss://")
 
 						if !first {
 							log(", ")
@@ -114,9 +110,9 @@ var dvm = &cli.Command{
 						first = false
 
 						if res.Error != nil {
-							log("%s: %s", color.RedString(cleanUrl), res.Error)
+							log("%s: %s", colors.errorf(cleanUrl), res.Error)
 						} else {
-							log("%s: ok", color.GreenString(cleanUrl))
+							log("%s: ok", colors.successf(cleanUrl))
 						}
 					}
 
diff --git a/event.go b/event.go
index 5bae947..f4b86bb 100644
--- a/event.go
+++ b/event.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/fatih/color"
 	"github.com/mailru/easyjson"
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/nip13"
@@ -133,8 +134,17 @@ example:
 	Action: func(ctx context.Context, c *cli.Command) error {
 		// try to connect to the relays here
 		var relays []*nostr.Relay
+
+		// these are defaults, they will be replaced if we use the magic dynamic thing
+		logthis := func(relayUrl string, s string, args ...any) { log(s, args...) }
+		colorizethis := func(relayUrl string, colorize func(string, ...any) string) {}
+
 		if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
-			relays = connectToAllRelays(ctx, relayUrls, nil)
+			relays = connectToAllRelays(ctx, c, relayUrls, nil,
+				nostr.WithAuthHandler(func(ctx context.Context, authEvent nostr.RelayEvent) error {
+					return authSigner(ctx, c, func(s string, args ...any) {}, authEvent)
+				}),
+			)
 			if len(relays) == 0 {
 				log("failed to connect to any of the given relays.\n")
 				os.Exit(3)
@@ -301,37 +311,104 @@ example:
 			successRelays := make([]string, 0, len(relays))
 			if len(relays) > 0 {
 				os.Stdout.Sync()
-				for _, relay := range relays {
-				publish:
-					log("publishing to %s... ", relay.URL)
+
+				if supportsDynamicMultilineMagic() {
+					// overcomplicated multiline rendering magic
 					ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 					defer cancel()
 
-					err := relay.Publish(ctx, evt)
-					if err == nil {
-						// published fine
-						log("success.\n")
-						successRelays = append(successRelays, relay.URL)
-						continue // continue to next relay
-					}
-
-					// error publishing
-					if strings.HasPrefix(err.Error(), "msg: auth-required:") && kr != nil && doAuth {
-						// if the relay is requesting auth and we can auth, let's do it
-						pk, _ := kr.GetPublicKey(ctx)
-						log("performing auth as %s... ", pk)
-						if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
-							return kr.SignEvent(ctx, authEvent)
-						}); err == nil {
-							// try to publish again, but this time don't try to auth again
-							doAuth = false
-							goto publish
-						} else {
-							log("auth error: %s. ", err)
+					urls := make([]string, len(relays))
+					lines := make([][][]byte, len(urls))
+					flush := func() {
+						for _, line := range lines {
+							for _, part := range line {
+								os.Stderr.Write(part)
+							}
+							os.Stderr.Write([]byte{'\n'})
 						}
 					}
-					log("failed: %s\n", err)
+					render := func() {
+						clearLines(len(lines))
+						flush()
+					}
+					flush()
+
+					logthis = func(relayUrl, s string, args ...any) {
+						idx := slices.Index(urls, relayUrl)
+						lines[idx] = append(lines[idx], []byte(fmt.Sprintf(s, args...)))
+						render()
+					}
+					colorizethis = func(relayUrl string, colorize func(string, ...any) string) {
+						cleanUrl, _ := strings.CutPrefix(relayUrl, "wss://")
+						idx := slices.Index(urls, relayUrl)
+						lines[idx][0] = []byte(fmt.Sprintf("publishing to %s... ", colorize(cleanUrl)))
+						render()
+					}
+
+					for i, relay := range relays {
+						urls[i] = relay.URL
+						lines[i] = make([][]byte, 1, 3)
+						colorizethis(relay.URL, color.CyanString)
+					}
+					render()
+
+					for res := range sys.Pool.PublishMany(ctx, urls, evt) {
+						if res.Error == nil {
+							colorizethis(res.RelayURL, colors.successf)
+							logthis(res.RelayURL, "success.")
+							successRelays = append(successRelays, res.RelayURL)
+						} else {
+							colorizethis(res.RelayURL, colors.errorf)
+
+							// in this case it's likely that the lowest-level error is the one that will be more helpful
+							low := unwrapAll(res.Error)
+
+							// hack for some messages such as from relay.westernbtc.com
+							msg := strings.ReplaceAll(low.Error(), evt.PubKey, "author")
+
+							// do not allow the message to overflow the term window
+							msg = clampMessage(msg, 20+len(res.RelayURL))
+
+							logthis(res.RelayURL, msg)
+						}
+					}
+				} else {
+					// normal dumb flow
+					for _, relay := range relays {
+					publish:
+						cleanUrl, _ := strings.CutPrefix(relay.URL, "wss://")
+						log("publishing to %s... ", color.CyanString(cleanUrl))
+						ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+						defer cancel()
+
+						err := relay.Publish(ctx, evt)
+						if err == nil {
+							// published fine
+							log("success.\n")
+							successRelays = append(successRelays, relay.URL)
+							continue // continue to next relay
+						}
+
+						// error publishing
+						if strings.HasPrefix(err.Error(), "msg: auth-required:") && kr != nil && doAuth {
+							// if the relay is requesting auth and we can auth, let's do it
+							pk, _ := kr.GetPublicKey(ctx)
+							npub, _ := nip19.EncodePublicKey(pk)
+							log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:]))
+							if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
+								return kr.SignEvent(ctx, authEvent)
+							}); err == nil {
+								// try to publish again, but this time don't try to auth again
+								doAuth = false
+								goto publish
+							} else {
+								log("auth error: %s. ", err)
+							}
+						}
+						log("failed: %s\n", err)
+					}
 				}
+
 				if len(successRelays) > 0 && c.Bool("nevent") {
 					nevent, _ := nip19.EncodeEvent(evt.ID, successRelays, evt.PubKey)
 					log(nevent + "\n")
diff --git a/go.mod b/go.mod
index 4b7b3a6..add0a56 100644
--- a/go.mod
+++ b/go.mod
@@ -77,3 +77,5 @@ require (
 	golang.org/x/sys v0.31.0 // indirect
 	golang.org/x/text v0.23.0 // indirect
 )
+
+replace github.com/nbd-wtf/go-nostr => ../go-nostr
diff --git a/go.sum b/go.sum
index 9a3a22d..af918e9 100644
--- a/go.sum
+++ b/go.sum
@@ -35,8 +35,6 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
 github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
 github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
-github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
-github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
 github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
 github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@@ -55,8 +53,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
 github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
 github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
-github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
-github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
 github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
 github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
 github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -157,8 +153,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/nbd-wtf/go-nostr v0.51.8 h1:CIoS+YqChcm4e1L1rfMZ3/mIwTz4CwApM2qx7MHNzmE=
-github.com/nbd-wtf/go-nostr v0.51.8/go.mod h1:d6+DfvMWYG5pA3dmNMBJd6WCHVDDhkXbHqvfljf0Gzg=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
diff --git a/helpers.go b/helpers.go
index 184136b..247dd83 100644
--- a/helpers.go
+++ b/helpers.go
@@ -3,6 +3,7 @@ package main
 import (
 	"bufio"
 	"context"
+	"errors"
 	"fmt"
 	"iter"
 	"math/rand"
@@ -19,6 +20,7 @@ import (
 	"github.com/fatih/color"
 	jsoniter "github.com/json-iterator/go"
 	"github.com/nbd-wtf/go-nostr"
+	"github.com/nbd-wtf/go-nostr/nip19"
 	"github.com/nbd-wtf/go-nostr/sdk"
 	"github.com/urfave/cli/v3"
 	"golang.org/x/term"
@@ -151,8 +153,9 @@ func normalizeAndValidateRelayURLs(wsurls []string) error {
 
 func connectToAllRelays(
 	ctx context.Context,
+	c *cli.Command,
 	relayUrls []string,
-	preAuthSigner func(ctx context.Context, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error), // if this exists we will force preauth
+	preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error), // if this exists we will force preauth
 	opts ...nostr.PoolOption,
 ) []*nostr.Relay {
 	sys.Pool = nostr.NewSimplePool(context.Background(),
@@ -198,7 +201,7 @@ func connectToAllRelays(
 			colorizepreamble(color.CyanString)
 
 			go func() {
-				relay := connectToSingleRelay(ctx, url, preAuthSigner, colorizepreamble, logthis)
+				relay := connectToSingleRelay(ctx, c, url, preAuthSigner, colorizepreamble, logthis)
 				if relay != nil {
 					relays = append(relays, relay)
 				}
@@ -210,7 +213,7 @@ func connectToAllRelays(
 		// simple flow
 		for _, url := range relayUrls {
 			log("connecting to %s... ", color.CyanString(url))
-			relay := connectToSingleRelay(ctx, url, preAuthSigner, nil, log)
+			relay := connectToSingleRelay(ctx, c, url, preAuthSigner, nil, log)
 			if relay != nil {
 				relays = append(relays, relay)
 			}
@@ -223,8 +226,9 @@ func connectToAllRelays(
 
 func connectToSingleRelay(
 	ctx context.Context,
+	c *cli.Command,
 	url string,
-	preAuthSigner func(ctx context.Context, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error),
+	preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error),
 	colorizepreamble func(c func(string, ...any) string),
 	logthis func(s string, args ...any),
 ) *nostr.Relay {
@@ -242,7 +246,7 @@ func connectToSingleRelay(
 					if challengeTag[1] == "" {
 						return fmt.Errorf("auth not received yet *****") // what a giant hack
 					}
-					return preAuthSigner(ctx, logthis, nostr.RelayEvent{Event: authEvent, Relay: relay})
+					return preAuthSigner(ctx, c, logthis, nostr.RelayEvent{Event: authEvent, Relay: relay})
 				}); err == nil {
 					// auth succeeded
 					goto preauthSuccess
@@ -255,7 +259,7 @@ func connectToSingleRelay(
 					} else {
 						// it failed for some other reason, so skip this relay
 						if colorizepreamble != nil {
-							colorizepreamble(color.RedString)
+							colorizepreamble(colors.errorf)
 						}
 						logthis(err.Error())
 						return nil
@@ -263,7 +267,7 @@ func connectToSingleRelay(
 				}
 			}
 			if colorizepreamble != nil {
-				colorizepreamble(color.RedString)
+				colorizepreamble(colors.errorf)
 			}
 			logthis("failed to get an AUTH challenge in enough time.")
 			return nil
@@ -271,15 +275,18 @@ func connectToSingleRelay(
 
 	preauthSuccess:
 		if colorizepreamble != nil {
-			colorizepreamble(color.GreenString)
+			colorizepreamble(colors.successf)
 		}
 		logthis("ok.")
 		return relay
 	} else {
 		if colorizepreamble != nil {
-			colorizepreamble(color.RedString)
+			colorizepreamble(colors.errorf)
 		}
-		logthis(err.Error())
+
+		// if we're here that means we've failed to connect, this may be a huge message
+		// but we're likely to only be interested in the lowest level error (although we can leave space)
+		logthis(clampError(err, len(url)+12))
 		return nil
 	}
 }
@@ -309,6 +316,29 @@ func supportsDynamicMultilineMagic() bool {
 	return true
 }
 
+func authSigner(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error) {
+	defer func() {
+		if err != nil {
+			cleanUrl, _ := strings.CutPrefix(authEvent.Relay.URL, "wss://")
+			log("%s auth failed: %s", colors.errorf(cleanUrl), err)
+		}
+	}()
+
+	if !c.Bool("auth") && !c.Bool("force-pre-auth") {
+		return fmt.Errorf("auth required, but --auth flag not given")
+	}
+	kr, _, err := gatherKeyerFromArguments(ctx, c)
+	if err != nil {
+		return err
+	}
+
+	pk, _ := kr.GetPublicKey(ctx)
+	npub, _ := nip19.EncodePublicKey(pk)
+	log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:]))
+
+	return kr.SignEvent(ctx, authEvent.Event)
+}
+
 func lineProcessingError(ctx context.Context, msg string, args ...any) context.Context {
 	log(msg+"\n", args...)
 	return context.WithValue(ctx, LINE_PROCESSING_ERROR, true)
@@ -334,16 +364,50 @@ func leftPadKey(k string) string {
 	return strings.Repeat("0", 64-len(k)) + k
 }
 
+func unwrapAll(err error) error {
+	low := err
+	for n := low; n != nil; n = errors.Unwrap(low) {
+		low = n
+	}
+	return low
+}
+
+func clampMessage(msg string, prefixAlreadyPrinted int) string {
+	termSize, _, _ := term.GetSize(0)
+	if len(msg) > termSize-prefixAlreadyPrinted {
+		msg = msg[0:termSize-prefixAlreadyPrinted-1] + "…"
+	}
+	return msg
+}
+
+func clampError(err error, prefixAlreadyPrinted int) string {
+	termSize, _, _ := term.GetSize(0)
+	msg := err.Error()
+	if len(msg) > termSize-prefixAlreadyPrinted {
+		err = unwrapAll(err)
+		msg = clampMessage(err.Error(), prefixAlreadyPrinted)
+	}
+	return msg
+}
+
 var colors = struct {
-	reset   func(...any) (int, error)
-	italic  func(...any) string
-	italicf func(string, ...any) string
-	bold    func(...any) string
-	boldf   func(string, ...any) string
+	reset    func(...any) (int, error)
+	italic   func(...any) string
+	italicf  func(string, ...any) string
+	bold     func(...any) string
+	boldf    func(string, ...any) string
+	error    func(...any) string
+	errorf   func(string, ...any) string
+	success  func(...any) string
+	successf func(string, ...any) string
 }{
 	color.New(color.Reset).Print,
 	color.New(color.Italic).Sprint,
 	color.New(color.Italic).Sprintf,
 	color.New(color.Bold).Sprint,
 	color.New(color.Bold).Sprintf,
+	color.New(color.Bold, color.FgHiRed).Sprint,
+	color.New(color.Bold, color.FgHiRed).Sprintf,
+	color.New(color.Bold, color.FgHiGreen).Sprint,
+	color.New(color.Bold, color.FgHiGreen).Sprintf,
 }
diff --git a/nostrfs/entitydir.go b/nostrfs/entitydir.go
index 852a53f..ed8bd76 100644
--- a/nostrfs/entitydir.go
+++ b/nostrfs/entitydir.go
@@ -348,11 +348,7 @@ func (e *EntityDir) handleWrite() {
 		success := false
 		first := true
 		for res := range e.root.sys.Pool.PublishMany(e.root.ctx, relays, evt) {
-			cleanUrl, ok := strings.CutPrefix(res.RelayURL, "wss://")
-			if !ok {
-				cleanUrl = res.RelayURL
-			}
-
+			cleanUrl, _ := strings.CutPrefix(res.RelayURL, "wss://")
 			if !first {
 				log(", ")
 			}
diff --git a/nostrfs/viewdir.go b/nostrfs/viewdir.go
index b861290..1dbbd90 100644
--- a/nostrfs/viewdir.go
+++ b/nostrfs/viewdir.go
@@ -179,11 +179,7 @@ func (n *ViewDir) publishNote() {
 	success := false
 	first := true
 	for res := range n.root.sys.Pool.PublishMany(n.root.ctx, relays, evt) {
-		cleanUrl, ok := strings.CutPrefix(res.RelayURL, "wss://")
-		if !ok {
-			cleanUrl = res.RelayURL
-		}
-
+		cleanUrl, _ := strings.CutPrefix(res.RelayURL, "wss://")
 		if !first {
 			log(", ")
 		}
diff --git a/req.go b/req.go
index a2b8d92..3221fdd 100644
--- a/req.go
+++ b/req.go
@@ -9,7 +9,6 @@ import (
 	"github.com/fatih/color"
 	"github.com/mailru/easyjson"
 	"github.com/nbd-wtf/go-nostr"
-	"github.com/nbd-wtf/go-nostr/nip19"
 	"github.com/nbd-wtf/go-nostr/nip77"
 	"github.com/urfave/cli/v3"
 )
@@ -79,44 +78,21 @@ example:
 		relayUrls := c.Args().Slice()
 		if len(relayUrls) > 0 {
 			// this is used both for the normal AUTH (after "auth-required:" is received) or forced pre-auth
-			authSigner := func(ctx context.Context, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error) {
-				defer func() {
-					if err != nil {
-						log("%s failed: %s",
-							authEvent.Tags.Find("relay")[1],
-							err,
-						)
-					}
-				}()
-
-				if !c.Bool("auth") && !c.Bool("force-pre-auth") {
-					return fmt.Errorf("auth required, but --auth flag not given")
-				}
-				kr, _, err := gatherKeyerFromArguments(ctx, c)
-				if err != nil {
-					return err
-				}
-
-				pk, _ := kr.GetPublicKey(ctx)
-				npub, _ := nip19.EncodePublicKey(pk)
-				log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:]))
-
-				return kr.SignEvent(ctx, authEvent.Event)
-			}
-
 			// connect to all relays we expect to use in this call in parallel
 			forcePreAuthSigner := authSigner
 			if !c.Bool("force-pre-auth") {
 				forcePreAuthSigner = nil
 			}
-			relays := connectToAllRelays(ctx,
+			relays := connectToAllRelays(
+				ctx,
+				c,
 				relayUrls,
 				forcePreAuthSigner,
 				nostr.WithAuthHandler(func(ctx context.Context, authEvent nostr.RelayEvent) error {
-					return authSigner(ctx, func(s string, args ...any) {
+					return authSigner(ctx, c, func(s string, args ...any) {
 						if strings.HasPrefix(s, "authenticating as") {
-							url := authEvent.Tags.Find("relay")[1]
-							s = "authenticating to " + color.CyanString(url) + " as" + s[len("authenticating as"):]
+							cleanUrl, _ := strings.CutPrefix(authEvent.Relay.URL, "wss://")
+							s = "authenticating to " + color.CyanString(cleanUrl) + " as" + s[len("authenticating as"):]
 						}
 						log(s+"\n", args...)
 					}, authEvent)
diff --git a/wallet.go b/wallet.go
index aebde63..088aaa5 100644
--- a/wallet.go
+++ b/wallet.go
@@ -6,7 +6,6 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/fatih/color"
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/nip60"
 	"github.com/nbd-wtf/go-nostr/nip61"
@@ -60,20 +59,16 @@ func prepareWallet(ctx context.Context, c *cli.Command) (*nip60.Wallet, func(),
 		log("- saving kind:%d event (%s)... ", event.Kind, desc)
 		first := true
 		for res := range sys.Pool.PublishMany(ctx, relays, event) {
-			cleanUrl, ok := strings.CutPrefix(res.RelayURL, "wss://")
-			if !ok {
-				cleanUrl = res.RelayURL
-			}
-
+			cleanUrl, _ := strings.CutPrefix(res.RelayURL, "wss://")
 			if !first {
 				log(", ")
 			}
 			first = false
 
 			if res.Error != nil {
-				log("%s: %s", color.RedString(cleanUrl), res.Error)
+				log("%s: %s", colors.errorf(cleanUrl), res.Error)
 			} else {
-				log("%s: ok", color.GreenString(cleanUrl))
+				log("%s: ok", colors.successf(cleanUrl))
 			}
 		}
 		log("\n")
@@ -376,19 +371,15 @@ var wallet = &cli.Command{
 				log("- publishing nutzap... ")
 				first := true
 				for res := range results {
-					cleanUrl, ok := strings.CutPrefix(res.RelayURL, "wss://")
-					if !ok {
-						cleanUrl = res.RelayURL
-					}
-
+					cleanUrl, _ := strings.CutPrefix(res.RelayURL, "wss://")
 					if !first {
 						log(", ")
 					}
 					first = false
 					if res.Error != nil {
-						log("%s: %s", color.RedString(cleanUrl), res.Error)
+						log("%s: %s", colors.errorf(cleanUrl), res.Error)
 					} else {
-						log("%s: ok", color.GreenString(cleanUrl))
+						log("%s: ok", colors.successf(cleanUrl))
 					}
 				}
 
@@ -463,10 +454,7 @@ var wallet = &cli.Command{
 						log("- saving kind:10019 event... ")
 						first := true
 						for res := range sys.Pool.PublishMany(ctx, relays, evt) {
-							cleanUrl, ok := strings.CutPrefix(res.RelayURL, "wss://")
-							if !ok {
-								cleanUrl = res.RelayURL
-							}
+							cleanUrl, _ := strings.CutPrefix(res.RelayURL, "wss://")
 
 							if !first {
 								log(", ")
@@ -474,9 +462,9 @@ var wallet = &cli.Command{
 							first = false
 
 							if res.Error != nil {
-								log("%s: %s", color.RedString(cleanUrl), res.Error)
+								log("%s: %s", colors.errorf(cleanUrl), res.Error)
 							} else {
-								log("%s: ok", color.GreenString(cleanUrl))
+								log("%s: ok", colors.successf(cleanUrl))
 							}
 						}