Compare commits

...

27 Commits

Author SHA1 Message Date
fiatjaf
1e353680bc wallet: adapt to single-wallet nip60 mode and implement nutzapping. 2025-02-04 22:38:40 -03:00
fiatjaf
ff8701a3b0 fetch: stop adding kind:0 to all requests when they already have a kind specified. 2025-02-03 15:54:12 -03:00
fiatjaf
ad6b8c4ba5 more mcp stuff. 2025-02-02 00:21:25 -03:00
fiatjaf
dba3f648ad experimental mcp server. 2025-01-31 23:44:03 -03:00
fiatjaf
12a1f1563e wallet pay and fix missing tokens because the program was exiting before they were saved. 2025-01-30 19:59:54 -03:00
fiatjaf
e2dd3ca544 fix -v verbose flag (it was being used by the default --version flag). 2025-01-30 19:58:17 -03:00
fiatjaf
df5ebd3f56 global verbose flag. 2025-01-30 16:06:16 -03:00
fiatjaf
81571c6952 global bold/italic helpers. 2025-01-30 16:05:51 -03:00
fiatjaf
6e43a6b733 reword NIP-XX to nipXX everywhere. 2025-01-29 19:13:30 -03:00
fiatjaf
943e8835f9 nak wallet 2025-01-28 23:50:09 -03:00
fiatjaf
6b659c1552 fix @mleku unnecessary newlines. 2025-01-28 23:50:09 -03:00
mleku
aa53f2cd60 show env var in help, reset terminal mode correctly 2025-01-21 16:14:59 -03:00
fiatjaf
5509095277 nak outbox 2025-01-18 18:40:19 -03:00
fiatjaf
a3ef9b45de update go-nostr to solve some websocket things like a stupid limit to the websocket message. 2025-01-18 15:45:08 -03:00
fiatjaf
df20a3241a update go-nostr to fix url path normalization (@pablof7z's @primalhq NWC bug). 2025-01-16 21:46:43 -03:00
fiatjaf
53a2451303 update go-nostr and adjust user-agent stuff.
this changes the websocket library we were using.
2025-01-16 16:02:08 -03:00
fiatjaf
2d992f235e bunker: fix MarshalIndent() is not allowed to have a prefix on json-iterator. 2024-12-13 23:27:08 -03:00
franzap
7675929056 Add zapstore.yaml 2024-12-11 19:02:56 -03:00
fiatjaf
7f608588a2 improve count and support hyperloglog aggregation. 2024-12-10 23:28:36 -03:00
fiatjaf
fd5cd55f6f replace encoding/json with json-iterator everywhere so we get rid of HTML encoding and maybe be faster. 2024-12-03 00:43:52 -03:00
redraw
932361fe8f fix(decode): handle event id flag 2024-12-02 10:25:38 -03:00
redraw
11ae7bc4d3 add test ExampleDecodePubkey 2024-12-02 09:11:19 -03:00
redraw
7033bfee19 fix(decode): handle pubkey flag 2024-12-02 09:11:19 -03:00
fiatjaf
f425097c5a allow filters with long tags (the 1-char restriction is only a convention, not a rule).
fixes https://github.com/fiatjaf/nak/issues/44
2024-11-26 12:05:40 -03:00
fiatjaf
dd0ef2ca64 relay management examples on help. 2024-11-25 12:50:47 -03:00
Yasuhiro Matsumoto
491a094e07 close ch 2024-11-23 08:14:13 -03:00
fiatjaf
9d619ddf00 remove note1 encoding. 2024-11-19 07:47:11 -03:00
21 changed files with 1116 additions and 167 deletions

View File

@@ -2,10 +2,10 @@ package main
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"slices"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -15,12 +15,11 @@ import (
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/nip46" "github.com/nbd-wtf/go-nostr/nip46"
"golang.org/x/exp/slices"
) )
var bunker = &cli.Command{ var bunker = &cli.Command{
Name: "bunker", Name: "bunker",
Usage: "starts a NIP-46 signer daemon with the given --sec key", Usage: "starts a nip46 signer daemon with the given --sec key",
ArgsUsage: "[relay...]", ArgsUsage: "[relay...]",
Description: ``, Description: ``,
DisableSliceFlagSeparator: true, DisableSliceFlagSeparator: true,
@@ -84,8 +83,6 @@ var bunker = &cli.Command{
return err return err
} }
npub, _ := nip19.EncodePublicKey(pubkey) npub, _ := nip19.EncodePublicKey(pubkey)
bold := color.New(color.Bold).Sprint
italic := color.New(color.Italic).Sprint
// this function will be called every now and then // this function will be called every now and then
printBunkerInfo := func() { printBunkerInfo := func() {
@@ -94,12 +91,12 @@ var bunker = &cli.Command{
authorizedKeysStr := "" authorizedKeysStr := ""
if len(authorizedKeys) != 0 { if len(authorizedKeys) != 0 {
authorizedKeysStr = "\n authorized keys:\n - " + italic(strings.Join(authorizedKeys, "\n - ")) authorizedKeysStr = "\n authorized keys:\n - " + colors.italic(strings.Join(authorizedKeys, "\n - "))
} }
authorizedSecretsStr := "" authorizedSecretsStr := ""
if len(authorizedSecrets) != 0 { if len(authorizedSecrets) != 0 {
authorizedSecretsStr = "\n authorized secrets:\n - " + italic(strings.Join(authorizedSecrets, "\n - ")) authorizedSecretsStr = "\n authorized secrets:\n - " + colors.italic(strings.Join(authorizedSecrets, "\n - "))
} }
preauthorizedFlags := "" preauthorizedFlags := ""
@@ -131,13 +128,13 @@ var bunker = &cli.Command{
) )
log("listening at %v:\n pubkey: %s \n npub: %s%s%s\n to restart: %s\n bunker: %s\n\n", log("listening at %v:\n pubkey: %s \n npub: %s%s%s\n to restart: %s\n bunker: %s\n\n",
bold(relayURLs), colors.bold(relayURLs),
bold(pubkey), colors.bold(pubkey),
bold(npub), colors.bold(npub),
authorizedKeysStr, authorizedKeysStr,
authorizedSecretsStr, authorizedSecretsStr,
color.CyanString(restartCommand), color.CyanString(restartCommand),
bold(bunkerURI), colors.bold(bunkerURI),
) )
} }
printBunkerInfo() printBunkerInfo()

View File

@@ -2,19 +2,20 @@ package main
import ( import (
"context" "context"
"encoding/json"
"errors"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/fiatjaf/cli/v3" "github.com/fiatjaf/cli/v3"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip45"
"github.com/nbd-wtf/go-nostr/nip45/hyperloglog"
) )
var count = &cli.Command{ var count = &cli.Command{
Name: "count", Name: "count",
Usage: "generates encoded COUNT messages and optionally use them to talk to relays", Usage: "generates encoded COUNT messages and optionally use them to talk to relays",
Description: `outputs a NIP-45 request (the flags are mostly the same as 'nak req').`, Description: `outputs a nip45 request (the flags are mostly the same as 'nak req').`,
DisableSliceFlagSeparator: true, DisableSliceFlagSeparator: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringSliceFlag{ &cli.StringSliceFlag{
@@ -66,6 +67,32 @@ var count = &cli.Command{
}, },
ArgsUsage: "[relay...]", ArgsUsage: "[relay...]",
Action: func(ctx context.Context, c *cli.Command) error { Action: func(ctx context.Context, c *cli.Command) error {
biggerUrlSize := 0
relayUrls := c.Args().Slice()
if len(relayUrls) > 0 {
relays := connectToAllRelays(ctx,
relayUrls,
false,
)
if len(relays) == 0 {
log("failed to connect to any of the given relays.\n")
os.Exit(3)
}
relayUrls = make([]string, len(relays))
for i, relay := range relays {
relayUrls[i] = relay.URL
if len(relay.URL) > biggerUrlSize {
biggerUrlSize = len(relay.URL)
}
}
defer func() {
for _, relay := range relays {
relay.Close()
}
}()
}
filter := nostr.Filter{} filter := nostr.Filter{}
if authors := c.StringSlice("author"); len(authors) > 0 { if authors := c.StringSlice("author"); len(authors) > 0 {
@@ -85,7 +112,7 @@ var count = &cli.Command{
tags := make([][]string, 0, 5) tags := make([][]string, 0, 5)
for _, tagFlag := range c.StringSlice("tag") { for _, tagFlag := range c.StringSlice("tag") {
spl := strings.SplitN(tagFlag, "=", 2) spl := strings.SplitN(tagFlag, "=", 2)
if len(spl) == 2 && len(spl[0]) == 1 { if len(spl) == 2 {
tags = append(tags, spl) tags = append(tags, spl)
} else { } else {
return fmt.Errorf("invalid --tag '%s'", tagFlag) return fmt.Errorf("invalid --tag '%s'", tagFlag)
@@ -119,26 +146,35 @@ var count = &cli.Command{
filter.Limit = int(limit) filter.Limit = int(limit)
} }
relays := c.Args().Slice()
successes := 0 successes := 0
failures := make([]error, 0, len(relays)) if len(relayUrls) > 0 {
if len(relays) > 0 { var hll *hyperloglog.HyperLogLog
for _, relayUrl := range relays { if offset := nip45.HyperLogLogEventPubkeyOffsetForFilter(filter); offset != -1 && len(relayUrls) > 1 {
relay, err := nostr.RelayConnect(ctx, relayUrl) hll = hyperloglog.New(offset)
}
for _, relayUrl := range relayUrls {
relay, _ := sys.Pool.EnsureRelay(relayUrl)
count, hllRegisters, err := relay.Count(ctx, nostr.Filters{filter})
fmt.Fprintf(os.Stderr, "%s%s: ", strings.Repeat(" ", biggerUrlSize-len(relayUrl)), relayUrl)
if err != nil { if err != nil {
failures = append(failures, err) fmt.Fprintf(os.Stderr, "❌ %s\n", err)
continue continue
} }
count, err := relay.Count(ctx, nostr.Filters{filter})
if err != nil { var hasHLLStr string
failures = append(failures, err) if hll != nil && len(hllRegisters) == 256 {
continue hll.MergeRegisters(hllRegisters)
hasHLLStr = " 📋"
} }
fmt.Printf("%s: %d\n", relay.URL, count)
fmt.Fprintf(os.Stderr, "%d%s\n", count, hasHLLStr)
successes++ successes++
} }
if successes == 0 { if successes == 0 {
return errors.Join(failures...) return fmt.Errorf("all relays have failed")
} else if hll != nil {
fmt.Fprintf(os.Stderr, "📋 HyperLogLog sum: %d\n", hll.Count())
} }
} else { } else {
// no relays given, will just print the filter // no relays given, will just print the filter

View File

@@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json"
"strings" "strings"
"github.com/fiatjaf/cli/v3" "github.com/fiatjaf/cli/v3"
@@ -56,8 +55,16 @@ var decode = &cli.Command{
} }
} else if evp := sdk.InputToEventPointer(input); evp != nil { } else if evp := sdk.InputToEventPointer(input); evp != nil {
decodeResult = DecodeResult{EventPointer: evp} decodeResult = DecodeResult{EventPointer: evp}
if c.Bool("id") {
stdout(evp.ID)
continue
}
} else if pp := sdk.InputToProfile(ctx, input); pp != nil { } else if pp := sdk.InputToProfile(ctx, input); pp != nil {
decodeResult = DecodeResult{ProfilePointer: pp} decodeResult = DecodeResult{ProfilePointer: pp}
if c.Bool("pubkey") {
stdout(pp.PublicKey)
continue
}
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" { } else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
if ep, ok := value.(nostr.EntityPointer); ok { if ep, ok := value.(nostr.EntityPointer); ok {
decodeResult = DecodeResult{EntityPointer: &ep} decodeResult = DecodeResult{EntityPointer: &ep}
@@ -72,6 +79,10 @@ var decode = &cli.Command{
continue continue
} }
if c.Bool("pubkey") || c.Bool("id") {
return nil
}
stdout(decodeResult.JSON()) stdout(decodeResult.JSON())
} }

View File

@@ -153,7 +153,7 @@ var encode = &cli.Command{
}, },
{ {
Name: "naddr", Name: "naddr",
Usage: "generate codes for NIP-33 parameterized replaceable events", Usage: "generate codes for addressable events",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "identifier", Name: "identifier",
@@ -189,7 +189,7 @@ var encode = &cli.Command{
kind := c.Int("kind") kind := c.Int("kind")
if kind < 30000 || kind >= 40000 { if kind < 30000 || kind >= 40000 {
return fmt.Errorf("kind must be between 30000 and 39999, as per NIP-16, got %d", kind) return fmt.Errorf("kind must be between 30000 and 39999, got %d", kind)
} }
if d == "" { if d == "" {
@@ -212,28 +212,6 @@ var encode = &cli.Command{
} }
} }
exitIfLineProcessingError(ctx)
return nil
},
},
{
Name: "note",
Usage: "generate note1 event codes (not recommended)",
DisableSliceFlagSeparator: true,
Action: func(ctx context.Context, c *cli.Command) error {
for target := range getStdinLinesOrArguments(c.Args()) {
if ok := nostr.IsValid32ByteHex(target); !ok {
ctx = lineProcessingError(ctx, "invalid event id: %s", target)
continue
}
if note, err := nip19.EncodeNote(target); err == nil {
stdout(note)
} else {
return err
}
}
exitIfLineProcessingError(ctx) exitIfLineProcessingError(ctx)
return nil return nil
}, },

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"slices"
"strings" "strings"
"time" "time"
@@ -12,7 +13,6 @@ import (
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip13" "github.com/nbd-wtf/go-nostr/nip13"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"golang.org/x/exp/slices"
) )
const ( const (
@@ -65,7 +65,7 @@ example:
// ~~~ // ~~~
&cli.UintFlag{ &cli.UintFlag{
Name: "pow", Name: "pow",
Usage: "NIP-13 difficulty to target when doing hash work on the event id", Usage: "nip13 difficulty to target when doing hash work on the event id",
Category: CATEGORY_EXTRAS, Category: CATEGORY_EXTRAS,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
@@ -75,7 +75,7 @@ example:
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "auth", Name: "auth",
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again", Usage: "always perform nip42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
Category: CATEGORY_EXTRAS, Category: CATEGORY_EXTRAS,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
@@ -276,7 +276,7 @@ example:
// print event as json // print event as json
var result string var result string
if c.Bool("envelope") { if c.Bool("envelope") {
j, _ := easyjson.Marshal(nostr.EventEnvelope{Event: evt}) j, _ := json.Marshal(nostr.EventEnvelope{Event: evt})
result = string(j) result = string(j)
} else { } else {
j, _ := easyjson.Marshal(&evt) j, _ := easyjson.Marshal(&evt)

View File

@@ -67,6 +67,20 @@ func ExampleDecode() {
// } // }
} }
func ExampleDecodePubkey() {
app.Run(ctx, []string{"nak", "decode", "-p", "npub10xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqpkge6d", "npub1ccz8l9zpa47k6vz9gphftsrumpw80rjt3nhnefat4symjhrsnmjs38mnyd"})
// Output:
// 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
// c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
}
func ExampleDecodeEventId() {
app.Run(ctx, []string{"nak", "decode", "-e", "nevent1qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309aex2mrp0yh8wetnw3jhymnzw33jucm0d5hszxthwden5te0wfjkccte9eekummjwsh8xmmrd9skctcpzamhxue69uhkzarvv9ejumn0wd68ytnvv9hxgtcqyqllp5v5j0nxr74fptqxkhvfv0h3uj870qpk3ln8a58agyxl3fka296ewr8", "nevent1qqswh48lurxs8u0pll9qj2rzctvjncwhstpzlstq59rdtzlty79awns5hl5uf"})
// Output:
// 3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5
// ebd4ffe0cd03f1e1ffca092862c2d929e1d782c22fc160a146d58beb278bd74e
}
func ExampleReq() { func ExampleReq() {
app.Run(ctx, []string{"nak", "req", "-k", "1", "-l", "18", "-a", "2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f", "-e", "aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6"}) app.Run(ctx, []string{"nak", "req", "-k", "1", "-l", "18", "-a", "2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f", "-e", "aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6"})
// Output: // Output:

View File

@@ -8,11 +8,12 @@ import (
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip05" "github.com/nbd-wtf/go-nostr/nip05"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/sdk/hints"
) )
var fetch = &cli.Command{ var fetch = &cli.Command{
Name: "fetch", Name: "fetch",
Usage: "fetches events related to the given nip19 or nip05 code from the included relay hints or the author's NIP-65 relays.", Usage: "fetches events related to the given nip19 or nip05 code from the included relay hints or the author's outbox relays.",
Description: `example usage: Description: `example usage:
nak fetch nevent1qqsxrwm0hd3s3fddh4jc2574z3xzufq6qwuyz2rvv3n087zvym3dpaqprpmhxue69uhhqatzd35kxtnjv4kxz7tfdenju6t0xpnej4 nak fetch nevent1qqsxrwm0hd3s3fddh4jc2574z3xzufq6qwuyz2rvv3n087zvym3dpaqprpmhxue69uhhqatzd35kxtnjv4kxz7tfdenju6t0xpnej4
echo npub1h8spmtw9m2huyv6v2j2qd5zv956z2zdugl6mgx02f2upffwpm3nqv0j4ps | nak fetch --relay wss://relay.nostr.band`, echo npub1h8spmtw9m2huyv6v2j2qd5zv956z2zdugl6mgx02f2upffwpm3nqv0j4ps | nak fetch --relay wss://relay.nostr.band`,
@@ -90,20 +91,23 @@ var fetch = &cli.Command{
} }
if authorHint != "" { if authorHint != "" {
relays := sys.FetchOutboxRelays(ctx, authorHint, 3)
for _, url := range relays { for _, url := range relays {
sys.Hints.Save(authorHint, nostr.NormalizeURL(url), hints.LastInHint, nostr.Now())
}
for _, url := range sys.FetchOutboxRelays(ctx, authorHint, 3) {
relays = append(relays, url) relays = append(relays, url)
} }
} }
if len(filter.Authors) > 0 && len(filter.Kinds) == 0 {
filter.Kinds = append(filter.Kinds, 0)
}
if err := applyFlagsToFilter(c, &filter); err != nil { if err := applyFlagsToFilter(c, &filter); err != nil {
return err return err
} }
if len(filter.Authors) > 0 && len(filter.Kinds) == 0 {
filter.Kinds = append(filter.Kinds, 0)
}
if len(relays) == 0 { if len(relays) == 0 {
ctx = lineProcessingError(ctx, "no relay hints found") ctx = lineProcessingError(ctx, "no relay hints found")
continue continue

46
go.mod
View File

@@ -1,6 +1,8 @@
module github.com/fiatjaf/nak module github.com/fiatjaf/nak
go 1.23.1 go 1.23.3
toolchain go1.23.4
require ( require (
github.com/bep/debounce v1.2.1 github.com/bep/debounce v1.2.1
@@ -9,53 +11,59 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
github.com/fatih/color v1.16.0 github.com/fatih/color v1.16.0
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae
github.com/fiatjaf/eventstore v0.12.0 github.com/fiatjaf/eventstore v0.15.0
github.com/fiatjaf/khatru v0.10.0 github.com/fiatjaf/khatru v0.15.0
github.com/mailru/easyjson v0.7.7 github.com/json-iterator/go v1.1.12
github.com/mailru/easyjson v0.9.0
github.com/mark3labs/mcp-go v0.8.3
github.com/markusmobius/go-dateparser v1.2.3 github.com/markusmobius/go-dateparser v1.2.3
github.com/nbd-wtf/go-nostr v0.42.2 github.com/nbd-wtf/go-nostr v0.49.3
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
) )
require ( require (
fiatjaf.com/lib v0.2.0 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/btcsuite/btcd v0.24.2 // indirect
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/logex v1.1.10 // indirect github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
github.com/dgraph-io/ristretto v1.0.0 // indirect github.com/dgraph-io/ristretto v1.0.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elliotchance/pie/v2 v2.7.0 // indirect github.com/elliotchance/pie/v2 v2.7.0 // indirect
github.com/elnosh/gonuts v0.3.1-0.20250123162555-7c0381a585e3 // indirect
github.com/fasthttp/websocket v1.5.7 // indirect github.com/fasthttp/websocket v1.5.7 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/graph-gophers/dataloader/v7 v7.1.0 // indirect github.com/graph-gophers/dataloader/v7 v7.1.0 // indirect
github.com/greatroar/blobloom v0.8.0 // indirect
github.com/hablullah/go-hijri v1.0.2 // indirect github.com/hablullah/go-hijri v1.0.2 // indirect
github.com/hablullah/go-juliandays v1.0.0 // indirect github.com/hablullah/go-juliandays v1.0.0 // indirect
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.10 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/magefile/mage v1.14.0 // indirect github.com/magefile/mage v1.14.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
github.com/rs/cors v1.7.0 // indirect github.com/rs/cors v1.11.1 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/tetratelabs/wazero v1.8.0 // indirect github.com/tetratelabs/wazero v1.8.0 // indirect
github.com/tidwall/gjson v1.17.3 // indirect github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/wasilibs/go-re2 v1.3.0 // indirect github.com/wasilibs/go-re2 v1.3.0 // indirect
golang.org/x/crypto v0.28.0 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/crypto v0.32.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
) )

101
go.sum
View File

@@ -1,5 +1,5 @@
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= fiatjaf.com/lib v0.2.0 h1:TgIJESbbND6GjOgGHxF5jsO6EMjuAxIzZHPo5DXYexs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= fiatjaf.com/lib v0.2.0/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
@@ -7,15 +7,17 @@ github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY=
github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
@@ -29,8 +31,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/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/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/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
@@ -39,6 +39,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -58,24 +60,22 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg=
github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og=
github.com/elnosh/gonuts v0.3.1-0.20250123162555-7c0381a585e3 h1:k7evIqJ2BtFn191DgY/b03N2bMYA/iQwzr4f/uHYn20=
github.com/elnosh/gonuts v0.3.1-0.20250123162555-7c0381a585e3/go.mod h1:vgZomh4YQk7R3w4ltZc0sHwCmndfHkuX6V4sga/8oNs=
github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4= github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4=
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU= github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae h1:0B/1dU3YECIbPoBIRTQ4c0scZCNz9TVHtQpiODGrTTo= github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae h1:0B/1dU3YECIbPoBIRTQ4c0scZCNz9TVHtQpiODGrTTo=
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae/go.mod h1:aAWPO4bixZZxPtOnH6K3q4GbQ0jftUNDW9Oa861IRew= github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae/go.mod h1:aAWPO4bixZZxPtOnH6K3q4GbQ0jftUNDW9Oa861IRew=
github.com/fiatjaf/eventstore v0.12.0 h1:ZdL+dZkIgBgIp5A3+3XLdPg/uucv5Tiws6DHzNfZG4M= github.com/fiatjaf/eventstore v0.15.0 h1:5UXe0+vIb30/cYcOWipks8nR3g+X8W224TFy5yPzivk=
github.com/fiatjaf/eventstore v0.12.0/go.mod h1:PxeYbZ3MsH0XLobANsp6c0cJjJYkfmBJ3TwrplFy/08= github.com/fiatjaf/eventstore v0.15.0/go.mod h1:KAsld5BhkmSck48aF11Txu8X+OGNmoabw4TlYVWqInc=
github.com/fiatjaf/khatru v0.10.0 h1:f43om33RZfkIAIW9vhHelFJXp8XCij/Jh30AmJ0AVF8= github.com/fiatjaf/khatru v0.15.0 h1:0aLWiTrdzoKD4WmW35GWL/Jsn4dACCUw325JKZg/AmI=
github.com/fiatjaf/khatru v0.10.0/go.mod h1:iCLz0bPcFSBHJrY2kZ1lji5IrIsv9YFwRS7aaXIPv+o= github.com/fiatjaf/khatru v0.15.0/go.mod h1:GBQJXZpitDatXF9RookRXcWB5zCJclCE4ufDK3jk80g=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -87,10 +87,12 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/dataloader/v7 v7.1.0 h1:Wn8HGF/q7MNXcvfaBnLEPEFJttVHR8zuEqP1obys/oc= github.com/graph-gophers/dataloader/v7 v7.1.0 h1:Wn8HGF/q7MNXcvfaBnLEPEFJttVHR8zuEqP1obys/oc=
github.com/graph-gophers/dataloader/v7 v7.1.0/go.mod h1:1bKE0Dm6OUcTB/OAuYVOZctgIz7Q3d0XrYtlIzTgg6Q= github.com/graph-gophers/dataloader/v7 v7.1.0/go.mod h1:1bKE0Dm6OUcTB/OAuYVOZctgIz7Q3d0XrYtlIzTgg6Q=
github.com/greatroar/blobloom v0.8.0 h1:I9RlEkfqK9/6f1v9mFmDYegDQ/x0mISCpiNpAm23Pt4=
github.com/greatroar/blobloom v0.8.0/go.mod h1:mjMJ1hh1wjGVfr93QIHJ6FfDNVrA0IELv8OvMHJxHKs=
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k= github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ= github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0= github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
@@ -103,13 +105,17 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.8.3 h1:IzlyN8BaP4YwUMUDqxOGJhGdZXEDQiAPX43dNPgnzrg=
github.com/mark3labs/mcp-go v0.8.3/go.mod h1:cjMlBU0cv/cj9kjlgmRhoJ5JREdS7YX83xeIG9Ko/jE=
github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw= github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw=
github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk= github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -117,8 +123,13 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nbd-wtf/go-nostr v0.42.2 h1:X8vpfLutvmyxqjsroKPHdIyPliNa6sYD8+CA0kDVySw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/nbd-wtf/go-nostr v0.42.2/go.mod h1:FBa4FBJO7NuANvkeKSlrf0BIyxGufmrUbuelr6Q4Ick= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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.49.3 h1:7tsEdMZOtJ764JuMLffkbhVUi4yyf688dbqArLvItPs=
github.com/nbd-wtf/go-nostr v0.49.3/go.mod h1:M50QnhkraC5Ol93v3jqxSMm1aGxUQm5mlmkYw5DJzh8=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -134,24 +145,20 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
@@ -165,20 +172,22 @@ github.com/wasilibs/go-re2 v1.3.0 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw
github.com/wasilibs/go-re2 v1.3.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg= github.com/wasilibs/go-re2 v1.3.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg=
github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ= github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ=
github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo= github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -191,13 +200,13 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -5,6 +5,8 @@ import (
"context" "context"
"fmt" "fmt"
"math/rand" "math/rand"
"net/http"
"net/textproto"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@@ -12,27 +14,29 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/fiatjaf/cli/v3" "github.com/fiatjaf/cli/v3"
jsoniter "github.com/json-iterator/go"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/sdk" "github.com/nbd-wtf/go-nostr/sdk"
) )
var sys = sdk.NewSystem() var sys *sdk.System
func init() { var (
sys.Pool = nostr.NewSimplePool(context.Background(), hintsFilePath string
nostr.WithUserAgent("nak/b"), hintsFileExists bool
) )
}
var json = jsoniter.ConfigFastest
const ( const (
LINE_PROCESSING_ERROR = iota LINE_PROCESSING_ERROR = iota
) )
var log = func(msg string, args ...any) { var (
fmt.Fprintf(color.Error, msg, args...) log = func(msg string, args ...any) { fmt.Fprintf(color.Error, msg, args...) }
} logverbose = func(msg string, args ...any) {} // by default do nothing
stdout = fmt.Println
var stdout = fmt.Println )
func isPiped() bool { func isPiped() bool {
stat, _ := os.Stdin.Stat() stat, _ := os.Stdin.Stat()
@@ -70,7 +74,9 @@ func getStdinLinesOrArgumentsFromSlice(args []string) chan string {
// try the stdin // try the stdin
multi := make(chan string) multi := make(chan string)
writeStdinLinesOrNothing(multi) if !writeStdinLinesOrNothing(multi) {
close(multi)
}
return multi return multi
} }
@@ -129,7 +135,9 @@ func connectToAllRelays(
append(opts, append(opts,
nostr.WithEventMiddleware(sys.TrackEventHints), nostr.WithEventMiddleware(sys.TrackEventHints),
nostr.WithPenaltyBox(), nostr.WithPenaltyBox(),
nostr.WithUserAgent("nak/s"), nostr.WithRelayOptions(
nostr.WithRequestHeader(http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/s"}}),
),
)..., )...,
) )
@@ -204,3 +212,17 @@ func randString(n int) string {
func leftPadKey(k string) string { func leftPadKey(k string) string {
return strings.Repeat("0", 64-len(k)) + k return strings.Repeat("0", 64-len(k)) + k
} }
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
}{
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,
}

View File

@@ -20,7 +20,7 @@ import (
var defaultKeyFlags = []cli.Flag{ var defaultKeyFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "sec", Name: "sec",
Usage: "secret key to sign the event, as nsec, ncryptsec or hex, or a bunker URL", Usage: "secret key to sign the event, as nsec, ncryptsec or hex, or a bunker URL, it is more secure to use the environment variable NOSTR_SECRET_KEY than this flag",
DefaultText: "the key '1'", DefaultText: "the key '1'",
Aliases: []string{"connect"}, Aliases: []string{"connect"},
Category: CATEGORY_SIGNER, Category: CATEGORY_SIGNER,
@@ -32,7 +32,7 @@ var defaultKeyFlags = []cli.Flag{
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "connect-as", Name: "connect-as",
Usage: "private key to use when communicating with NIP-46 bunkers", Usage: "private key to use when communicating with nip46 bunkers",
DefaultText: "a random key", DefaultText: "a random key",
Category: CATEGORY_SIGNER, Category: CATEGORY_SIGNER,
Sources: cli.EnvVars("NOSTR_CLIENT_KEY"), Sources: cli.EnvVars("NOSTR_CLIENT_KEY"),

7
key.go
View File

@@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"strings" "strings"
@@ -18,7 +17,7 @@ import (
var key = &cli.Command{ var key = &cli.Command{
Name: "key", Name: "key",
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.", Usage: "operations on secret keys: generate, derive, encrypt, decrypt",
Description: ``, Description: ``,
DisableSliceFlagSeparator: true, DisableSliceFlagSeparator: true,
Commands: []*cli.Command{ Commands: []*cli.Command{
@@ -72,7 +71,7 @@ var public = &cli.Command{
var encryptKey = &cli.Command{ var encryptKey = &cli.Command{
Name: "encrypt", Name: "encrypt",
Usage: "encrypts a secret key and prints an ncryptsec code", Usage: "encrypts a secret key and prints an ncryptsec code",
Description: `uses the NIP-49 standard.`, Description: `uses the nip49 standard.`,
ArgsUsage: "<secret> <password>", ArgsUsage: "<secret> <password>",
DisableSliceFlagSeparator: true, DisableSliceFlagSeparator: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
@@ -111,7 +110,7 @@ var encryptKey = &cli.Command{
var decryptKey = &cli.Command{ var decryptKey = &cli.Command{
Name: "decrypt", Name: "decrypt",
Usage: "takes an ncrypsec and a password and decrypts it into an nsec", Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
Description: `uses the NIP-49 standard.`, Description: `uses the nip49 standard.`,
ArgsUsage: "<ncryptsec-code> <password>", ArgsUsage: "<ncryptsec-code> <password>",
DisableSliceFlagSeparator: true, DisableSliceFlagSeparator: true,
Action: func(ctx context.Context, c *cli.Command) error { Action: func(ctx context.Context, c *cli.Command) error {

88
main.go
View File

@@ -2,9 +2,15 @@ package main
import ( import (
"context" "context"
"net/http"
"net/textproto"
"os" "os"
"path/filepath"
"github.com/fiatjaf/cli/v3" "github.com/fiatjaf/cli/v3"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/sdk"
"github.com/nbd-wtf/go-nostr/sdk/hints/memoryh"
) )
var version string = "debug" var version string = "debug"
@@ -30,9 +36,17 @@ var app = &cli.Command{
serve, serve,
encrypt, encrypt,
decrypt, decrypt,
outbox,
wallet,
mcpServer,
}, },
Version: version, Version: version,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{
Name: "config-path",
Hidden: true,
Persistent: true,
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "quiet", Name: "quiet",
Usage: "do not print logs and info messages to stderr, use -qq to also not print anything to stdout", Usage: "do not print logs and info messages to stderr, use -qq to also not print anything to stdout",
@@ -49,12 +63,86 @@ var app = &cli.Command{
return nil return nil
}, },
}, },
&cli.BoolFlag{
Name: "verbose",
Usage: "print more stuff than normally",
Aliases: []string{"v"},
Persistent: true,
Action: func(ctx context.Context, c *cli.Command, b bool) error {
v := c.Count("verbose")
if v >= 1 {
logverbose = log
}
return nil
},
},
},
Before: func(ctx context.Context, c *cli.Command) error {
configPath := c.String("config-path")
if configPath == "" {
if home, err := os.UserHomeDir(); err == nil {
configPath = filepath.Join(home, ".config/nak")
}
}
if configPath != "" {
hintsFilePath = filepath.Join(configPath, "outbox/hints.db")
}
if hintsFilePath != "" {
if _, err := os.Stat(hintsFilePath); !os.IsNotExist(err) {
hintsFileExists = true
}
}
if hintsFilePath != "" {
if data, err := os.ReadFile(hintsFilePath); err == nil {
hintsdb := memoryh.NewHintDB()
if err := json.Unmarshal(data, &hintsdb); err == nil {
sys = sdk.NewSystem(
sdk.WithHintsDB(hintsdb),
)
goto systemOperational
}
}
}
sys = sdk.NewSystem()
systemOperational:
sys.Pool = nostr.NewSimplePool(context.Background(),
nostr.WithAuthorKindQueryMiddleware(sys.TrackQueryAttempts),
nostr.WithEventMiddleware(sys.TrackEventHints),
nostr.WithRelayOptions(
nostr.WithRequestHeader(http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/b"}}),
),
)
return nil
},
After: func(ctx context.Context, c *cli.Command) error {
// save hints database on exit
if hintsFileExists {
data, err := json.Marshal(sys.Hints)
if err != nil {
return err
}
return os.WriteFile(hintsFilePath, data, 0644)
}
return nil
}, },
} }
func main() { func main() {
defer colors.reset()
cli.VersionFlag = &cli.BoolFlag{
Name: "version",
Usage: "prints the version",
}
if err := app.Run(context.Background(), os.Args); err != nil { if err := app.Run(context.Background(), os.Args); err != nil {
stdout(err) stdout(err)
colors.reset()
os.Exit(1) os.Exit(1)
} }
} }

217
mcp.go Normal file
View File

@@ -0,0 +1,217 @@
package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/fiatjaf/cli/v3"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
var mcpServer = &cli.Command{
Name: "mcp",
Usage: "pander to the AI gods",
Description: ``,
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{},
Action: func(ctx context.Context, c *cli.Command) error {
s := server.NewMCPServer(
"nak",
version,
)
s.AddTool(mcp.NewTool("publish_note",
mcp.WithDescription("Publish a short note event to Nostr with the given text content"),
mcp.WithString("content",
mcp.Required(),
mcp.Description("Arbitrary string to be published"),
),
mcp.WithString("mention",
mcp.Required(),
mcp.Description("Nostr user's public key to be mentioned"),
),
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
content, _ := request.Params.Arguments["content"].(string)
mention, _ := request.Params.Arguments["mention"].(string)
if mention != "" && !nostr.IsValidPublicKey(mention) {
return mcp.NewToolResultError("the given mention isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil
}
sk := os.Getenv("NOSTR_SECRET_KEY")
if sk == "" {
sk = "0000000000000000000000000000000000000000000000000000000000000001"
}
var relays []string
evt := nostr.Event{
Kind: 1,
Tags: nostr.Tags{{"client", "goose/nak"}},
Content: content,
CreatedAt: nostr.Now(),
}
if mention != "" {
evt.Tags = append(evt.Tags, nostr.Tag{"p", mention})
// their inbox relays
relays = sys.FetchInboxRelays(ctx, mention, 3)
}
evt.Sign(sk)
// our write relays
relays = append(relays, sys.FetchOutboxRelays(ctx, evt.PubKey, 3)...)
if len(relays) == 0 {
relays = []string{"nos.lol", "relay.damus.io"}
}
for res := range sys.Pool.PublishMany(ctx, []string{"nos.lol"}, evt) {
if res.Error != nil {
return mcp.NewToolResultError(
fmt.Sprintf("there was an error publishing the event to the relay %s",
res.RelayURL),
), nil
}
}
return mcp.NewToolResultText("event was successfully published with id " + evt.ID), nil
})
s.AddTool(mcp.NewTool("resolve_nostr_uri",
mcp.WithDescription("Resolve URIs prefixed with nostr:, including nostr:nevent1..., nostr:npub1..., nostr:nprofile1... and nostr:naddr1..."),
mcp.WithString("uri",
mcp.Required(),
mcp.Description("URI to be resolved"),
),
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
uri, _ := request.Params.Arguments["uri"].(string)
if strings.HasPrefix(uri, "nostr:") {
uri = uri[6:]
}
prefix, data, err := nip19.Decode(uri)
if err != nil {
return mcp.NewToolResultError("this Nostr uri is invalid"), nil
}
switch prefix {
case "npub":
pm := sys.FetchProfileMetadata(ctx, data.(string))
return mcp.NewToolResultText(
fmt.Sprintf("this is a Nostr profile named '%s', their public key is '%s'",
pm.ShortName(), pm.PubKey),
), nil
case "nprofile":
pm, _ := sys.FetchProfileFromInput(ctx, uri)
return mcp.NewToolResultText(
fmt.Sprintf("this is a Nostr profile named '%s', their public key is '%s'",
pm.ShortName(), pm.PubKey),
), nil
case "nevent":
event, _, err := sys.FetchSpecificEventFromInput(ctx, uri, false)
if err != nil {
return mcp.NewToolResultError("Couldn't find this event anywhere"), nil
}
return mcp.NewToolResultText(
fmt.Sprintf("this is a Nostr event: %s", event),
), nil
case "naddr":
return mcp.NewToolResultError("For now we can't handle this kind of Nostr uri"), nil
default:
return mcp.NewToolResultError("We don't know how to handle this Nostr uri"), nil
}
})
s.AddTool(mcp.NewTool("search_profile",
mcp.WithDescription("Search for the public key of a Nostr user given their name"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("Name to be searched"),
),
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, _ := request.Params.Arguments["name"].(string)
re := sys.Pool.QuerySingle(ctx, []string{"relay.nostr.band", "nostr.wine"}, nostr.Filter{Search: name, Kinds: []int{0}})
if re == nil {
return mcp.NewToolResultError("couldn't find anyone with that name"), nil
}
return mcp.NewToolResultText(re.PubKey), nil
})
s.AddTool(mcp.NewTool("get_outbox_relay_for_pubkey",
mcp.WithDescription("Get the best relay from where to read notes from a specific Nostr user"),
mcp.WithString("pubkey",
mcp.Required(),
mcp.Description("Public key of Nostr user we want to know the relay from where to read"),
),
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, _ := request.Params.Arguments["name"].(string)
re := sys.Pool.QuerySingle(ctx, []string{"relay.nostr.band", "nostr.wine"}, nostr.Filter{Search: name, Kinds: []int{0}})
if re == nil {
return mcp.NewToolResultError("couldn't find anyone with that name"), nil
}
return mcp.NewToolResultText(re.PubKey), nil
})
s.AddTool(mcp.NewTool("read_events_from_relay",
mcp.WithDescription("Makes a REQ query to one relay using the specified parameters, this can be used to fetch notes from a profile"),
mcp.WithNumber("kind",
mcp.Required(),
mcp.Description("event kind number to include in the 'kinds' field"),
),
mcp.WithString("pubkey",
mcp.Description("pubkey to include in the 'authors' field"),
),
mcp.WithNumber("limit",
mcp.Required(),
mcp.Description("maximum number of events to query"),
),
mcp.WithString("relay",
mcp.Required(),
mcp.Description("relay URL to send the query to"),
),
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
relay, _ := request.Params.Arguments["relay"].(string)
limit, _ := request.Params.Arguments["limit"].(int)
kind, _ := request.Params.Arguments["kind"].(int)
pubkey, _ := request.Params.Arguments["pubkey"].(string)
if pubkey != "" && !nostr.IsValidPublicKey(pubkey) {
return mcp.NewToolResultError("the given pubkey isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil
}
filter := nostr.Filter{
Limit: limit,
Kinds: []int{kind},
}
if pubkey != "" {
filter.Authors = []string{pubkey}
}
events := sys.Pool.SubManyEose(ctx, []string{relay}, nostr.Filters{filter})
result := strings.Builder{}
for ie := range events {
result.WriteString("author public key: ")
result.WriteString(ie.PubKey)
result.WriteString("content: '")
result.WriteString(ie.Content)
result.WriteString("'")
result.WriteString("\n---\n")
}
return mcp.NewToolResultText(result.String()), nil
})
return server.ServeStdio(s)
},
}

68
outbox.go Normal file
View File

@@ -0,0 +1,68 @@
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/fiatjaf/cli/v3"
"github.com/nbd-wtf/go-nostr"
)
var outbox = &cli.Command{
Name: "outbox",
Usage: "manage outbox relay hints database",
DisableSliceFlagSeparator: true,
Commands: []*cli.Command{
{
Name: "init",
Usage: "initialize the outbox hints database",
DisableSliceFlagSeparator: true,
Action: func(ctx context.Context, c *cli.Command) error {
if hintsFileExists {
return nil
}
if hintsFilePath == "" {
return fmt.Errorf("couldn't find a place to store the hints, pass --config-path to fix.")
}
if err := os.MkdirAll(filepath.Dir(hintsFilePath), 0777); err == nil {
if err := os.WriteFile(hintsFilePath, []byte("{}"), 0644); err != nil {
return fmt.Errorf("failed to create hints database: %w", err)
}
}
log("initialized hints database at %s\n", hintsFilePath)
return nil
},
},
{
Name: "list",
Usage: "list outbox relays for a given pubkey",
ArgsUsage: "<pubkey>",
DisableSliceFlagSeparator: true,
Action: func(ctx context.Context, c *cli.Command) error {
if !hintsFileExists {
log("running with temporary fragile data.\n")
log("call `nak outbox init` to setup persistence.\n")
}
if c.Args().Len() != 1 {
return fmt.Errorf("expected exactly one argument (pubkey)")
}
pubkey := c.Args().First()
if !nostr.IsValidPublicKey(pubkey) {
return fmt.Errorf("invalid public key: %s", pubkey)
}
for _, relay := range sys.FetchOutboxRelays(ctx, pubkey, 6) {
stdout(relay)
}
return nil
},
},
},
}

View File

@@ -6,7 +6,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -19,9 +18,13 @@ import (
var relay = &cli.Command{ var relay = &cli.Command{
Name: "relay", Name: "relay",
Usage: "gets the relay information document for the given relay, as JSON", Usage: "gets the relay information document for the given relay, as JSON -- or allows usage of the relay management API.",
Description: `example: Description: `examples:
nak relay nostr.wine`, fetching relay information:
nak relay nostr.wine
managing a relay
nak relay nostr.wine banevent --sec 1234 --id 037eb3751073770ff17483b1b1ff125866cd5147668271975ef0a8a8e7ee184a --reason "I don't like it"`,
ArgsUsage: "<relay-url>", ArgsUsage: "<relay-url>",
DisableSliceFlagSeparator: true, DisableSliceFlagSeparator: true,
Action: func(ctx context.Context, c *cli.Command) error { Action: func(ctx context.Context, c *cli.Command) error {

11
req.go
View File

@@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@@ -20,7 +19,7 @@ const (
var req = &cli.Command{ var req = &cli.Command{
Name: "req", Name: "req",
Usage: "generates encoded REQ messages and optionally use them to talk to relays", Usage: "generates encoded REQ messages and optionally use them to talk to relays",
Description: `outputs a NIP-01 Nostr filter. when a relay is not given, will print the filter, otherwise will connect to the given relay and send the filter. Description: `outputs a nip01 Nostr filter. when a relay is not given, will print the filter, otherwise will connect to the given relay and send the filter.
example: example:
nak req -k 1 -l 15 wss://nostr.wine wss://nostr-pub.wellorder.net nak req -k 1 -l 15 wss://nostr.wine wss://nostr-pub.wellorder.net
@@ -58,12 +57,12 @@ example:
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "auth", Name: "auth",
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again", Usage: "always perform nip42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "force-pre-auth", Name: "force-pre-auth",
Aliases: []string{"fpa"}, Aliases: []string{"fpa"},
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"", Usage: "after connecting, for a nip42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
Category: CATEGORY_SIGNER, Category: CATEGORY_SIGNER,
}, },
)..., )...,
@@ -211,7 +210,7 @@ var reqFilterFlags = []cli.Flag{
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "search", Name: "search",
Usage: "a NIP-50 search query, use it only with relays that explicitly support it", Usage: "a nip50 search query, use it only with relays that explicitly support it",
Category: CATEGORY_FILTER_ATTRIBUTES, Category: CATEGORY_FILTER_ATTRIBUTES,
}, },
} }
@@ -232,7 +231,7 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error {
tags := make([][]string, 0, 5) tags := make([][]string, 0, 5)
for _, tagFlag := range c.StringSlice("tag") { for _, tagFlag := range c.StringSlice("tag") {
spl := strings.SplitN(tagFlag, "=", 2) spl := strings.SplitN(tagFlag, "=", 2)
if len(spl) == 2 && len(spl[0]) == 1 { if len(spl) == 2 {
tags = append(tags, spl) tags = append(tags, spl)
} else { } else {
return fmt.Errorf("invalid --tag '%s'", tagFlag) return fmt.Errorf("invalid --tag '%s'", tagFlag)

View File

@@ -3,7 +3,6 @@ package main
import ( import (
"bufio" "bufio"
"context" "context"
"encoding/json"
"fmt" "fmt"
"math" "math"
"os" "os"
@@ -82,24 +81,21 @@ var serve = &cli.Command{
exited <- err exited <- err
}() }()
bold := color.New(color.Bold).Sprintf
italic := color.New(color.Italic).Sprint
var printStatus func() var printStatus func()
// relay logging // relay logging
rl.RejectFilter = append(rl.RejectFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { rl.RejectFilter = append(rl.RejectFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
log(" got %s %v\n", color.HiYellowString("request"), italic(filter)) log(" got %s %v\n", color.HiYellowString("request"), colors.italic(filter))
printStatus() printStatus()
return false, "" return false, ""
}) })
rl.RejectCountFilter = append(rl.RejectCountFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { rl.RejectCountFilter = append(rl.RejectCountFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
log(" got %s %v\n", color.HiCyanString("count request"), italic(filter)) log(" got %s %v\n", color.HiCyanString("count request"), colors.italic(filter))
printStatus() printStatus()
return false, "" return false, ""
}) })
rl.RejectEvent = append(rl.RejectEvent, func(ctx context.Context, event *nostr.Event) (reject bool, msg string) { rl.RejectEvent = append(rl.RejectEvent, func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
log(" got %s %v\n", color.BlueString("event"), italic(event)) log(" got %s %v\n", color.BlueString("event"), colors.italic(event))
printStatus() printStatus()
return false, "" return false, ""
}) })
@@ -119,7 +115,7 @@ var serve = &cli.Command{
} }
<-started <-started
log("%s relay running at %s\n", color.HiRedString(">"), bold("ws://%s:%d", hostname, port)) log("%s relay running at %s\n", color.HiRedString(">"), colors.boldf("ws://%s:%d", hostname, port))
return <-exited return <-exited
}, },

View File

@@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"github.com/fiatjaf/cli/v3" "github.com/fiatjaf/cli/v3"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"

487
wallet.go Normal file
View File

@@ -0,0 +1,487 @@
package main
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/fiatjaf/cli/v3"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip60"
"github.com/nbd-wtf/go-nostr/nip61"
)
func prepareWallet(ctx context.Context, c *cli.Command) (*nip60.Wallet, func(), error) {
kr, _, err := gatherKeyerFromArguments(ctx, c)
if err != nil {
return nil, nil, err
}
pk, err := kr.GetPublicKey(ctx)
if err != nil {
return nil, nil, err
}
relays := sys.FetchOutboxRelays(ctx, pk, 3)
w := nip60.LoadWallet(ctx, kr, sys.Pool, relays)
if w == nil {
return nil, nil, fmt.Errorf("error loading walle")
}
w.Processed = func(evt *nostr.Event, err error) {
if err == nil {
logverbose("processed event %s\n", evt)
} else {
log("error processing event %s: %s\n", evt, err)
}
}
w.PublishUpdate = func(event nostr.Event, deleted, received, change *nip60.Token, isHistory bool) {
desc := "wallet"
if received != nil {
mint, _ := strings.CutPrefix(received.Mint, "https://")
desc = fmt.Sprintf("received from %s with %d proofs totalling %d",
mint, len(received.Proofs), received.Proofs.Amount())
} else if change != nil {
mint, _ := strings.CutPrefix(change.Mint, "https://")
desc = fmt.Sprintf("change from %s with %d proofs totalling %d",
mint, len(change.Proofs), change.Proofs.Amount())
} else if deleted != nil {
mint, _ := strings.CutPrefix(deleted.Mint, "https://")
desc = fmt.Sprintf("deleting a used token from %s with %d proofs totalling %d",
mint, len(deleted.Proofs), deleted.Proofs.Amount())
} else if isHistory {
desc = "history entry"
}
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
}
if !first {
log(", ")
}
first = false
if res.Error != nil {
log("%s: %s", color.RedString(cleanUrl), res.Error)
} else {
log("%s: ok", color.GreenString(cleanUrl))
}
}
log("\n")
}
<-w.Stable
return w, func() {
w.Close()
}, nil
}
var wallet = &cli.Command{
Name: "wallet",
Usage: "displays the current wallet balance",
Description: "all wallet data is stored on Nostr relays, signed and encrypted with the given key, and reloaded again from relays on every call.\n\nthe same data can be accessed by other compatible nip60 clients.",
DisableSliceFlagSeparator: true,
Flags: defaultKeyFlags,
Action: func(ctx context.Context, c *cli.Command) error {
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
stdout(w.Balance())
closew()
return nil
},
Commands: []*cli.Command{
{
Name: "mints",
Usage: "lists, adds or remove default mints from the wallet",
DisableSliceFlagSeparator: true,
Action: func(ctx context.Context, c *cli.Command) error {
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
for _, url := range w.Mints {
stdout(strings.Split(url, "://")[1])
}
closew()
return nil
},
Commands: []*cli.Command{
{
Name: "add",
DisableSliceFlagSeparator: true,
ArgsUsage: "<mint>...",
Action: func(ctx context.Context, c *cli.Command) error {
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
if err := w.AddMint(ctx, c.Args().Slice()...); err != nil {
return err
}
closew()
return nil
},
},
{
Name: "remove",
DisableSliceFlagSeparator: true,
ArgsUsage: "<mint>...",
Action: func(ctx context.Context, c *cli.Command) error {
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
if err := w.RemoveMint(ctx, c.Args().Slice()...); err != nil {
return err
}
closew()
return nil
},
},
},
},
{
Name: "tokens",
Usage: "lists existing tokens with their mints and aggregated amounts",
DisableSliceFlagSeparator: true,
Action: func(ctx context.Context, c *cli.Command) error {
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
for _, token := range w.Tokens {
stdout(token.ID(), token.Proofs.Amount(), strings.Split(token.Mint, "://")[1])
}
closew()
return nil
},
},
{
Name: "receive",
Usage: "takes a cashu token string as an argument and adds it to the wallet",
ArgsUsage: "<token>",
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "mint",
Usage: "mint to swap the token into",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
args := c.Args().Slice()
if len(args) != 1 {
return fmt.Errorf("must be called as `nak wallet receive <token>")
}
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
proofs, mint, err := nip60.GetProofsAndMint(args[0])
if err != nil {
return err
}
opts := make([]nip60.ReceiveOption, 0, 1)
for _, url := range c.StringSlice("mint") {
opts = append(opts, nip60.WithMintDestination(url))
}
if err := w.Receive(ctx, proofs, mint, opts...); err != nil {
return err
}
closew()
return nil
},
},
{
Name: "send",
Usage: "prints a cashu token with the given amount for sending to someone else",
ArgsUsage: "<amount>",
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mint",
Usage: "send from a specific mint",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
args := c.Args().Slice()
if len(args) != 1 {
return fmt.Errorf("must be called as `nak wallet send <amount>")
}
amount, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("amount '%s' is invalid", args[0])
}
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
opts := make([]nip60.SendOption, 0, 1)
if mint := c.String("mint"); mint != "" {
mint = "http" + nostr.NormalizeURL(mint)[2:]
opts = append(opts, nip60.WithMint(mint))
}
proofs, mint, err := w.Send(ctx, amount, opts...)
if err != nil {
return err
}
stdout(nip60.MakeTokenString(proofs, mint))
closew()
return nil
},
},
{
Name: "pay",
Usage: "pays a bolt11 lightning invoice and outputs the preimage",
ArgsUsage: "<invoice>",
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mint",
Usage: "pay from a specific mint",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
args := c.Args().Slice()
if len(args) != 1 {
return fmt.Errorf("must be called as `nak wallet pay <invoice>")
}
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
opts := make([]nip60.SendOption, 0, 1)
if mint := c.String("mint"); mint != "" {
mint = "http" + nostr.NormalizeURL(mint)[2:]
opts = append(opts, nip60.WithMint(mint))
}
preimage, err := w.PayBolt11(ctx, args[0], opts...)
if err != nil {
return err
}
stdout(preimage)
closew()
return nil
},
},
{
Name: "nutzap",
Usage: "sends a nip61 nutzap to one or more Nostr profiles and/or events",
ArgsUsage: "<amount> <target>",
Description: "<amount> is in satoshis, <target> can be an npub, nprofile, nevent or hex pubkey.",
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mint",
Usage: "send from a specific mint",
},
&cli.StringFlag{
Name: "message",
Usage: "attach a message to the nutzap",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
args := c.Args().Slice()
if len(args) >= 2 {
return fmt.Errorf("must be called as `nak wallet send <amount> <target>...")
}
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
amount := c.Uint("amount")
target := c.String("target")
var evt *nostr.Event
var eventId string
if strings.HasPrefix(target, "nevent1") {
evt, _, err = sys.FetchSpecificEventFromInput(ctx, target, false)
if err != nil {
return err
}
eventId = evt.ID
target = evt.PubKey
}
pm, err := sys.FetchProfileFromInput(ctx, target)
if err != nil {
return err
}
log("sending %d sat to '%s' (%s)", amount, pm.ShortName(), pm.Npub())
opts := make([]nip60.SendOption, 0, 1)
if mint := c.String("mint"); mint != "" {
mint = "http" + nostr.NormalizeURL(mint)[2:]
opts = append(opts, nip60.WithMint(mint))
}
kr, _, _ := gatherKeyerFromArguments(ctx, c)
results, err := nip61.SendNutzap(
ctx,
kr,
w,
sys.Pool,
pm.PubKey,
sys.FetchInboxRelays,
sys.FetchOutboxRelays(ctx, pm.PubKey, 3),
eventId,
amount,
c.String("message"),
)
if err != nil {
return err
}
log("- publishing nutzap... ")
first := true
for res := range results {
cleanUrl, ok := strings.CutPrefix(res.RelayURL, "wss://")
if !ok {
cleanUrl = res.RelayURL
}
if !first {
log(", ")
}
first = false
if res.Error != nil {
log("%s: %s", color.RedString(cleanUrl), res.Error)
} else {
log("%s: ok", color.GreenString(cleanUrl))
}
}
closew()
return nil
},
Commands: []*cli.Command{
{
Name: "setup",
Usage: "setup your wallet private key and kind:10019 event for receiving nutzaps",
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "mint",
Usage: "mints to receive nutzaps in",
},
&cli.StringFlag{
Name: "private-key",
Usage: "private key used for receiving nutzaps",
},
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "forces replacement of private-key",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
w, closew, err := prepareWallet(ctx, c)
if err != nil {
return err
}
if w.PrivateKey == nil {
if sk := c.String("private-key"); sk != "" {
if err := w.SetPrivateKey(ctx, sk); err != nil {
return err
}
} else {
return fmt.Errorf("missing --private-key")
}
} else if sk := c.String("private-key"); sk != "" && !c.Bool("force") {
return fmt.Errorf("refusing to replace existing private key, use the --force flag")
}
kr, _, _ := gatherKeyerFromArguments(ctx, c)
pk, _ := kr.GetPublicKey(ctx)
relays := sys.FetchWriteRelays(ctx, pk, 6)
info := nip61.Info{}
ie := sys.Pool.QuerySingle(ctx, relays, nostr.Filter{
Kinds: []int{10019},
Authors: []string{pk},
Limit: 1,
})
if ie != nil {
info.ParseEvent(ie.Event)
}
if mints := c.StringSlice("mints"); len(mints) == 0 && len(info.Mints) == 0 {
info.Mints = w.Mints
}
if len(info.Mints) == 0 {
return fmt.Errorf("missing --mint")
}
evt := nostr.Event{}
if err := info.ToEvent(ctx, kr, &evt); err != nil {
return err
}
stdout(evt)
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
}
if !first {
log(", ")
}
first = false
if res.Error != nil {
log("%s: %s", color.RedString(cleanUrl), res.Error)
} else {
log("%s: ok", color.GreenString(cleanUrl))
}
}
closew()
return nil
},
},
},
},
},
}

14
zapstore.yaml Normal file
View File

@@ -0,0 +1,14 @@
nak:
cli:
name: nak
summary: a command line tool for doing all things nostr
repository: https://github.com/fiatjaf/nak
artifacts:
nak-v%v-darwin-arm64:
platforms: [darwin-arm64]
nak-v%v-darwin-amd64:
platforms: [darwin-x86_64]
nak-v%v-linux-arm64:
platforms: [linux-aarch64]
nak-v%v-linux-amd64:
platforms: [linux-x86_64]