summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-13 17:35:32 -0800
committerbndw <ben@bdw.to>2026-02-13 17:35:32 -0800
commit6c840f03524187d1f056fdaa70e5f1f9b24cf793 (patch)
tree9b068d5125e79320321ac1a35df30f43482d4aba
parent581ceecbf046f99b39885c74e2780a5320e5b15e (diff)
feat: add Protocol Buffer definitions and build tooling
-rw-r--r--Makefile39
-rw-r--r--buf.gen.yaml15
-rw-r--r--buf.yaml9
-rw-r--r--proto/nostr/v1/nostr.proto183
4 files changed, 246 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d01b68b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
1.PHONY: proto proto-lint proto-breaking test build clean
2
3# Generate proto files
4proto:
5 buf generate
6
7# Lint proto files
8proto-lint:
9 buf lint
10
11# Check for breaking changes
12proto-breaking:
13 buf breaking --against '.git#branch=main'
14
15# Run tests
16test:
17 go test ./...
18
19# Build the relay
20build:
21 go build -o bin/relay ./cmd/relay
22
23# Clean generated files
24clean:
25 rm -rf api/
26 rm -f bin/relay
27
28# Install buf (if not already installed)
29install-buf:
30 @if ! command -v buf &> /dev/null; then \
31 echo "Installing buf..."; \
32 mkdir -p ~/.local/bin; \
33 curl -sSL "https://github.com/bufbuild/buf/releases/latest/download/buf-$$(uname -s)-$$(uname -m)" -o ~/.local/bin/buf; \
34 chmod +x ~/.local/bin/buf; \
35 echo "buf installed to ~/.local/bin/buf"; \
36 echo "Make sure ~/.local/bin is in your PATH"; \
37 else \
38 echo "buf is already installed"; \
39 fi
diff --git a/buf.gen.yaml b/buf.gen.yaml
new file mode 100644
index 0000000..e6f485a
--- /dev/null
+++ b/buf.gen.yaml
@@ -0,0 +1,15 @@
1version: v2
2managed:
3 enabled: true
4 override:
5 - file_option: go_package_prefix
6 value: northwest.io/nostr-grpc/api
7plugins:
8 - remote: buf.build/protocolbuffers/go
9 out: api
10 opt:
11 - paths=source_relative
12 - remote: buf.build/grpc/go
13 out: api
14 opt:
15 - paths=source_relative
diff --git a/buf.yaml b/buf.yaml
new file mode 100644
index 0000000..c7e30e3
--- /dev/null
+++ b/buf.yaml
@@ -0,0 +1,9 @@
1version: v2
2modules:
3 - path: proto
4lint:
5 use:
6 - STANDARD
7breaking:
8 use:
9 - FILE
diff --git a/proto/nostr/v1/nostr.proto b/proto/nostr/v1/nostr.proto
new file mode 100644
index 0000000..7e8eacb
--- /dev/null
+++ b/proto/nostr/v1/nostr.proto
@@ -0,0 +1,183 @@
1syntax = "proto3";
2
3package nostr.v1;
4
5option go_package = "northwest.io/nostr-grpc/api/nostr/v1;nostrv1";
6
7// Core Nostr event as defined in NIP-01
8message Event {
9 string id = 1;
10 string pubkey = 2;
11 int64 created_at = 3;
12 int32 kind = 4;
13 repeated Tag tags = 5;
14 string content = 6;
15 string sig = 7;
16
17 // Optional: only populated if client requests verification
18 // Contains exact canonical JSON bytes that were signed
19 optional bytes canonical_json = 8;
20}
21
22// Tag is an array of strings (e.g., ["e", "event_id", "relay_url"])
23message Tag {
24 repeated string values = 1;
25}
26
27// Nostr filter for querying events (REQ)
28message Filter {
29 repeated string ids = 1;
30 repeated string authors = 2; // pubkeys
31 repeated int32 kinds = 3;
32 repeated string e_tags = 4; // #e tag values
33 repeated string p_tags = 5; // #p tag values
34 optional int64 since = 6;
35 optional int64 until = 7;
36 optional int32 limit = 8;
37
38 // Extension: support for arbitrary tag filters
39 map<string, TagFilter> tag_filters = 9;
40}
41
42// Filter for arbitrary tags
43message TagFilter {
44 repeated string values = 1;
45}
46
47// Request to publish a single event
48message PublishEventRequest {
49 Event event = 1;
50}
51
52// Response after publishing an event
53message PublishEventResponse {
54 bool accepted = 1;
55 string message = 2; // Error message or "duplicate" or "success"
56
57 // Always include canonical JSON so client can verify
58 // what the relay stored
59 bytes canonical_json = 3;
60}
61
62// Request to subscribe to events matching filters
63message SubscribeRequest {
64 repeated Filter filters = 1;
65
66 // If true, include canonical_json in streamed Event messages
67 // Allows client-side signature verification
68 // Default: false (most clients trust the relay)
69 bool include_canonical_json = 2;
70
71 // Optional client-provided subscription ID for tracking
72 string subscription_id = 3;
73}
74
75// Request to unsubscribe from an active subscription
76message UnsubscribeRequest {
77 string subscription_id = 1;
78}
79
80// Batch publish request
81message PublishBatchRequest {
82 repeated Event events = 1;
83}
84
85// Batch publish response
86message PublishBatchResponse {
87 repeated PublishEventResponse results = 1;
88}
89
90// Paginated query request
91message QueryRequest {
92 repeated Filter filters = 1;
93 bool include_canonical_json = 2;
94
95 // Pagination
96 string cursor = 3; // Opaque cursor from previous response
97 int32 page_size = 4; // Default: 100
98}
99
100// Paginated query response
101message QueryResponse {
102 repeated Event events = 1;
103 string next_cursor = 2; // Empty if no more results
104 int32 total_count = 3; // Optional: total matching events
105}
106
107// Event count request (NIP-45)
108message CountRequest {
109 repeated Filter filters = 1;
110}
111
112// Event count response
113message CountResponse {
114 int64 count = 1;
115}
116
117// Empty message
118message Empty {}
119
120// Main relay service
121service NostrRelay {
122 // Publish a single event
123 rpc PublishEvent(PublishEventRequest) returns (PublishEventResponse);
124
125 // Subscribe to events matching filters (streaming)
126 rpc Subscribe(SubscribeRequest) returns (stream Event);
127
128 // Unsubscribe from an active subscription
129 rpc Unsubscribe(UnsubscribeRequest) returns (Empty);
130
131 // gRPC-specific: batch publish
132 rpc PublishBatch(PublishBatchRequest) returns (PublishBatchResponse);
133
134 // gRPC-specific: paginated query (non-streaming)
135 rpc QueryEvents(QueryRequest) returns (QueryResponse);
136
137 // Event counts (NIP-45)
138 rpc CountEvents(CountRequest) returns (CountResponse);
139}
140
141// Admin service (optional, secured separately)
142service RelayAdmin {
143 rpc GetStats(Empty) returns (RelayStats);
144 rpc GetConnections(Empty) returns (ConnectionList);
145 rpc BanPublicKey(BanRequest) returns (Empty);
146 rpc GetStorageInfo(Empty) returns (StorageStats);
147}
148
149// Relay statistics
150message RelayStats {
151 int64 total_events = 1;
152 int64 total_subscriptions = 2;
153 int64 connected_clients = 3;
154 int64 events_per_second = 4;
155 int64 uptime_seconds = 5;
156}
157
158// List of active connections
159message ConnectionList {
160 repeated Connection connections = 1;
161}
162
163// Single connection info
164message Connection {
165 string client_id = 1;
166 string protocol = 2; // "websocket" or "grpc"
167 int64 connected_at = 3;
168 int32 active_subscriptions = 4;
169}
170
171// Request to ban a public key
172message BanRequest {
173 string pubkey = 1;
174 int64 until = 2; // Unix timestamp, 0 for permanent
175 string reason = 3;
176}
177
178// Storage statistics
179message StorageStats {
180 int64 total_bytes = 1;
181 int64 total_events = 2;
182 int64 db_size_bytes = 3;
183}