@toon-protocol/relay 1.3.4 → 2.0.1

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.
package/dist/index.js CHANGED
@@ -1,478 +1,126 @@
1
1
  import {
2
2
  ConnectionHandler,
3
3
  DEFAULT_RELAY_CONFIG,
4
- EIP_3009_TYPES,
5
4
  InMemoryEventStore,
6
5
  NostrRelayServer,
7
6
  RelayError,
8
7
  RelaySubscriber,
9
8
  SqliteEventStore,
10
- ToonDecodeError,
11
- ToonEncodeError,
12
- USDC_ABI,
13
- USDC_EIP712_DOMAIN,
14
- calculateX402Price,
15
- createEventStorageHandler,
9
+ VERSION,
16
10
  createHealthResponse,
17
- createX402Handler,
18
- decodeEventFromToon,
19
- encodeEventToToon,
11
+ createWriteHandler,
20
12
  matchFilter,
21
- runPreflight,
22
- settleEip3009,
23
- startRelay,
24
- startTown
25
- } from "./chunk-NYKVCNJL.js";
13
+ startRelay
14
+ } from "./chunk-FXQSNOCG.js";
26
15
 
27
- // src/bls/types.ts
28
- var PUBKEY_REGEX = /^[0-9a-f]{64}$/;
29
- function isValidPubkey(pubkey) {
30
- return PUBKEY_REGEX.test(pubkey);
31
- }
32
- var ILP_ERROR_CODES = {
33
- BAD_REQUEST: "F00",
34
- INSUFFICIENT_AMOUNT: "F06",
35
- INTERNAL_ERROR: "T00"
36
- };
37
- var BlsError = class extends RelayError {
38
- constructor(message, code = "BLS_ERROR") {
39
- super(message, code);
40
- this.name = "BlsError";
16
+ // src/toon/codec.ts
17
+ import { encode, decode } from "@toon-format/toon";
18
+ var ToonEncodeError = class extends Error {
19
+ code = "TOON_ENCODE_ERROR";
20
+ constructor(message, cause) {
21
+ super(message, { cause });
22
+ this.name = "ToonEncodeError";
41
23
  }
42
24
  };
43
-
44
- // src/bls/BusinessLogicServer.ts
45
- import { Hono } from "hono";
46
- import { verifyEvent } from "nostr-tools/pure";
47
- var BusinessLogicServer = class {
48
- constructor(config, eventStore) {
49
- this.config = config;
50
- this.eventStore = eventStore;
51
- if (config.ownerPubkey !== void 0 && !isValidPubkey(config.ownerPubkey)) {
52
- throw new BlsError(
53
- "Invalid ownerPubkey format: must be 64 lowercase hex characters",
54
- "INVALID_CONFIG"
55
- );
56
- }
57
- this.app = new Hono();
58
- this.setupRoutes();
59
- }
60
- app;
61
- /**
62
- * Set up HTTP routes.
63
- */
64
- setupRoutes() {
65
- this.app.post("/handle-packet", async (c) => {
66
- try {
67
- const body = await c.req.json();
68
- const response = this.handlePacket(body);
69
- return c.json(response, response.accept ? 200 : 400);
70
- } catch (error) {
71
- const response = {
72
- accept: false,
73
- code: ILP_ERROR_CODES.INTERNAL_ERROR,
74
- message: error instanceof Error ? error.message : "Internal server error"
75
- };
76
- return c.json(response, 500);
77
- }
78
- });
79
- this.app.get("/health", (c) => {
80
- return c.json({
81
- status: "healthy",
82
- timestamp: Date.now()
83
- });
84
- });
85
- }
86
- /**
87
- * Process a packet request.
88
- *
89
- * This method is public to support direct connector integration in embedded mode,
90
- * where the connector calls this method directly via setPacketHandler() instead
91
- * of making HTTP requests.
92
- */
93
- handlePacket(request) {
94
- if (!request.amount || !request.destination || !request.data) {
95
- return {
96
- accept: false,
97
- code: ILP_ERROR_CODES.BAD_REQUEST,
98
- message: "Missing required fields: amount, destination, data"
99
- };
100
- }
101
- let toonBytes;
102
- try {
103
- toonBytes = Uint8Array.from(Buffer.from(request.data, "base64"));
104
- } catch {
105
- return {
106
- accept: false,
107
- code: ILP_ERROR_CODES.BAD_REQUEST,
108
- message: "Invalid base64 encoding in data field"
109
- };
110
- }
111
- let event;
112
- try {
113
- event = decodeEventFromToon(toonBytes);
114
- } catch (error) {
115
- return {
116
- accept: false,
117
- code: ILP_ERROR_CODES.BAD_REQUEST,
118
- message: `Invalid TOON data: ${error instanceof Error ? error.message : "Unknown error"}`
119
- };
120
- }
121
- if (!verifyEvent(event)) {
122
- return {
123
- accept: false,
124
- code: ILP_ERROR_CODES.BAD_REQUEST,
125
- message: "Invalid event signature"
126
- };
127
- }
128
- if (this.config.ownerPubkey && event.pubkey === this.config.ownerPubkey) {
129
- try {
130
- this.eventStore.store(event);
131
- } catch (error) {
132
- throw new BlsError(
133
- `Failed to store event: ${error instanceof Error ? error.message : "Unknown error"}`,
134
- "STORAGE_ERROR"
135
- );
136
- }
137
- return {
138
- accept: true,
139
- metadata: {
140
- eventId: event.id,
141
- storedAt: Date.now()
142
- }
143
- };
144
- }
145
- const price = this.config.pricingService ? this.config.pricingService.calculatePriceFromBytes(
146
- toonBytes,
147
- event.kind
148
- ) : BigInt(toonBytes.length) * this.config.basePricePerByte;
149
- let amount;
150
- try {
151
- amount = BigInt(request.amount);
152
- } catch {
153
- return {
154
- accept: false,
155
- code: ILP_ERROR_CODES.BAD_REQUEST,
156
- message: "Invalid amount format"
157
- };
158
- }
159
- if (amount < price) {
160
- return {
161
- accept: false,
162
- code: ILP_ERROR_CODES.INSUFFICIENT_AMOUNT,
163
- message: "Insufficient payment amount",
164
- metadata: {
165
- required: price.toString(),
166
- received: amount.toString()
167
- }
168
- };
169
- }
170
- try {
171
- this.eventStore.store(event);
172
- } catch (error) {
173
- throw new BlsError(
174
- `Failed to store event: ${error instanceof Error ? error.message : "Unknown error"}`,
175
- "STORAGE_ERROR"
176
- );
177
- }
178
- const storedAt = Date.now();
179
- return {
180
- accept: true,
181
- metadata: {
182
- eventId: event.id,
183
- storedAt
184
- }
185
- };
186
- }
187
- /**
188
- * Get the Hono app instance for testing or composition.
189
- */
190
- getApp() {
191
- return this.app;
192
- }
193
- /**
194
- * Start the HTTP server on the specified port.
195
- */
196
- start(port) {
197
- console.log(`BLS would start on port ${port}`);
25
+ var ToonDecodeError = class extends Error {
26
+ code = "TOON_DECODE_ERROR";
27
+ constructor(message, cause) {
28
+ super(message, { cause });
29
+ this.name = "ToonDecodeError";
198
30
  }
199
31
  };
200
-
201
- // src/pricing/types.ts
202
- var PricingError = class extends RelayError {
203
- constructor(message, code = "PRICING_ERROR") {
204
- super(message, code);
205
- this.name = "PricingError";
206
- }
207
- };
208
-
209
- // src/pricing/PricingService.ts
210
- var PricingService = class {
211
- basePricePerByte;
212
- kindOverrides;
213
- constructor(config) {
214
- if (config.basePricePerByte < 0n) {
215
- throw new PricingError(
216
- "basePricePerByte must be non-negative",
217
- "INVALID_CONFIG"
218
- );
219
- }
220
- if (config.kindOverrides) {
221
- for (const [kind, price] of config.kindOverrides.entries()) {
222
- if (price < 0n) {
223
- throw new PricingError(
224
- `kindOverride for kind ${kind} must be non-negative`,
225
- "INVALID_CONFIG"
226
- );
227
- }
228
- }
229
- }
230
- this.basePricePerByte = config.basePricePerByte;
231
- this.kindOverrides = config.kindOverrides ?? /* @__PURE__ */ new Map();
232
- }
233
- /**
234
- * Calculate price for a Nostr event.
235
- *
236
- * @param event - The Nostr event to price
237
- * @returns The calculated price as bigint
238
- */
239
- calculatePrice(event) {
240
- const toonBytes = encodeEventToToon(event);
241
- return this.calculatePriceFromBytes(toonBytes, event.kind);
242
- }
243
- /**
244
- * Calculate price from raw TOON bytes and event kind.
245
- *
246
- * @param bytes - The TOON-encoded event bytes
247
- * @param kind - The event kind number
248
- * @returns The calculated price as bigint
249
- */
250
- calculatePriceFromBytes(bytes, kind) {
251
- const pricePerByte = this.getPricePerByte(kind);
252
- return BigInt(bytes.length) * pricePerByte;
253
- }
254
- /**
255
- * Get the effective price per byte for a given kind.
256
- *
257
- * @param kind - The event kind number
258
- * @returns The price per byte (kind override if exists, otherwise base price)
259
- */
260
- getPricePerByte(kind) {
261
- return this.kindOverrides.get(kind) ?? this.basePricePerByte;
262
- }
263
- };
264
-
265
- // src/pricing/config.ts
266
- import { readFileSync } from "fs";
267
- function loadPricingConfigFromEnv() {
268
- const basePriceStr = process.env["RELAY_BASE_PRICE_PER_BYTE"] ?? "1";
269
- let basePricePerByte;
270
- try {
271
- basePricePerByte = BigInt(basePriceStr);
272
- } catch {
273
- throw new PricingError(
274
- `Invalid RELAY_BASE_PRICE_PER_BYTE: "${basePriceStr}" is not a valid integer`,
275
- "INVALID_ENV_CONFIG"
276
- );
277
- }
278
- if (basePricePerByte < 0n) {
279
- throw new PricingError(
280
- `Invalid RELAY_BASE_PRICE_PER_BYTE: value must be non-negative`,
281
- "INVALID_ENV_CONFIG"
282
- );
283
- }
284
- const kindOverridesStr = process.env["RELAY_KIND_OVERRIDES"];
285
- let kindOverrides;
286
- if (kindOverridesStr) {
287
- kindOverrides = parseKindOverridesJson(kindOverridesStr);
288
- }
289
- return {
290
- basePricePerByte,
291
- kindOverrides
292
- };
32
+ function encodeEventToToon(event) {
33
+ return new TextEncoder().encode(encodeEventToToonString(event));
293
34
  }
294
- function loadPricingConfigFromFile(path) {
295
- let fileContent;
35
+ function encodeEventToToonString(event) {
296
36
  try {
297
- fileContent = readFileSync(path, "utf-8");
37
+ return encode(event);
298
38
  } catch (error) {
299
- throw new PricingError(
300
- `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
301
- "CONFIG_FILE_ERROR"
39
+ throw new ToonEncodeError(
40
+ `Failed to encode event to TOON: ${error instanceof Error ? error.message : String(error)}`,
41
+ error instanceof Error ? error : void 0
302
42
  );
303
43
  }
304
- let parsed;
305
- try {
306
- parsed = JSON.parse(fileContent);
307
- } catch {
308
- throw new PricingError(
309
- `Invalid JSON in config file: ${path}`,
310
- "INVALID_FILE_CONFIG"
44
+ }
45
+ function isValidHex(value, length) {
46
+ return typeof value === "string" && value.length === length && /^[0-9a-f]+$/i.test(value);
47
+ }
48
+ function validateNostrEvent(obj) {
49
+ if (typeof obj !== "object" || obj === null) {
50
+ throw new ToonDecodeError("Decoded value is not an object");
51
+ }
52
+ const event = obj;
53
+ if (!isValidHex(event["id"], 64)) {
54
+ throw new ToonDecodeError(
55
+ "Invalid event id: must be a 64-character hex string"
311
56
  );
312
57
  }
313
- if (typeof parsed !== "object" || parsed === null) {
314
- throw new PricingError(
315
- `Config file must contain a JSON object`,
316
- "INVALID_FILE_CONFIG"
58
+ if (!isValidHex(event["pubkey"], 64)) {
59
+ throw new ToonDecodeError(
60
+ "Invalid event pubkey: must be a 64-character hex string"
317
61
  );
318
62
  }
319
- const config = parsed;
320
- const basePriceStr = config["basePricePerByte"];
321
- if (typeof basePriceStr !== "string" && typeof basePriceStr !== "number") {
322
- throw new PricingError(
323
- `Config file must contain "basePricePerByte" as string or number`,
324
- "INVALID_FILE_CONFIG"
325
- );
63
+ if (typeof event["kind"] !== "number" || !Number.isInteger(event["kind"])) {
64
+ throw new ToonDecodeError("Invalid event kind: must be an integer");
326
65
  }
327
- let basePricePerByte;
328
- try {
329
- basePricePerByte = BigInt(basePriceStr);
330
- } catch {
331
- throw new PricingError(
332
- `Invalid basePricePerByte: "${basePriceStr}" is not a valid integer`,
333
- "INVALID_FILE_CONFIG"
334
- );
66
+ if (typeof event["content"] !== "string") {
67
+ throw new ToonDecodeError("Invalid event content: must be a string");
335
68
  }
336
- if (basePricePerByte < 0n) {
337
- throw new PricingError(
338
- `basePricePerByte must be non-negative`,
339
- "INVALID_FILE_CONFIG"
340
- );
69
+ const tags = event["tags"];
70
+ if (!Array.isArray(tags)) {
71
+ throw new ToonDecodeError("Invalid event tags: must be an array");
341
72
  }
342
- let kindOverrides;
343
- const kindOverridesValue = config["kindOverrides"];
344
- if (kindOverridesValue !== void 0) {
345
- if (typeof kindOverridesValue !== "object" || kindOverridesValue === null) {
346
- throw new PricingError(
347
- `kindOverrides must be an object`,
348
- "INVALID_FILE_CONFIG"
349
- );
73
+ for (let i = 0; i < tags.length; i++) {
74
+ const tag = tags[i];
75
+ if (!Array.isArray(tag)) {
76
+ throw new ToonDecodeError(`Invalid event tags[${i}]: must be an array`);
350
77
  }
351
- kindOverrides = /* @__PURE__ */ new Map();
352
- const overridesObj = kindOverridesValue;
353
- for (const [kindStr, priceValue] of Object.entries(overridesObj)) {
354
- const kind = parseInt(kindStr, 10);
355
- if (isNaN(kind)) {
356
- throw new PricingError(
357
- `Invalid kind in kindOverrides: "${kindStr}" is not a valid integer`,
358
- "INVALID_FILE_CONFIG"
359
- );
360
- }
361
- if (typeof priceValue !== "string" && typeof priceValue !== "number") {
362
- throw new PricingError(
363
- `Invalid price for kind ${kind}: must be string or number`,
364
- "INVALID_FILE_CONFIG"
365
- );
366
- }
367
- let price;
368
- try {
369
- price = BigInt(priceValue);
370
- } catch {
371
- throw new PricingError(
372
- `Invalid price for kind ${kind}: "${priceValue}" is not a valid integer`,
373
- "INVALID_FILE_CONFIG"
374
- );
375
- }
376
- if (price < 0n) {
377
- throw new PricingError(
378
- `Price for kind ${kind} must be non-negative`,
379
- "INVALID_FILE_CONFIG"
78
+ for (let j = 0; j < tag.length; j++) {
79
+ if (typeof tag[j] !== "string") {
80
+ throw new ToonDecodeError(
81
+ `Invalid event tags[${i}][${j}]: must be a string`
380
82
  );
381
83
  }
382
- kindOverrides.set(kind, price);
383
84
  }
384
85
  }
385
- return {
386
- basePricePerByte,
387
- kindOverrides
388
- };
389
- }
390
- function parseKindOverridesJson(jsonStr) {
391
- let parsed;
392
- try {
393
- parsed = JSON.parse(jsonStr);
394
- } catch {
395
- throw new PricingError(
396
- `Invalid JSON in RELAY_KIND_OVERRIDES: ${jsonStr}`,
397
- "INVALID_ENV_CONFIG"
398
- );
86
+ if (typeof event["created_at"] !== "number" || !Number.isInteger(event["created_at"])) {
87
+ throw new ToonDecodeError("Invalid event created_at: must be an integer");
399
88
  }
400
- if (typeof parsed !== "object" || parsed === null) {
401
- throw new PricingError(
402
- `RELAY_KIND_OVERRIDES must be a JSON object`,
403
- "INVALID_ENV_CONFIG"
89
+ if (!isValidHex(event["sig"], 128)) {
90
+ throw new ToonDecodeError(
91
+ "Invalid event sig: must be a 128-character hex string"
404
92
  );
405
93
  }
406
- const result = /* @__PURE__ */ new Map();
407
- const obj = parsed;
408
- for (const [kindStr, priceValue] of Object.entries(obj)) {
409
- const kind = parseInt(kindStr, 10);
410
- if (isNaN(kind)) {
411
- throw new PricingError(
412
- `Invalid kind in RELAY_KIND_OVERRIDES: "${kindStr}" is not a valid integer`,
413
- "INVALID_ENV_CONFIG"
414
- );
415
- }
416
- if (typeof priceValue !== "string" && typeof priceValue !== "number") {
417
- throw new PricingError(
418
- `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: must be string or number`,
419
- "INVALID_ENV_CONFIG"
420
- );
421
- }
422
- let price;
423
- try {
424
- price = BigInt(priceValue);
425
- } catch {
426
- throw new PricingError(
427
- `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: "${priceValue}" is not a valid integer`,
428
- "INVALID_ENV_CONFIG"
429
- );
430
- }
431
- if (price < 0n) {
432
- throw new PricingError(
433
- `Price for kind ${kind} in RELAY_KIND_OVERRIDES must be non-negative`,
434
- "INVALID_ENV_CONFIG"
435
- );
436
- }
437
- result.set(kind, price);
94
+ }
95
+ function decodeEventFromToon(data) {
96
+ let decoded;
97
+ try {
98
+ decoded = decode(new TextDecoder().decode(data));
99
+ } catch (error) {
100
+ throw new ToonDecodeError(
101
+ `Failed to decode TOON data: ${error instanceof Error ? error.message : String(error)}`,
102
+ error instanceof Error ? error : void 0
103
+ );
438
104
  }
439
- return result;
105
+ validateNostrEvent(decoded);
106
+ return decoded;
440
107
  }
441
-
442
- // src/index.ts
443
- var VERSION = "0.1.0";
444
108
  export {
445
- BlsError,
446
- BusinessLogicServer,
447
109
  ConnectionHandler,
448
110
  DEFAULT_RELAY_CONFIG,
449
- EIP_3009_TYPES,
450
- ILP_ERROR_CODES,
451
111
  InMemoryEventStore,
452
112
  NostrRelayServer,
453
- PricingError,
454
- PricingService,
455
113
  RelayError,
456
114
  RelaySubscriber,
457
115
  SqliteEventStore,
458
116
  ToonDecodeError,
459
117
  ToonEncodeError,
460
- USDC_ABI,
461
- USDC_EIP712_DOMAIN,
462
118
  VERSION,
463
- calculateX402Price,
464
- createEventStorageHandler,
465
119
  createHealthResponse,
466
- createX402Handler,
120
+ createWriteHandler,
467
121
  decodeEventFromToon,
468
122
  encodeEventToToon,
469
- isValidPubkey,
470
- loadPricingConfigFromEnv,
471
- loadPricingConfigFromFile,
472
123
  matchFilter,
473
- runPreflight,
474
- settleEip3009,
475
- startRelay,
476
- startTown
124
+ startRelay
477
125
  };
478
126
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bls/types.ts","../src/bls/BusinessLogicServer.ts","../src/pricing/types.ts","../src/pricing/PricingService.ts","../src/pricing/config.ts","../src/index.ts"],"sourcesContent":["import { RelayError } from '../storage/index.js';\nimport type { PricingService } from '../pricing/index.js';\n\n/**\n * Regex for validating Nostr pubkeys (64 lowercase hex characters).\n */\nexport const PUBKEY_REGEX = /^[0-9a-f]{64}$/;\n\n/**\n * Validate that a string is a valid Nostr pubkey format.\n * @param pubkey - The pubkey to validate\n * @returns true if valid 64-character lowercase hex string\n */\nexport function isValidPubkey(pubkey: string): boolean {\n return PUBKEY_REGEX.test(pubkey);\n}\n\n/**\n * Configuration for the Business Logic Server.\n */\nexport interface BlsConfig {\n /** Base price per byte for event storage (used for simple pricing) */\n basePricePerByte: bigint;\n /** Optional PricingService for kind-based pricing overrides */\n pricingService?: PricingService;\n /** Optional owner pubkey - events from this pubkey bypass payment */\n ownerPubkey?: string;\n}\n\n/**\n * Incoming packet request from ILP connector.\n */\nexport interface HandlePacketRequest {\n /** Payment amount as string (parsed to bigint) */\n amount: string;\n /** ILP destination address */\n destination: string;\n /** Base64-encoded TOON Nostr event */\n data: string;\n /** Source ILP address */\n sourceAccount?: string;\n}\n\n/**\n * Response for accepted packet.\n */\nexport interface HandlePacketAcceptResponse {\n accept: true;\n metadata?: {\n eventId: string;\n storedAt: number;\n };\n}\n\n/**\n * Response for rejected packet.\n */\nexport interface HandlePacketRejectResponse {\n accept: false;\n /** ILP error code (F00, F06, etc.) */\n code: string;\n /** Human-readable error message */\n message: string;\n metadata?: {\n required?: string;\n received?: string;\n };\n}\n\n/**\n * Union type for packet response.\n */\nexport type HandlePacketResponse =\n | HandlePacketAcceptResponse\n | HandlePacketRejectResponse;\n\n/**\n * ILP error code constants.\n */\nexport const ILP_ERROR_CODES = {\n BAD_REQUEST: 'F00',\n INSUFFICIENT_AMOUNT: 'F06',\n INTERNAL_ERROR: 'T00',\n} as const;\n\n/**\n * BLS-specific error class.\n */\nexport class BlsError extends RelayError {\n constructor(message: string, code = 'BLS_ERROR') {\n super(message, code);\n this.name = 'BlsError';\n }\n}\n","import { Hono } from 'hono';\nimport { verifyEvent } from 'nostr-tools/pure';\nimport type { EventStore } from '../storage/index.js';\nimport { decodeEventFromToon } from '../toon/index.js';\nimport type {\n BlsConfig,\n HandlePacketRequest,\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n} from './types.js';\nimport { ILP_ERROR_CODES, BlsError, isValidPubkey } from './types.js';\n\n/**\n * Business Logic Server for ILP payment verification.\n *\n * Handles payment requests from an ILP connector, verifying that the\n * payment amount meets the required price for storing the included\n * Nostr event.\n */\nexport class BusinessLogicServer {\n private app: Hono;\n\n constructor(\n private config: BlsConfig,\n private eventStore: EventStore\n ) {\n // Validate ownerPubkey format if provided\n if (\n config.ownerPubkey !== undefined &&\n !isValidPubkey(config.ownerPubkey)\n ) {\n throw new BlsError(\n 'Invalid ownerPubkey format: must be 64 lowercase hex characters',\n 'INVALID_CONFIG'\n );\n }\n this.app = new Hono();\n this.setupRoutes();\n }\n\n /**\n * Set up HTTP routes.\n */\n private setupRoutes(): void {\n this.app.post('/handle-packet', async (c) => {\n try {\n const body = await c.req.json<HandlePacketRequest>();\n const response = this.handlePacket(body);\n return c.json(response, response.accept ? 200 : 400);\n } catch (error) {\n const response: HandlePacketRejectResponse = {\n accept: false,\n code: ILP_ERROR_CODES.INTERNAL_ERROR,\n message:\n error instanceof Error ? error.message : 'Internal server error',\n };\n return c.json(response, 500);\n }\n });\n\n this.app.get('/health', (c) => {\n return c.json({\n status: 'healthy',\n timestamp: Date.now(),\n });\n });\n }\n\n /**\n * Process a packet request.\n *\n * This method is public to support direct connector integration in embedded mode,\n * where the connector calls this method directly via setPacketHandler() instead\n * of making HTTP requests.\n */\n public handlePacket(\n request: HandlePacketRequest\n ): HandlePacketAcceptResponse | HandlePacketRejectResponse {\n // Validate required fields\n if (!request.amount || !request.destination || !request.data) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Missing required fields: amount, destination, data',\n };\n }\n\n // Decode base64 data\n let toonBytes: Uint8Array;\n try {\n toonBytes = Uint8Array.from(Buffer.from(request.data, 'base64'));\n } catch {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid base64 encoding in data field',\n };\n }\n\n // Decode TOON to Nostr event\n let event;\n try {\n event = decodeEventFromToon(toonBytes);\n } catch (error) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: `Invalid TOON data: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n\n // Verify event signature\n if (!verifyEvent(event)) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid event signature',\n };\n }\n\n // Self-write bypass: owner events skip payment verification\n if (this.config.ownerPubkey && event.pubkey === this.config.ownerPubkey) {\n try {\n this.eventStore.store(event);\n } catch (error) {\n throw new BlsError(\n `Failed to store event: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'STORAGE_ERROR'\n );\n }\n\n return {\n accept: true,\n metadata: {\n eventId: event.id,\n storedAt: Date.now(),\n },\n };\n }\n\n // Calculate price: use PricingService if provided, otherwise simple calculation\n const price = this.config.pricingService\n ? this.config.pricingService.calculatePriceFromBytes(\n toonBytes,\n event.kind\n )\n : BigInt(toonBytes.length) * this.config.basePricePerByte;\n\n // Parse and compare amounts\n let amount: bigint;\n try {\n amount = BigInt(request.amount);\n } catch {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid amount format',\n };\n }\n\n if (amount < price) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.INSUFFICIENT_AMOUNT,\n message: 'Insufficient payment amount',\n metadata: {\n required: price.toString(),\n received: amount.toString(),\n },\n };\n }\n\n // Store event\n try {\n this.eventStore.store(event);\n } catch (error) {\n throw new BlsError(\n `Failed to store event: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'STORAGE_ERROR'\n );\n }\n\n const storedAt = Date.now();\n return {\n accept: true,\n metadata: {\n eventId: event.id,\n storedAt,\n },\n };\n }\n\n /**\n * Get the Hono app instance for testing or composition.\n */\n getApp(): Hono {\n return this.app;\n }\n\n /**\n * Start the HTTP server on the specified port.\n */\n start(port: number): void {\n // Note: In Node.js, you would typically use @hono/node-server\n // For now, this method is a placeholder for actual server startup\n // The actual server binding would be done by the consumer\n console.log(`BLS would start on port ${port}`);\n }\n}\n","import { RelayError } from '../storage/index.js';\n\n/**\n * Configuration for the Pricing Service.\n */\nexport interface PricingConfig {\n /** Base price per byte for event storage */\n basePricePerByte: bigint;\n /** Optional price overrides by event kind */\n kindOverrides?: Map<number, bigint>;\n}\n\n/**\n * Error class for pricing-specific errors.\n */\nexport class PricingError extends RelayError {\n constructor(message: string, code = 'PRICING_ERROR') {\n super(message, code);\n this.name = 'PricingError';\n }\n}\n","import type { NostrEvent } from 'nostr-tools/pure';\nimport { encodeEventToToon } from '../toon/index.js';\nimport type { PricingConfig } from './types.js';\nimport { PricingError } from './types.js';\n\n/**\n * Service for calculating event storage prices with kind-based overrides.\n */\nexport class PricingService {\n private readonly basePricePerByte: bigint;\n private readonly kindOverrides: Map<number, bigint>;\n\n constructor(config: PricingConfig) {\n // Validate basePricePerByte is non-negative\n if (config.basePricePerByte < 0n) {\n throw new PricingError(\n 'basePricePerByte must be non-negative',\n 'INVALID_CONFIG'\n );\n }\n\n // Validate all kindOverrides are non-negative\n if (config.kindOverrides) {\n for (const [kind, price] of config.kindOverrides.entries()) {\n if (price < 0n) {\n throw new PricingError(\n `kindOverride for kind ${kind} must be non-negative`,\n 'INVALID_CONFIG'\n );\n }\n }\n }\n\n this.basePricePerByte = config.basePricePerByte;\n this.kindOverrides = config.kindOverrides ?? new Map();\n }\n\n /**\n * Calculate price for a Nostr event.\n *\n * @param event - The Nostr event to price\n * @returns The calculated price as bigint\n */\n calculatePrice(event: NostrEvent): bigint {\n const toonBytes = encodeEventToToon(event);\n return this.calculatePriceFromBytes(toonBytes, event.kind);\n }\n\n /**\n * Calculate price from raw TOON bytes and event kind.\n *\n * @param bytes - The TOON-encoded event bytes\n * @param kind - The event kind number\n * @returns The calculated price as bigint\n */\n calculatePriceFromBytes(bytes: Uint8Array, kind: number): bigint {\n const pricePerByte = this.getPricePerByte(kind);\n return BigInt(bytes.length) * pricePerByte;\n }\n\n /**\n * Get the effective price per byte for a given kind.\n *\n * @param kind - The event kind number\n * @returns The price per byte (kind override if exists, otherwise base price)\n */\n getPricePerByte(kind: number): bigint {\n return this.kindOverrides.get(kind) ?? this.basePricePerByte;\n }\n}\n","import { readFileSync } from 'fs';\nimport type { PricingConfig } from './types.js';\nimport { PricingError } from './types.js';\n\n/**\n * Load pricing configuration from environment variables.\n *\n * Environment variables:\n * - RELAY_BASE_PRICE_PER_BYTE: Base price per byte (default: \"1\")\n * - RELAY_KIND_OVERRIDES: JSON object mapping kind to price (optional)\n * Format: {\"1\":\"5\",\"30023\":\"100\"}\n *\n * @returns PricingConfig loaded from environment\n * @throws PricingError if env vars contain invalid values\n */\nexport function loadPricingConfigFromEnv(): PricingConfig {\n // Parse base price\n const basePriceStr = process.env['RELAY_BASE_PRICE_PER_BYTE'] ?? '1';\n let basePricePerByte: bigint;\n try {\n basePricePerByte = BigInt(basePriceStr);\n } catch {\n throw new PricingError(\n `Invalid RELAY_BASE_PRICE_PER_BYTE: \"${basePriceStr}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (basePricePerByte < 0n) {\n throw new PricingError(\n `Invalid RELAY_BASE_PRICE_PER_BYTE: value must be non-negative`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n // Parse kind overrides if present\n const kindOverridesStr = process.env['RELAY_KIND_OVERRIDES'];\n let kindOverrides: Map<number, bigint> | undefined;\n\n if (kindOverridesStr) {\n kindOverrides = parseKindOverridesJson(kindOverridesStr);\n }\n\n return {\n basePricePerByte,\n kindOverrides,\n };\n}\n\n/**\n * Load pricing configuration from a JSON file.\n *\n * File format:\n * {\n * \"basePricePerByte\": \"10\",\n * \"kindOverrides\": {\n * \"0\": \"0\",\n * \"1\": \"5\",\n * \"30023\": \"100\"\n * }\n * }\n *\n * @param path - Path to the JSON config file\n * @returns PricingConfig loaded from file\n * @throws PricingError if file cannot be read or contains invalid values\n */\nexport function loadPricingConfigFromFile(path: string): PricingConfig {\n let fileContent: string;\n try {\n fileContent = readFileSync(path, 'utf-8');\n } catch (error) {\n throw new PricingError(\n `Failed to read config file: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'CONFIG_FILE_ERROR'\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fileContent);\n } catch {\n throw new PricingError(\n `Invalid JSON in config file: ${path}`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new PricingError(\n `Config file must contain a JSON object`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n const config = parsed as Record<string, unknown>;\n\n // Parse base price\n const basePriceStr = config['basePricePerByte'];\n if (typeof basePriceStr !== 'string' && typeof basePriceStr !== 'number') {\n throw new PricingError(\n `Config file must contain \"basePricePerByte\" as string or number`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n let basePricePerByte: bigint;\n try {\n basePricePerByte = BigInt(basePriceStr);\n } catch {\n throw new PricingError(\n `Invalid basePricePerByte: \"${basePriceStr}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (basePricePerByte < 0n) {\n throw new PricingError(\n `basePricePerByte must be non-negative`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n // Parse kind overrides if present\n let kindOverrides: Map<number, bigint> | undefined;\n const kindOverridesValue = config['kindOverrides'];\n if (kindOverridesValue !== undefined) {\n if (typeof kindOverridesValue !== 'object' || kindOverridesValue === null) {\n throw new PricingError(\n `kindOverrides must be an object`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n kindOverrides = new Map();\n const overridesObj = kindOverridesValue as Record<string, unknown>;\n\n for (const [kindStr, priceValue] of Object.entries(overridesObj)) {\n const kind = parseInt(kindStr, 10);\n if (isNaN(kind)) {\n throw new PricingError(\n `Invalid kind in kindOverrides: \"${kindStr}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (typeof priceValue !== 'string' && typeof priceValue !== 'number') {\n throw new PricingError(\n `Invalid price for kind ${kind}: must be string or number`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n let price: bigint;\n try {\n price = BigInt(priceValue);\n } catch {\n throw new PricingError(\n `Invalid price for kind ${kind}: \"${priceValue}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (price < 0n) {\n throw new PricingError(\n `Price for kind ${kind} must be non-negative`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n kindOverrides.set(kind, price);\n }\n }\n\n return {\n basePricePerByte,\n kindOverrides,\n };\n}\n\n/**\n * Parse kind overrides JSON string to Map.\n */\nfunction parseKindOverridesJson(jsonStr: string): Map<number, bigint> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(jsonStr);\n } catch {\n throw new PricingError(\n `Invalid JSON in RELAY_KIND_OVERRIDES: ${jsonStr}`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new PricingError(\n `RELAY_KIND_OVERRIDES must be a JSON object`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n const result = new Map<number, bigint>();\n const obj = parsed as Record<string, unknown>;\n\n for (const [kindStr, priceValue] of Object.entries(obj)) {\n const kind = parseInt(kindStr, 10);\n if (isNaN(kind)) {\n throw new PricingError(\n `Invalid kind in RELAY_KIND_OVERRIDES: \"${kindStr}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (typeof priceValue !== 'string' && typeof priceValue !== 'number') {\n throw new PricingError(\n `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: must be string or number`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n let price: bigint;\n try {\n price = BigInt(priceValue);\n } catch {\n throw new PricingError(\n `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: \"${priceValue}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (price < 0n) {\n throw new PricingError(\n `Price for kind ${kind} in RELAY_KIND_OVERRIDES must be non-negative`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n result.set(kind, price);\n }\n\n return result;\n}\n","/**\n * @toon-protocol/relay\n *\n * ILP-gated Nostr relay with Business Logic Server.\n */\n\nexport const VERSION = '0.1.0';\n\n// Types\n// NOTE: the low-level WebSocket relay-server config is exported as\n// `RelayServerConfig` (renamed from `RelayConfig`) so the launcher's\n// `RelayConfig` (formerly `TownConfig`) can take the `RelayConfig` name.\nexport type { RelayServerConfig } from './types.js';\nexport { DEFAULT_RELAY_CONFIG } from './types.js';\n\n// Storage\nexport type { EventStore } from './storage/index.js';\nexport {\n InMemoryEventStore,\n SqliteEventStore,\n RelayError,\n} from './storage/index.js';\n\n// Filters\nexport { matchFilter } from './filters/index.js';\n\n// WebSocket\nexport type { Subscription } from './websocket/index.js';\nexport { ConnectionHandler, NostrRelayServer } from './websocket/index.js';\n\n// TOON encoding/decoding\nexport {\n encodeEventToToon,\n decodeEventFromToon,\n ToonEncodeError,\n ToonDecodeError,\n} from './toon/index.js';\n\n// Business Logic Server\nexport type {\n BlsConfig,\n HandlePacketRequest,\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n HandlePacketResponse,\n} from './bls/index.js';\nexport {\n BlsError,\n ILP_ERROR_CODES,\n BusinessLogicServer,\n isValidPubkey,\n} from './bls/index.js';\n\n// Pricing\nexport type { PricingConfig } from './pricing/index.js';\nexport {\n PricingError,\n PricingService,\n loadPricingConfigFromEnv,\n loadPricingConfigFromFile,\n} from './pricing/index.js';\n\n// Subscriber\nexport type { RelaySubscriberConfig } from './subscriber/index.js';\nexport { RelaySubscriber } from './subscriber/index.js';\n\n// ---------------------------------------------------------------------------\n// Launcher (formerly @toon-protocol/town)\n//\n// SDK-based relay launcher with a one-call programmatic API (startRelay()) and\n// handler implementations for ILP-gated Nostr services. Merged in from the\n// @toon-protocol/town package. Old `startTown`/`Town*` names are re-exported\n// as deprecated aliases.\n// ---------------------------------------------------------------------------\n\n// Relay launcher lifecycle API\nexport { startRelay } from './launcher/town.js';\nexport type {\n RelayConfig,\n RelayInstance,\n RelaySubscription,\n ResolvedRelayConfig,\n} from './launcher/town.js';\n\n// Deprecated launcher aliases (town → relay merge)\nexport { startTown } from './launcher/town.js';\nexport type {\n TownConfig,\n TownInstance,\n TownSubscription,\n ResolvedTownConfig,\n} from './launcher/town.js';\n\n// Health response\nexport { createHealthResponse } from './launcher/health.js';\nexport type {\n HealthConfig,\n HealthResponse,\n TeeHealthInfo,\n} from './launcher/health.js';\n\n// Event storage handler\nexport { createEventStorageHandler } from './launcher/handlers/event-storage-handler.js';\nexport type { EventStorageHandlerConfig } from './launcher/handlers/event-storage-handler.js';\n\n// x402 publish handler\nexport { createX402Handler } from './launcher/handlers/x402-publish-handler.js';\nexport type {\n X402HandlerConfig,\n X402Handler,\n} from './launcher/handlers/x402-publish-handler.js';\n\n// x402 pricing\nexport { calculateX402Price } from './launcher/handlers/x402-pricing.js';\nexport type { X402PricingConfig } from './launcher/handlers/x402-pricing.js';\n\n// x402 pre-flight\nexport { runPreflight } from './launcher/handlers/x402-preflight.js';\nexport type {\n PreflightResult,\n PreflightConfig,\n} from './launcher/handlers/x402-preflight.js';\n\n// x402 settlement\nexport { settleEip3009 } from './launcher/handlers/x402-settlement.js';\nexport type {\n X402SettlementResult,\n X402SettlementConfig,\n} from './launcher/handlers/x402-settlement.js';\n// Deprecated aliases -- use X402SettlementResult / X402SettlementConfig instead\nexport type {\n SettlementResult,\n SettlementConfig,\n} from './launcher/handlers/x402-settlement.js';\n\n// x402 types\nexport type {\n Eip3009Authorization,\n EventStoreLike,\n X402PublishRequest,\n X402PublishResponse,\n X402PricingResponse,\n} from './launcher/handlers/x402-types.js';\nexport {\n EIP_3009_TYPES,\n USDC_EIP712_DOMAIN,\n USDC_ABI,\n} from './launcher/handlers/x402-types.js';\n\n// Re-exports from @toon-protocol/bls removed to avoid circular dependency\n// Downstream consumers should import directly from @toon-protocol/bls instead\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/toon/codec.ts"],"sourcesContent":["/**\n * TOON event codec.\n *\n * Encodes/decodes Nostr events to and from the TOON text format. Vendored from\n * `@toon-protocol/core` so the relay depends only on the lightweight\n * `@toon-format/toon` encoder rather than core's full transitive tree (which\n * pulls Arweave / web3 wallet stacks the relay does not use). Same MIT\n * license / org.\n *\n * NOTE: this codec is NOT used on the relay's NIP-01 read surface. Outbound\n * EVENT frames are canonical NIP-01 JSON (see\n * `websocket/ConnectionHandler.sendEvent`, #46) so that standard nostr clients\n * can parse events and verify signatures from the wire. The codec remains\n * exported for library consumers that exchange TOON-text events elsewhere.\n *\n * @module\n */\n\nimport { encode, decode } from '@toon-format/toon';\nimport type { NostrEvent } from 'nostr-tools/pure';\n\n/** Thrown when a Nostr event cannot be encoded to TOON. */\nexport class ToonEncodeError extends Error {\n readonly code = 'TOON_ENCODE_ERROR';\n constructor(message: string, cause?: Error) {\n super(message, { cause });\n this.name = 'ToonEncodeError';\n }\n}\n\n/** Thrown when TOON data cannot be decoded into a valid Nostr event. */\nexport class ToonDecodeError extends Error {\n readonly code = 'TOON_DECODE_ERROR';\n constructor(message: string, cause?: Error) {\n super(message, { cause });\n this.name = 'ToonDecodeError';\n }\n}\n\n/** Encode a Nostr event to TOON bytes (UTF-8). */\nexport function encodeEventToToon(event: NostrEvent): Uint8Array {\n return new TextEncoder().encode(encodeEventToToonString(event));\n}\n\n/** Encode a Nostr event to a TOON string. */\nexport function encodeEventToToonString(event: NostrEvent): string {\n try {\n return encode(event);\n } catch (error) {\n throw new ToonEncodeError(\n `Failed to encode event to TOON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\nfunction isValidHex(value: unknown, length: number): boolean {\n return (\n typeof value === 'string' &&\n value.length === length &&\n /^[0-9a-f]+$/i.test(value)\n );\n}\n\nfunction validateNostrEvent(obj: unknown): asserts obj is NostrEvent {\n if (typeof obj !== 'object' || obj === null) {\n throw new ToonDecodeError('Decoded value is not an object');\n }\n const event = obj as Record<string, unknown>;\n if (!isValidHex(event['id'], 64)) {\n throw new ToonDecodeError(\n 'Invalid event id: must be a 64-character hex string'\n );\n }\n if (!isValidHex(event['pubkey'], 64)) {\n throw new ToonDecodeError(\n 'Invalid event pubkey: must be a 64-character hex string'\n );\n }\n if (typeof event['kind'] !== 'number' || !Number.isInteger(event['kind'])) {\n throw new ToonDecodeError('Invalid event kind: must be an integer');\n }\n if (typeof event['content'] !== 'string') {\n throw new ToonDecodeError('Invalid event content: must be a string');\n }\n const tags = event['tags'];\n if (!Array.isArray(tags)) {\n throw new ToonDecodeError('Invalid event tags: must be an array');\n }\n for (let i = 0; i < tags.length; i++) {\n const tag: unknown = tags[i];\n if (!Array.isArray(tag)) {\n throw new ToonDecodeError(`Invalid event tags[${i}]: must be an array`);\n }\n for (let j = 0; j < tag.length; j++) {\n if (typeof tag[j] !== 'string') {\n throw new ToonDecodeError(\n `Invalid event tags[${i}][${j}]: must be a string`\n );\n }\n }\n }\n if (\n typeof event['created_at'] !== 'number' ||\n !Number.isInteger(event['created_at'])\n ) {\n throw new ToonDecodeError('Invalid event created_at: must be an integer');\n }\n if (!isValidHex(event['sig'], 128)) {\n throw new ToonDecodeError(\n 'Invalid event sig: must be a 128-character hex string'\n );\n }\n}\n\n/** Decode TOON bytes into a validated Nostr event. */\nexport function decodeEventFromToon(data: Uint8Array): NostrEvent {\n let decoded: unknown;\n try {\n decoded = decode(new TextDecoder().decode(data));\n } catch (error) {\n throw new ToonDecodeError(\n `Failed to decode TOON data: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Error ? error : undefined\n );\n }\n validateNostrEvent(decoded);\n return decoded;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@toon-protocol/relay",
3
- "version": "1.3.4",
4
- "description": "ILP-gated Nostr relay with Business Logic Server",
3
+ "version": "2.0.1",
4
+ "description": "Nostr relay app: free NIP-01 reads + HTTP POST /write",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -39,16 +39,11 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@hono/node-server": "^1.0.0",
42
- "@toon-protocol/connector": "^3.10.0",
43
- "@toon-protocol/core": "^1.4.2",
44
- "@toon-protocol/sdk": "^0.5.1",
45
42
  "@toon-format/toon": "^1.0.0",
46
43
  "better-sqlite3": "^11.0.0",
47
44
  "hono": "^4.11.10",
48
45
  "nostr-tools": "^2.20.0",
49
- "viem": "^2.47.2",
50
- "ws": "^8.0.0",
51
- "@toon-protocol/bls": "^1.2.4"
46
+ "ws": "^8.0.0"
52
47
  },
53
48
  "devDependencies": {
54
49
  "@types/better-sqlite3": "^7.6.0",