@effectstream/runtime 0.100.0

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.
@@ -0,0 +1,920 @@
1
+ import {
2
+ createAsyncMiddleware,
3
+ JsonRpcEngine,
4
+ } from "@metamask/json-rpc-engine";
5
+ import {
6
+ createInvalidParamsError,
7
+ createMethodNotFoundRpcError,
8
+ createMethodNotSupportedRpcError,
9
+ type EvmRpcReturn,
10
+ type PaimaEvmRpcSchema,
11
+ } from "./rpc-types.ts";
12
+ import {
13
+ // getBlockByHash,
14
+ getLatestProcessedBlockHeight,
15
+ } from "@effectstream/db";
16
+ import type { Pool } from "pg";
17
+ import {
18
+ type EIP1193Parameters,
19
+ isAddress,
20
+ type RpcBlock,
21
+ type RpcError,
22
+ type RpcTransaction,
23
+ toHex,
24
+ } from "viem";
25
+ import { Buffer } from "node:buffer";
26
+ import sha3 from "js-sha3";
27
+ import {
28
+ toRpcBlock,
29
+ toRpcLog,
30
+ toRpcTransaction,
31
+ toRpcTransactionReceipt,
32
+ } from "./rpc-payloads.ts";
33
+ import {
34
+ isValidBlockHash,
35
+ isValidHexadecimal,
36
+ isValidTxHash,
37
+ strip0x,
38
+ } from "./rpc-utils.ts";
39
+ const { keccak_256 } = sha3;
40
+ import { runPreparedQuery } from "@effectstream/db";
41
+ import { ENV } from "@effectstream/utils/node-env";
42
+
43
+ export function evmRpcEngine(dbConn: Pool): JsonRpcEngine {
44
+ const engine = new JsonRpcEngine();
45
+ // TODO: add cache middleware
46
+ // registerCacheMiddleware(evmRpcEngine);
47
+
48
+ /**
49
+ * ===============================
50
+ * JSON-RPC Middleware definitions
51
+ * ===============================
52
+ */
53
+
54
+ // TODO: Most of these methods are not implemented, or partially implemented.
55
+ // Check effectstream-v1 eip1193.ts for the full implementation details.
56
+ engine.push(
57
+ createAsyncMiddleware(async (req, res) => {
58
+ const evmRpc: typeof req & EIP1193Parameters<PaimaEvmRpcSchema> =
59
+ req as any;
60
+
61
+ const setResult = <Method extends string>(
62
+ result: EvmRpcReturn<Method>,
63
+ ): void => {
64
+ res.result = result as any;
65
+ };
66
+
67
+ // TODO: missing these validity checks:
68
+ // - InvalidInputRpcError / InvalidParamsRpcError
69
+ switch (evmRpc.method) {
70
+ /*
71
+ * ==============
72
+ * Gossip methods
73
+ * ==============
74
+ */
75
+ case "eth_blockNumber": {
76
+ const [lastBlockHeight] = await runPreparedQuery(
77
+ getLatestProcessedBlockHeight.run(
78
+ undefined,
79
+ dbConn,
80
+ ),
81
+ "eth_blockNumber",
82
+ );
83
+
84
+ setResult<typeof evmRpc.method>(
85
+ `0x${lastBlockHeight.block_height.toString(16)}`,
86
+ );
87
+ return;
88
+ }
89
+ case "eth_sendRawTransaction": {
90
+ // TODO: we can probably support this
91
+ // but supporting this conversion would have to be a Effectstream-level feature and not a EVM-RPC feature
92
+ res.error = createMethodNotSupportedRpcError(
93
+ evmRpc.method,
94
+ `${evmRpc.method} currently unsupported`,
95
+ );
96
+ return;
97
+ }
98
+
99
+ /**
100
+ * =============
101
+ * State methods
102
+ * =============
103
+ */
104
+
105
+ case "eth_getBalance": {
106
+ setResult<typeof evmRpc.method>(
107
+ `${toHex(BigInt(0))}`,
108
+ );
109
+ return;
110
+ }
111
+
112
+ case "eth_getStorageAt": {
113
+ // not possible in Paima Engine at the moment, but maybe in the future related to precompiles
114
+ res.error = createMethodNotSupportedRpcError(
115
+ evmRpc.method,
116
+ `${evmRpc.method} currently unsupported`,
117
+ );
118
+ return;
119
+ }
120
+ case "eth_getTransactionCount": {
121
+ const address = evmRpc.params[0];
122
+ if (!isAddress(address)) {
123
+ res.error = createInvalidParamsError(
124
+ { address },
125
+ `Address is not a valid Ethereum address`,
126
+ );
127
+ return;
128
+ }
129
+ let blockHeight: number;
130
+ // TODO
131
+ // const blockTag = simplifyBlockTag(evmRpc.params[1] ?? "finalized");
132
+
133
+ // if (blockParam == "earliest") return FIRST_BLOCK_HEIGHT;
134
+ // if (blockParam == "latest") {
135
+ // const [lastBlockHeight] = await getLatestProcessedBlockHeight.run(
136
+ // undefined,
137
+ // dbConn,
138
+ // );
139
+ // blockHeight = lastBlockHeight.block_height;
140
+ // }
141
+
142
+ // try {
143
+ // blockHeight = await toBlockNumber(evmRpc.params[1] ?? "finalized");
144
+ // } catch (err) {
145
+ // res.error = err as RpcError;
146
+ // return;
147
+ // }
148
+ // const { data, error } = await getPaimaNodeRestClient().GET(
149
+ // "/transaction_count/address",
150
+ // {
151
+ // params: { query: { blockHeight, address } },
152
+ // },
153
+ // );
154
+ // if (error != null) {
155
+ // res.error = createInternalRpcError({}, error?.errorMessage);
156
+ // return;
157
+ // }
158
+ // if (data.success === false) {
159
+ // res.error = createInternalRpcError({}, data.errorMessage);
160
+ // return;
161
+ // }
162
+ setResult<typeof evmRpc.method>(
163
+ `0x${
164
+ Number(0)
165
+ // (data.result.scheduledData + data.result.submittedInputs)
166
+ .toString(
167
+ 16,
168
+ )
169
+ }`,
170
+ );
171
+ return;
172
+ }
173
+ case "eth_getCode": {
174
+ // not possible in Paima Engine at the moment, but maybe in the future related to precompiles
175
+ res.error = createMethodNotSupportedRpcError(
176
+ evmRpc.method,
177
+ `${evmRpc.method} currently unsupported`,
178
+ );
179
+ return;
180
+ }
181
+ case "eth_call": {
182
+ // not possible in Paima Engine at the moment, but maybe in the future related to precompiles
183
+ res.error = createMethodNotSupportedRpcError(
184
+ evmRpc.method,
185
+ `${evmRpc.method} currently unsupported`,
186
+ );
187
+ return;
188
+ }
189
+ case "eth_estimateGas": {
190
+ // no concept of gas in Paima (gas is from the underlying chain)
191
+ setResult<typeof evmRpc.method>(`0x0`);
192
+ return;
193
+ }
194
+
195
+ /**
196
+ * ===============
197
+ * History methods
198
+ * ===============
199
+ */
200
+
201
+ case "eth_getBlockTransactionCountByHash": {
202
+ const blockHash = evmRpc.params[0];
203
+ if (typeof blockHash !== "string" || !isValidBlockHash(blockHash)) {
204
+ res.error = createInvalidParamsError(
205
+ { blockHash },
206
+ `Needs to be a valid block hash (0x...)`,
207
+ );
208
+ return;
209
+ }
210
+ // const { data, error } = await getPaimaNodeRestClient().GET(
211
+ // "/transaction_count/blockHash",
212
+ // {
213
+ // params: { query: { blockHash: blockHash } },
214
+ // },
215
+ // );
216
+ // if (error != null) {
217
+ // res.error = createInternalRpcError({}, error?.errorMessage);
218
+ // return;
219
+ // }
220
+ // if (data.success === false) {
221
+ // res.error = createInternalRpcError({}, data.errorMessage);
222
+ // return;
223
+ // }
224
+ setResult<typeof evmRpc.method>(
225
+ `0x${
226
+ Number(0)
227
+ // (data.result.scheduledData + data.result.submittedInputs)
228
+ .toString(
229
+ 16,
230
+ )
231
+ }`,
232
+ );
233
+ return;
234
+ }
235
+ case "eth_getBlockTransactionCountByNumber": {
236
+ let blockHeight: number;
237
+ // try {
238
+ // blockHeight = await toBlockNumber(evmRpc.params[0] ?? "finalized");
239
+ // } catch (err) {
240
+ // res.error = err as RpcError;
241
+ // return;
242
+ // }
243
+ // const { data, error } = await getPaimaNodeRestClient().GET(
244
+ // "/transaction_count/blockHeight",
245
+ // {
246
+ // params: { query: { blockHeight } },
247
+ // },
248
+ // );
249
+ // if (error != null) {
250
+ // res.error = createInternalRpcError({}, error?.errorMessage);
251
+ // return;
252
+ // }
253
+ // if (data.success === false) {
254
+ // res.error = createInternalRpcError({}, data.errorMessage);
255
+ // return;
256
+ // }
257
+ setResult<typeof evmRpc.method>(
258
+ `0x${
259
+ Number(0)
260
+ // (data.result.scheduledData + data.result.submittedInputs)
261
+ .toString(
262
+ 16,
263
+ )
264
+ }`,
265
+ );
266
+ return;
267
+ }
268
+ case "eth_getUncleCountByBlockHash": {
269
+ setResult<typeof evmRpc.method>(`0x0`); // no uncles in Paima
270
+ return;
271
+ }
272
+ case "eth_getUncleCountByBlockNumber": {
273
+ setResult<typeof evmRpc.method>(`0x0`); // no uncles in Paima
274
+ return;
275
+ }
276
+ case "eth_getBlockByHash": {
277
+ const blockHash = evmRpc.params[0];
278
+ if (typeof blockHash !== "string" || !isValidBlockHash(blockHash)) {
279
+ res.error = createInvalidParamsError(
280
+ { blockHash },
281
+ `Needs to be a valid block hash (0x...)`,
282
+ );
283
+ return;
284
+ }
285
+
286
+ const transactionDetails = evmRpc.params[1];
287
+ if (typeof transactionDetails !== "boolean") {
288
+ res.error = createInvalidParamsError(
289
+ { transactionDetails },
290
+ `Transaction details must be specified as a boolean`,
291
+ );
292
+ return;
293
+ }
294
+
295
+ const [block] = await runPreparedQuery(
296
+ getLatestProcessedBlockHeight.run(
297
+ undefined,
298
+ // { block_hash: Buffer.from(blockHash.slice(2), "hex") },
299
+ dbConn,
300
+ ),
301
+ "eth_getBlockByHash",
302
+ );
303
+
304
+ if (!block) {
305
+ res.error = createInvalidParamsError(
306
+ { blockHash },
307
+ `Block not found`,
308
+ );
309
+ return;
310
+ }
311
+ // const { data, error } = await getPaimaNodeRestClient().GET(
312
+ // "/block_content/blockHash",
313
+ // {
314
+ // params: {
315
+ // query: {
316
+ // blockHash,
317
+ // txDetails: transactionDetails ? "full" : "hash",
318
+ // },
319
+ // },
320
+ // },
321
+ // );
322
+ // if (error != null) {
323
+ // res.error = createInternalRpcError({}, error?.errorMessage);
324
+ // return;
325
+ // }
326
+ // if (data.success === false) {
327
+ // res.error = createInternalRpcError({}, data.errorMessage);
328
+ // return;
329
+ // }
330
+ const randomBlockHash = (): string =>
331
+ "0x" +
332
+ Array(64).fill(0).map(() =>
333
+ Math.floor(Math.random() * 16).toString(16)
334
+ ).join("");
335
+ const mockInput = {
336
+ blockHeight: 0, // block.block_height,
337
+ blockHash: randomBlockHash(),
338
+ prevBlockHash: randomBlockHash(),
339
+ msTimestamp: Date.now(),
340
+ txs: [],
341
+ };
342
+ const mock: RpcBlock = toRpcBlock(mockInput, transactionDetails);
343
+ setResult<typeof evmRpc.method>(mock);
344
+ return;
345
+ }
346
+ case "eth_getBlockByNumber": {
347
+ let blockHeight: number;
348
+ let block;
349
+ try {
350
+ // TODO this should be dynamic
351
+ // TOOD for now just passing the block
352
+ const [block_] = await runPreparedQuery(
353
+ getLatestProcessedBlockHeight.run(
354
+ undefined,
355
+ dbConn,
356
+ ),
357
+ "eth_getBlockByNumber",
358
+ );
359
+ block = block_;
360
+ blockHeight = block.block_height;
361
+ // blockHeight = await toBlockNumber(evmRpc.params[0] ?? "finalized");
362
+ } catch (err) {
363
+ res.error = err as RpcError;
364
+ return;
365
+ }
366
+
367
+ const transactionDetails = evmRpc.params[1];
368
+ if (typeof transactionDetails !== "boolean") {
369
+ res.error = createInvalidParamsError(
370
+ { transactionDetails },
371
+ `Transaction details must be specified as a boolean`,
372
+ );
373
+ return;
374
+ }
375
+
376
+ // const { data, error } = await getPaimaNodeRestClient().GET(
377
+ // "/block_content/blockHeight",
378
+ // {
379
+ // params: {
380
+ // query: {
381
+ // blockHeight,
382
+ // txDetails: transactionDetails ? "full" : "hash",
383
+ // },
384
+ // },
385
+ // },
386
+ // );
387
+ // if (error != null) {
388
+ // res.error = createInternalRpcError({}, error?.errorMessage);
389
+ // return;
390
+ // }
391
+ // if (data.success === false) {
392
+ // res.error = createInternalRpcError({}, data.errorMessage);
393
+ // return;
394
+ // }
395
+
396
+ // example: 0x1e888a0eadd622fe55b53692139e64f23852b247060fcb31322c3a76b265e760
397
+ const randomBlockHash = (): string =>
398
+ "0x" +
399
+ Array(64).fill(0).map(() =>
400
+ Math.floor(Math.random() * 16).toString(16)
401
+ ).join("");
402
+ const mockInput = {
403
+ blockHeight: block.block_height,
404
+ blockHash: randomBlockHash(),
405
+ prevBlockHash: randomBlockHash(),
406
+ msTimestamp: Date.now(),
407
+ txs: [],
408
+ };
409
+ const mock: RpcBlock = toRpcBlock(mockInput, transactionDetails);
410
+ setResult<typeof evmRpc.method>(mock);
411
+ return;
412
+ }
413
+ case "eth_getTransactionByHash": {
414
+ const txHash = evmRpc.params[0];
415
+ if (!isValidTxHash(txHash)) {
416
+ res.error = createInvalidParamsError(
417
+ { txHash },
418
+ `Transaction hash did not match EVM format: ${txHash}`,
419
+ );
420
+ return;
421
+ }
422
+ // TODO
423
+ // const { data, error } = await getPaimaNodeRestClient().GET(
424
+ // "/transaction_content/txHash",
425
+ // {
426
+ // params: { query: { txHash } },
427
+ // },
428
+ // );
429
+ // if (error != null) {
430
+ // res.error = createInternalRpcError({}, error?.errorMessage);
431
+ // return;
432
+ // }
433
+ // if (data.success === false) {
434
+ // res.error = createInternalRpcError({}, data.errorMessage);
435
+ // return;
436
+ // }
437
+ const [block] = await runPreparedQuery(
438
+ getLatestProcessedBlockHeight.run(
439
+ undefined,
440
+ dbConn,
441
+ ),
442
+ "eth_getTransactionByHash",
443
+ );
444
+ if (!block) {
445
+ res.error = createInvalidParamsError(
446
+ { txHash },
447
+ `Block not found`,
448
+ );
449
+ return;
450
+ }
451
+ const randomBlockHash = (): string =>
452
+ "0x" +
453
+ Array(64).fill(0).map(() =>
454
+ Math.floor(Math.random() * 16).toString(16)
455
+ ).join("");
456
+ const mockInput = {
457
+ blockHash: randomBlockHash(),
458
+ blockNumber: block.block_height,
459
+ from: randomBlockHash(),
460
+ inputData: "hello",
461
+ txHash: txHash,
462
+ txIndex: 0,
463
+ };
464
+ const mock: RpcTransaction = toRpcTransaction(mockInput);
465
+ setResult<typeof evmRpc.method>(mock);
466
+ return;
467
+ }
468
+ case "eth_getTransactionByBlockHashAndIndex": {
469
+ const blockHash = evmRpc.params[0];
470
+ if (typeof blockHash !== "string" || !isValidBlockHash(blockHash)) {
471
+ res.error = createInvalidParamsError(
472
+ { blockHash },
473
+ `Needs to be a valid block hash (0x...)`,
474
+ );
475
+ return;
476
+ }
477
+ // const txIndex = Number.parseInt(evmRpc.params[1], 16);
478
+ // if (typeof txIndex !== "number") {
479
+ // res.error = createInvalidParamsError(
480
+ // { txIndex },
481
+ // `Transaction index must be specified as a number`,
482
+ // );
483
+ // return;
484
+ // }
485
+
486
+ // const { data, error } = await getPaimaNodeRestClient().GET(
487
+ // "/transaction_content/blockHashAndIndex",
488
+ // { params: { query: { blockHash, txIndex } } },
489
+ // );
490
+ // if (error != null) {
491
+ // res.error = createInternalRpcError({}, error?.errorMessage);
492
+ // return;
493
+ // }
494
+ // if (data.success === false) {
495
+ // res.error = createInternalRpcError({}, data.errorMessage);
496
+ // return;
497
+ // }
498
+ const [block] = await runPreparedQuery(
499
+ getLatestProcessedBlockHeight.run(
500
+ undefined,
501
+ dbConn,
502
+ ),
503
+ "eth_getTransactionByBlockHashAndIndex",
504
+ );
505
+ const randomBlockHash = (): string =>
506
+ "0x" +
507
+ Array(64).fill(0).map(() =>
508
+ Math.floor(Math.random() * 16).toString(16)
509
+ ).join("");
510
+ const mockInput = {
511
+ blockHash: randomBlockHash(),
512
+ blockNumber: block.block_height,
513
+ from: randomBlockHash(),
514
+ inputData: randomBlockHash(),
515
+ txHash: randomBlockHash(),
516
+ txIndex: 0,
517
+ };
518
+ const mock = toRpcTransaction(mockInput);
519
+ setResult<typeof evmRpc.method>(mock);
520
+ return;
521
+ }
522
+ case "eth_getTransactionByBlockNumberAndIndex": {
523
+ let blockHeight: number;
524
+ try {
525
+ const [block] = await runPreparedQuery(
526
+ getLatestProcessedBlockHeight.run(
527
+ undefined,
528
+ dbConn,
529
+ ),
530
+ "eth_getTransactionByBlockNumberAndIndex",
531
+ );
532
+ blockHeight = block.block_height;
533
+ // blockHeight = await toBlockNumber(evmRpc.params[0]);
534
+ } catch (err) {
535
+ res.error = err as RpcError;
536
+ return;
537
+ }
538
+ const txIndex = Number.parseInt(evmRpc.params[1], 16);
539
+ if (typeof txIndex !== "number") {
540
+ res.error = createInvalidParamsError(
541
+ { txIndex },
542
+ `Transaction index must be specified as a number`,
543
+ );
544
+ return;
545
+ }
546
+
547
+ // const { data, error } = await getPaimaNodeRestClient().GET(
548
+ // "/transaction_content/blockNumberAndIndex",
549
+ // { params: { query: { blockHeight, txIndex } } },
550
+ // );
551
+ // if (error != null) {
552
+ // res.error = createInternalRpcError({}, error?.errorMessage);
553
+ // return;
554
+ // }
555
+ // if (data.success === false) {
556
+ // res.error = createInternalRpcError({}, data.errorMessage);
557
+ // return;
558
+ // }
559
+ const [block] = await runPreparedQuery(
560
+ getLatestProcessedBlockHeight.run(
561
+ undefined,
562
+ dbConn,
563
+ ),
564
+ "eth_getTransactionByBlockNumberAndIndex",
565
+ );
566
+ const randomBlockHash = (): string =>
567
+ "0x" +
568
+ Array(64).fill(0).map(() =>
569
+ Math.floor(Math.random() * 16).toString(16)
570
+ ).join("");
571
+ const mockInput = {
572
+ blockHash: randomBlockHash(),
573
+ blockNumber: block.block_height,
574
+ from: randomBlockHash(),
575
+ inputData: randomBlockHash(),
576
+ txHash: randomBlockHash(),
577
+ txIndex: 0,
578
+ };
579
+ const mock = toRpcTransaction(mockInput);
580
+ setResult<typeof evmRpc.method>(mock);
581
+ return;
582
+ }
583
+ case "eth_getLogs": {
584
+ const request = evmRpc.params[0];
585
+
586
+ let fromBlock: number;
587
+ let toBlock: number;
588
+ // if (request.blockHash) {
589
+ // if (
590
+ // typeof request.blockHash !== "string" ||
591
+ // !isValidBlockHash(request.blockHash)
592
+ // ) {
593
+ // res.error = createInvalidParamsError(
594
+ // { blockHash: request.blockHash },
595
+ // `Needs to be a valid block hash (0x...)`,
596
+ // );
597
+ // return;
598
+ // }
599
+ // const { data, error } = await getPaimaNodeRestClient().GET(
600
+ // "/block_content/blockHash",
601
+ // {
602
+ // params: {
603
+ // query: { blockHash: request.blockHash, txDetails: "none" },
604
+ // },
605
+ // },
606
+ // );
607
+ // if (error != null) {
608
+ // res.error = createInternalRpcError({}, error?.errorMessage);
609
+ // return;
610
+ // }
611
+ // if (data.success === false) {
612
+ // res.error = createInternalRpcError({}, data.errorMessage);
613
+ // return;
614
+ // }
615
+
616
+ // fromBlock = data.result.blockHeight;
617
+ // toBlock = data.result.blockHeight;
618
+ // } else {
619
+ // try {
620
+ // fromBlock = await toBlockNumber(request.fromBlock ?? "latest");
621
+ // } catch (err) {
622
+ // res.error = err as RpcError;
623
+ // return;
624
+ // }
625
+ // try {
626
+ // toBlock = await toBlockNumber(request.toBlock ?? "latest");
627
+ // } catch (err) {
628
+ // res.error = err as RpcError;
629
+ // return;
630
+ // }
631
+ // }
632
+
633
+ // const filterFields: Partial<
634
+ // Pick<
635
+ // PaimaNodeRestComponents["schemas"]["GetLogsParams"],
636
+ // "filters" | "topic"
637
+ // >
638
+ // > = {};
639
+ // if (request.topics != null && request.topics.length > 0) {
640
+ // // https://ethereum.stackexchange.com/a/107326/140076
641
+ // // problem: how do we know if something is an address, or a string that happens to be the same length?
642
+ // const [topicHash, ...data] = request.topics;
643
+ // filterFields.topic = strip0x(topicHash as string);
644
+
645
+ // // we need to map any other data provided from EVM format to Paima format
646
+ // if (data.length > 0) {
647
+ // const eventDefinition = (():
648
+ // | undefined
649
+ // | RegisteredEvent<LogEvent<LogEventFields<TSchema>[]>> => {
650
+ // const appEvents = EngineService.INSTANCE.getSM().getAppEvents();
651
+
652
+ // for (const defs of Object.values(appEvents)) {
653
+ // for (const def of defs) {
654
+ // if (def.topicHash === filterFields.topic) {
655
+ // return def;
656
+ // }
657
+ // }
658
+ // }
659
+
660
+ // return undefined;
661
+ // })();
662
+
663
+ // // if no event is found for this topic, we can just return right away
664
+ // // note: EVM RPCs also do not return an error for not-found topics, as they may be registered later
665
+ // if (eventDefinition == null) {
666
+ // setResult<typeof evmRpc.method>([]);
667
+ // return;
668
+ // }
669
+
670
+ // const eventPaths = eventDefinition.path as EventPath;
671
+ // const evmAbi = toEvmAbi(eventDefinition.definition);
672
+ // if (evmAbi == null) {
673
+ // res.error = createInternalRpcError(
674
+ // {},
675
+ // `EVM ABI conversion for this event type is not supported`,
676
+ // );
677
+ // return;
678
+ // }
679
+ // for (let i = 0, j = 0; i < data.length; i++) {
680
+ // const topic = data[i];
681
+ // if (topic == null) continue;
682
+ // if (Array.isArray(topic)) {
683
+ // res.error = createInvalidParamsError(
684
+ // { topic },
685
+ // `Array-style OR topics unsupported at the moment`,
686
+ // );
687
+ // return;
688
+ // }
689
+ // let nextFilter: undefined | ArgPath = undefined;
690
+ // // increase `j` to the index of the next filter for this event
691
+ // for (; j < eventPaths.length; j++) {
692
+ // let path = eventPaths[j];
693
+ // if (typeof path !== "string") {
694
+ // nextFilter = path;
695
+ // break;
696
+ // }
697
+ // }
698
+ // // no fields left (there are more event RPC filters specified than fields for this event)
699
+ // if (nextFilter == null) {
700
+ // break;
701
+ // }
702
+ // const filter = eventPaths[j] as ArgPath;
703
+
704
+ // if (filterFields.filters == null) {
705
+ // filterFields.filters = {};
706
+ // }
707
+ // filterFields.filters[filter.name] = decodeAbiParameters(
708
+ // [evmAbi[i]],
709
+ // topic,
710
+ // )[0] as string;
711
+ // }
712
+ // }
713
+ // }
714
+
715
+ // const { data, error } = await getPaimaNodeRestClient().POST(
716
+ // "/get_logs",
717
+ // {
718
+ // body: {
719
+ // fromBlock,
720
+ // toBlock,
721
+ // ...filterFields,
722
+ // address: request.address,
723
+ // },
724
+ // },
725
+ // );
726
+ // if (error != null) {
727
+ // res.error = createInternalRpcError({}, error?.errorMessage);
728
+ // return;
729
+ // }
730
+ // if (data.success === false) {
731
+ // res.error = createInternalRpcError({}, data.errorMessage);
732
+ // return;
733
+ // }
734
+
735
+ setResult<typeof evmRpc.method>([].map(toRpcLog));
736
+ return;
737
+ }
738
+ case "eth_getTransactionReceipt": {
739
+ const txHash = evmRpc.params[0];
740
+ if (!isValidTxHash(txHash)) {
741
+ res.error = createInvalidParamsError(
742
+ { txHash },
743
+ `Transaction hash did not match EVM format: ${txHash}`,
744
+ );
745
+ return;
746
+ }
747
+
748
+ // const { data: txData, error: txError } = await getPaimaNodeRestClient()
749
+ // .GET(
750
+ // "/transaction_content/txHash",
751
+ // {
752
+ // params: { query: { txHash } },
753
+ // },
754
+ // );
755
+ // if (txError != null) {
756
+ // res.error = createInternalRpcError({}, txError?.errorMessage);
757
+ // return;
758
+ // }
759
+ // if (txData.success === false) {
760
+ // res.error = createInternalRpcError({}, txData.errorMessage);
761
+ // return;
762
+ // }
763
+
764
+ // // TODO: create a version of get_logs for specific transactions
765
+
766
+ const [block] = await runPreparedQuery(
767
+ getLatestProcessedBlockHeight.run(
768
+ undefined,
769
+ dbConn,
770
+ ),
771
+ "eth_getTransactionReceipt",
772
+ );
773
+
774
+ if (!block) {
775
+ res.error = createInvalidParamsError(
776
+ { txHash },
777
+ `Block not found`,
778
+ );
779
+ return;
780
+ }
781
+
782
+ const randomBlockHash = (): string =>
783
+ "0x" +
784
+ Array(64).fill(0).map(() =>
785
+ Math.floor(Math.random() * 16).toString(16)
786
+ ).join("");
787
+
788
+ const mockInput = {
789
+ result: {
790
+ txHash: randomBlockHash(),
791
+ blockHash: randomBlockHash(),
792
+ blockNumber: block.block_height,
793
+ txIndex: 0,
794
+ from: randomBlockHash(),
795
+ success: true,
796
+ },
797
+ };
798
+ setResult<typeof evmRpc.method>(toRpcTransactionReceipt(mockInput));
799
+ return;
800
+ }
801
+ case "eth_getUncleByBlockHashAndIndex": {
802
+ setResult<typeof evmRpc.method>(null); // no uncles in Paima
803
+ return;
804
+ }
805
+ case "eth_getUncleByBlockNumberAndIndex": {
806
+ setResult<typeof evmRpc.method>(null); // no uncles in Paima
807
+ return;
808
+ }
809
+
810
+ // /**
811
+ // * ===============
812
+ // * Other methods
813
+ // * ===============
814
+ // */
815
+
816
+ case "web3_clientVersion": {
817
+ // TODO: should include the following
818
+ // 1: commit hash somehow
819
+ // 2: platform
820
+ // 3: node version
821
+ // example of Geth: Geth/v1.9.15-stable-0f77f34b/linux-amd64/go1.14.4"
822
+ // we just keep it simple for now
823
+ // TODO: UPDATE THIS
824
+ setResult<typeof evmRpc.method>(
825
+ `PaimaEngine/v0.0.1`,
826
+ );
827
+ return;
828
+ }
829
+ case "web3_sha3": {
830
+ const data = evmRpc.params[0];
831
+ if (typeof data !== "string" || !isValidHexadecimal(strip0x(data))) {
832
+ res.error = createInvalidParamsError(
833
+ { data },
834
+ `Data must be a hex-encoded string 0x...`,
835
+ );
836
+ return;
837
+ }
838
+ // web3_sha3 passes the data as a hex-string, so we need to convert it
839
+ const buffer = Buffer.from(strip0x(data), "hex");
840
+
841
+ setResult<typeof evmRpc.method>(`0x${keccak_256(buffer as any)}`);
842
+ return;
843
+ }
844
+ case "net_version": {
845
+ // net_version is an older version of eth_chainId that predates the ETC hardfork
846
+ // eth_chainId should always be used instead, but net_version===eth_chainId in almost all networks
847
+ // the only real notable exception being ETC where these values are not equal
848
+ // learn more: https://medium.com/@pedrouid/chainid-vs-networkid-how-do-they-differ-on-ethereum-eec2ed41635b
849
+ setResult<typeof evmRpc.method>(
850
+ `0x${ENV.EFFECTSTREAM_CHAIN_ID.toString(16)}`,
851
+ );
852
+ return;
853
+ }
854
+ case "net_listening": {
855
+ // note: depending on your interpretation,
856
+ // this should one of
857
+ // A) always return true (is the server online at all)
858
+ // B) Only return true if this RPC should be used by others (is one RPC better than another)
859
+ // C) Can you sync blocks from connecting to this node
860
+ // ex: in wagmi, it's used to rank multiple RPCs and it prioritizes RPCs that return true
861
+ setResult<typeof evmRpc.method>(true); // maybe this should be ENV.SERVER_ONLY_MODE
862
+ return;
863
+ }
864
+ case "net_peerCount": {
865
+ // todo: theoretically we could support this based on MQTT connections or something similar, but it's not worth it
866
+ setResult<typeof evmRpc.method>(`0x0`);
867
+ return;
868
+ }
869
+ case "eth_gasPrice": {
870
+ // no concept of gas in Paima (gas is from the underlying chain)
871
+ setResult<typeof evmRpc.method>(`0x0`);
872
+ return;
873
+ }
874
+ case "eth_chainId": {
875
+ // TODO: this is not a great thing to return, since every game is its own chain. Not sure what to do about this
876
+ // maybe the hash of the game name or something?
877
+ setResult<typeof evmRpc.method>(
878
+ `0x${ENV.EFFECTSTREAM_CHAIN_ID.toString(16)}`,
879
+ );
880
+ return;
881
+ }
882
+
883
+ case "eth_syncing": {
884
+ // if (ENV.SERVER_ONLY_MODE) {
885
+ // setResult<typeof evmRpc.method>(false);
886
+ // }
887
+ // const { data, error } = await getPaimaNodeRestClient().GET(
888
+ // "/latest_processed_blockheight",
889
+ // );
890
+ // if (error != null) {
891
+ // res.error = createInternalRpcError({}, error?.errorMessage);
892
+ // return;
893
+ // }
894
+
895
+ const [block] = await runPreparedQuery(
896
+ getLatestProcessedBlockHeight.run(
897
+ undefined,
898
+ dbConn,
899
+ ),
900
+ "eth_syncing",
901
+ );
902
+
903
+ // TODO Updaste this
904
+ const ENV_START_BLOCKHEIGHT = 0;
905
+ setResult<typeof evmRpc.method>({
906
+ startingBlock: `0x${ENV_START_BLOCKHEIGHT.toString(16)}`,
907
+ currentBlock: `0x${block.block_height.toString(16)}`,
908
+ });
909
+ return;
910
+ }
911
+
912
+ default: {
913
+ res.error = createMethodNotFoundRpcError(evmRpc.method);
914
+ }
915
+ }
916
+ }),
917
+ );
918
+
919
+ return engine;
920
+ }