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.md294
1 files changed, 0 insertions, 294 deletions
diff --git a/A5.md b/A5.md
deleted file mode 100644
index 2bff38c..0000000
--- a/A5.md
+++ /dev/null
@@ -1,294 +0,0 @@
1# NIP-A5
2
3## Scrolls
4
5`draft` `optional`
6
7This NIP defines a standard for publishing self-contained WebAssembly programs as Nostr events ("scrolls"). Scrolls are composed of basic metadata (`name`, `description` and `image`) and initial execution parameters defined as tags and a WASM binary, base64-encoded as the content. The binaries are executed in a sandbox inside a "host" (i.e. a proper Nostr client). Scrolls interact with Nostr only through a set of simple APIs provided by the host.
8
9---
10
11## Event Format
12
13```yaml
14{
15 "kind": 1227,
16 "content": "<base64-encoded WASM binary>",
17 "tags":
18 [
19 ["name", "<name>"],
20 ["description", "<description>"],
21 ["icon", "<image-url>"],
22 ["param", "<... (see below)>"]
23 ]
24}
25```
26
27## Favorite scrolls list
28
29Clients can publish a list of favorite scrolls using a NIP-51 standard list event with kind `10027`. The list should include one `"e"` tag per scroll event, with optional relay hints and author hints.
30
31## String Convention
32
33Unless 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.
34
35For 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"`.
36
37## Memory Management Convention
38
39In 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.
40
41Because 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.
42
43Hosts are encouraged to enforce resource limits for scroll execution, including memory consumption, number of open handles, and CPU usage.
44
45---
46
47## Global Exports
48
49### `run(params_ptr: i32)`
50
51The WASM module must export a function named `run` with a single pointer parameter. The client calls this on launch.
52
53### `on_event(sub_handle: i32, event_handle: i32, eosed: i32)`
54
55If 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.
56
57### `on_eose(sub_handle: i32)`
58
59Likewise, this will be called by the host whenever a subscription sends an `EOSE`.
60
61### `alloc(size: i32) -> i32`
62
63This function should allocate a buffer of the given size and return a pointer to it the host can write to.
64
65---
66
67## Global Imports
68
69The WASM module **must** export its linear memory so the host can read guest strings and write return buffers:
70
71```wat
72(memory (export "memory") 1)
73```
74
75---
76
77## Parameters
78
79Scrolls can declare parameters that the host must provide when calling `run()`.
80
81The tag format is `["param", "<name>", "<description>", "<type>", "<required>"]` where:
82
83- `<name>` is the parameter identifier (for UI purposes).
84- `<description>` is an optional human-readable description, can be left empty.
85- `<type>` is one of: `public_key`, `event`, `string`, `number`, `timestamp`, `relay`.
86- `<required>` is `"required"` if the parameter is required, empty otherwise.
87
88The 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.
89
90A special parameter called `"me"` of type `public_key` can be specified to be automatically filled with the public key of the current user.
91
92Parameters of type `event` may take an extra tag item `<supported_kinds>` after `<required>` with a comma-separated list of kinds that are acceptable.
93
94### Example
95
96For example, this scroll could be displayed as an option to be clicked on any user profile.
97
98```yaml
99{
100 "kind": 1227,
101 "content": "<base64-encoded WASM binary>",
102 "tags":
103 [
104 ["name", "interactions"],
105 ["description", "dispĺays interactions between me and someone else"],
106 ["param", "me", "myself", "public_key", "required"],
107 [
108 "param",
109 "public_key",
110 "person with whom I want to see my interactions",
111 "public_key",
112 "required"
113 ]
114 ]
115}
116```
117
118### Parameter Passing
119
120When 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:
121
122| Type | Encoding | Size |
123| ------------ | --------------------------------------------------------------------- | ------------- |
124| `public_key` | 32 bytes (should all be set to zero if the parameter is not provided) | 32 bytes |
125| `event` | i32 handle | 4 bytes |
126| `string` | u32_be length followed by UTF-8 bytes | 4 + len bytes |
127| `number` | i32 | 4 bytes |
128| `timestamp` | unix timestamp as u32_be | 4 bytes |
129| `relay` | relay URL, same as string | 4 + len bytes |
130
131For example: in a scroll with parameters `[me: public_key, target_event: event, target_relay: relay]` the buffer layout would be:
132
133```
134[ 32-byte current user pubkey ][ 4-byte i32 handle ][ 4-byte len for relay URL ][ UTF-8 relay URL ]
135```
136
137The WASM program reads this by iterating through the declared parameters in order, reading the appropriate number of bytes for each type.
138
139---
140
141## Host Provided API
142
143These are the functions the host must expose to be called by the WASM program.
144
145Many 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.
146
147### `nostr.req_new() -> i32`
148
149Create a new empty request handle.
150
151### `nostr.req_add_author(req: i32, pubkey_ptr: i32) -> void`
152
153Add a pubkey to the `authors` filter. It must be a pointer to a 32-byte buffer.
154
155### `nostr.req_add_author_hex(req: i32, pubkey_hex_ptr: i32) -> void`
156
157Same as `req_add_author` but with a hex-encoded pubkey string. Length is assumed to be 64 characters.
158
159### `nostr.req_add_id(req: i32, id_ptr: i32) -> void`
160
161Add an id to the `ids` filter. It must be a pointer to a 32-byte buffer.
162
163### `nostr.req_add_id_hex(req: i32, id_hex_ptr: i32) -> void`
164
165Same as `req_add_id` but with a hex-encoded id string. Length is assumed to be 64 characters.
166
167### `nostr.req_add_kind(req: i32, kind: i32) -> void`
168
169Add a kind integer to the `kinds` filter.
170
171### `nostr.req_add_tag(req: i32, tag_ptr: i32, tag_len: i32, value_ptr: i32, value_len: i32) -> void`
172
173Add 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.
174
175### `nostr.req_add_tag_bin32(req: i32, tag_ptr: i32, value_ptr: i32) -> void`
176
177Same 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.
178
179Hosts may deduplicate filter values (authors, ids, kinds, and tag values) when the module adds the same value more than once via `req_add_*`.
180
181### `nostr.req_set_limit(req: i32, limit: i32) -> void`
182
183Sets the `"limit"` attribute of the filter.
184
185### `nostr.req_set_since(req: i32, timestamp: i32) -> void`
186
187Sets the `"since"` attribute of the filter.
188
189### `nostr.req_set_until(req: i32, timestamp: i32) -> void`
190
191Sets the `"until"` attribute of the filter.
192
193### `nostr.req_set_search(req: i32, ptr: i32, len: i32) -> void`
194
195Sets the `"search"` attribute of the filter.
196
197### `nostr.req_add_relay(req: i32, ptr: i32, len: i32) -> void`
198
199Explicitly target a relay URL for this request.
200
201If no relays are added to a request, the client should apply the following relay selection heuristics:
202
203- If `authors` are specified, use the NIP-65 **write** relays of those pubkeys.
204- If `#p` or `#P` tag filters are specified, use the NIP-65 **read** relays of those pubkeys.
205- Otherwise, use an arbitrary set of relays or just fail.
206
207### `nostr.req_close_on_eose(req: i32) -> void`
208
209Mark this request to automatically close after `EOSE`.
210
211### `nostr.subscribe(req: i32) -> i32`
212
213Sends the REQ to the target relays and returns a subscription handle. The request handle is consumed by this call — do not drop it separately.
214
215The globally exported `on_event` function will be called with the results.
216
217- The module must call `nostr.drop(event)` on each non-zero event handle when done with it.
218- If `nostr.req_close_on_eose` was set, the host drops the subscription handle after the EOSE callback. The module must not drop it.
219
220To cancel a live subscription call `nostr.drop(sub)`.
221
222### `nostr.event_get_id(event_handle: i32) -> i32`
223
224Returns a pointer to a 32-byte buffer containing the event ID.
225
226### `nostr.event_get_id_hex(event_handle: i32) -> i32`
227
228Returns a pointer to a 64-character string containing the hex event ID.
229
230### `nostr.event_get_pubkey(event_handle: i32) -> i32`
231
232Returns a pointer to a 32-byte buffer containing the public key of the event author.
233
234### `nostr.event_get_pubkey_hex(event_handle: i32) -> i32`
235
236Returns a pointer to a 64-character string containing the hex public key of the event author.
237
238### `nostr.event_get_kind(event_handle: i32) -> i32`
239
240Returns the kind integer directly.
241
242### `nostr.event_get_created_at(event_handle: i32) -> i32`
243
244Returns the unix timestamp directly.
245
246### `nostr.event_get_content(event_handle: i32) -> i32`
247
248Returns a pointer to a buffer containing the event content.
249
250### `nostr.event_get_tag_count(event_handle: i32) -> i32`
251
252Returns the total number of tags on this event.
253
254### `nostr.event_get_tag_item_count(event_handle: i32, tag_index: i32) -> i32`
255
256Returns the number of items in the tag at `tag_index`.
257
258### `nostr.event_get_tag_item(event_handle: i32, tag_index: i32, item_index: i32) -> i32`
259
260Returns a pointer to a buffer containing the item at `(tag_index, item_index)`.
261
262### `nostr.event_get_tag_item_bin32(event_handle: i32, tag_index: i32, item_index: i32) -> i32`
263
264Same 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.
265
266### `nostr.event_get_tag_item_by_name(event_handle: i32, name_ptr: i32, name_len: i32, item_index: i32) -> i32`
267
268Finds 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.
269
270### `nostr.event_get_tag_item_by_name_bin32(event_handle: i32, name_ptr: i32, name_len: i32, item_index: i32) -> i32`
271
272Same 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.
273
274### `nostr.display(event: i32) -> void`
275
276Render an event through the client's native note renderer. The event handle is not consumed.
277
278### `nostr.log(ptr: i32, len: i32) -> void`
279
280Emit a log message to the host's debug console or developer tooling. The string at `ptr/len` is the message.
281
282### `nostr.drop(handle: i32) -> void`
283
284Releases any handle: unconsumed request, active subscription (cancels it), individual event, or list (also releases all contained event handles).
285
286---
287
288## Example programs and host code
289
290- JavaScript host runtime example: https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/src/nprogram-host.ts
291- Rust example (basic, just reads from a relay): https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/examples/basic/src/lib.rs
292- 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
293- TinyGo example (fetches all notifications, only display the unreplied ones): https://viewsource.win/fiatjaf.com/nprogram/_/2e7d56d1536fcca8c49e7b2e99db8062f8df9ae1/~/examples/unreplied/main.go
294- 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