@appliedblockchain/silentdatarollup-custom-rpc 1.0.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.
- package/README.md +70 -0
- package/dist/index.js +462 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Silent Data [Rollup] Providers - Custom RPC Package
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Introduction](#introduction)
|
|
6
|
+
- [Prerequisites](#prerequisites)
|
|
7
|
+
- [Integration](#integration)
|
|
8
|
+
- [Custom RPC Integration](#custom-rpc-integration)
|
|
9
|
+
- [Installing Custom RPC Dependencies](#installing-custom-rpc-dependencies)
|
|
10
|
+
- [Custom RPC Integration Example](#custom-rpc-integration-example)
|
|
11
|
+
- [Troubleshooting](#troubleshooting)
|
|
12
|
+
- [License](#license)
|
|
13
|
+
- [Additional Resources](#additional-resources)
|
|
14
|
+
|
|
15
|
+
## Introduction
|
|
16
|
+
|
|
17
|
+
Custom RPC provider for Silent Data [Rollup], providing a local development environment with Silent Data [Rollup] integration.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Node.js (version 18 or higher)
|
|
22
|
+
- npm
|
|
23
|
+
- Basic knowledge of Ethereum and smart contracts
|
|
24
|
+
- Hardhat (for local development)
|
|
25
|
+
|
|
26
|
+
## Integration
|
|
27
|
+
|
|
28
|
+
### Custom RPC Integration
|
|
29
|
+
|
|
30
|
+
#### Installing Custom RPC Dependencies
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @appliedblockchain/silentdatarollup-custom-rpc
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Custom RPC Integration Example
|
|
37
|
+
|
|
38
|
+
The Custom RPC package provides a local development environment that integrates Silent Data [Rollup] with a local Hardhat node. To start the development environment:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This command will start both a Hardhat node and the Custom RPC server concurrently. The Custom RPC server will proxy requests to the Hardhat node while adding Silent Data [Rollup] functionality.
|
|
45
|
+
|
|
46
|
+
You can configure the Custom RPC server by creating a `.env` file in your project root:
|
|
47
|
+
|
|
48
|
+
```env
|
|
49
|
+
PORT=3000
|
|
50
|
+
HARDHAT_RPC_URL=http://localhost:8545
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Troubleshooting
|
|
54
|
+
|
|
55
|
+
If you encounter any issues, please check the following:
|
|
56
|
+
|
|
57
|
+
1. Ensure Hardhat is properly installed and configured
|
|
58
|
+
2. Verify that the port specified in your `.env` file is available
|
|
59
|
+
3. Check that the Hardhat node is running and accessible
|
|
60
|
+
4. Ensure all required environment variables are set
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
65
|
+
|
|
66
|
+
## Additional Resources
|
|
67
|
+
|
|
68
|
+
- [Silent Data [Rollup] Documentation](https://docs.silentdata.com)
|
|
69
|
+
- [Hardhat Documentation](https://hardhat.org/docs)
|
|
70
|
+
- [Ethers.js Documentation](https://docs.ethers.org/v6/)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_config = require("dotenv/config");
|
|
28
|
+
var import_express = __toESM(require("express"));
|
|
29
|
+
var import_body_parser = __toESM(require("body-parser"));
|
|
30
|
+
var import_http_proxy_middleware = require("http-proxy-middleware");
|
|
31
|
+
var import_ethers2 = require("ethers");
|
|
32
|
+
|
|
33
|
+
// src/middleware.ts
|
|
34
|
+
var import_silentdatarollup_core2 = require("@appliedblockchain/silentdatarollup-core");
|
|
35
|
+
|
|
36
|
+
// src/signatures.ts
|
|
37
|
+
var import_ethers = require("ethers");
|
|
38
|
+
var import_silentdatarollup_core = require("@appliedblockchain/silentdatarollup-core");
|
|
39
|
+
var TIMESTAMP_MIN_OFFSET = -12;
|
|
40
|
+
var TIMESTAMP_MAX_OFFSET = 4;
|
|
41
|
+
function validateRFC3339Timestamp(timestamp) {
|
|
42
|
+
const rfc3339Pattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
43
|
+
if (!rfc3339Pattern.test(timestamp)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const timestampDate = new Date(timestamp);
|
|
47
|
+
if (Number.isNaN(timestampDate.getTime())) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const now = /* @__PURE__ */ new Date();
|
|
51
|
+
const minAllowedTime = new Date(
|
|
52
|
+
now.getTime() + TIMESTAMP_MIN_OFFSET * 60 * 1e3
|
|
53
|
+
);
|
|
54
|
+
const maxAllowedTime = new Date(
|
|
55
|
+
now.getTime() + TIMESTAMP_MAX_OFFSET * 60 * 1e3
|
|
56
|
+
);
|
|
57
|
+
return timestampDate >= minAllowedTime && timestampDate <= maxAllowedTime;
|
|
58
|
+
}
|
|
59
|
+
function recoverSerialSigner(message, signature) {
|
|
60
|
+
try {
|
|
61
|
+
const messageHash = import_ethers.ethers.hashMessage(message);
|
|
62
|
+
const recoveredAddress = import_ethers.ethers.recoverAddress(messageHash, signature);
|
|
63
|
+
return recoveredAddress;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function recoverTypedSigner(data, signature) {
|
|
69
|
+
try {
|
|
70
|
+
const domain = import_silentdatarollup_core.eip721Domain;
|
|
71
|
+
const types = {
|
|
72
|
+
Request: [
|
|
73
|
+
{ name: "method", type: "string" },
|
|
74
|
+
{ name: "params", type: "string" },
|
|
75
|
+
{ name: "timestamp", type: "string" }
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
const recoveredAddress = import_ethers.ethers.verifyTypedData(
|
|
79
|
+
domain,
|
|
80
|
+
types,
|
|
81
|
+
data,
|
|
82
|
+
signature
|
|
83
|
+
);
|
|
84
|
+
return recoveredAddress;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function recoverSignerWithDelegate(delegateTicket, delegateSignature, message, isEIP712) {
|
|
90
|
+
try {
|
|
91
|
+
const ticket = JSON.parse(delegateTicket);
|
|
92
|
+
if (!ticket.expires || !ticket.ephemeralAddress) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const expiryDate = new Date(ticket.expires);
|
|
96
|
+
if (Number.isNaN(expiryDate.getTime()) || expiryDate <= /* @__PURE__ */ new Date()) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
let delegateAddress;
|
|
100
|
+
if (isEIP712) {
|
|
101
|
+
const data = {
|
|
102
|
+
method: JSON.parse(message).method,
|
|
103
|
+
params: JSON.stringify(JSON.parse(message).params),
|
|
104
|
+
timestamp: ticket.timestamp
|
|
105
|
+
};
|
|
106
|
+
delegateAddress = recoverTypedSigner(data, delegateSignature);
|
|
107
|
+
} else {
|
|
108
|
+
delegateAddress = recoverSerialSigner(message, delegateSignature);
|
|
109
|
+
}
|
|
110
|
+
if (!delegateAddress || delegateAddress.toLowerCase() !== ticket.ephemeralAddress.toLowerCase()) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const ticketData = {
|
|
114
|
+
expires: ticket.expires,
|
|
115
|
+
ephemeralAddress: ticket.ephemeralAddress
|
|
116
|
+
};
|
|
117
|
+
const delegatorAddress = import_ethers.ethers.verifyTypedData(
|
|
118
|
+
import_silentdatarollup_core.eip721Domain,
|
|
119
|
+
import_silentdatarollup_core.delegateEIP721Types,
|
|
120
|
+
ticketData,
|
|
121
|
+
ticket.signature
|
|
122
|
+
);
|
|
123
|
+
return {
|
|
124
|
+
delegate: delegateAddress,
|
|
125
|
+
delegator: delegatorAddress
|
|
126
|
+
};
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function recoverSigner(headers, body) {
|
|
132
|
+
const timestamp = headers[import_silentdatarollup_core.HEADER_TIMESTAMP];
|
|
133
|
+
if (!timestamp) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (!validateRFC3339Timestamp(timestamp)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const delegateTicket = headers[import_silentdatarollup_core.HEADER_DELEGATE];
|
|
140
|
+
const delegateSignature = headers[import_silentdatarollup_core.HEADER_DELEGATE_SIGNATURE];
|
|
141
|
+
const eip712DelegateSignature = headers[import_silentdatarollup_core.HEADER_EIP712_DELEGATE_SIGNATURE];
|
|
142
|
+
const signature = headers[import_silentdatarollup_core.HEADER_SIGNATURE];
|
|
143
|
+
const eip712Signature = headers[import_silentdatarollup_core.HEADER_EIP712_SIGNATURE];
|
|
144
|
+
if (signature && eip712Signature || delegateSignature && eip712DelegateSignature) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (delegateTicket && (delegateSignature || eip712DelegateSignature)) {
|
|
148
|
+
const result = recoverSignerWithDelegate(
|
|
149
|
+
delegateTicket,
|
|
150
|
+
delegateSignature || eip712DelegateSignature || "",
|
|
151
|
+
body,
|
|
152
|
+
!!eip712DelegateSignature
|
|
153
|
+
);
|
|
154
|
+
return result ? result.delegator : null;
|
|
155
|
+
}
|
|
156
|
+
if (signature) {
|
|
157
|
+
const message = `${body}${timestamp}`;
|
|
158
|
+
return recoverSerialSigner(message, signature);
|
|
159
|
+
}
|
|
160
|
+
if (eip712Signature) {
|
|
161
|
+
try {
|
|
162
|
+
const parsedBody = JSON.parse(body);
|
|
163
|
+
const data = {
|
|
164
|
+
method: parsedBody.method,
|
|
165
|
+
params: JSON.stringify(parsedBody.params),
|
|
166
|
+
timestamp
|
|
167
|
+
};
|
|
168
|
+
return recoverTypedSigner(data, eip712Signature);
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function generateRandomKeyPair() {
|
|
176
|
+
const wallet = import_ethers.ethers.Wallet.createRandom();
|
|
177
|
+
return {
|
|
178
|
+
privateKey: wallet.privateKey,
|
|
179
|
+
address: wallet.address
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function validateAddress(signer, address) {
|
|
183
|
+
if (!signer || !address) {
|
|
184
|
+
return new Error("Missing signer or address");
|
|
185
|
+
}
|
|
186
|
+
if (signer.toLowerCase() !== address.toLowerCase()) {
|
|
187
|
+
return new Error(`Signer ${signer} does not match address ${address}`);
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function validateMethodAccess(method, params, signer, headers) {
|
|
192
|
+
switch (method) {
|
|
193
|
+
case "eth_getTransactionCount":
|
|
194
|
+
case "eth_getProof":
|
|
195
|
+
if (!signer) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
if (!params || !params[0]) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return params[0].toLowerCase() === signer.toLowerCase();
|
|
202
|
+
case "eth_call":
|
|
203
|
+
return sanitiseEthCallFrom(params, signer ?? "", headers);
|
|
204
|
+
default:
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function sanitiseEthCallFrom(params, signer, headers) {
|
|
209
|
+
if (!params || params.length === 0) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
const validationError = validateSignerForEthCall(params, signer, headers);
|
|
213
|
+
if (validationError) {
|
|
214
|
+
try {
|
|
215
|
+
let param0;
|
|
216
|
+
if (typeof params[0] === "object" && params[0] !== null) {
|
|
217
|
+
param0 = params[0];
|
|
218
|
+
} else {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
const { address } = generateRandomKeyPair();
|
|
222
|
+
param0.from = address;
|
|
223
|
+
params[0] = param0;
|
|
224
|
+
return true;
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
function validateSignerForEthCall(params, signer, headers) {
|
|
232
|
+
if (params.length === 0) {
|
|
233
|
+
const err = new Error("empty params for eth_call");
|
|
234
|
+
return err;
|
|
235
|
+
}
|
|
236
|
+
let param0;
|
|
237
|
+
if (typeof params[0] === "object" && params[0] !== null) {
|
|
238
|
+
param0 = params[0];
|
|
239
|
+
} else {
|
|
240
|
+
const err = new Error("eth_call param[0] is not a json object");
|
|
241
|
+
return err;
|
|
242
|
+
}
|
|
243
|
+
const hasSignature = headers && (headers[import_silentdatarollup_core.HEADER_SIGNATURE] || headers[import_silentdatarollup_core.HEADER_EIP712_SIGNATURE]);
|
|
244
|
+
if (!hasSignature) {
|
|
245
|
+
const err = new Error("no signature provided");
|
|
246
|
+
return err;
|
|
247
|
+
}
|
|
248
|
+
if (param0.from === void 0 || param0.from === null) {
|
|
249
|
+
param0.from = signer;
|
|
250
|
+
return null;
|
|
251
|
+
} else if (typeof param0.from === "string") {
|
|
252
|
+
const validationError = validateAddress(signer, param0.from);
|
|
253
|
+
if (validationError) {
|
|
254
|
+
return validationError;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
} else {
|
|
258
|
+
const err = new Error(
|
|
259
|
+
"'from' field in eth_call must be a valid address or null"
|
|
260
|
+
);
|
|
261
|
+
return err;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/transactions.ts
|
|
266
|
+
function sanitizeTransaction(transaction) {
|
|
267
|
+
if (!transaction) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
blockHash: transaction.blockHash ?? null,
|
|
272
|
+
blockNumber: transaction.blockNumber?.toString() ?? null,
|
|
273
|
+
transactionHash: transaction.hash ?? null,
|
|
274
|
+
transactionIndex: transaction.index?.toString() ?? null
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function sanitizeTransactionReceipt(transactionReceipt) {
|
|
278
|
+
if (!transactionReceipt) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
status: transactionReceipt.status?.toString() ?? null,
|
|
283
|
+
blockHash: transactionReceipt.blockHash ?? null,
|
|
284
|
+
blockNumber: transactionReceipt.blockNumber?.toString() ?? null,
|
|
285
|
+
transactionHash: transactionReceipt.hash ?? null,
|
|
286
|
+
transactionIndex: transactionReceipt.index?.toString() ?? null
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function validateTransactionAccess(transaction, signer) {
|
|
290
|
+
if (!signer || !transaction) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const signerLower = signer.toLowerCase();
|
|
294
|
+
if (transaction.from && transaction.from.toLowerCase() === signerLower) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
if (transaction.to && transaction.to.toLowerCase() === signerLower) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
function validateSignerAgainstResponse(transaction, signer) {
|
|
303
|
+
return validateTransactionAccess(transaction, signer);
|
|
304
|
+
}
|
|
305
|
+
async function handleGetTransactionByHash(provider2, txHash, signer) {
|
|
306
|
+
const transaction = await provider2.getTransaction(txHash);
|
|
307
|
+
if (!transaction) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
if (validateSignerAgainstResponse(transaction, signer)) {
|
|
311
|
+
return transaction;
|
|
312
|
+
} else {
|
|
313
|
+
const sanitizedData = sanitizeTransaction(transaction);
|
|
314
|
+
return sanitizedData;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function handleGetTransactionReceipt(provider2, txHash, signer) {
|
|
318
|
+
const transaction = await provider2.getTransactionReceipt(txHash);
|
|
319
|
+
if (!transaction) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
if (validateSignerAgainstResponse(transaction, signer)) {
|
|
323
|
+
return transaction;
|
|
324
|
+
} else {
|
|
325
|
+
const sanitizedData = sanitizeTransactionReceipt(transaction);
|
|
326
|
+
return sanitizedData;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function validateTransactionMethod(provider2, method, params, signer) {
|
|
330
|
+
if (!params || !params[0]) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const txHash = params[0];
|
|
334
|
+
try {
|
|
335
|
+
if (method === "eth_getTransactionByHash") {
|
|
336
|
+
return await handleGetTransactionByHash(provider2, txHash, signer);
|
|
337
|
+
} else if (method === "eth_getTransactionReceipt") {
|
|
338
|
+
return await handleGetTransactionReceipt(provider2, txHash, signer);
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/middleware.ts
|
|
347
|
+
function errorResponse(id, jsonrpc) {
|
|
348
|
+
return {
|
|
349
|
+
id,
|
|
350
|
+
jsonrpc,
|
|
351
|
+
error: {
|
|
352
|
+
code: -1,
|
|
353
|
+
message: "An error occurred",
|
|
354
|
+
data: "An error occurred"
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function parseRpcRequest(req) {
|
|
359
|
+
let rpcRequest = null;
|
|
360
|
+
let bodyString = "";
|
|
361
|
+
if (req.method !== "POST") {
|
|
362
|
+
return { rpcRequest: null, bodyString };
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
bodyString = req.body.toString();
|
|
366
|
+
rpcRequest = JSON.parse(bodyString);
|
|
367
|
+
req.body = rpcRequest;
|
|
368
|
+
} catch {
|
|
369
|
+
return { rpcRequest: null, bodyString };
|
|
370
|
+
}
|
|
371
|
+
if (!rpcRequest || typeof rpcRequest !== "object" || !("method" in rpcRequest) || !("id" in rpcRequest) || !("jsonrpc" in rpcRequest)) {
|
|
372
|
+
return { rpcRequest: null, bodyString };
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
rpcRequest,
|
|
376
|
+
bodyString
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function createRpcValidationMiddleware(provider2) {
|
|
380
|
+
return async (req, res, next) => {
|
|
381
|
+
const { rpcRequest, bodyString } = parseRpcRequest(req);
|
|
382
|
+
if (!rpcRequest) {
|
|
383
|
+
next();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const { method, id, jsonrpc, params } = rpcRequest;
|
|
387
|
+
console.log(`Received request with method: ${method}`);
|
|
388
|
+
if (!import_silentdatarollup_core2.WHITELISTED_METHODS.includes(method)) {
|
|
389
|
+
console.log(`Method ${method} is not whitelisted`);
|
|
390
|
+
return res.status(403).json(errorResponse(id, jsonrpc));
|
|
391
|
+
}
|
|
392
|
+
if (!import_silentdatarollup_core2.SIGN_RPC_METHODS.includes(method)) {
|
|
393
|
+
next();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const signer = recoverSigner(
|
|
397
|
+
req.headers,
|
|
398
|
+
bodyString
|
|
399
|
+
);
|
|
400
|
+
if (["eth_getTransactionByHash", "eth_getTransactionReceipt"].includes(method)) {
|
|
401
|
+
const transaction = await validateTransactionMethod(
|
|
402
|
+
provider2,
|
|
403
|
+
method,
|
|
404
|
+
params,
|
|
405
|
+
signer
|
|
406
|
+
);
|
|
407
|
+
console.log(`Sending transaction response for method: ${method}`);
|
|
408
|
+
return res.status(200).json({
|
|
409
|
+
id,
|
|
410
|
+
jsonrpc,
|
|
411
|
+
result: transaction
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (!validateMethodAccess(method, params, signer)) {
|
|
415
|
+
console.log(`Failed to validate method access for method: ${method}`);
|
|
416
|
+
return res.status(403).json(errorResponse(id, jsonrpc));
|
|
417
|
+
}
|
|
418
|
+
next();
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/index.ts
|
|
423
|
+
var import_silentdatarollup_core3 = require("@appliedblockchain/silentdatarollup-core");
|
|
424
|
+
var CUSTOM_RPC_PORT = process.env.CUSTOM_RPC_PORT || 54321;
|
|
425
|
+
var CUSTOM_RPC_PROXY_URL = process.env.CUSTOM_RPC_PROXY_URL || "http://localhost:8545";
|
|
426
|
+
var provider = new import_ethers2.JsonRpcProvider(CUSTOM_RPC_PROXY_URL);
|
|
427
|
+
var app = (0, import_express.default)();
|
|
428
|
+
app.use((req, res, next) => {
|
|
429
|
+
res.header("Access-Control-Allow-Origin", "*");
|
|
430
|
+
res.header("Access-Control-Allow-Credentials", "true");
|
|
431
|
+
res.header(
|
|
432
|
+
"Access-Control-Allow-Headers",
|
|
433
|
+
`Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, ${import_silentdatarollup_core3.HEADER_TIMESTAMP}, ${import_silentdatarollup_core3.HEADER_SIGNATURE}, ${import_silentdatarollup_core3.HEADER_DELEGATE}, ${import_silentdatarollup_core3.HEADER_DELEGATE_SIGNATURE}, ${import_silentdatarollup_core3.HEADER_EIP712_DELEGATE_SIGNATURE}, ${import_silentdatarollup_core3.HEADER_EIP712_SIGNATURE}`
|
|
434
|
+
);
|
|
435
|
+
res.header("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT");
|
|
436
|
+
if (req.method === "OPTIONS") {
|
|
437
|
+
return res.sendStatus(204);
|
|
438
|
+
}
|
|
439
|
+
next();
|
|
440
|
+
});
|
|
441
|
+
app.use(import_body_parser.default.raw({ type: "*/*" }));
|
|
442
|
+
app.use(createRpcValidationMiddleware(provider));
|
|
443
|
+
app.use(
|
|
444
|
+
(0, import_http_proxy_middleware.createProxyMiddleware)({
|
|
445
|
+
target: CUSTOM_RPC_PROXY_URL,
|
|
446
|
+
changeOrigin: true,
|
|
447
|
+
on: {
|
|
448
|
+
proxyReq: (proxyReq, req) => {
|
|
449
|
+
if (req.body) {
|
|
450
|
+
const bodyData = JSON.stringify(req.body);
|
|
451
|
+
console.log(`Proxying request with body: ${bodyData}`);
|
|
452
|
+
proxyReq.setHeader("Content-Length", Buffer.byteLength(bodyData));
|
|
453
|
+
proxyReq.write(bodyData);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
app.disable("x-powered-by");
|
|
460
|
+
app.listen(CUSTOM_RPC_PORT, () => {
|
|
461
|
+
console.log(`Custom RPC listening at http://localhost:${CUSTOM_RPC_PORT}`);
|
|
462
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@appliedblockchain/silentdatarollup-custom-rpc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Custom RPC for Silent Data [Rollup]",
|
|
5
|
+
"author": "Applied Blockchain",
|
|
6
|
+
"homepage": "https://github.com/appliedblockchain/silent-data-rollup-providers#readme",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"ethereum",
|
|
9
|
+
"provider",
|
|
10
|
+
"silentdata",
|
|
11
|
+
"rollup",
|
|
12
|
+
"custom-rpc"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": "https://github.com/appliedblockchain/silent-data-rollup-providers",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"bin": "dist/index.js",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup src/index.ts --format cjs --clean",
|
|
23
|
+
"check-exports": "attw --pack . --profile node16",
|
|
24
|
+
"dev": "concurrently --kill-others-on-fail -s all -p '[{name}]' -n 'HARDHAT,CUSTOM RPC' -c 'bgGreen.bold,bgBlue.bold' 'hardhat node' 'nodemon'",
|
|
25
|
+
"prepack": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@appliedblockchain/silentdatarollup-core": "1.0.0",
|
|
29
|
+
"body-parser": "1.20.3",
|
|
30
|
+
"dotenv": "16.4.7",
|
|
31
|
+
"ethers": "6.13.2",
|
|
32
|
+
"express": "4.21.2",
|
|
33
|
+
"http-proxy-middleware": "3.0.3"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/express": "4.17.21",
|
|
37
|
+
"@types/node": "22.5.4",
|
|
38
|
+
"concurrently": "9.1.2",
|
|
39
|
+
"hardhat": "^0.0.7",
|
|
40
|
+
"nodemon": "3.0.1",
|
|
41
|
+
"ts-node": "10.9.2",
|
|
42
|
+
"typescript": "5.6.2"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|