upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/A5.md
diff options
context:
space:
mode:
Diffstat (limited to 'A5.md')
-rw-r--r--A5.md288
1 files changed, 288 insertions, 0 deletions
diff --git a/A5.md b/A5.md
new file mode 100644
index 0000000..190412e
--- /dev/null
+++ b/A5.md
@@ -0,0 +1,288 @@
1# NIP-A5
2
3## WASM Programs
4
5`draft` `optional`
6
7This NIP defines a standard for publishing self-contained WebAssembly programs as Nostr events. Programs are base64-encoded, executed client-side, and interact with Nostr exclusively through a host-provided handle-based API for building requests, querying relays, and rendering results.
8
9---
10
11## Event Format
12
13```yaml
14{
15 "kind": 1227,
16 "content": "<base64-encoded WASM binary>",
17 "tags":
18 [
19 ["name", "<program-name>"],
20 ["description", "<optional-description>"],
21 ["icon", "<image-url>"],
22 ["param", "<... (see below)>"]
23 ]
24}
25```
26
27## String Convention
28
29Unless noted otherwise (for hex pubkeys and ids), the module passes strings to the host as `(ptr: i32, len: i32)` pairs into its linear memory. The host returns strings (and other variable-length data) by returning an `i32` pointer to a buffer in linear memory whose first 4 bytes are the `u32_be` byte-length of the payload that follows.
30
31For example, if `nostr.get_content(handle)` returns `7165` that will be a pointer to a buffer at location `7165`. If that buffer starts with `[ 00 00 00 03 ]` that means it contains 3 bytes (aside from the initial four), so we can keep reading it `[ 00 00 00 03 66 6f 6f ]` and conclude that the string is `"foo"`.
32
33## Memory Management Convention
34
35In order to keep the size of WASM programs small (<10kb) we recommend not including any type of garbage collector or complex allocator. Just use the simplest possible bump allocator and do not free any memory -- programs will not use a lot of memory anyway, at most they will receive a bunch of `i32` event handles from the host, which are cheap.
36
37Because of this the WASM program is expected to expose just a simple `alloc()` function to the host, which is used for the host to know a place it can write things to. No function capable of freeing memory is expected.
38
39---
40
41## Global Exports
42
43### `run(params_ptr: i32)`
44
45The WASM module must export a function named `run` with a single pointer parameter. The client calls this on launch.
46
47### `on_event(sub_handle: i32, event_handle: i32, eosed: i32)`
48
49If the WASM module ever calls `nostr.subscribe` it must also export a function named `on_event` that will be called with every received event from any subscription. `sub` will be the subscription handle, `event` will be the event handle, `eosed` will be 0 if the event was received before `EOSE`, 1 otherwise.
50
51### `on_eose(sub_handle: i32)`
52
53Likewise, this will be called by the host whenever a subscription sends an `EOSE`.
54
55### `alloc(size: i32) -> i32`
56
57This function should allocate a buffer of the given size and return a pointer to it the host can write to.
58
59---
60
61## Global Imports
62
63The WASM module **must** export its linear memory so the host can read guest strings and write return buffers:
64
65```wat
66(memory (export "memory") 1)
67```
68
69---
70
71## Parameters
72
73Programs can declare parameters that the host must provide when calling `run()`. Parameters are declared as tags on the program event.
74
75The tag format is `["param", "<name>", "<description>", "<type>", "<required>"]` where:
76
77- `<name>` is the parameter identifier (for UI purposes).
78- `<description>` is an optional human-readable description, can be left empty.
79- `<type>` is one of: `public_key`, `event`, `string`, `number`, `timestamp`, `relay`.
80- `<required>` is `"required"` if the parameter is required, empty otherwise.
81
82The client running the program may prompt the user to provide these parameters or it may take them from the context automatically (for example, by providing the option of running a program that takes an `event` param from a context-menu on such an event). A simple program will probably use between 0 and 2 parameters usually.
83
84A special parameter called `"me"` of type `public_key` can be specified to be automatically filled with the public key of the current user.
85
86Parameters of type `event` may take an extra tag item `<supported_kinds>` after `<required>` with a comma-separated list of kinds that are acceptable.
87
88### Example Event
89
90For example, this program could be displayed as an option to be clicked on any user profile.
91
92```yaml
93{
94 "kind": 1227,
95 "content": "<base64-encoded WASM binary>",
96 "tags":
97 [
98 ["name", "interactions"],
99 ["description", "dispĺays interactions between me and someone else"],
100 ["param", "me", "myself", "public_key", "required"],
101 [
102 "param",
103 "public_key",
104 "person with whom I want to see my interactions",
105 "public_key",
106 "required"
107 ]
108 ]
109}
110```
111
112### Parameter Passing
113
114When the host calls `run()`, it passes a single pointer to a buffer containing all parameters in the order they appear in the tags. Each type has a specific encoding:
115
116| Type | Encoding | Size |
117| ------------ | --------------------------------------------------------------------- | ------------- |
118| `public_key` | 32 bytes (should all be set to zero if the parameter is not provided) | 32 bytes |
119| `event` | i32 handle | 4 bytes |
120| `string` | u32_be length followed by UTF-8 bytes | 4 + len bytes |
121| `number` | i32 | 4 bytes |
122| `timestamp` | unix timestamp as u32_be | 4 bytes |
123| `relay` | relay URL, same as string | 4 + len bytes |
124
125For example: in a program with parameters `[me: public_key, target_event: event, target_relay: relay]` the buffer layout would be:
126
127```
128[ 32-byte current user pubkey ][ 4-byte i32 handle ][ 4-byte len for relay URL ][ UTF-8 relay URL ]
129```
130
131The WASM program reads this by iterating through the declared parameters in order, reading the appropriate number of bytes for each type.
132
133---
134
135## Host Provided API
136
137These are the functions the host must expose to be called by the WASM program.
138
139Many of these expect and return handles. Handles are just `i32` numbers. The idea of dealing with handles and providing accessor functions is that the WASM programs don't have to include JSON parsers or any other form of encoder or decoder that would be necessary in order to send and receive structured data to and from the host. Instead they just deal with handles and request data to be extracted from these handles on their behalf to the host. This allows WASM programs to remain small and easy to write.
140
141### `nostr.req_new() -> i32`
142
143Create a new empty request handle.
144
145### `nostr.req_add_author(req: i32, pubkey_ptr: i32) -> void`
146
147Add a pubkey to the `authors` filter. It must be a pointer to a 32-byte buffer.
148
149### `nostr.req_add_author_hex(req: i32, pubkey_hex_ptr: i32) -> void`
150
151Same as `req_add_author` but with a hex-encoded pubkey string. Length is assumed to be 64 characters.
152
153### `nostr.req_add_id(req: i32, id_ptr: i32) -> void`
154
155Add an id to the `ids` filter. It must be a pointer to a 32-byte buffer.
156
157### `nostr.req_add_id_hex(req: i32, id_hex_ptr: i32) -> void`
158
159Same as `req_add_id` but with a hex-encoded id string. Length is assumed to be 64 characters.
160
161### `nostr.req_add_kind(req: i32, kind: i32) -> void`
162
163Add a kind integer to the `kinds` filter.
164
165### `nostr.req_add_tag(req: i32, tag_ptr: i32, tag_len: i32, value_ptr: i32, value_len: i32) -> void`
166
167Add a value to a tag filter. `tag_ptr/tag_len` points to the tag name (e.g. `"p"` -- will be added to the filter as `"#p"`); `value_ptr/value_len` points to the tag value to match. The value is treated as a string.
168
169### `nostr.req_add_tag_bin32(req: i32, tag_ptr: i32, value_ptr: i32) -> void`
170
171Same as `req_add_tag` but the value is a 32-byte binary buffer that the host will convert to hex. `value_ptr` must point to a 32-byte buffer. This is useful for `#p`, `#e`, and other tag filters that need pubkey or event ID values.
172
173Hosts may deduplicate filter values (authors, ids, kinds, and tag values) when the module adds the same value more than once via `req_add_*`.
174
175### `nostr.req_set_limit(req: i32, limit: i32) -> void`
176
177Sets the `"limit"` attribute of the filter.
178
179### `nostr.req_set_since(req: i32, timestamp: i32) -> void`
180
181Sets the `"since"` attribute of the filter.
182
183### `nostr.req_set_until(req: i32, timestamp: i32) -> void`
184
185Sets the `"until"` attribute of the filter.
186
187### `nostr.req_set_search(req: i32, ptr: i32, len: i32) -> void`
188
189Sets the `"search"` attribute of the filter.
190
191### `nostr.req_add_relay(req: i32, ptr: i32, len: i32) -> void`
192
193Explicitly target a relay URL for this request.
194
195If no relays are added to a request, the client should apply the following relay selection heuristics:
196
197- If `authors` are specified, use the NIP-65 **write** relays of those pubkeys.
198- If `#p` or `#P` tag filters are specified, use the NIP-65 **read** relays of those pubkeys.
199- Otherwise, use an arbitrary set of relays or just fail.
200
201### `nostr.req_close_on_eose(req: i32) -> void`
202
203Mark this request to automatically close after `EOSE`.
204
205### `nostr.subscribe(req: i32) -> i32`
206
207Sends the REQ to the target relays and returns a subscription handle. The request handle is consumed by this call — do not drop it separately.
208
209The globally exported `on_event` function will be called with the results.
210
211- The module must call `nostr.drop(event)` on each non-zero event handle when done with it.
212- If `nostr.req_close_on_eose` was set, the host drops the subscription handle after the EOSE callback. The module must not drop it.
213
214To cancel a live subscription call `nostr.drop(sub)`.
215
216### `nostr.event_get_id(event_handle: i32) -> i32`
217
218Returns a pointer to a 32-byte buffer containing the event ID.
219
220### `nostr.event_get_id_hex(event_handle: i32) -> i32`
221
222Returns a pointer to a 64-character string containing the hex event ID.
223
224### `nostr.event_get_pubkey(event_handle: i32) -> i32`
225
226Returns a pointer to a 32-byte buffer containing the public key of the event author.
227
228### `nostr.event_get_pubkey_hex(event_handle: i32) -> i32`
229
230Returns a pointer to a 64-character string containing the hex public key of the event author.
231
232### `nostr.event_get_kind(event_handle: i32) -> i32`
233
234Returns the kind integer directly.
235
236### `nostr.event_get_created_at(event_handle: i32) -> i32`
237
238Returns the unix timestamp directly.
239
240### `nostr.event_get_content(event_handle: i32) -> i32`
241
242Returns a pointer to a buffer containing the event content.
243
244### `nostr.event_get_tag_count(event_handle: i32) -> i32`
245
246Returns the total number of tags on this event.
247
248### `nostr.event_get_tag_item_count(event_handle: i32, tag_index: i32) -> i32`
249
250Returns the number of items in the tag at `tag_index`.
251
252### `nostr.event_get_tag_item(event_handle: i32, tag_index: i32, item_index: i32) -> i32`
253
254Returns a pointer to a buffer containing the item at `(tag_index, item_index)`.
255
256### `nostr.event_get_tag_item_bin32(event_handle: i32, tag_index: i32, item_index: i32) -> i32`
257
258Same as `event_get_tag_item`, but returns a 32-byte buffer of the item if it happened to be a pubkey or an event id; 0 otherwise.
259
260### `nostr.event_get_tag_item_by_name(event_handle: i32, name_ptr: i32, name_len: i32, item_index: i32) -> i32`
261
262Finds the first tag whose name (item 0) matches the string at `name_ptr/name_len`, then returns a pointer to a buffer containing item `item_index` from that tag; `0` if no matching tag is found.
263
264### `nostr.event_get_tag_item_by_name_bin32(event_handle: i32, name_ptr: i32, name_len: i32, item_index: i32) -> i32`
265
266Same as `event_get_tag_item_by_name`, but returns a 32-byte buffer of the value if it happened to be a pubkey or an event id; 0 otherwise.
267
268### `nostr.display(event: i32) -> void`
269
270Render an event through the client's native note renderer. The event handle is not consumed.
271
272### `nostr.log(ptr: i32, len: i32) -> void`
273
274Emit a log message to the host's debug console or developer tooling. The string at `ptr/len` is the message.
275
276### `nostr.drop(handle: i32) -> void`
277
278Releases any handle: unconsumed request, active subscription (cancels it), individual event, or list (also releases all contained event handles).
279
280---
281
282## Example Programs and Host
283
284- JavaScript host runtime example: https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/src/nprogram-host.ts
285- Rust example (basic, just reads from a relay): https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/examples/basic/src/lib.rs
286- Zig example (displays the history of all interactions between the current user and some other user): https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/examples/interactions/main.zig
287- TinyGo example (fetches all notifications, only display the unreplied ones): https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/examples/unreplied/main.go
288- AssemblyScript example (fetches events from multiple relays and does a diff of the content): https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/examples/old-trending/main.ts