@msgboard/sdk 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +55 -7
  2. package/openrpc.json +254 -39
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -10,7 +10,7 @@ npm i --save @msgboard/sdk
10
10
 
11
11
  ## Quickstart
12
12
 
13
- You need an RPC endpoint whose node runs the `msgboard_` module. Public PulseChain/Ethereum RPCs do **not** run it yet; valve.city does (for example `https://one.valve.city/rpc/vk_demo/evm/369`). You can also run your own node with the module.
13
+ You need an RPC endpoint whose node runs the `msgboard_` module. Public PulseChain/Ethereum RPCs do **not** run it yet; [valve.city](https://valve.city) does. Its RPC endpoints are **keyed** — the API key sits in the path (`https://one.valve.city/rpc/<key>/evm/<chainId>`), and `vk_demo` is a public demo key for trying it out (for example `https://one.valve.city/rpc/vk_demo/evm/369`). You can also run your own node with the module.
14
14
 
15
15
  ### with viem
16
16
 
@@ -72,13 +72,15 @@ board.getDifficulty('0x...') // bigint
72
72
 
73
73
  `workMultiplier` and `workDivisor` come from `status()` and are applied automatically by `doPoW`.
74
74
 
75
+ Because each node validates incoming messages against **its own** current factors, these two numbers are also an implicit board-level setting, not just a per-message cost. Raising the required work — a higher `workMultiplier`, or a lower `workDivisor` — makes the board reject any message whose proof of work falls below the new threshold, so it accumulates **fewer** messages; loosening them admits **more**. Operators tune the same two knobs to trade spam-resistance against message volume. A client must therefore grind against the board's **live** factors (which `doPoW` reads from `status()`): work that was valid under looser settings can be rejected once a board tightens them.
76
+
75
77
  ## Categories
76
78
 
77
79
  A category is a 32-byte hash. Pass a string and the client hashes it for you (`categoryHash`); pass hex and it is used as-is. The demo board uses the `gasmoneyplease` category.
78
80
 
79
81
  ## Ephemerality
80
82
 
81
- Messages are short-lived: the board retains roughly the last 120 blocks of messages, so the board is a live signal, not durable storage.
83
+ Messages are short-lived: the board retains roughly the last 120 blocks of messages, so the board is a live signal, not durable storage. The board also has a maximum size cap — if a burst of large messages fills the cap before the 120-block window expires, new submissions may be rejected until older messages age out. Design for loss: treat the board as a delivery channel, not a store.
82
84
 
83
85
  ## Keeping work off the UI thread
84
86
 
@@ -299,15 +301,61 @@ Object whose values are `RPCMessage[]`.
299
301
 
300
302
  ## Client methods (not JSON-RPC)
301
303
 
302
- These run in the client, not on the node, so they are not in the OpenRPC spec:
304
+ These run in the client process, not on the node, so they are not part of the OpenRPC spec.
305
+
306
+ ### `doPoW(category, data, limit?)`
307
+
308
+ Grinds a valid proof-of-work message. Reads current difficulty from `status()` before starting, so the work is always valid for the live board settings. Returns `{ message, stats }` where `stats` includes `nonce`, `duration`, and the number of iterations. The `limit` parameter sets a maximum number of iterations — useful for streaming progress or cancellation in long-running environments.
309
+
310
+ ### `getDifficulty(data)`
311
+
312
+ Returns the difficulty threshold for a given payload hex string as a `bigint`. Helpful for estimating how long `doPoW` will take before committing to it.
313
+
314
+ ### `categoryHash(name)`
315
+
316
+ Encodes a plain-text category name to the 32-byte hex hash the board stores. Pass the result directly to `doPoW` or `content()` filters.
317
+
318
+ ### `wrapLegacySend(provider)`
319
+
320
+ Wraps an ethers v5 `JsonRpcProvider` (or any provider with a `send` method) into the `Provider` interface the client expects. Use this when you cannot upgrade to viem.
321
+
322
+ ### Other utilities
323
+
324
+ `checkWork`, `difficulty`, `encodeData`, `toRLP`, `fromRLP`, `fromRPCMessage`, `toRPCMessage` — lower-level building blocks for custom proof-of-work loops, message encoding, and RPC message conversion. Their signatures are in the TypeScript types shipped with the package.
303
325
 
304
- - `doPoW(category, data, limit?)` — grind a valid proof-of-work message. Returns `{ message, stats }`.
305
- - `getDifficulty(data)` — the difficulty for a given payload, as a `bigint`.
306
- - Utilities: `categoryHash`, `checkWork`, `difficulty`, `encodeData`, `toRLP`, `fromRLP`, `fromRPCMessage`, `toRPCMessage`, `wrapLegacySend`.
326
+ ## Building automations
327
+
328
+ For server-side work polling continuously, reacting to specific categories, archiving messages, triggering cross-chain actions — install the companion relayer package:
329
+
330
+ ```sh
331
+ npm i @msgboard/relayer
332
+ ```
333
+
334
+ ```ts
335
+ import { http } from 'viem'
336
+ import { Relayer, msgboardContentSource, noopAction } from '@msgboard/relayer'
337
+ import type { RPCMessage } from '@msgboard/sdk'
338
+
339
+ const relayer = new Relayer<RPCMessage>({
340
+ node: { transport: http('https://one.valve.city/rpc/vk_demo/evm/369') },
341
+ // chain is auto-detected via eth_chainId — pass node.chain to override
342
+ source: msgboardContentSource({ category: 'myapp' }),
343
+ key: (msg) => msg.hash,
344
+ action: noopAction(),
345
+ // mode defaults to 'observe' — swap in webhookAction or submitMessageAction and set
346
+ // mode: 'live' to execute real effects
347
+ })
348
+
349
+ relayer.start()
350
+ ```
351
+
352
+ `Relayer` polls on a configurable heartbeat, deduplicates via a pluggable store (in-memory, Postgres), records everything to an optional archive sink regardless of mode, and gates `action.execute` on `mode: 'live'`. See the [`@msgboard/relayer`](https://www.npmjs.com/package/@msgboard/relayer) package for the full API, built-in sources/actions, and runnable examples.
307
353
 
308
354
  ## Machine-readable spec
309
355
 
310
- The JSON-RPC surface is published as an OpenRPC document at `openrpc.json` in this package. Point the OpenRPC Playground or a code generator at it.
356
+ The JSON-RPC surface is published as an OpenRPC document [`openrpc.json`](https://github.com/valve-tech/msgboard/blob/master/packages/sdk/openrpc.json) in this package, and hosted at [`msgboard.xyz/openrpc.json`](https://msgboard.xyz/openrpc.json). Open it in the [OpenRPC Playground](https://playground.open-rpc.org/?schemaUrl=https%3A%2F%2Fmsgboard.xyz%2Fopenrpc.json) (it loads the schema and pre-selects the valve.city PulseChain mainnet endpoint so you can call live methods directly), or point a code generator at the hosted spec.
357
+
358
+ All published packages are under the [`@msgboard`](https://www.npmjs.com/search?q=%40msgboard) scope on npm.
311
359
 
312
360
  ## License
313
361
 
package/openrpc.json CHANGED
@@ -4,94 +4,309 @@
4
4
  "title": "MsgBoard JSON-RPC API",
5
5
  "version": "0.0.28",
6
6
  "description": "JSON-RPC methods exposed by the msgboard_ module. Any node running the module serves these methods. Proof of work, not a fee, gates message submission.",
7
- "license": { "name": "MIT" }
7
+ "license": {
8
+ "name": "MIT"
9
+ }
8
10
  },
11
+ "servers": [
12
+ {
13
+ "name": "PulseChain mainnet (valve.city demo)",
14
+ "url": "https://one.valve.city/rpc/vk_demo/evm/369"
15
+ }
16
+ ],
9
17
  "methods": [
10
18
  {
11
19
  "name": "msgboard_status",
12
20
  "summary": "Board status and the difficulty factors required for valid messages.",
13
21
  "params": [],
14
- "result": { "name": "status", "schema": { "$ref": "#/components/schemas/Status" } },
22
+ "result": {
23
+ "name": "status",
24
+ "schema": {
25
+ "$ref": "#/components/schemas/Status"
26
+ }
27
+ },
15
28
  "examples": [
16
- { "name": "default", "params": [], "result": { "name": "status", "value": { "enabled": true, "count": "0x0", "size": "0x0", "workMultiplier": "0x2710", "workDivisor": "0xf4240" } } }
29
+ {
30
+ "name": "default",
31
+ "params": [],
32
+ "result": {
33
+ "name": "status",
34
+ "value": {
35
+ "enabled": true,
36
+ "count": "0x0",
37
+ "size": "0x0",
38
+ "workMultiplier": "0x2710",
39
+ "workDivisor": "0xf4240"
40
+ }
41
+ }
42
+ }
17
43
  ]
18
44
  },
19
45
  {
20
46
  "name": "msgboard_categories",
21
47
  "summary": "The list of 32-byte category hashes currently present on the board.",
22
48
  "params": [],
23
- "result": { "name": "categories", "schema": { "$ref": "#/components/schemas/Categories" } },
49
+ "result": {
50
+ "name": "categories",
51
+ "schema": {
52
+ "$ref": "#/components/schemas/Categories"
53
+ }
54
+ },
24
55
  "examples": [
25
- { "name": "gasmoneyplease", "params": [], "result": { "name": "categories", "value": ["0x6761736d6f6e6579706c65617365000000000000000000000000000000000000"] } }
56
+ {
57
+ "name": "gasmoneyplease",
58
+ "params": [],
59
+ "result": {
60
+ "name": "categories",
61
+ "value": [
62
+ "0x6761736d6f6e6579706c65617365000000000000000000000000000000000000"
63
+ ]
64
+ }
65
+ }
26
66
  ]
27
67
  },
28
68
  {
29
69
  "name": "msgboard_content",
30
70
  "summary": "All messages on the board, grouped by category hash. Optionally filtered.",
31
- "params": [ { "name": "filter", "required": false, "schema": { "$ref": "#/components/schemas/ContentFilter" } } ],
32
- "result": { "name": "content", "schema": { "$ref": "#/components/schemas/Content" } },
71
+ "params": [
72
+ {
73
+ "name": "filter",
74
+ "required": false,
75
+ "schema": {
76
+ "$ref": "#/components/schemas/ContentFilter"
77
+ }
78
+ }
79
+ ],
80
+ "result": {
81
+ "name": "content",
82
+ "schema": {
83
+ "$ref": "#/components/schemas/Content"
84
+ }
85
+ },
33
86
  "examples": [
34
- { "name": "empty", "params": [{ "name": "filter", "value": {} }], "result": { "name": "content", "value": {} } }
87
+ {
88
+ "name": "empty",
89
+ "params": [
90
+ {
91
+ "name": "filter",
92
+ "value": {}
93
+ }
94
+ ],
95
+ "result": {
96
+ "name": "content",
97
+ "value": {}
98
+ }
99
+ }
35
100
  ]
36
101
  },
37
102
  {
38
103
  "name": "msgboard_addMessage",
39
104
  "summary": "Submit a proof-of-work message (RLP-encoded) to the board.",
40
- "params": [ { "name": "rlp", "required": true, "schema": { "$ref": "#/components/schemas/Hex" } } ],
41
- "result": { "name": "hash", "schema": { "$ref": "#/components/schemas/Hex" } },
105
+ "params": [
106
+ {
107
+ "name": "rlp",
108
+ "required": true,
109
+ "schema": {
110
+ "$ref": "#/components/schemas/Hex"
111
+ }
112
+ }
113
+ ],
114
+ "result": {
115
+ "name": "hash",
116
+ "schema": {
117
+ "$ref": "#/components/schemas/Hex"
118
+ }
119
+ },
42
120
  "examples": [
43
- { "name": "submit", "params": [{ "name": "rlp", "value": "0xf800" }], "result": { "name": "hash", "value": "0x0d1e2f00000000000000000000000000000000000000000000000000c46845f9" } }
121
+ {
122
+ "name": "submit",
123
+ "params": [
124
+ {
125
+ "name": "rlp",
126
+ "value": "0xf800"
127
+ }
128
+ ],
129
+ "result": {
130
+ "name": "hash",
131
+ "value": "0x0d1e2f00000000000000000000000000000000000000000000000000c46845f9"
132
+ }
133
+ }
44
134
  ]
45
135
  },
46
136
  {
47
137
  "name": "msgboard_getMessage",
48
138
  "summary": "Fetch a single message by its hash.",
49
- "params": [ { "name": "hash", "required": true, "schema": { "$ref": "#/components/schemas/Hex" } } ],
50
- "result": { "name": "message", "schema": { "$ref": "#/components/schemas/RPCMessage" } },
139
+ "params": [
140
+ {
141
+ "name": "hash",
142
+ "required": true,
143
+ "schema": {
144
+ "$ref": "#/components/schemas/Hex"
145
+ }
146
+ }
147
+ ],
148
+ "result": {
149
+ "name": "message",
150
+ "schema": {
151
+ "$ref": "#/components/schemas/RPCMessage"
152
+ }
153
+ },
51
154
  "examples": [
52
- { "name": "zero", "params": [{ "name": "hash", "value": "0x0000000000000000000000000000000000000000000000000000000000000000" }], "result": { "name": "message", "value": { "version": "0x1", "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "blockNumber": "0x0", "category": "0x0000000000000000000000000000000000000000000000000000000000000000", "data": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0", "hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "workMultiplier": "0x0", "workDivisor": "0x0" } } }
155
+ {
156
+ "name": "zero",
157
+ "params": [
158
+ {
159
+ "name": "hash",
160
+ "value": "0x0000000000000000000000000000000000000000000000000000000000000000"
161
+ }
162
+ ],
163
+ "result": {
164
+ "name": "message",
165
+ "value": {
166
+ "version": "0x1",
167
+ "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
168
+ "blockNumber": "0x0",
169
+ "category": "0x0000000000000000000000000000000000000000000000000000000000000000",
170
+ "data": "0x0000000000000000000000000000000000000000000000000000000000000000",
171
+ "nonce": "0x0",
172
+ "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
173
+ "workMultiplier": "0x0",
174
+ "workDivisor": "0x0"
175
+ }
176
+ }
177
+ }
53
178
  ]
54
179
  }
55
180
  ],
56
181
  "components": {
57
182
  "schemas": {
58
- "Hex": { "title": "Hex", "type": "string", "pattern": "^0x[0-9a-fA-F]*$" },
183
+ "Hex": {
184
+ "title": "Hex",
185
+ "type": "string",
186
+ "pattern": "^0x[0-9a-fA-F]*$"
187
+ },
59
188
  "Status": {
60
- "title": "Status", "type": "object",
61
- "required": ["enabled", "count", "size", "workMultiplier", "workDivisor"],
189
+ "title": "Status",
190
+ "type": "object",
191
+ "required": [
192
+ "enabled",
193
+ "count",
194
+ "size",
195
+ "workMultiplier",
196
+ "workDivisor"
197
+ ],
62
198
  "properties": {
63
- "enabled": { "type": "boolean", "description": "Whether the module is enabled on this node." },
64
- "count": { "$ref": "#/components/schemas/Hex", "description": "Overall count of messages stored on the board." },
65
- "size": { "$ref": "#/components/schemas/Hex", "description": "Overall size of messages stored on the board." },
66
- "workMultiplier": { "$ref": "#/components/schemas/Hex", "description": "Factor that increases required work." },
67
- "workDivisor": { "$ref": "#/components/schemas/Hex", "description": "Factor that decreases required work." }
199
+ "enabled": {
200
+ "type": "boolean",
201
+ "description": "Whether the module is enabled on this node."
202
+ },
203
+ "count": {
204
+ "$ref": "#/components/schemas/Hex",
205
+ "description": "Overall count of messages stored on the board."
206
+ },
207
+ "size": {
208
+ "$ref": "#/components/schemas/Hex",
209
+ "description": "Overall size of messages stored on the board."
210
+ },
211
+ "workMultiplier": {
212
+ "$ref": "#/components/schemas/Hex",
213
+ "description": "Factor that increases required work."
214
+ },
215
+ "workDivisor": {
216
+ "$ref": "#/components/schemas/Hex",
217
+ "description": "Factor that decreases required work."
218
+ }
219
+ }
220
+ },
221
+ "Categories": {
222
+ "title": "Categories",
223
+ "type": "array",
224
+ "items": {
225
+ "$ref": "#/components/schemas/Hex"
68
226
  }
69
227
  },
70
- "Categories": { "title": "Categories", "type": "array", "items": { "$ref": "#/components/schemas/Hex" } },
71
228
  "ContentFilter": {
72
- "title": "ContentFilter", "type": "object",
229
+ "title": "ContentFilter",
230
+ "type": "object",
73
231
  "properties": {
74
- "category": { "$ref": "#/components/schemas/Hex", "description": "Restrict to one category hash." },
75
- "fromBlock": { "$ref": "#/components/schemas/Hex", "description": "Lower block bound (hex quantity)." },
76
- "toBlock": { "$ref": "#/components/schemas/Hex", "description": "Upper block bound (hex quantity)." }
232
+ "category": {
233
+ "$ref": "#/components/schemas/Hex",
234
+ "description": "Restrict to one category hash."
235
+ },
236
+ "fromBlock": {
237
+ "$ref": "#/components/schemas/Hex",
238
+ "description": "Lower block bound (hex quantity)."
239
+ },
240
+ "toBlock": {
241
+ "$ref": "#/components/schemas/Hex",
242
+ "description": "Upper block bound (hex quantity)."
243
+ }
77
244
  }
78
245
  },
79
246
  "RPCMessage": {
80
- "title": "RPCMessage", "type": "object",
81
- "required": ["version", "blockHash", "blockNumber", "category", "data", "nonce", "hash", "workMultiplier", "workDivisor"],
247
+ "title": "RPCMessage",
248
+ "type": "object",
249
+ "required": [
250
+ "version",
251
+ "blockHash",
252
+ "blockNumber",
253
+ "category",
254
+ "data",
255
+ "nonce",
256
+ "hash",
257
+ "workMultiplier",
258
+ "workDivisor"
259
+ ],
82
260
  "properties": {
83
- "version": { "$ref": "#/components/schemas/Hex", "description": "Message/encoding version." },
84
- "blockHash": { "$ref": "#/components/schemas/Hex", "description": "Hash of the block the message is rooted to." },
85
- "blockNumber": { "$ref": "#/components/schemas/Hex", "description": "Number of the block the message is rooted to." },
86
- "category": { "$ref": "#/components/schemas/Hex", "description": "32-byte category hash." },
87
- "data": { "$ref": "#/components/schemas/Hex", "description": "Arbitrary message data." },
88
- "nonce": { "$ref": "#/components/schemas/Hex", "description": "Nonce discovered through proof of work." },
89
- "hash": { "$ref": "#/components/schemas/Hex", "description": "The message hash." },
90
- "workMultiplier": { "$ref": "#/components/schemas/Hex", "description": "Work multiplier in force when posted." },
91
- "workDivisor": { "$ref": "#/components/schemas/Hex", "description": "Work divisor in force when posted." }
261
+ "version": {
262
+ "$ref": "#/components/schemas/Hex",
263
+ "description": "Message/encoding version."
264
+ },
265
+ "blockHash": {
266
+ "$ref": "#/components/schemas/Hex",
267
+ "description": "Hash of the block the message is rooted to."
268
+ },
269
+ "blockNumber": {
270
+ "$ref": "#/components/schemas/Hex",
271
+ "description": "Number of the block the message is rooted to."
272
+ },
273
+ "category": {
274
+ "$ref": "#/components/schemas/Hex",
275
+ "description": "32-byte category hash."
276
+ },
277
+ "data": {
278
+ "$ref": "#/components/schemas/Hex",
279
+ "description": "Arbitrary message data."
280
+ },
281
+ "nonce": {
282
+ "$ref": "#/components/schemas/Hex",
283
+ "description": "Nonce discovered through proof of work."
284
+ },
285
+ "hash": {
286
+ "$ref": "#/components/schemas/Hex",
287
+ "description": "The message hash."
288
+ },
289
+ "workMultiplier": {
290
+ "$ref": "#/components/schemas/Hex",
291
+ "description": "Work multiplier in force when posted."
292
+ },
293
+ "workDivisor": {
294
+ "$ref": "#/components/schemas/Hex",
295
+ "description": "Work divisor in force when posted."
296
+ }
92
297
  }
93
298
  },
94
- "Content": { "title": "Content", "type": "object", "description": "Messages grouped by category hash.", "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/RPCMessage" } } }
299
+ "Content": {
300
+ "title": "Content",
301
+ "type": "object",
302
+ "description": "Messages grouped by category hash.",
303
+ "additionalProperties": {
304
+ "type": "array",
305
+ "items": {
306
+ "$ref": "#/components/schemas/RPCMessage"
307
+ }
308
+ }
309
+ }
95
310
  }
96
311
  }
97
312
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msgboard/sdk",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "MsgBoard client SDK for the msgboard_ JSON-RPC module",
5
5
  "repository": "github:valve-tech/msgboard",
6
6
  "author": "MsgBoard",
@@ -59,7 +59,7 @@
59
59
  "vitest": "^3.1.1"
60
60
  },
61
61
  "dependencies": {
62
- "@msgboard/core": "^0.0.1",
62
+ "@msgboard/core": "^0.0.30",
63
63
  "bn.js": "^5.2.1",
64
64
  "debug": "^4.4.0",
65
65
  "elliptic": "^6.6.1",