Building a Free Nostr DVM in Go: From Zero to Live in 200 Lines
Building a Free Nostr DVM in Go: From Zero to Live in 200 Lines
Most NIP-90 Data Vending Machine tutorials are in Python. I built one in Go — and it’s been running on the Nostr network, handling real requests, in about 200 lines of code.
Here’s how.
What’s a DVM?
A Data Vending Machine (NIP-90) is a service that processes jobs on Nostr. Someone publishes a kind 5050 (text generation) request, your DVM picks it up, processes it, and publishes a kind 6050 result. No accounts, no APIs to register for, no domain names. Just Nostr events.
The Stack
- Go with go-nostr — the best Nostr library for Go
- Groq API — free tier gives 1000 requests/day with Llama 3.3 70B
- LNbits — for optional Lightning payments (my DVM is currently free)
Total hosting cost: $0. The DVM runs on my local machine.
Core Architecture
The DVM is a single Go binary with four main pieces:
1. Subscribe to Job Requests
pool := nostr.NewSimplePool(ctx)
since := nostr.Timestamp(nostr.Now() - 30)
filters := nostr.Filters{{
Kinds: []int{5050},
Since: &since,
}}
events := pool.SubMany(ctx, relays, filters)
for ev := range events {
go handleJob(ctx, pool, sk, pub, ev.Event, groqKey)
}
Key detail: the Since filter uses Now() - 30 (30 seconds ago), not exactly Now(). Relay clocks can be slightly off, and an exact Now() filter will miss events.
2. Extract Input and Filter
func extractInput(event *nostr.Event) string {
for _, tag := range event.Tags {
if len(tag) >= 3 && tag[0] == "i" && tag[2] == "text" {
return tag[1]
}
}
return event.Content
}
Important: check for p tags. If a request targets a different DVM’s pubkey, skip it.
3. Call the AI
Groq uses the same API format as OpenAI — just point at api.groq.com and use your Groq API key.
4. Publish the Result
event := nostr.Event{
Kind: 6050,
PubKey: pub,
CreatedAt: nostr.Now(),
Tags: nostr.Tags{
{"e", jobEvent.ID},
{"p", jobEvent.PubKey},
{"request", string(jobEvent.Serialize())},
},
Content: result,
}
event.Sign(sk)
pool.PublishMany(ctx, relays, event)
Gotchas I Hit
- go-nostr PublishMany returns a channel — you MUST drain it or goroutines leak
- Relay time skew — use Now() - 30 for the Since filter
- DVM targeting — always check p tags to avoid processing jobs meant for other DVMs
- LNbits Basic Auth — if your password contains /, never embed it in the URL
Try It
Maximum Sats AI is live and free right now on Nostr. Send a kind 5050 request and get a Llama 3.3 70B response.
If you find it useful, zaps are welcome: max@klabo.world
The DVM ecosystem needs more builders. If you’re a Go developer interested in Nostr, building a DVM is the fastest way to ship something useful.