diff --git a/dvm.go b/dvm.go
new file mode 100644
index 0000000..659ca61
--- /dev/null
+++ b/dvm.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/fatih/color"
+	"github.com/nbd-wtf/go-nostr"
+	"github.com/nbd-wtf/go-nostr/nip90"
+	"github.com/urfave/cli/v3"
+)
+
+var dvm = &cli.Command{
+	Name:  "dvm",
+	Usage: "deal with nip90 data-vending-machine things (experimental)",
+	Description: `example usage:
+	nak dvm 5001 --input "What is the capital of France?" --input-type text --output "text/plain" --bid 1000 wss://relay.example.com`,
+	DisableSliceFlagSeparator: true,
+	Flags: append(defaultKeyFlags,
+		&cli.StringSliceFlag{
+			Name:    "relay",
+			Aliases: []string{"r"},
+		},
+	),
+	Commands: append([]*cli.Command{
+		{
+			Name:  "list",
+			Usage: "find DVMs that have announced themselves for a specific kind",
+			Action: func(ctx context.Context, c *cli.Command) error {
+				return fmt.Errorf("we don't know how to do this yet")
+			},
+		},
+	}, (func() []*cli.Command {
+		commands := make([]*cli.Command, len(nip90.Jobs))
+		for i, job := range nip90.Jobs {
+			flags := make([]cli.Flag, 0, 2+len(job.Params))
+
+			if job.InputType != "" {
+				flags = append(flags, &cli.StringSliceFlag{
+					Name:     "input",
+					Category: "INPUT",
+				})
+			}
+
+			for _, param := range job.Params {
+				flags = append(flags, &cli.StringSliceFlag{
+					Name:     param,
+					Category: "PARAMETER",
+				})
+			}
+
+			commands[i] = &cli.Command{
+				Name:        strconv.Itoa(job.InputKind),
+				Usage:       job.Name,
+				Description: job.Description,
+				Flags:       flags,
+				Action: func(ctx context.Context, c *cli.Command) error {
+					relayUrls := c.StringSlice("relay")
+					relays := connectToAllRelays(ctx, relayUrls, false)
+					if len(relays) == 0 {
+						log("failed to connect to any of the given relays.\n")
+						os.Exit(3)
+					}
+					defer func() {
+						for _, relay := range relays {
+							relay.Close()
+						}
+					}()
+
+					evt := nostr.Event{
+						Kind:      job.InputKind,
+						Tags:      make(nostr.Tags, 0, 2+len(job.Params)),
+						CreatedAt: nostr.Now(),
+					}
+
+					for _, input := range c.StringSlice("input") {
+						evt.Tags = append(evt.Tags, nostr.Tag{"i", input, job.InputType})
+					}
+					for _, paramN := range job.Params {
+						for _, paramV := range c.StringSlice(paramN) {
+							tag := nostr.Tag{"param", paramN, "", ""}[0:2]
+							for _, v := range strings.Split(paramV, ";") {
+								tag = append(tag, v)
+							}
+							evt.Tags = append(evt.Tags, tag)
+						}
+					}
+
+					kr, _, err := gatherKeyerFromArguments(ctx, c)
+					if err != nil {
+						return err
+					}
+					if err := kr.SignEvent(ctx, &evt); err != nil {
+						return err
+					}
+
+					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
+						}
+
+						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- waiting for response...")
+					for ie := range sys.Pool.SubscribeMany(ctx, relayUrls, nostr.Filter{
+						Kinds: []int{7000, job.OutputKind},
+						Tags:  nostr.TagMap{"e": []string{evt.ID}},
+					}) {
+						stdout(ie.Event)
+					}
+
+					return nil
+				},
+			}
+		}
+		return commands
+	})()...),
+}
diff --git a/event.go b/event.go
index d30bec8..dbc2a7c 100644
--- a/event.go
+++ b/event.go
@@ -140,7 +140,6 @@ example:
 				os.Exit(3)
 			}
 		}
-
 		defer func() {
 			for _, relay := range relays {
 				relay.Close()
diff --git a/go.mod b/go.mod
index 0866e84..b0bf039 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
 	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/nbd-wtf/go-nostr v0.50.1
+	github.com/nbd-wtf/go-nostr v0.50.2
 	github.com/urfave/cli/v3 v3.0.0-beta1
 	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
 )
@@ -70,3 +70,5 @@ require (
 	golang.org/x/sys v0.30.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
 )
+
+replace github.com/nbd-wtf/go-nostr => ../go-nostr
diff --git a/go.sum b/go.sum
index 9f4c18c..1ef0112 100644
--- a/go.sum
+++ b/go.sum
@@ -133,8 +133,8 @@ 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.50.1 h1:l02wKcnYVyvjnj53CNB3I/C161uoH1W51sPWbrLb9C0=
-github.com/nbd-wtf/go-nostr v0.50.1/go.mod h1:gOzf8mcTlMs7e+LjYDssRU4Vb/YGeeYO61aDNrxDStY=
+github.com/nbd-wtf/go-nostr v0.50.2 h1:OMmxztiAPj9cjZ16C+Z13YTJBF91RKEm4HZm5ogKD/c=
+github.com/nbd-wtf/go-nostr v0.50.2/go.mod h1:gOzf8mcTlMs7e+LjYDssRU4Vb/YGeeYO61aDNrxDStY=
 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/main.go b/main.go
index 495f129..96c4b47 100644
--- a/main.go
+++ b/main.go
@@ -40,6 +40,7 @@ var app = &cli.Command{
 		wallet,
 		mcpServer,
 		curl,
+		dvm,
 	},
 	Version: version,
 	Flags: []cli.Flag{
diff --git a/relay.go b/relay.go
index 36f5729..e464fcb 100644
--- a/relay.go
+++ b/relay.go
@@ -10,10 +10,10 @@ import (
 	"io"
 	"net/http"
 
-	"github.com/urfave/cli/v3"
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/nip11"
 	"github.com/nbd-wtf/go-nostr/nip86"
+	"github.com/urfave/cli/v3"
 )
 
 var relay = &cli.Command{
@@ -45,9 +45,7 @@ var relay = &cli.Command{
 		return nil
 	},
 	Commands: (func() []*cli.Command {
-		commands := make([]*cli.Command, 0, 12)
-
-		for _, def := range []struct {
+		methods := []struct {
 			method string
 			args   []string
 		}{
@@ -69,7 +67,10 @@ var relay = &cli.Command{
 			{"blockip", []string{"ip", "reason"}},
 			{"unblockip", []string{"ip", "reason"}},
 			{"listblockedips", nil},
-		} {
+		}
+
+		commands := make([]*cli.Command, 0, len(methods))
+		for _, def := range methods {
 			def := def
 
 			flags := make([]cli.Flag, len(def.args), len(def.args)+4)