univo 0.1.15
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/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +71 -0
- package/dist/client.d.mts +24 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.mjs +43 -0
- package/dist/index.d.mts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.mjs +516 -0
- package/dist/realtime.d.mts +50 -0
- package/dist/realtime.d.ts +50 -0
- package/dist/realtime.mjs +210 -0
- package/dist/shared/univo.rTUTP64t.mjs +154 -0
- package/package.json +55 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import { c as createLogger, v as verifySignature, d as decompress, a as createException, g as getException, h as hexToNumber, i as isAddressEqual, b as catchException, r as retry, e as version, n as nonNullable, f as getSignature } from './shared/univo.rTUTP64t.mjs';
|
|
2
|
+
|
|
3
|
+
function matchFilter(block, filter) {
|
|
4
|
+
if (!chainValid(block, filter)) return false;
|
|
5
|
+
if (!toBlockValid(block, filter)) return false;
|
|
6
|
+
if (!fromBlockValid(block, filter)) return false;
|
|
7
|
+
if (!includesLogEvent(block, filter)) return false;
|
|
8
|
+
if (!includesLogAddress(block, filter)) return false;
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
const chainValid = (block, filter) => {
|
|
12
|
+
if (hexToNumber(block.eth_chainId) === filter.chain) return true;
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
const fromBlockValid = (block, filter) => {
|
|
16
|
+
if (hexToNumber(block.eth_getBlockByHash.number) >= filter.fromBlock) return true;
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
const toBlockValid = (block, filter) => {
|
|
20
|
+
if (filter.toBlock === void 0) return true;
|
|
21
|
+
if (hexToNumber(block.eth_getBlockByHash.number) <= filter.toBlock) return true;
|
|
22
|
+
return false;
|
|
23
|
+
};
|
|
24
|
+
const includesLogAddress = (block, filter) => {
|
|
25
|
+
if (filter.address === void 0) return true;
|
|
26
|
+
for (const receipt of block.eth_getBlockReceipts) {
|
|
27
|
+
for (const log of receipt.logs) {
|
|
28
|
+
if (isAddressEqual(log.address, filter.address)) return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
const includesLogEvent = (block, filter) => {
|
|
34
|
+
if (filter.event === void 0) return true;
|
|
35
|
+
for (const receipt of block.eth_getBlockReceipts) {
|
|
36
|
+
for (const log of receipt.logs) {
|
|
37
|
+
if (log.topics[0] === filter.event) return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
};
|
|
42
|
+
function indexer(opts) {
|
|
43
|
+
const log = createLogger({ quiet: opts.quiet || false });
|
|
44
|
+
const all_events = [];
|
|
45
|
+
const events_grouped_by_storage_map = /* @__PURE__ */ new Map();
|
|
46
|
+
const private_getMetadata = async () => {
|
|
47
|
+
return {
|
|
48
|
+
version,
|
|
49
|
+
language: "javascript",
|
|
50
|
+
max_concurrency: opts.maxConcurrency
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const private_getEvents = async () => {
|
|
54
|
+
return all_events.map((event2) => {
|
|
55
|
+
const filters = event2.filters.map((filter) => {
|
|
56
|
+
return {
|
|
57
|
+
chain: filter.chain,
|
|
58
|
+
event: filter.event,
|
|
59
|
+
address: filter.address,
|
|
60
|
+
to_block: filter.toBlock,
|
|
61
|
+
from_block: filter.fromBlock
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
return { id: event2.id, filters };
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const createResultsForEndpoint = async (endpoint, results) => {
|
|
68
|
+
const body = JSON.stringify({ endpoint, results });
|
|
69
|
+
const signature = await getSignature({ body, key: opts.signingKey });
|
|
70
|
+
const res = await fetch("https://api.univo.app/v1/results", {
|
|
71
|
+
body,
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: {
|
|
74
|
+
"X-Univo-Signature": signature,
|
|
75
|
+
"Content-Type": "application/json"
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (res.status !== 200) throw new Error("Received non-200 response from univo API");
|
|
79
|
+
};
|
|
80
|
+
const public_writeBlocks = async (endpoint, heads) => {
|
|
81
|
+
log.debug(`Received ${heads.length} heads...`);
|
|
82
|
+
const blocks_start = Date.now();
|
|
83
|
+
const results_map = {};
|
|
84
|
+
const blocks_nullable = await Promise.all(
|
|
85
|
+
heads.map(async (head) => {
|
|
86
|
+
try {
|
|
87
|
+
if (opts.getBlock === void 0) {
|
|
88
|
+
throw new Error("Provide a `getBlock` function to `serve` so that univo can process events in realtime");
|
|
89
|
+
}
|
|
90
|
+
return await retry(opts.getBlock, [head], 2).catch(() => {
|
|
91
|
+
throw new Error("Failed to load block from the provided `getBlock` function after 3 attempts");
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof Error) {
|
|
95
|
+
log.error(error.message);
|
|
96
|
+
}
|
|
97
|
+
for (const event2 of all_events) {
|
|
98
|
+
const event_id = event2.id;
|
|
99
|
+
const chain = head.chain;
|
|
100
|
+
const block_hash = head.hash;
|
|
101
|
+
const block_number = head.number;
|
|
102
|
+
results_map[chain + block_number + block_hash + event_id] ??= {
|
|
103
|
+
status: "block_error",
|
|
104
|
+
event_id,
|
|
105
|
+
chain,
|
|
106
|
+
block_hash,
|
|
107
|
+
block_number,
|
|
108
|
+
created_at: Date.now()
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
const blocks = blocks_nullable.filter(nonNullable);
|
|
116
|
+
log.debug(`Loaded ${blocks.length} block(s) in ${Date.now() - blocks_start}ms`);
|
|
117
|
+
if (blocks.length > 0) {
|
|
118
|
+
const events_start = Date.now();
|
|
119
|
+
const promises = events_grouped_by_storage_map.entries().map(async ([storage, grouped_events]) => {
|
|
120
|
+
const batch = [];
|
|
121
|
+
for (const block of blocks) {
|
|
122
|
+
for (const event2 of grouped_events) {
|
|
123
|
+
try {
|
|
124
|
+
if (!event2.filters.some((filter) => matchFilter(block, filter))) {
|
|
125
|
+
log.debug(`Block matches no filters for event ${event2.id}`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const events = event2.handler(block);
|
|
129
|
+
for (const event3 of events) {
|
|
130
|
+
batch.push(event3);
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error instanceof Error) {
|
|
134
|
+
log.error(error.message);
|
|
135
|
+
}
|
|
136
|
+
const event_id = event2.id;
|
|
137
|
+
const chain = block.eth_chainId;
|
|
138
|
+
const block_hash = block.eth_getBlockByHash.hash;
|
|
139
|
+
const block_number = block.eth_getBlockByHash.number;
|
|
140
|
+
results_map[chain + block_number + block_hash + event_id] ??= {
|
|
141
|
+
status: "handler_error",
|
|
142
|
+
event_id,
|
|
143
|
+
chain,
|
|
144
|
+
block_hash,
|
|
145
|
+
block_number,
|
|
146
|
+
created_at: Date.now()
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (batch.length > 0) {
|
|
152
|
+
const start = Date.now();
|
|
153
|
+
await retry(storage.upsert, [batch], 2).catch((error) => {
|
|
154
|
+
if (error instanceof Error) {
|
|
155
|
+
log.error(error.message);
|
|
156
|
+
}
|
|
157
|
+
for (const block of blocks) {
|
|
158
|
+
for (const event2 of grouped_events) {
|
|
159
|
+
if (!event2.filters.some((filter) => matchFilter(block, filter))) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const event_id = event2.id;
|
|
163
|
+
const chain = block.eth_chainId;
|
|
164
|
+
const block_hash = block.eth_getBlockByHash.hash;
|
|
165
|
+
const block_number = block.eth_getBlockByHash.number;
|
|
166
|
+
results_map[chain + block_number + block_hash + event_id] ??= {
|
|
167
|
+
status: "upsert_error",
|
|
168
|
+
event_id,
|
|
169
|
+
chain,
|
|
170
|
+
block_hash,
|
|
171
|
+
block_number,
|
|
172
|
+
created_at: Date.now()
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
for (const event2 of grouped_events) {
|
|
178
|
+
log.debug(`Recorded ${batch.length} ${event2.id} in ${Date.now() - start}ms`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const block of blocks) {
|
|
182
|
+
for (const event2 of grouped_events) {
|
|
183
|
+
if (!event2.filters.some((filter) => matchFilter(block, filter))) continue;
|
|
184
|
+
const event_id = event2.id;
|
|
185
|
+
const chain = block.eth_chainId;
|
|
186
|
+
const block_hash = block.eth_getBlockByHash.hash;
|
|
187
|
+
const block_number = block.eth_getBlockByHash.number;
|
|
188
|
+
results_map[chain + block_number + block_hash + event_id] ??= {
|
|
189
|
+
status: "ok",
|
|
190
|
+
event_id,
|
|
191
|
+
chain,
|
|
192
|
+
block_hash,
|
|
193
|
+
block_number,
|
|
194
|
+
created_at: Date.now()
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
await Promise.all(promises);
|
|
200
|
+
log.debug(`Wrote ${heads.length} heads in ${Date.now() - events_start}ms`);
|
|
201
|
+
}
|
|
202
|
+
const results_array = Object.values(results_map);
|
|
203
|
+
if (results_array.length === 0) return log.debug(`No results to record for ${heads.length} heads`);
|
|
204
|
+
try {
|
|
205
|
+
await retry(createResultsForEndpoint, [endpoint, results_array], 2);
|
|
206
|
+
log.debug(`Recorded ${results_array.length} results`);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
log.error("Failed to submit realtime results to univo after 3 attempts");
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const ignored = [
|
|
212
|
+
"toJSON",
|
|
213
|
+
"toString",
|
|
214
|
+
"valueOf",
|
|
215
|
+
"inspect",
|
|
216
|
+
"constructor",
|
|
217
|
+
"hasOwnProperty",
|
|
218
|
+
"isPrototypeOf",
|
|
219
|
+
"propertyIsEnumerable",
|
|
220
|
+
"__proto__",
|
|
221
|
+
"__defineGetter__",
|
|
222
|
+
"__defineSetter__",
|
|
223
|
+
"__lookupGetter__",
|
|
224
|
+
"__lookupSetter__"
|
|
225
|
+
];
|
|
226
|
+
const getWriteEventsProxy = (value, error) => {
|
|
227
|
+
const createProxyHandler = (path) => {
|
|
228
|
+
return {
|
|
229
|
+
get(target, key, receiver) {
|
|
230
|
+
const value2 = Reflect.get(target, key, receiver);
|
|
231
|
+
if (Array.isArray(target)) {
|
|
232
|
+
if (typeof value2 === "object" && value2 !== null) {
|
|
233
|
+
return new Proxy(value2, createProxyHandler());
|
|
234
|
+
}
|
|
235
|
+
return value2;
|
|
236
|
+
}
|
|
237
|
+
if (value2 === void 0) {
|
|
238
|
+
if (ignored.some((ignored2) => key.endsWith(ignored2))) {
|
|
239
|
+
return value2;
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
if (typeof value2 === "object" && value2 !== null) {
|
|
244
|
+
return new Proxy(value2, createProxyHandler());
|
|
245
|
+
}
|
|
246
|
+
return value2;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
return new Proxy(value, createProxyHandler());
|
|
251
|
+
};
|
|
252
|
+
const private_writeEvents = async (params) => {
|
|
253
|
+
if (all_events.length === 0) return { failures: [] };
|
|
254
|
+
const relevant_events = all_events.filter((event2) => params.events.includes(event2.id));
|
|
255
|
+
if (relevant_events.length === 0) return { failures: [] };
|
|
256
|
+
if (params.events.length === 0) return { failures: [] };
|
|
257
|
+
const failures = {};
|
|
258
|
+
const proxied_blocks = getWriteEventsProxy(params.blocks, new Error(IncompleteBlockError));
|
|
259
|
+
const promises = events_grouped_by_storage_map.entries().map(async ([storage, grouped_events]) => {
|
|
260
|
+
const batch = [];
|
|
261
|
+
for (const event2 of grouped_events) {
|
|
262
|
+
if (!params.events.includes(event2.id)) continue;
|
|
263
|
+
for (const block of proxied_blocks) {
|
|
264
|
+
try {
|
|
265
|
+
if (!event2.filters.some((filter) => matchFilter(block, filter))) continue;
|
|
266
|
+
const events = event2.handler(block);
|
|
267
|
+
for (const event3 of events) batch.push(event3);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const status = catchException(error, IncompleteBlockError) ? "incomplete_error" : "handler_error";
|
|
270
|
+
if (status === "handler_error") {
|
|
271
|
+
if (error instanceof Error) {
|
|
272
|
+
log.error(error.message);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
failures[event2.id + block.eth_getBlockByHash.number] ??= {
|
|
276
|
+
status,
|
|
277
|
+
event_id: event2.id,
|
|
278
|
+
chain: block.eth_chainId,
|
|
279
|
+
block_hash: block.eth_getBlockByHash.hash,
|
|
280
|
+
block_number: block.eth_getBlockByHash.number,
|
|
281
|
+
created_at: Date.now()
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (batch.length === 0) return;
|
|
287
|
+
const start = Date.now();
|
|
288
|
+
await retry(storage.upsert, [batch], 2).catch((error) => {
|
|
289
|
+
const status = catchException(error, IncompleteBlockError) ? "incomplete_error" : "upsert_error";
|
|
290
|
+
if (status === "upsert_error") {
|
|
291
|
+
if (error instanceof Error) {
|
|
292
|
+
log.error(error.message);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
for (const event2 of relevant_events) {
|
|
296
|
+
for (const block of proxied_blocks) {
|
|
297
|
+
if (!event2.filters.some((filter) => matchFilter(block, filter))) continue;
|
|
298
|
+
failures[event2.id + block.eth_getBlockByHash.number] ??= {
|
|
299
|
+
status,
|
|
300
|
+
event_id: event2.id,
|
|
301
|
+
chain: block.eth_chainId,
|
|
302
|
+
block_hash: block.eth_getBlockByHash.hash,
|
|
303
|
+
block_number: block.eth_getBlockByHash.number,
|
|
304
|
+
created_at: Date.now()
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
const stop = Date.now() - start;
|
|
310
|
+
for (const event2 of relevant_events) {
|
|
311
|
+
log.debug(`Recorded ${batch.length} ${event2.id} in ${stop}ms`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
await Promise.all(promises);
|
|
315
|
+
const failures_array = Object.values(failures);
|
|
316
|
+
return { failures: failures_array };
|
|
317
|
+
};
|
|
318
|
+
const private_writeEventsAndGetKeys = async (params) => {
|
|
319
|
+
if (all_events.length === 0) return { results: [], keys: [] };
|
|
320
|
+
if (params.events.length === 0) return { results: [], keys: [] };
|
|
321
|
+
const relevant_events = all_events.filter((event2) => params.events.includes(event2.id));
|
|
322
|
+
if (relevant_events.length === 0) return { results: [], keys: [] };
|
|
323
|
+
const keys = /* @__PURE__ */ new Set();
|
|
324
|
+
const results = {};
|
|
325
|
+
function createProxyHandler(path) {
|
|
326
|
+
return {
|
|
327
|
+
get(target, key, receiver) {
|
|
328
|
+
const value = Reflect.get(target, key, receiver);
|
|
329
|
+
if (Array.isArray(target)) {
|
|
330
|
+
if (typeof value === "object" && value !== null) {
|
|
331
|
+
return new Proxy(value, createProxyHandler(path));
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
335
|
+
keys.add(path + key);
|
|
336
|
+
if (typeof value === "object" && value !== null) {
|
|
337
|
+
const delimiter = Array.isArray(value) ? "/" : ".";
|
|
338
|
+
return new Proxy(value, createProxyHandler(path + key + delimiter));
|
|
339
|
+
}
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const proxy = new Proxy(params.block, createProxyHandler(""));
|
|
345
|
+
await Promise.all(
|
|
346
|
+
relevant_events.map(async (event2) => {
|
|
347
|
+
if (!event2.filters.some((filter) => matchFilter(proxy, filter))) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
let events = [];
|
|
351
|
+
try {
|
|
352
|
+
events = event2.handler(proxy);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (error instanceof Error) {
|
|
355
|
+
log.error(error.message);
|
|
356
|
+
}
|
|
357
|
+
results[event2.id + proxy.eth_getBlockByHash.number] = {
|
|
358
|
+
status: "handler_error",
|
|
359
|
+
event_id: event2.id,
|
|
360
|
+
chain: proxy.eth_chainId,
|
|
361
|
+
block_hash: proxy.eth_getBlockByHash.hash,
|
|
362
|
+
block_number: proxy.eth_getBlockByHash.number,
|
|
363
|
+
created_at: Date.now()
|
|
364
|
+
};
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (events.length === 0) {
|
|
368
|
+
results[event2.id + proxy.eth_getBlockByHash.number] = {
|
|
369
|
+
status: "ok",
|
|
370
|
+
event_id: event2.id,
|
|
371
|
+
chain: proxy.eth_chainId,
|
|
372
|
+
block_hash: proxy.eth_getBlockByHash.hash,
|
|
373
|
+
block_number: proxy.eth_getBlockByHash.number,
|
|
374
|
+
created_at: Date.now()
|
|
375
|
+
};
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const start = Date.now();
|
|
380
|
+
await event2.storage.upsert(events);
|
|
381
|
+
log.debug(`Recorded ${events.length} ${event2.id} in ${Date.now() - start}ms`);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (error instanceof Error) {
|
|
384
|
+
log.error(error.message);
|
|
385
|
+
}
|
|
386
|
+
results[event2.id + proxy.eth_getBlockByHash.number] = {
|
|
387
|
+
status: "upsert_error",
|
|
388
|
+
event_id: event2.id,
|
|
389
|
+
chain: proxy.eth_chainId,
|
|
390
|
+
block_hash: proxy.eth_getBlockByHash.hash,
|
|
391
|
+
block_number: proxy.eth_getBlockByHash.number,
|
|
392
|
+
created_at: Date.now()
|
|
393
|
+
};
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
results[event2.id + proxy.eth_getBlockByHash.number] = {
|
|
397
|
+
status: "ok",
|
|
398
|
+
event_id: event2.id,
|
|
399
|
+
chain: proxy.eth_chainId,
|
|
400
|
+
block_hash: proxy.eth_getBlockByHash.hash,
|
|
401
|
+
block_number: proxy.eth_getBlockByHash.number,
|
|
402
|
+
created_at: Date.now()
|
|
403
|
+
};
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
const results_array = Object.values(results);
|
|
407
|
+
const keys_array = Array.from(keys);
|
|
408
|
+
const filtered_keys = keys_array.filter((key, i) => {
|
|
409
|
+
if (ignored.some((ignored2) => key.endsWith(ignored2))) return false;
|
|
410
|
+
if (keys_array.some((_key) => _key.startsWith(`${key}/`) || _key.startsWith(`${key}.`))) return false;
|
|
411
|
+
return true;
|
|
412
|
+
});
|
|
413
|
+
return { results: results_array, keys: filtered_keys };
|
|
414
|
+
};
|
|
415
|
+
const rpc = {
|
|
416
|
+
public_writeBlocks,
|
|
417
|
+
private_getEvents,
|
|
418
|
+
private_getMetadata,
|
|
419
|
+
private_writeEvents,
|
|
420
|
+
private_writeEventsAndGetKeys
|
|
421
|
+
};
|
|
422
|
+
const event = (event2) => {
|
|
423
|
+
if (!/^[A-Za-z0-9_-]+$/.test(event2.id)) {
|
|
424
|
+
throw new Error(`Invalid event id \`${event2.id}\`. Only characters A-Z, a-z, 0-9, underscores, and hyphens are permitted.`);
|
|
425
|
+
}
|
|
426
|
+
all_events.push(event2);
|
|
427
|
+
const group = events_grouped_by_storage_map.get(event2.storage) ?? [];
|
|
428
|
+
group.push(event2);
|
|
429
|
+
events_grouped_by_storage_map.set(event2.storage, group);
|
|
430
|
+
return event2;
|
|
431
|
+
};
|
|
432
|
+
const handler = async (req) => {
|
|
433
|
+
let body_buffer;
|
|
434
|
+
try {
|
|
435
|
+
body_buffer = new Uint8Array(await req.arrayBuffer());
|
|
436
|
+
} catch {
|
|
437
|
+
return Response.json(
|
|
438
|
+
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid request body" } },
|
|
439
|
+
//
|
|
440
|
+
{ status: 400 }
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
let authenticated = false;
|
|
444
|
+
const signature = req.headers.get("X-Univo-Signature");
|
|
445
|
+
if (signature) {
|
|
446
|
+
try {
|
|
447
|
+
authenticated = await verifySignature({ key: opts.signingKey, body: body_buffer, signature });
|
|
448
|
+
} catch {
|
|
449
|
+
return Response.json(
|
|
450
|
+
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Failed to verify request signature" } },
|
|
451
|
+
//
|
|
452
|
+
{ status: 400 }
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
let body_string;
|
|
457
|
+
if (req.headers.get("Content-Encoding") === "gzip") {
|
|
458
|
+
body_string = await decompress(body_buffer);
|
|
459
|
+
} else {
|
|
460
|
+
body_string = new TextDecoder().decode(body_buffer);
|
|
461
|
+
}
|
|
462
|
+
let json;
|
|
463
|
+
try {
|
|
464
|
+
json = JSON.parse(body_string);
|
|
465
|
+
} catch {
|
|
466
|
+
return Response.json(
|
|
467
|
+
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Malformed JSON request body" } },
|
|
468
|
+
//
|
|
469
|
+
{ status: 400 }
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
if (json.method.startsWith("private_")) {
|
|
473
|
+
if (!signature) {
|
|
474
|
+
return Response.json(
|
|
475
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "No request signature provided" } },
|
|
476
|
+
//
|
|
477
|
+
{ status: 400 }
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (!authenticated) {
|
|
481
|
+
return Response.json(
|
|
482
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Invalid request signature" } },
|
|
483
|
+
//
|
|
484
|
+
{ status: 400 }
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
if (rpc[json.method] === void 0) throw new Error(UnknownMethodError);
|
|
490
|
+
const result = await rpc[json.method](...json.params);
|
|
491
|
+
return Response.json({ jsonrpc: "2.0", id: json.id, result });
|
|
492
|
+
} catch (error) {
|
|
493
|
+
const message = getException(error);
|
|
494
|
+
if (message) {
|
|
495
|
+
return Response.json(
|
|
496
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message } },
|
|
497
|
+
//
|
|
498
|
+
{ status: 400 }
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
if (error instanceof Error) {
|
|
502
|
+
log.error(error.message);
|
|
503
|
+
}
|
|
504
|
+
return Response.json(
|
|
505
|
+
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Internal server error" } },
|
|
506
|
+
//
|
|
507
|
+
{ status: 500 }
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
return { fetch: handler, event };
|
|
512
|
+
}
|
|
513
|
+
const UnknownMethodError = createException("The requested method does not exist");
|
|
514
|
+
const IncompleteBlockError = createException("Received block with missing required property");
|
|
515
|
+
|
|
516
|
+
export { indexer, matchFilter };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport -----------------------------------------------------------------------------------------------------------------------------------
|
|
3
|
+
*/
|
|
4
|
+
type Transport = {
|
|
5
|
+
request(opts: {
|
|
6
|
+
method: string;
|
|
7
|
+
params: any[];
|
|
8
|
+
}): Promise<any>;
|
|
9
|
+
subscribe(param: string, handler: (message: any) => void): Promise<{
|
|
10
|
+
unsubscribe(): Promise<void>;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
declare function defineTransport(url: string): Transport;
|
|
14
|
+
/**
|
|
15
|
+
* Blockchain -----------------------------------------------------------------------------------------------------------------------------------
|
|
16
|
+
*/
|
|
17
|
+
type Head = {
|
|
18
|
+
chain: `0x${string}`;
|
|
19
|
+
hash: `0x${string}`;
|
|
20
|
+
number: `0x${string}`;
|
|
21
|
+
parentHash: `0x${string}`;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Stream -----------------------------------------------------------------------------------------------------------------------------------
|
|
25
|
+
*/
|
|
26
|
+
type Handler = (head: Head) => Promise<void> | void;
|
|
27
|
+
type Stream = {
|
|
28
|
+
on(event: "block", handler: Handler): {
|
|
29
|
+
unsubscribe: () => void;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
type CreateRealtimeStreamOpts = {
|
|
33
|
+
quiet: boolean;
|
|
34
|
+
transport: Transport;
|
|
35
|
+
};
|
|
36
|
+
declare function createRealtimeStream(opts: CreateRealtimeStreamOpts): Stream;
|
|
37
|
+
/**
|
|
38
|
+
* Realtime -----------------------------------------------------------------------------------------------------------------------------------
|
|
39
|
+
*/
|
|
40
|
+
type RealtimeOptions = {
|
|
41
|
+
/** Custom transport */
|
|
42
|
+
transport: Transport;
|
|
43
|
+
/** Endpoints to publish realtime blocks to */
|
|
44
|
+
endpoints: string[];
|
|
45
|
+
/** Logs are emitted based on the environment LOG_LEVEL. Set `quiet: true` to surpress all logs. */
|
|
46
|
+
quiet?: boolean;
|
|
47
|
+
};
|
|
48
|
+
declare function realtime(opts: RealtimeOptions): void;
|
|
49
|
+
|
|
50
|
+
export { createRealtimeStream, defineTransport, realtime };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport -----------------------------------------------------------------------------------------------------------------------------------
|
|
3
|
+
*/
|
|
4
|
+
type Transport = {
|
|
5
|
+
request(opts: {
|
|
6
|
+
method: string;
|
|
7
|
+
params: any[];
|
|
8
|
+
}): Promise<any>;
|
|
9
|
+
subscribe(param: string, handler: (message: any) => void): Promise<{
|
|
10
|
+
unsubscribe(): Promise<void>;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
declare function defineTransport(url: string): Transport;
|
|
14
|
+
/**
|
|
15
|
+
* Blockchain -----------------------------------------------------------------------------------------------------------------------------------
|
|
16
|
+
*/
|
|
17
|
+
type Head = {
|
|
18
|
+
chain: `0x${string}`;
|
|
19
|
+
hash: `0x${string}`;
|
|
20
|
+
number: `0x${string}`;
|
|
21
|
+
parentHash: `0x${string}`;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Stream -----------------------------------------------------------------------------------------------------------------------------------
|
|
25
|
+
*/
|
|
26
|
+
type Handler = (head: Head) => Promise<void> | void;
|
|
27
|
+
type Stream = {
|
|
28
|
+
on(event: "block", handler: Handler): {
|
|
29
|
+
unsubscribe: () => void;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
type CreateRealtimeStreamOpts = {
|
|
33
|
+
quiet: boolean;
|
|
34
|
+
transport: Transport;
|
|
35
|
+
};
|
|
36
|
+
declare function createRealtimeStream(opts: CreateRealtimeStreamOpts): Stream;
|
|
37
|
+
/**
|
|
38
|
+
* Realtime -----------------------------------------------------------------------------------------------------------------------------------
|
|
39
|
+
*/
|
|
40
|
+
type RealtimeOptions = {
|
|
41
|
+
/** Custom transport */
|
|
42
|
+
transport: Transport;
|
|
43
|
+
/** Endpoints to publish realtime blocks to */
|
|
44
|
+
endpoints: string[];
|
|
45
|
+
/** Logs are emitted based on the environment LOG_LEVEL. Set `quiet: true` to surpress all logs. */
|
|
46
|
+
quiet?: boolean;
|
|
47
|
+
};
|
|
48
|
+
declare function realtime(opts: RealtimeOptions): void;
|
|
49
|
+
|
|
50
|
+
export { createRealtimeStream, defineTransport, realtime };
|